async def test_event(self) -> None: bot = MockBot() conn = MockConn(nick="bot") event = Event( irc_command="PRIVMSG", event_type=EventType.message, channel="#foo", nick="bar", conn=conn, content=".foo bar", ) event.notice = MagicMock() plugin = MagicMock() run_hooks = [] @hook.event(EventType.message) async def coro(hook): run_hooks.append(hook) full_event_hook = EventHook(plugin, hook._get_hook(coro, "event")) for event_type in full_event_hook.types: bot.plugin_manager.event_type_hooks[event_type].append( full_event_hook) await CloudBot.process(bot, event) assert sorted(run_hooks, key=id) == sorted( [ full_event_hook, ], key=id, )
async def test_command(self) -> None: bot = MockBot() conn = MockConn(nick="bot") event = Event( irc_command="PRIVMSG", event_type=EventType.message, channel="#foo", nick="bar", conn=conn, content=".foo bar", ) event.notice = MagicMock() plugin = MagicMock() run_hooks = [] @hook.command("foo") async def coro(hook): run_hooks.append(hook) full_hook = CommandHook(plugin, hook._get_hook(coro, "command")) for cmd in full_hook.aliases: bot.plugin_manager.commands[cmd] = full_hook await CloudBot.process(bot, event) assert sorted(run_hooks, key=id) == sorted( [ full_hook, ], key=id, )
async def test_irc_catch_all(self) -> None: bot = MockBot() conn = MockConn(nick="bot") event = Event( irc_command="PRIVMSG", event_type=EventType.message, channel="#foo", nick="bar", conn=conn, content=".foo bar", ) event.notice = MagicMock() plugin = MagicMock() run_hooks = [] @hook.irc_raw("*") async def coro(hook): run_hooks.append(hook) full_hook = RawHook(plugin, hook._get_hook(coro, "irc_raw")) bot.plugin_manager.catch_all_triggers.append(full_hook) await CloudBot.process(bot, event) assert sorted(run_hooks, key=id) == sorted( [ full_hook, ], key=id, )
async def test_command_partial(self) -> None: bot = MockBot() conn = MockConn(nick="bot") event = Event( irc_command="PRIVMSG", event_type=EventType.message, channel="#foo", nick="bar", conn=conn, content=".foo bar", ) event.notice = MagicMock() plugin = MagicMock() run_hooks = [] @hook.command("foob", "fooc") async def coro(hook): # pragma: no cover run_hooks.append(hook) full_hook = CommandHook(plugin, hook._get_hook(coro, "command")) for cmd in full_hook.aliases: bot.plugin_manager.commands[cmd] = full_hook await CloudBot.process(bot, event) assert sorted(run_hooks, key=id) == sorted( [], key=id, ) event.notice.assert_called_once_with("Possible matches: foob or fooc")
async def perm_sieve(bot: CloudBot, event: Event, _hook: Hook) -> Optional[Event]: """check permissions""" allowed_permissions = _hook.permissions if allowed_permissions: allowed = False for perm in allowed_permissions: if await event.check_permission(perm): allowed = True break if not allowed: event.notice("Sorry, you are not allowed to use this command.") return None return event
async def _connect(self, timeout=None): # connect to the clients server if self.connected: logger.info("[%s] Reconnecting", self.name) self.quit("Reconnecting...") else: logger.info("[%s] Connecting", self.name) self._active = True optional_params = {} if self.local_bind: optional_params["local_addr"] = self.local_bind coro = self.loop.create_connection( partial(_IrcProtocol, self), host=self.server, port=self.port, ssl=self.ssl_context, **optional_params, ) if timeout is not None: coro = asyncio.wait_for(coro, timeout) self._transport, self._protocol = await coro tasks = [ self.bot.plugin_manager.launch( hook, Event(bot=self.bot, conn=self, hook=hook)) for hook in self.bot.plugin_manager.connect_hooks if not hook.clients or self.type in hook.clients ] # TODO stop connecting if a connect hook fails? await asyncio.gather(*tasks)
def test_hook_args(hook): bot = MockBot() if hook.type in ( "irc_raw", "perm_check", "periodic", "on_start", "on_stop", "event", "on_connect", ): event = Event(bot=bot) elif hook.type == "command": event = CommandEvent(bot=bot, hook=hook, text="", triggered_command="", cmd_prefix=".") elif hook.type == "regex": event = RegexEvent(bot=bot, hook=hook, match=None) elif hook.type.startswith("on_cap"): event = CapEvent(bot=bot, cap="") elif hook.type == "post_hook": event = PostHookEvent(bot=bot) elif hook.type == "irc_out": event = IrcOutEvent(bot=bot) elif hook.type == "sieve": return else: # pragma: no cover assert False, "Unhandled hook type '{}' in tests".format(hook.type) for arg in hook.required_args: assert hasattr( event, arg), "Undefined parameter '{}' for hook function".format(arg)
def connect(self): """ Connects to the IRC server, or reconnects if already connected. """ # connect to the clients server if self._quit: # we've quit, so close instead (because this has probably been called because of EOF received) self.close() return if self._connected: logger.info("[{}] Reconnecting".format(self.name)) self._transport.close() else: self._connected = True logger.info("[{}] Connecting".format(self.name)) optional_params = {} if self.local_bind: optional_params["local_addr"] = self.local_bind self._transport, self._protocol = yield from self.loop.create_connection( lambda: _IrcProtocol(self), host=self.server, port=self.port, ssl=self.ssl_context, **optional_params) tasks = [ self.bot.plugin_manager.launch( hook, Event(bot=self.bot, conn=self, hook=hook)) for hook in self.bot.plugin_manager.connect_hooks ] # TODO stop connecting if a connect hook fails? yield from asyncio.gather(*tasks)
def test_flood(self, clear_cache, freeze_time): chan = "#foo" nick = "foobaruser" nick1 = "foonick" chan1 = "#barchan" herald.herald_cache[chan].update({ nick: "Some herald", nick1: "Other herald," }) herald.herald_cache[chan1].update({ nick: "Someother herald", nick1: "Yet another herald" }) conn = MagicMock(name="fooconn") conn.mock_add_spec(["name", "bot", "action", "message", "notice"]) event = Event(conn=conn, bot=conn.bot, channel=chan, nick=nick) event1 = Event(conn=conn, bot=conn.bot, channel=chan, nick=nick1) event2 = Event( conn=conn, bot=conn.bot, channel=chan1, nick=nick, ) event3 = Event(conn=conn, bot=conn.bot, channel=chan1, nick=nick1) def check(ev): return wrap_hook_response(herald.welcome, ev) assert check(event) == [("message", ("#foo", "\u200b Some herald"))] assert check(event) == [] assert check(event1) == [] assert check(event2) == [("message", ("#barchan", "\u200b Someother herald"))] assert check(event3) == [] freeze_time.tick(timedelta(seconds=10)) # Channel spam time expired assert check(event1) == [("message", ("#foo", "\u200b Other herald,"))] # User spam time still in effect assert check(event) == [] freeze_time.tick(timedelta(minutes=5)) # User spam time expired assert check(event) == [("message", ("#foo", "\u200b Some herald"))]
def _do_test( plugin_name, loader, data_name, cmd, text: Optional[str] = "test _ data", is_nick_valid=None, nick=None, bot_nick=None, ): plugin = importlib.import_module("plugins." + plugin_name) bot = MagicMock() bot.data_dir = "data" bot.loop = asyncio.get_event_loop() event = Event( hook=MagicMock(), bot=bot, conn=MagicMock(), channel="#foo", nick=nick or "foobar", ) if bot_nick: event.conn.nick = bot_nick else: event.conn.nick = "TestBot" if is_nick_valid: event.is_nick_valid = is_nick_valid if loader: _call(getattr(plugin, loader), event) if data_name: assert getattr(plugin, data_name) cmd_func = getattr(plugin, cmd) cmd_event = CommandEvent( text=text or "", cmd_prefix=".", hook=MagicMock(), triggered_command="foo", base_event=event, ) if is_nick_valid: cmd_event.is_nick_valid = is_nick_valid return _call(cmd_func, cmd_event), cmd_event
def _run(self): conn = MagicMock() event = Event(hook=MagicMock(), bot=conn.bot, conn=conn, channel='#foo', nick='foobaruser') return wrap_hook_response(herald.welcome, event)
async def _start_periodic(self, hook): interval = hook.interval initial_interval = hook.initial_interval await asyncio.sleep(initial_interval) while True: event = Event(bot=self.bot, hook=hook) await self.launch(hook, event) await asyncio.sleep(interval)
def _start_periodic(self, hook): interval = hook.interval initial_interval = hook.initial_interval yield from asyncio.sleep(initial_interval) while True: event = Event(bot=self.bot, hook=hook) yield from self.launch(hook, event) yield from asyncio.sleep(interval)
def cmd(cmd, subcmd, *args): calls.append((cmd, subcmd) + args) p = ParamList.parse("* ACK :" + " ".join(args)) cmd_event = Event( irc_paramlist=p, bot=event.bot, conn=event.conn, ) async_util.wrap_future(cap.on_cap(p, cmd_event), loop=event.loop)
def test_event_copy(): from cloudbot.event import Event event = Event(bot=object(), conn=object(), hook=object(), event_type=object(), channel=object(), nick=object(), user=object(), host=object()) new_event = Event(base_event=event) assert event.bot is new_event.bot assert event.conn is new_event.conn assert event.hook is new_event.hook assert event.nick is new_event.nick
def _do_test(plugin_name, loader, data_name, cmd, text='test _ data', is_nick_valid=None, nick=None, bot_nick=None): plugin = importlib.import_module('plugins.' + plugin_name) bot = MagicMock() bot.data_dir = 'data' bot.loop = asyncio.get_event_loop() event = Event(hook=MagicMock(), bot=bot, conn=MagicMock(), channel='#foo', nick=nick or 'foobar') if bot_nick: event.conn.nick = bot_nick else: event.conn.nick = 'TestBot' if is_nick_valid: event.is_nick_valid = is_nick_valid if loader: _call(getattr(plugin, loader), event) if data_name: assert getattr(plugin, data_name) cmd_func = getattr(plugin, cmd) cmd_event = CommandEvent(text=text or '', cmd_prefix='.', hook=MagicMock(), triggered_command='foo', base_event=event) if is_nick_valid: cmd_event.is_nick_valid = is_nick_valid return _call(cmd_func, cmd_event), cmd_event
async def reload_config(self): self.config.load_config() # reload permissions for connection in self.connections.values(): connection.reload() tasks = [ self.plugin_manager.launch(hook, Event(bot=self, hook=hook)) for hook in self.plugin_manager.config_hooks ] await asyncio.gather(*tasks)
async def test_irc_raw_block(self): bot = MockBot() conn = MockConn(nick="bot") event = Event( irc_command="PRIVMSG", event_type=EventType.message, channel="#foo", nick="bar", conn=conn, content=".foo bar", ) event.notice = MagicMock() plugin = MagicMock() run_hooks = [] @hook.irc_raw("PRIVMSG", priority=Priority.HIGH, action=Action.HALTTYPE) async def coro(hook): run_hooks.append(hook) @hook.irc_raw("PRIVMSG", priority=Priority.NORMAL) async def coro1(hook): # pragma: no cover run_hooks.append(hook) full_hook = RawHook(plugin, hook._get_hook(coro, "irc_raw")) full_hook1 = RawHook(plugin, hook._get_hook(coro1, "irc_raw")) bot.plugin_manager.raw_triggers["PRIVMSG"].append(full_hook) bot.plugin_manager.raw_triggers["PRIVMSG"].append(full_hook1) await CloudBot.process(bot, event) assert sorted(run_hooks, key=id) == sorted( [ full_hook, ], key=id, )
def test_get_cmd_regex(): event = Event(channel="TestUser", nick="TestUser", conn=MockConn("Bot")) regex = get_cmd_regex(event) assert textwrap.dedent(regex.pattern) == textwrap.dedent(r""" ^ # Prefix or nick (?: (?P<prefix>[\.])? | Bot[,;:]+\s+ ) (?P<command>\w+) # Command (?:$|\s+) (?P<text>.*) # Text """)
def quit(self, reason=None): if self._quit: return self._quit = True if reason: self.cmd("QUIT", reason) else: self.cmd("QUIT") # Log ourselves quitting quit_event = Event(bot=self.bot, conn=self, event_type=EventType.quit, nick=self.bot_nick) yield from self._process_quit(quit_event)
def test_get_cmd_regex(): from cloudbot.bot import get_cmd_regex from cloudbot.event import Event event = Event(channel='TestUser', nick='TestUser', conn=MockConn('Bot')) regex = get_cmd_regex(event) assert textwrap.dedent(regex.pattern) == textwrap.dedent(r""" ^ # Prefix or nick (?: (?P<prefix>[\.])? | Bot[,;:]+\s+ ) (?P<command>\w+) # Command (?:$|\s+) (?P<text>.*) # Text """)
async def load_plugin(self, path): """ Loads a plugin from the given path and plugin object, then registers all hooks from that plugin. :type path: str | Path """ path = Path(path) file_path = self.safe_resolve(path) file_name = file_path.name # Resolve the path relative to the current directory plugin_path = file_path.relative_to(self.bot.base_dir) title = '.'.join(plugin_path.parts[1:]).rsplit('.', 1)[0] if not self.can_load(title): return # make sure to unload the previously loaded plugin from this path, if it was loaded. if self.get_plugin(file_path): await self.unload_plugin(file_path) module_name = "plugins.{}".format(title) try: plugin_module = self._load_mod(module_name) except Exception: logger.exception("Error loading %s:", title) return # create the plugin plugin = Plugin(str(file_path), file_name, title, plugin_module) # proceed to register hooks # create database tables await plugin.create_tables(self.bot) # run on_start hooks for on_start_hook in plugin.hooks["on_start"]: success = await self.launch( on_start_hook, Event(bot=self.bot, hook=on_start_hook)) if not success: logger.warning( "Not registering hooks from plugin %s: on_start hook errored", plugin.title) # unregister databases plugin.unregister_tables(self.bot) return self._add_plugin(plugin) for on_cap_available_hook in plugin.hooks["on_cap_available"]: for cap in on_cap_available_hook.caps: self.cap_hooks["on_available"][cap.casefold()].append( on_cap_available_hook) self._log_hook(on_cap_available_hook) for on_cap_ack_hook in plugin.hooks["on_cap_ack"]: for cap in on_cap_ack_hook.caps: self.cap_hooks["on_ack"][cap.casefold()].append( on_cap_ack_hook) self._log_hook(on_cap_ack_hook) for periodic_hook in plugin.hooks["periodic"]: task = async_util.wrap_future(self._start_periodic(periodic_hook)) plugin.tasks.append(task) self._log_hook(periodic_hook) # register commands for command_hook in plugin.hooks["command"]: for alias in command_hook.aliases: if alias in self.commands: logger.warning( "Plugin %s attempted to register command %s which was " "already registered by %s. Ignoring new assignment.", plugin.title, alias, self.commands[alias].plugin.title) else: self.commands[alias] = command_hook self._log_hook(command_hook) # register raw hooks for raw_hook in plugin.hooks["irc_raw"]: if raw_hook.is_catch_all(): self.catch_all_triggers.append(raw_hook) else: for trigger in raw_hook.triggers: if trigger in self.raw_triggers: self.raw_triggers[trigger].append(raw_hook) else: self.raw_triggers[trigger] = [raw_hook] self._log_hook(raw_hook) # register events for event_hook in plugin.hooks["event"]: for event_type in event_hook.types: if event_type in self.event_type_hooks: self.event_type_hooks[event_type].append(event_hook) else: self.event_type_hooks[event_type] = [event_hook] self._log_hook(event_hook) # register regexps for regex_hook in plugin.hooks["regex"]: for regex_match in regex_hook.regexes: self.regex_hooks.append((regex_match, regex_hook)) self._log_hook(regex_hook) # register sieves for sieve_hook in plugin.hooks["sieve"]: self.sieves.append(sieve_hook) self._log_hook(sieve_hook) # register connect hooks for connect_hook in plugin.hooks["on_connect"]: self.connect_hooks.append(connect_hook) self._log_hook(connect_hook) for out_hook in plugin.hooks["irc_out"]: self.out_sieves.append(out_hook) self._log_hook(out_hook) for post_hook in plugin.hooks["post_hook"]: self.hook_hooks["post"].append(post_hook) self._log_hook(post_hook) for perm_hook in plugin.hooks["perm_check"]: for perm in perm_hook.perms: self.perm_hooks[perm].append(perm_hook) self._log_hook(perm_hook) # Sort hooks self.regex_hooks.sort(key=lambda x: x[1].priority) dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers, self.perm_hooks, self.hook_hooks) lists_of_hooks = [ self.catch_all_triggers, self.sieves, self.connect_hooks, self.out_sieves ] lists_of_hooks.extend( chain.from_iterable(d.values() for d in dicts_of_lists_of_hooks)) for lst in lists_of_hooks: lst.sort(key=attrgetter("priority")) # we don't need this anymore del plugin.hooks["on_start"]
def parse_line(self, line: str) -> Event: message = Message.parse(line) command = message.command command_params = message.parameters # Reply to pings immediately if command == "PING": self.conn.send("PONG " + command_params[-1], log=False) # Parse the command and params # Content content_raw = _get_param(message, content_params) if content_raw is not None: content = irc_clean(content_raw) else: content = None # Event type event_type = irc_command_to_event_type.get(command, EventType.other) target = _get_param(message, target_params) # Parse for CTCP if event_type is EventType.message and content_raw.startswith("\x01"): possible_ctcp = content_raw[1:] if content_raw.endswith("\x01"): possible_ctcp = possible_ctcp[:-1] if "\x01" in possible_ctcp: logger.debug( "[%s] Invalid CTCP message received, " "treating it as a mornal message", self.conn.name, ) ctcp_text = None else: ctcp_text = possible_ctcp ctcp_text_split = ctcp_text.split(None, 1) if ctcp_text_split[0] == "ACTION": # this is a CTCP ACTION, set event_type and content accordingly event_type = EventType.action content = irc_clean(ctcp_text_split[1]) else: # this shouldn't be considered a regular message event_type = EventType.other else: ctcp_text = None # Channel channel = _get_param(message, chan_params) prefix = message.prefix if prefix is None: nick = None user = None host = None mask = None else: nick = prefix.nick user = prefix.user host = prefix.host mask = prefix.mask if channel: # TODO Migrate plugins to accept the original case of the channel channel = channel.lower() channel = channel.split()[0] # Just in case there is more data # Channel for a PM is the sending user if channel == self.conn.nick.lower(): channel = nick.lower() else: # If the channel isn't set, it's the sending user/server channel = nick.lower() if nick else nick # Set up parsed message # TODO: Do we really want to send the raw `prefix` and `command_params` here? event = Event( bot=self.bot, conn=self.conn, event_type=event_type, content_raw=content_raw, content=content, target=target, channel=channel, nick=nick, user=user, host=host, mask=mask, irc_raw=line, irc_prefix=mask, irc_command=command, irc_paramlist=command_params, irc_ctcp_text=ctcp_text, irc_tags=message.tags, ) return event
def load_plugin(self, path): """ Loads a plugin from the given path and plugin object, then registers all hooks from that plugin. Won't load any plugins listed in "disabled_plugins". :type path: str """ file_path = os.path.abspath(path) file_name = os.path.basename(path) title = os.path.splitext(file_name)[0] if "plugin_loading" in self.bot.config: pl = self.bot.config.get("plugin_loading") if pl.get("use_whitelist", False): if title not in pl.get("whitelist", []): logger.info( 'Not loading plugin module "{}": plugin not whitelisted' .format(file_name)) return else: if title in pl.get("blacklist", []): logger.info( 'Not loading plugin module "{}": plugin blacklisted'. format(file_name)) return # make sure to unload the previously loaded plugin from this path, if it was loaded. if file_name in self.plugins: yield from self.unload_plugin(file_path) module_name = "plugins.{}".format(title) try: plugin_module = importlib.import_module(module_name) # if this plugin was loaded before, reload it if hasattr(plugin_module, "_cloudbot_loaded"): importlib.reload(plugin_module) except Exception: logger.exception("Error loading {}:".format(file_name)) return # create the plugin plugin = Plugin(file_path, file_name, title, plugin_module) # proceed to register hooks # create database tables yield from plugin.create_tables(self.bot) # run on_start hooks for on_start_hook in plugin.run_on_start: success = yield from self.launch( on_start_hook, Event(bot=self.bot, hook=on_start_hook)) if not success: logger.warning( "Not registering hooks from plugin {}: on_start hook errored" .format(plugin.title)) # unregister databases plugin.unregister_tables(self.bot) return self.plugins[plugin.file_name] = plugin for periodic_hook in plugin.periodic: asyncio. async (self._start_periodic(periodic_hook)) self._log_hook(periodic_hook) # register commands for command_hook in plugin.commands: for alias in command_hook.aliases: if alias in self.commands: logger.warning( "Plugin {} attempted to register command {} which was already registered by {}. " "Ignoring new assignment.".format( plugin.title, alias, self.commands[alias].plugin.title)) else: self.commands[alias] = command_hook self._log_hook(command_hook) # register raw hooks for raw_hook in plugin.raw_hooks: if raw_hook.is_catch_all(): self.catch_all_triggers.append(raw_hook) else: for trigger in raw_hook.triggers: if trigger in self.raw_triggers: self.raw_triggers[trigger].append(raw_hook) else: self.raw_triggers[trigger] = [raw_hook] self._log_hook(raw_hook) # register events for event_hook in plugin.events: for event_type in event_hook.types: if event_type in self.event_type_hooks: self.event_type_hooks[event_type].append(event_hook) else: self.event_type_hooks[event_type] = [event_hook] self._log_hook(event_hook) # register regexps for regex_hook in plugin.regexes: for regex_match in regex_hook.regexes: self.regex_hooks.append((regex_match, regex_hook)) self._log_hook(regex_hook) # register sieves for sieve_hook in plugin.sieves: self.sieves.append(sieve_hook) self._log_hook(sieve_hook) # sort sieve hooks by priority self.sieves.sort(key=lambda x: x.priority) # we don't need this anymore del plugin.run_on_start
async def unload_plugin(self, path): """ Unloads the plugin from the given path, unregistering all hooks from the plugin. Returns True if the plugin was unloaded, False if the plugin wasn't loaded in the first place. :type path: str | Path :rtype: bool """ path = Path(path) file_path = self.safe_resolve(path) # make sure this plugin is actually loaded plugin = self.get_plugin(file_path) if not plugin: return False for on_cap_available_hook in plugin.hooks["on_cap_available"]: available_hooks = self.cap_hooks["on_available"] for cap in on_cap_available_hook.caps: cap_cf = cap.casefold() available_hooks[cap_cf].remove(on_cap_available_hook) if not available_hooks[cap_cf]: del available_hooks[cap_cf] for on_cap_ack in plugin.hooks["on_cap_ack"]: ack_hooks = self.cap_hooks["on_ack"] for cap in on_cap_ack.caps: cap_cf = cap.casefold() ack_hooks[cap_cf].remove(on_cap_ack) if not ack_hooks[cap_cf]: del ack_hooks[cap_cf] # unregister commands for command_hook in plugin.hooks["command"]: for alias in command_hook.aliases: if alias in self.commands and self.commands[ alias] == command_hook: # we need to make sure that there wasn't a conflict, so we don't delete another plugin's command del self.commands[alias] # unregister raw hooks for raw_hook in plugin.hooks["irc_raw"]: if raw_hook.is_catch_all(): self.catch_all_triggers.remove(raw_hook) else: for trigger in raw_hook.triggers: assert trigger in self.raw_triggers # this can't be not true self.raw_triggers[trigger].remove(raw_hook) if not self.raw_triggers[ trigger]: # if that was the last hook for this trigger del self.raw_triggers[trigger] # unregister events for event_hook in plugin.hooks["event"]: for event_type in event_hook.types: assert event_type in self.event_type_hooks # this can't be not true self.event_type_hooks[event_type].remove(event_hook) if not self.event_type_hooks[ event_type]: # if that was the last hook for this event type del self.event_type_hooks[event_type] # unregister regexps for regex_hook in plugin.hooks["regex"]: for regex_match in regex_hook.regexes: self.regex_hooks.remove((regex_match, regex_hook)) # unregister sieves for sieve_hook in plugin.hooks["sieve"]: self.sieves.remove(sieve_hook) # unregister connect hooks for connect_hook in plugin.hooks["on_connect"]: self.connect_hooks.remove(connect_hook) for out_hook in plugin.hooks["irc_out"]: self.out_sieves.remove(out_hook) for post_hook in plugin.hooks["post_hook"]: self.hook_hooks["post"].remove(post_hook) for perm_hook in plugin.hooks["perm_check"]: for perm in perm_hook.perms: self.perm_hooks[perm].remove(perm_hook) # Run on_stop hooks for on_stop_hook in plugin.hooks["on_stop"]: event = Event(bot=self.bot, hook=on_stop_hook) await self.launch(on_stop_hook, event) # unregister databases plugin.unregister_tables(self.bot) task_count = len(plugin.tasks) if task_count > 0: logger.debug("Cancelling running tasks in %s", plugin.title) for task in plugin.tasks: task.cancel() logger.info("Cancelled %d tasks from %s", task_count, plugin.title) # remove last reference to plugin self._rem_plugin(plugin) if self.bot.config.get("logging", {}).get("show_plugin_loading", True): logger.info("Unloaded all plugins from %s", plugin.title) return True
def process(self, event): """ :type event: Event """ run_before_tasks = [] tasks = [] command_prefix = event.conn.config.get('command_prefix', '.') halted = False def add_hook(hook, _event, _run_before=False): nonlocal halted if halted: return False coro = self.plugin_manager.launch(hook, _event) if _run_before: run_before_tasks.append(coro) else: tasks.append(coro) if hook.action is Action.HALTALL: halted = True return False elif hook.action is Action.HALTTYPE: return False return True # Raw IRC hook for raw_hook in self.plugin_manager.catch_all_triggers: # run catch-all coroutine hooks before all others - TODO: Make this a plugin argument run_before = not raw_hook.threaded if not add_hook(raw_hook, Event(hook=raw_hook, base_event=event), _run_before=run_before): # The hook has an action of Action.HALT* so stop adding new tasks break if event.irc_command in self.plugin_manager.raw_triggers: for raw_hook in self.plugin_manager.raw_triggers[ event.irc_command]: if not add_hook(raw_hook, Event(hook=raw_hook, base_event=event)): # The hook has an action of Action.HALT* so stop adding new tasks break # Event hooks if event.type in self.plugin_manager.event_type_hooks: for event_hook in self.plugin_manager.event_type_hooks[event.type]: if not add_hook(event_hook, Event(hook=event_hook, base_event=event)): # The hook has an action of Action.HALT* so stop adding new tasks break if event.type is EventType.message: # Commands if event.chan.lower() == event.nick.lower( ): # private message, no command prefix command_re = r'(?i)^(?:[{}]?|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)' else: command_re = r'(?i)^(?:[{}]|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)' cmd_match = re.match( command_re.format(command_prefix, event.conn.nick), event.content_raw) if cmd_match: command = cmd_match.group(1).lower() text = irc_clean(cmd_match.group(2).strip()) if command in self.plugin_manager.commands: command_hook = self.plugin_manager.commands[command] command_event = CommandEvent(hook=command_hook, text=text, triggered_command=command, base_event=event) add_hook(command_hook, command_event) else: potential_matches = [] for potential_match, plugin in self.plugin_manager.commands.items( ): if potential_match.startswith(command): potential_matches.append((potential_match, plugin)) if potential_matches: if len(potential_matches) == 1: command_hook = potential_matches[0][1] command_event = CommandEvent( hook=command_hook, text=text, triggered_command=command, base_event=event) add_hook(command_hook, command_event) else: event.notice("Possible matches: {}".format( formatting.get_text_list([ command for command, plugin in potential_matches ]))) # Regex hooks regex_matched = False for regex, regex_hook in self.plugin_manager.regex_hooks: if not regex_hook.run_on_cmd and cmd_match: continue if regex_hook.only_no_match and regex_matched: continue regex_match = regex.search(event.content) if regex_match: regex_matched = True regex_event = RegexEvent(hook=regex_hook, match=regex_match, base_event=event) if not add_hook(regex_hook, regex_event): # The hook has an action of Action.HALT* so stop adding new tasks break # Run the tasks yield from asyncio.gather(*run_before_tasks, loop=self.loop) yield from asyncio.gather(*tasks, loop=self.loop)
def data_received(self, data): self._input_buffer += data while b"\r\n" in self._input_buffer: line_data, self._input_buffer = self._input_buffer.split(b"\r\n", 1) line = decode(line_data) try: message = Message.parse(line) except Exception: logger.exception( "[%s] Error occurred while parsing IRC line '%s' from %s", self.conn.name, line, self.conn.describe_server() ) continue command = message.command command_params = message.parameters # Reply to pings immediately if command == "PING": self.conn.send("PONG " + command_params[-1], log=False) # Parse the command and params # Content if command_params.has_trail: content_raw = command_params[-1] content = irc_clean(content_raw) else: content_raw = None content = None # Event type event_type = irc_command_to_event_type.get( command, EventType.other ) # Target (for KICK, INVITE) if event_type is EventType.kick: target = command_params[1] elif command in ("INVITE", "MODE"): target = command_params[0] else: # TODO: Find more commands which give a target target = None # Parse for CTCP if event_type is EventType.message and content_raw.startswith("\x01"): possible_ctcp = content_raw[1:] if content_raw.endswith('\x01'): possible_ctcp = possible_ctcp[:-1] if '\x01' in possible_ctcp: logger.debug( "[%s] Invalid CTCP message received, " "treating it as a mornal message", self.conn.name ) ctcp_text = None else: ctcp_text = possible_ctcp ctcp_text_split = ctcp_text.split(None, 1) if ctcp_text_split[0] == "ACTION": # this is a CTCP ACTION, set event_type and content accordingly event_type = EventType.action content = irc_clean(ctcp_text_split[1]) else: # this shouldn't be considered a regular message event_type = EventType.other else: ctcp_text = None # Channel channel = None if command_params: if command in ["NOTICE", "PRIVMSG", "KICK", "JOIN", "PART", "MODE"]: channel = command_params[0] elif command == "INVITE": channel = command_params[1] elif len(command_params) > 2 or not (command_params.has_trail and len(command_params) == 1): channel = command_params[0] prefix = message.prefix if prefix is None: nick = None user = None host = None mask = None else: nick = prefix.nick user = prefix.user host = prefix.host mask = prefix.mask if channel: # TODO Migrate plugins to accept the original case of the channel channel = channel.lower() channel = channel.split()[0] # Just in case there is more data if channel == self.conn.nick.lower(): channel = nick.lower() # Set up parsed message # TODO: Do we really want to send the raw `prefix` and `command_params` here? event = Event( bot=self.bot, conn=self.conn, event_type=event_type, content_raw=content_raw, content=content, target=target, channel=channel, nick=nick, user=user, host=host, mask=mask, irc_raw=line, irc_prefix=mask, irc_command=command, irc_paramlist=command_params, irc_ctcp_text=ctcp_text ) # handle the message, async async_util.wrap_future(self.bot.process(event), loop=self.loop)
def handle_error(event: Event, error): event.reply("Failed to contact thetvdb.com") raise error
async def process(self, event): """ :type event: Event """ run_before_tasks = [] tasks = [] halted = False def add_hook(hook, _event, _run_before=False): nonlocal halted if halted: return False if hook.clients and _event.conn.type not in hook.clients: return True coro = self.plugin_manager.launch(hook, _event) if _run_before: run_before_tasks.append(coro) else: tasks.append(coro) if hook.action is Action.HALTALL: halted = True return False if hook.action is Action.HALTTYPE: return False return True # Raw IRC hook for raw_hook in self.plugin_manager.catch_all_triggers: # run catch-all coroutine hooks before all others - TODO: Make this a plugin argument run_before = not raw_hook.threaded if not add_hook(raw_hook, Event(hook=raw_hook, base_event=event), _run_before=run_before): # The hook has an action of Action.HALT* so stop adding new tasks break if event.irc_command in self.plugin_manager.raw_triggers: for raw_hook in self.plugin_manager.raw_triggers[ event.irc_command]: if not add_hook(raw_hook, Event(hook=raw_hook, base_event=event)): # The hook has an action of Action.HALT* so stop adding new tasks break # Event hooks if event.type in self.plugin_manager.event_type_hooks: for event_hook in self.plugin_manager.event_type_hooks[event.type]: if not add_hook(event_hook, Event(hook=event_hook, base_event=event)): # The hook has an action of Action.HALT* so stop adding new tasks break matched_command = False if event.type is EventType.message: # Commands cmd_match = get_cmd_regex(event).match(event.content) if cmd_match: command_prefix = event.conn.config.get('command_prefix', '.') prefix = cmd_match.group('prefix') or command_prefix[0] command = cmd_match.group('command').lower() text = cmd_match.group('text').strip() cmd_event = partial(CommandEvent, text=text, triggered_command=command, base_event=event, cmd_prefix=prefix) if command in self.plugin_manager.commands: command_hook = self.plugin_manager.commands[command] command_event = cmd_event(hook=command_hook) add_hook(command_hook, command_event) matched_command = True else: potential_matches = [] for potential_match, plugin in self.plugin_manager.commands.items( ): if potential_match.startswith(command): potential_matches.append((potential_match, plugin)) if potential_matches: matched_command = True if len(potential_matches) == 1: command_hook = potential_matches[0][1] command_event = cmd_event(hook=command_hook) add_hook(command_hook, command_event) else: commands = sorted( command for command, plugin in potential_matches) txt_list = formatting.get_text_list(commands) event.notice( "Possible matches: {}".format(txt_list)) if event.type in (EventType.message, EventType.action): # Regex hooks regex_matched = False for regex, regex_hook in self.plugin_manager.regex_hooks: if not regex_hook.run_on_cmd and matched_command: continue if regex_hook.only_no_match and regex_matched: continue regex_match = regex.search(event.content) if regex_match: regex_matched = True regex_event = RegexEvent(hook=regex_hook, match=regex_match, base_event=event) if not add_hook(regex_hook, regex_event): # The hook has an action of Action.HALT* so stop adding new tasks break # Run the tasks await asyncio.gather(*run_before_tasks, loop=self.loop) await asyncio.gather(*tasks, loop=self.loop)
def process(self, event): """ :type event: Event """ run_before_tasks = [] tasks = [] command_prefix = event.conn.config.get('command_prefix', '.') # Raw IRC hook for raw_hook in self.plugin_manager.catch_all_triggers: # run catch-all coroutine hooks before all others - TODO: Make this a plugin argument if not raw_hook.threaded: run_before_tasks.append( self.plugin_manager.launch( raw_hook, Event(hook=raw_hook, base_event=event))) else: tasks.append( self.plugin_manager.launch( raw_hook, Event(hook=raw_hook, base_event=event))) if event.irc_command in self.plugin_manager.raw_triggers: for raw_hook in self.plugin_manager.raw_triggers[ event.irc_command]: tasks.append( self.plugin_manager.launch( raw_hook, Event(hook=raw_hook, base_event=event))) # Event hooks if event.type in self.plugin_manager.event_type_hooks: for event_hook in self.plugin_manager.event_type_hooks[event.type]: tasks.append( self.plugin_manager.launch( event_hook, Event(hook=event_hook, base_event=event))) if event.type is EventType.message: # Commands if event.chan.lower() == event.nick.lower( ): # private message, no command prefix command_re = r'(?i)^(?:[{}]?|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)'.format( command_prefix, event.conn.nick) else: command_re = r'(?i)^(?:[{}]|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)'.format( command_prefix, event.conn.nick) cmd_match = re.match(command_re, event.content) if cmd_match: command = cmd_match.group(1).lower() if command in self.plugin_manager.commands: command_hook = self.plugin_manager.commands[command] command_event = CommandEvent( hook=command_hook, text=cmd_match.group(2).strip(), triggered_command=command, base_event=event) tasks.append( self.plugin_manager.launch(command_hook, command_event)) else: potential_matches = [] for potential_match, plugin in self.plugin_manager.commands.items( ): if potential_match.startswith(command): potential_matches.append((potential_match, plugin)) if potential_matches: if len(potential_matches) == 1: command_hook = potential_matches[0][1] command_event = CommandEvent( hook=command_hook, text=cmd_match.group(2).strip(), triggered_command=command, base_event=event) tasks.append( self.plugin_manager.launch( command_hook, command_event)) else: event.notice("Possible matches: {}".format( formatting.get_text_list([ command for command, plugin in potential_matches ]))) # Regex hooks for regex, regex_hook in self.plugin_manager.regex_hooks: if not regex_hook.run_on_cmd and cmd_match: pass else: regex_match = regex.search(event.content) if regex_match: regex_event = RegexEvent(hook=regex_hook, match=regex_match, base_event=event) tasks.append( self.plugin_manager.launch(regex_hook, regex_event)) # Run the tasks yield from asyncio.gather(*run_before_tasks, loop=self.loop) yield from asyncio.gather(*tasks, loop=self.loop)