Compare commits

...

7 Commits

Author SHA1 Message Date
Brian S. Stephan f898f35ce6
replace execute_delayed with reactor.scheduler.execute_after
the former was deprecated forever ago, and apparently removed. this may
fix the disconnect detection logic
2023-03-02 00:51:22 -06:00
Brian S. Stephan 4289f95800
report on the version of dr.botzo in CTCP VERSION 2023-03-02 00:45:55 -06:00
Brian S. Stephan 572ecddceb
do some small cleanups 2023-03-02 00:45:29 -06:00
Brian S. Stephan 3aadde4b71
remove XMLRPC inheritence that overrode a method no longer in existence
this is probably from python 2 days; we inherited from
SimpleXMLRPCRequestHandler to change the logging, but the method
overrode no longer exists so this did nothing
2023-03-02 00:20:25 -06:00
Brian S. Stephan c2d26f404e
deduplicate Channel object from irc library
I think this is an extremely ancient copy and paste job I never fully
corrected
2023-03-02 00:19:27 -06:00
Brian S. Stephan ecaabbce89
unpin the irc library 2023-03-02 00:16:32 -06:00
Brian S. Stephan 051e656a82
fix errant reference to IrcChannel object rather than just the name 2023-03-02 00:15:06 -06:00
4 changed files with 36 additions and 239 deletions

View File

@ -1,46 +1,36 @@
"""Provide the base IRC client bot which other code can latch onto."""
import bisect
import collections
import copy
import importlib
import logging
import re
from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
import socket
import ssl
import sys
import threading
import time
from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from django.conf import settings
import irc.buffer
import irc.client
import irc.modes
from irc.bot import Channel
from irc.connection import Factory
from irc.dict import IRCDict
import irc.modes
from jaraco.stream import buffer
import ircbot.lib as ircbotlib
from dr_botzo import __version__
from ircbot.models import Alias, IrcChannel, IrcPlugin, IrcServer
log = logging.getLogger('ircbot.bot')
class IrcBotXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
"""Override the basic request handler to change the logging."""
def log_message(self, format, *args):
"""Use a logger rather than stderr."""
log.debug("XML-RPC - %s - %s", self.client_address[0], format % args)
class PrioritizedRegexHandler(collections.namedtuple('Base', ('priority', 'regex', 'callback'))):
"""Regex handler that still uses the normal handler priority stuff."""
def __lt__(self, other):
"""When sorting prioritized handlers, only use the priority"""
"""When sorting prioritized handlers, only use the priority."""
return self.priority < other.priority
@ -52,7 +42,7 @@ class LenientServerConnection(irc.client.ServerConnection):
method on a Reactor object.
"""
buffer_class = irc.buffer.LenientDecodingLineBuffer
buffer_class = buffer.LenientDecodingLineBuffer
server_config = None
@ -65,6 +55,9 @@ class LenientServerConnection(irc.client.ServerConnection):
class DrReactor(irc.client.Reactor):
"""Customize the basic IRC library's Reactor with more features."""
# used by Reactor.server() to initialize
connection_class = LenientServerConnection
def __do_nothing(*args, **kwargs):
pass
@ -73,18 +66,10 @@ class DrReactor(irc.client.Reactor):
super(DrReactor, self).__init__(on_connect=on_connect, on_disconnect=on_disconnect)
self.regex_handlers = {}
def server(self):
"""Creates and returns a ServerConnection object."""
c = LenientServerConnection(self)
with self.mutex:
self.connections.append(c)
return c
def add_global_regex_handler(self, events, regex, handler, priority=0):
"""Adds a global handler function for a specific event type and regex.
"""Add a global handler function for a specific event type and regex.
Arguments:
events --- Event type(s) (a list of strings).
handler -- Callback function taking connection and event
@ -114,10 +99,9 @@ class DrReactor(irc.client.Reactor):
bisect.insort(event_regex_handlers, handler)
def remove_global_regex_handler(self, events, handler):
"""Removes a global regex handler function.
"""Remove a global regex handler function.
Arguments:
events -- Event type(s) (a list of strings).
handler -- Callback function.
@ -228,8 +212,7 @@ class DrReactor(irc.client.Reactor):
if result == "NO MORE":
return
except Exception as ex:
log.error("caught exception!")
log.exception(ex)
log.exception("caught exception!")
connection.privmsg(event.target, str(ex))
def try_recursion(self, connection, event):
@ -416,7 +399,7 @@ class IRCBot(irc.client.SimpleIRCClient):
# load XML-RPC server
self.xmlrpc = SimpleXMLRPCServer((self.server_config.xmlrpc_host, self.server_config.xmlrpc_port),
requestHandler=IrcBotXMLRPCRequestHandler, allow_none=True)
requestHandler=SimpleXMLRPCRequestHandler, allow_none=True)
self.xmlrpc.register_introspection_functions()
t = threading.Thread(target=self._xmlrpc_listen, args=())
@ -429,8 +412,7 @@ class IRCBot(irc.client.SimpleIRCClient):
def _connected_checker(self):
if not self.connection.is_connected():
self.connection.execute_delayed(self.reconnection_interval,
self._connected_checker)
self.reactor.scheduler.execute_after(self.reconnection_interval, self._connected_checker)
self.jump_server()
def _connect(self):
@ -448,8 +430,7 @@ class IRCBot(irc.client.SimpleIRCClient):
def _on_disconnect(self, c, e):
self.channels = IRCDict()
self.connection.execute_delayed(self.reconnection_interval,
self._connected_checker)
self.reactor.scheduler.execute_after(self.reconnection_interval, self._connected_checker)
def _on_join(self, c, e):
ch = e.target
@ -558,7 +539,7 @@ class IRCBot(irc.client.SimpleIRCClient):
for chan in IrcChannel.objects.filter(autojoin=True, server=connection.server_config):
log.info("autojoining %s", chan.name)
self.connection.join(chan)
self.connection.join(chan.name)
for plugin in IrcPlugin.objects.filter(autoload=True):
log.info("autoloading %s", plugin.path)
@ -592,12 +573,11 @@ class IRCBot(irc.client.SimpleIRCClient):
self.connection.disconnect(msg)
def get_version(self):
"""Returns the bot version.
"""Return the bot version.
Used when answering a CTCP VERSION request.
"""
return "Python irc.bot ({version})".format(
version=irc.client.VERSION_STRING)
return f"dr.botzo {__version__}"
def jump_server(self, msg="Changing servers"):
"""Connect to a new server, potentially disconnecting from the current one."""
@ -607,7 +587,7 @@ class IRCBot(irc.client.SimpleIRCClient):
self._connect()
def on_ctcp(self, c, e):
"""Default handler for ctcp events.
"""Handle for ctcp events.
Replies to VERSION and PING requests and relays DCC requests
to the on_dccchat method.
@ -997,165 +977,3 @@ class IRCBot(irc.client.SimpleIRCClient):
del sys.modules[path]
self.die(msg="Shutting down...")
class Channel(object):
"""A class for keeping information about an IRC channel."""
def __init__(self):
"""Initialize channel object."""
self.userdict = IRCDict()
self.operdict = IRCDict()
self.voiceddict = IRCDict()
self.ownerdict = IRCDict()
self.halfopdict = IRCDict()
self.modes = {}
def users(self):
"""Returns an unsorted list of the channel's users."""
return list(self.userdict.keys())
def opers(self):
"""Returns an unsorted list of the channel's operators."""
return list(self.operdict.keys())
def voiced(self):
"""Returns an unsorted list of the persons that have voice mode set in the channel."""
return list(self.voiceddict.keys())
def owners(self):
"""Returns an unsorted list of the channel's owners."""
return list(self.ownerdict.keys())
def halfops(self):
"""Returns an unsorted list of the channel's half-operators."""
return list(self.halfopdict.keys())
def has_user(self, nick):
"""Check whether the channel has a user."""
return nick in self.userdict
def is_oper(self, nick):
"""Check whether a user has operator status in the channel."""
return nick in self.operdict
def is_voiced(self, nick):
"""Check whether a user has voice mode set in the channel."""
return nick in self.voiceddict
def is_owner(self, nick):
"""Check whether a user has owner status in the channel."""
return nick in self.ownerdict
def is_halfop(self, nick):
"""Check whether a user has half-operator status in the channel."""
return nick in self.halfopdict
def add_user(self, nick):
"""Add user."""
self.userdict[nick] = 1
def remove_user(self, nick):
"""Remove user."""
for d in self.userdict, self.operdict, self.voiceddict:
if nick in d:
del d[nick]
def change_nick(self, before, after):
"""Handle a nick change."""
self.userdict[after] = self.userdict.pop(before)
if before in self.operdict:
self.operdict[after] = self.operdict.pop(before)
if before in self.voiceddict:
self.voiceddict[after] = self.voiceddict.pop(before)
def set_userdetails(self, nick, details):
"""Set user details."""
if nick in self.userdict:
self.userdict[nick] = details
def set_mode(self, mode, value=None):
"""Set mode on the channel.
Arguments:
mode -- The mode (a single-character string).
value -- Value
"""
if mode == "o":
self.operdict[value] = 1
elif mode == "v":
self.voiceddict[value] = 1
elif mode == "q":
self.ownerdict[value] = 1
elif mode == "h":
self.halfopdict[value] = 1
else:
self.modes[mode] = value
def clear_mode(self, mode, value=None):
"""Clear mode on the channel.
Arguments:
mode -- The mode (a single-character string).
value -- Value
"""
try:
if mode == "o":
del self.operdict[value]
elif mode == "v":
del self.voiceddict[value]
elif mode == "q":
del self.ownerdict[value]
elif mode == "h":
del self.halfopdict[value]
else:
del self.modes[mode]
except KeyError:
pass
def has_mode(self, mode):
"""Return if mode is in channel modes."""
return mode in self.modes
def is_moderated(self):
"""Return if the channel is +m."""
return self.has_mode("m")
def is_secret(self):
"""Return if the channel is +s."""
return self.has_mode("s")
def is_protected(self):
"""Return if the channel is +p."""
return self.has_mode("p")
def has_topic_lock(self):
"""Return if the channel is +t."""
return self.has_mode("t")
def is_invite_only(self):
"""Return if the channel is +i."""
return self.has_mode("i")
def has_allow_external_messages(self):
"""Return if the channel is +n."""
return self.has_mode("n")
def has_limit(self):
"""Return if the channel is +l."""
return self.has_mode("l")
def limit(self):
"""Return the channel limit count."""
if self.has_limit():
return self.modes["l"]
else:
return None
def has_key(self):
"""Return if the channel is +k."""
return self.has_mode("k")

View File

@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.8
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in
@ -22,7 +22,7 @@ click==8.1.3
# via
# pip-tools
# safety
coverage[toml]==7.1.0
coverage[toml]==7.2.1
# via pytest-cov
distlib==0.3.6
# via virtualenv
@ -77,19 +77,15 @@ flake8-mutable==1.2.0
# via -r requirements/requirements-dev.in
gitdb==4.0.10
# via gitpython
gitpython==3.1.30
gitpython==3.1.31
# via bandit
idna==3.4
# via requests
importlib-resources==5.10.2
# via jaraco-text
inflect==6.0.2
# via
# jaraco-itertools
# jaraco-text
# via jaraco-text
iniconfig==2.0.0
# via pytest
irc==15.0.6
irc==20.1.0
# via -r requirements/requirements.in
isort==5.12.0
# via flake8-isort
@ -99,13 +95,11 @@ jaraco-collections==3.8.0
# via irc
jaraco-context==4.3.0
# via jaraco-text
jaraco-functools==3.5.2
jaraco-functools==3.6.0
# via
# irc
# jaraco-text
# tempora
jaraco-itertools==6.2.1
# via irc
jaraco-logging==3.1.2
# via irc
jaraco-stream==3.0.3
@ -116,12 +110,11 @@ jaraco-text==3.11.1
# jaraco-collections
mccabe==0.7.0
# via flake8
more-itertools==9.0.0
more-itertools==9.1.0
# via
# irc
# jaraco-classes
# jaraco-functools
# jaraco-itertools
# jaraco-text
packaging==21.3
# via
@ -134,7 +127,7 @@ parsedatetime==2.6
# via -r requirements/requirements.in
pbr==5.11.1
# via stevedore
pip-tools==6.12.2
pip-tools==6.12.3
# via -r requirements/requirements-dev.in
platformdirs==3.0.0
# via virtualenv
@ -197,7 +190,6 @@ safety==2.3.5
# via -r requirements/requirements-dev.in
six==1.16.0
# via
# irc
# python-dateutil
# tox
smmap==5.0.0
@ -233,7 +225,7 @@ urllib3==1.26.14
# via requests
versioneer==0.28
# via -r requirements/requirements-dev.in
virtualenv==20.19.0
virtualenv==20.20.0
# via tox
wheel==0.38.4
# via
@ -241,8 +233,6 @@ wheel==0.38.4
# tox-wheel
zalgo-text==0.6
# via -r requirements/requirements.in
zipp==3.13.0
# via importlib-resources
# The following packages are considered to be unsafe in a requirements file:
# pip

View File

@ -3,7 +3,7 @@ django-adminplus # admin.site.register_view
django-bootstrap3 # bootstrap layout
django-extensions # more commands
djangorestframework # dispatch WS API
irc==15.0.6 # core, pinned until I can bother to update --- 17.x has API changes
irc # core
parsedatetime # relative date stuff in countdown
ply # dice lex/yacc compiler
python-dateutil # countdown relative math

View File

@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.8
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --output-file=requirements/requirements.txt requirements/requirements.in
@ -28,13 +28,9 @@ djangorestframework==3.14.0
# via -r requirements/requirements.in
idna==3.4
# via requests
importlib-resources==5.10.2
# via jaraco-text
inflect==6.0.2
# via
# jaraco-itertools
# jaraco-text
irc==15.0.6
# via jaraco-text
irc==20.1.0
# via -r requirements/requirements.in
jaraco-classes==3.2.3
# via jaraco-collections
@ -42,13 +38,11 @@ jaraco-collections==3.8.0
# via irc
jaraco-context==4.3.0
# via jaraco-text
jaraco-functools==3.5.2
jaraco-functools==3.6.0
# via
# irc
# jaraco-text
# tempora
jaraco-itertools==6.2.1
# via irc
jaraco-logging==3.1.2
# via irc
jaraco-stream==3.0.3
@ -57,12 +51,11 @@ jaraco-text==3.11.1
# via
# irc
# jaraco-collections
more-itertools==9.0.0
more-itertools==9.1.0
# via
# irc
# jaraco-classes
# jaraco-functools
# jaraco-itertools
# jaraco-text
parsedatetime==2.6
# via -r requirements/requirements.in
@ -90,9 +83,7 @@ requests==2.28.2
requests-toolbelt==0.10.1
# via python-gitlab
six==1.16.0
# via
# irc
# python-dateutil
# via python-dateutil
sqlparse==0.4.3
# via django
tempora==5.2.1
@ -105,5 +96,3 @@ urllib3==1.26.14
# via requests
zalgo-text==0.6
# via -r requirements/requirements.in
zipp==3.13.0
# via importlib-resources