Compare commits
	
		
			No commits in common. "f898f35ce679067a006c6a32f791c0d8806b8234" and "0ea54a5ee2e0b8d47f588b7b109534bad5d125b0" have entirely different histories.
		
	
	
		
			f898f35ce6
			...
			0ea54a5ee2
		
	
		
							
								
								
									
										222
									
								
								ircbot/bot.py
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								ircbot/bot.py
									
									
									
									
									
								
							| @ -1,36 +1,46 @@ | ||||
| """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 | ||||
| from jaraco.stream import buffer | ||||
| import irc.modes | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
| @ -42,7 +52,7 @@ class LenientServerConnection(irc.client.ServerConnection): | ||||
|     method on a Reactor object. | ||||
|     """ | ||||
| 
 | ||||
|     buffer_class = buffer.LenientDecodingLineBuffer | ||||
|     buffer_class = irc.buffer.LenientDecodingLineBuffer | ||||
| 
 | ||||
|     server_config = None | ||||
| 
 | ||||
| @ -55,9 +65,6 @@ 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 | ||||
| 
 | ||||
| @ -66,10 +73,18 @@ 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): | ||||
|         """Add a global handler function for a specific event type and regex. | ||||
|         """Adds 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 | ||||
| @ -99,9 +114,10 @@ class DrReactor(irc.client.Reactor): | ||||
|                 bisect.insort(event_regex_handlers, handler) | ||||
| 
 | ||||
|     def remove_global_regex_handler(self, events, handler): | ||||
|         """Remove a global regex handler function. | ||||
|         """Removes a global regex handler function. | ||||
| 
 | ||||
|         Arguments: | ||||
| 
 | ||||
|             events -- Event type(s) (a list of strings). | ||||
|             handler -- Callback function. | ||||
| 
 | ||||
| @ -212,7 +228,8 @@ class DrReactor(irc.client.Reactor): | ||||
|                     if result == "NO MORE": | ||||
|                         return | ||||
|         except Exception as ex: | ||||
|             log.exception("caught exception!") | ||||
|             log.error("caught exception!") | ||||
|             log.exception(ex) | ||||
|             connection.privmsg(event.target, str(ex)) | ||||
| 
 | ||||
|     def try_recursion(self, connection, event): | ||||
| @ -399,7 +416,7 @@ class IRCBot(irc.client.SimpleIRCClient): | ||||
| 
 | ||||
|         # load XML-RPC server | ||||
|         self.xmlrpc = SimpleXMLRPCServer((self.server_config.xmlrpc_host, self.server_config.xmlrpc_port), | ||||
|                                          requestHandler=SimpleXMLRPCRequestHandler, allow_none=True) | ||||
|                                          requestHandler=IrcBotXMLRPCRequestHandler, allow_none=True) | ||||
|         self.xmlrpc.register_introspection_functions() | ||||
| 
 | ||||
|         t = threading.Thread(target=self._xmlrpc_listen, args=()) | ||||
| @ -412,7 +429,8 @@ class IRCBot(irc.client.SimpleIRCClient): | ||||
| 
 | ||||
|     def _connected_checker(self): | ||||
|         if not self.connection.is_connected(): | ||||
|             self.reactor.scheduler.execute_after(self.reconnection_interval, self._connected_checker) | ||||
|             self.connection.execute_delayed(self.reconnection_interval, | ||||
|                                             self._connected_checker) | ||||
|             self.jump_server() | ||||
| 
 | ||||
|     def _connect(self): | ||||
| @ -430,7 +448,8 @@ class IRCBot(irc.client.SimpleIRCClient): | ||||
| 
 | ||||
|     def _on_disconnect(self, c, e): | ||||
|         self.channels = IRCDict() | ||||
|         self.reactor.scheduler.execute_after(self.reconnection_interval, self._connected_checker) | ||||
|         self.connection.execute_delayed(self.reconnection_interval, | ||||
|                                         self._connected_checker) | ||||
| 
 | ||||
|     def _on_join(self, c, e): | ||||
|         ch = e.target | ||||
| @ -539,7 +558,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.name) | ||||
|             self.connection.join(chan) | ||||
| 
 | ||||
|         for plugin in IrcPlugin.objects.filter(autoload=True): | ||||
|             log.info("autoloading %s", plugin.path) | ||||
| @ -573,11 +592,12 @@ class IRCBot(irc.client.SimpleIRCClient): | ||||
|         self.connection.disconnect(msg) | ||||
| 
 | ||||
|     def get_version(self): | ||||
|         """Return the bot version. | ||||
|         """Returns the bot version. | ||||
| 
 | ||||
|         Used when answering a CTCP VERSION request. | ||||
|         """ | ||||
|         return f"dr.botzo {__version__}" | ||||
|         return "Python irc.bot ({version})".format( | ||||
|             version=irc.client.VERSION_STRING) | ||||
| 
 | ||||
|     def jump_server(self, msg="Changing servers"): | ||||
|         """Connect to a new server, potentially disconnecting from the current one.""" | ||||
| @ -587,7 +607,7 @@ class IRCBot(irc.client.SimpleIRCClient): | ||||
|         self._connect() | ||||
| 
 | ||||
|     def on_ctcp(self, c, e): | ||||
|         """Handle for ctcp events. | ||||
|         """Default handler for ctcp events. | ||||
| 
 | ||||
|         Replies to VERSION and PING requests and relays DCC requests | ||||
|         to the on_dccchat method. | ||||
| @ -977,3 +997,165 @@ 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") | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile with Python 3.10 | ||||
| # This file is autogenerated by pip-compile with Python 3.8 | ||||
| # 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.2.1 | ||||
| coverage[toml]==7.1.0 | ||||
|     # via pytest-cov | ||||
| distlib==0.3.6 | ||||
|     # via virtualenv | ||||
| @ -77,15 +77,19 @@ flake8-mutable==1.2.0 | ||||
|     # via -r requirements/requirements-dev.in | ||||
| gitdb==4.0.10 | ||||
|     # via gitpython | ||||
| gitpython==3.1.31 | ||||
| gitpython==3.1.30 | ||||
|     # via bandit | ||||
| idna==3.4 | ||||
|     # via requests | ||||
| inflect==6.0.2 | ||||
| importlib-resources==5.10.2 | ||||
|     # via jaraco-text | ||||
| inflect==6.0.2 | ||||
|     # via | ||||
|     #   jaraco-itertools | ||||
|     #   jaraco-text | ||||
| iniconfig==2.0.0 | ||||
|     # via pytest | ||||
| irc==20.1.0 | ||||
| irc==15.0.6 | ||||
|     # via -r requirements/requirements.in | ||||
| isort==5.12.0 | ||||
|     # via flake8-isort | ||||
| @ -95,11 +99,13 @@ jaraco-collections==3.8.0 | ||||
|     # via irc | ||||
| jaraco-context==4.3.0 | ||||
|     # via jaraco-text | ||||
| jaraco-functools==3.6.0 | ||||
| jaraco-functools==3.5.2 | ||||
|     # via | ||||
|     #   irc | ||||
|     #   jaraco-text | ||||
|     #   tempora | ||||
| jaraco-itertools==6.2.1 | ||||
|     # via irc | ||||
| jaraco-logging==3.1.2 | ||||
|     # via irc | ||||
| jaraco-stream==3.0.3 | ||||
| @ -110,11 +116,12 @@ jaraco-text==3.11.1 | ||||
|     #   jaraco-collections | ||||
| mccabe==0.7.0 | ||||
|     # via flake8 | ||||
| more-itertools==9.1.0 | ||||
| more-itertools==9.0.0 | ||||
|     # via | ||||
|     #   irc | ||||
|     #   jaraco-classes | ||||
|     #   jaraco-functools | ||||
|     #   jaraco-itertools | ||||
|     #   jaraco-text | ||||
| packaging==21.3 | ||||
|     # via | ||||
| @ -127,7 +134,7 @@ parsedatetime==2.6 | ||||
|     # via -r requirements/requirements.in | ||||
| pbr==5.11.1 | ||||
|     # via stevedore | ||||
| pip-tools==6.12.3 | ||||
| pip-tools==6.12.2 | ||||
|     # via -r requirements/requirements-dev.in | ||||
| platformdirs==3.0.0 | ||||
|     # via virtualenv | ||||
| @ -190,6 +197,7 @@ safety==2.3.5 | ||||
|     # via -r requirements/requirements-dev.in | ||||
| six==1.16.0 | ||||
|     # via | ||||
|     #   irc | ||||
|     #   python-dateutil | ||||
|     #   tox | ||||
| smmap==5.0.0 | ||||
| @ -225,7 +233,7 @@ urllib3==1.26.14 | ||||
|     # via requests | ||||
| versioneer==0.28 | ||||
|     # via -r requirements/requirements-dev.in | ||||
| virtualenv==20.20.0 | ||||
| virtualenv==20.19.0 | ||||
|     # via tox | ||||
| wheel==0.38.4 | ||||
|     # via | ||||
| @ -233,6 +241,8 @@ 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 | ||||
|  | ||||
| @ -3,7 +3,7 @@ django-adminplus                # admin.site.register_view | ||||
| django-bootstrap3               # bootstrap layout | ||||
| django-extensions               # more commands | ||||
| djangorestframework             # dispatch WS API | ||||
| irc                             # core | ||||
| irc==15.0.6                     # core, pinned until I can bother to update --- 17.x has API changes | ||||
| parsedatetime                   # relative date stuff in countdown | ||||
| ply                             # dice lex/yacc compiler | ||||
| python-dateutil                 # countdown relative math | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| # | ||||
| # This file is autogenerated by pip-compile with Python 3.10 | ||||
| # This file is autogenerated by pip-compile with Python 3.8 | ||||
| # by the following command: | ||||
| # | ||||
| #    pip-compile --output-file=requirements/requirements.txt requirements/requirements.in | ||||
| @ -28,9 +28,13 @@ djangorestframework==3.14.0 | ||||
|     # via -r requirements/requirements.in | ||||
| idna==3.4 | ||||
|     # via requests | ||||
| inflect==6.0.2 | ||||
| importlib-resources==5.10.2 | ||||
|     # via jaraco-text | ||||
| irc==20.1.0 | ||||
| inflect==6.0.2 | ||||
|     # via | ||||
|     #   jaraco-itertools | ||||
|     #   jaraco-text | ||||
| irc==15.0.6 | ||||
|     # via -r requirements/requirements.in | ||||
| jaraco-classes==3.2.3 | ||||
|     # via jaraco-collections | ||||
| @ -38,11 +42,13 @@ jaraco-collections==3.8.0 | ||||
|     # via irc | ||||
| jaraco-context==4.3.0 | ||||
|     # via jaraco-text | ||||
| jaraco-functools==3.6.0 | ||||
| jaraco-functools==3.5.2 | ||||
|     # via | ||||
|     #   irc | ||||
|     #   jaraco-text | ||||
|     #   tempora | ||||
| jaraco-itertools==6.2.1 | ||||
|     # via irc | ||||
| jaraco-logging==3.1.2 | ||||
|     # via irc | ||||
| jaraco-stream==3.0.3 | ||||
| @ -51,11 +57,12 @@ jaraco-text==3.11.1 | ||||
|     # via | ||||
|     #   irc | ||||
|     #   jaraco-collections | ||||
| more-itertools==9.1.0 | ||||
| more-itertools==9.0.0 | ||||
|     # via | ||||
|     #   irc | ||||
|     #   jaraco-classes | ||||
|     #   jaraco-functools | ||||
|     #   jaraco-itertools | ||||
|     #   jaraco-text | ||||
| parsedatetime==2.6 | ||||
|     # via -r requirements/requirements.in | ||||
| @ -83,7 +90,9 @@ requests==2.28.2 | ||||
| requests-toolbelt==0.10.1 | ||||
|     # via python-gitlab | ||||
| six==1.16.0 | ||||
|     # via python-dateutil | ||||
|     # via | ||||
|     #   irc | ||||
|     #   python-dateutil | ||||
| sqlparse==0.4.3 | ||||
|     # via django | ||||
| tempora==5.2.1 | ||||
| @ -96,3 +105,5 @@ urllib3==1.26.14 | ||||
|     # via requests | ||||
| zalgo-text==0.6 | ||||
|     # via -r requirements/requirements.in | ||||
| zipp==3.13.0 | ||||
|     # via importlib-resources | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user