async def me(text: str, conn: IrcClient, chan: str, nick: str, event: CommandEvent) -> None: """ [#channel] <action> - acts out <action> in a [#channel], or in the current channel of none is specified """ channel, text = get_chan(chan, text) event.admin_log('{} used ME to make me ACT "{}" in {}.'.format( nick, text, channel)) conn.ctcp(channel, "ACTION", text)
def test_crypto_cmd(mock_requests): init_response(mock_requests) conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text="BTC USD", cmd_prefix=".", triggered_command="crypto", hook=MagicMock(), bot=conn.bot, conn=conn, channel="#foo", nick="foobaruser", ) res = wrap_hook_response(cryptocurrency.crypto_command, event) assert res == [ HookResult( return_type="return", value="BTC (bitcoin) // \x0307$50,000,000,000.00\x0f USD - 2.0000000 BTC // \x0303+18.9%\x0f change", ) ]
def wrap_event(_hook, event, cmd, args): cmd_event = CommandEvent(base_event=event, text=args.strip(), triggered_command=cmd, hook=_hook, cmd_prefix='') return cmd_event
def test_crypto_cmd(mock_requests): init_response(mock_requests) conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text='BTC USD', cmd_prefix='.', triggered_command='crypto', hook=MagicMock(), bot=conn.bot, conn=conn, channel='#foo', nick='foobaruser', ) res = wrap_hook_response(cryptocurrency.crypto_command, event) assert res == [ HookResult( return_type='return', value= 'BTC (bitcoin) // \x0307$50,000,000,000.00\x0f USD - 2.0000000 BTC // \x0303+18.9%\x0f change', ) ]
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 _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 test_parse_no_results(mock_requests, patch_try_shorten, mock_db): mock_requests.add('GET', 'https://maps.googleapis.com/maps/api/geocode/json', json={ 'status': 'OK', 'results': [], }) from plugins import weather weather.table.create(mock_db.engine, True) bot = MockBot( { 'api_keys': { 'google_dev_key': 'AIzatestapikey', 'darksky': 'abc12345' * 4, } }, mock_db) weather.create_maps_api(bot) conn = MagicMock() conn.config = {} conn.bot = bot cmd_event = CommandEvent(text='myloc', cmd_prefix='.', triggered_command='we', hook=MagicMock(), bot=bot, conn=conn, channel='#foo', nick='foobaruser') cmd_event.hook.required_args = ['event', 'db'] cmd_event.hook.doc = "- foobar" cmd_event.prepare_threaded() res = wrap_hook_response(weather.check_and_parse, cmd_event) assert res == [('return', (None, "Unable to find location 'myloc'"))]
def call(self, text: str, results=None): event = CommandEvent( cmd_prefix=".", hook=MagicMock(), text=text, triggered_command="tv", conn=MagicMock(), channel="#foo", nick="nick", ) return wrap_hook_response(self.get_func(), event, results=results)
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
def do_search(query, results=None): conn = MagicMock() conn.config = {} conn.bot = None cmd_event = CommandEvent(text=query, cmd_prefix='.', triggered_command='wiki', hook=MagicMock(), bot=conn.bot, conn=conn, channel='#foo', nick='foobaruser') return wrap_hook_response(wikipedia.wiki, cmd_event, results=results)
def _run_alias(): conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text="", cmd_prefix=".", triggered_command="btc", hook=MagicMock(), bot=conn.bot, conn=conn, channel="#foo", nick="foobaruser", ) return wrap_hook_response(cryptocurrency.btc_alias, event)
def _run_alias(): conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text='', cmd_prefix='.', triggered_command='btc', hook=MagicMock(), bot=conn.bot, conn=conn, channel='#foo', nick='foobaruser', ) return wrap_hook_response(cryptocurrency.btc_alias, event)
def test_rounding(mock_requests, patch_try_shorten, mock_db): from plugins import weather bot = setup_api(mock_requests, mock_db) conn = MagicMock() conn.config = {} conn.bot = bot cmd_event = CommandEvent(text='', cmd_prefix='.', triggered_command='we', hook=MagicMock(), bot=bot, conn=conn, channel='#foo', nick='foobar') weather.location_cache.append(('foobar', 'test location')) new_data = deepcopy(FIO_DATA) new_data['json']['currently']['temperature'] = 31.9 mock_requests.add(mock_requests.GET, re.compile(r'^https://api\.darksky\.net/forecast/.*'), **new_data) out_text = ( '(foobar) \x02Current\x02: foobar, 32F/0C\x0f; \x02High\x02: 64F/18C\x0f; ' '\x02Low\x02: 57F/14C\x0f; \x02Humidity\x02: 45%\x0f; ' '\x02Wind\x02: 12MPH/20KPH SE\x0f ' '-- 123 Test St, Example City, CA - ' '\x1fhttps://darksky.net/forecast/30.123,123.456\x0f ' '(\x1dTo get a forecast, use .fc\x1d)') calls = [( 'message', ( '#foo', out_text, ), {}, )] assert wrap_hook_response(weather.weather, cmd_event) == calls
async def test_launch( mock_manager, patch_import_module, do_sieve, sieve_allow, single_thread ): called = False sieve_called = False @hook.command('test', do_sieve=do_sieve, singlethread=single_thread) def foo_cb(): nonlocal called called = True @hook.sieve() def sieve_cb(_bot, _event, _hook): nonlocal sieve_called sieve_called = True if sieve_allow: return _event return None mod = MockModule() mod.sieve_cb = sieve_cb mod.foo_cb = foo_cb patch_import_module.return_value = mod await mock_manager.load_plugin('plugins/test.py') from cloudbot.event import CommandEvent event = CommandEvent( bot=mock_manager.bot, hook=mock_manager.commands['test'], cmd_prefix='.', text='', triggered_command='test', ) result = await mock_manager.launch(event.hook, event) assert result == called and called == (sieve_allow or not do_sieve) assert sieve_called == do_sieve
def test_crypto_cmd_bad_fiat(mock_requests): init_response(mock_requests, quote=False) conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text='BTC ABC', cmd_prefix='.', triggered_command='crypto', hook=MagicMock(), bot=conn.bot, conn=conn, channel='#foo', nick='foobaruser', ) res = wrap_hook_response(cryptocurrency.crypto_command, event) assert res == [HookResult('return', "Unknown fiat currency 'ABC'")]
async def test_join(input_text, chan, key): conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text=input_text, cmd_prefix=".", triggered_command="join", hook=MagicMock(), bot=conn.bot, conn=conn, channel="#foo", nick="foobaruser", ) await async_util.run_func_with_args(asyncio.get_event_loop(), admin_bot.join, event) event.conn.join.assert_called_with(chan, key)
def test_crypto_cmd_bad_fiat(mock_requests): init_response(mock_requests, quote=False) conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text="BTC ABC", cmd_prefix=".", triggered_command="crypto", hook=MagicMock(), bot=conn.bot, conn=conn, channel="#foo", nick="foobaruser", ) res = wrap_hook_response(cryptocurrency.crypto_command, event) assert res == [HookResult("return", "Unknown fiat currency 'ABC'")]
def test_cmd_api_error(mock_requests): init_response(mock_requests, error_msg="FooBar") conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text="BTC USD", cmd_prefix=".", triggered_command="crypto", hook=MagicMock(), bot=conn.bot, conn=conn, channel="#foo", nick="foobaruser", ) res = [] with pytest.raises(cryptocurrency.APIError, match="FooBar"): wrap_hook_response(cryptocurrency.crypto_command, event, res) assert res == [HookResult("message", ("#foo", "(foobaruser) Unknown API error"))]
def make_command_event(chan: Optional[str] = "#foo") -> CommandEvent: conn = MagicMock() conn.name = "foobarconn" conn.config = {} conn.bot = MagicMock() hook = MagicMock() hook.type = "command" hook.function_name = "foo" event = CommandEvent( text="bar", cmd_prefix=".", triggered_command="foo", hook=hook, bot=conn.bot, conn=conn, channel=chan, nick="foobaruser", user="******", host="host", ) return event
def test_cmd_api_error(mock_requests): init_response(mock_requests, error_msg='FooBar') conn = MagicMock() conn.config = {} conn.bot = None event = CommandEvent( text='BTC USD', cmd_prefix='.', triggered_command='crypto', hook=MagicMock(), bot=conn.bot, conn=conn, channel='#foo', nick='foobaruser', ) res = [] with pytest.raises(cryptocurrency.APIError, match='FooBar'): wrap_hook_response(cryptocurrency.crypto_command, event, res) assert res == [ HookResult('message', ('#foo', '(foobaruser) Unknown API error')) ]
def test_rate_limit_command() -> None: conn = MagicMock() conn.name = "foobarconn" conn.config = {} conn.bot = MagicMock() hook = MagicMock() hook.type = "command" event = CommandEvent( text="bar", cmd_prefix=".", triggered_command="foo", hook=hook, bot=conn.bot, conn=conn, channel="#foo", nick="foobaruser", ) for _ in range(3): res = core_sieve.rate_limit(event.bot, event, event.hook) assert res is event res = core_sieve.rate_limit(event.bot, event, event.hook) assert res is None
def test_find_location(mock_requests, patch_try_shorten, mock_db): from plugins import weather bot = MockBot({}, mock_db) weather.create_maps_api(bot) weather.location_cache.clear() assert weather.data.maps_api is None bot = setup_api(mock_requests, mock_db) assert weather.find_location('Foo Bar') == { 'lat': 30.123, 'lng': 123.456, 'address': '123 Test St, Example City, CA', } conn = MagicMock() conn.config = {} conn.bot = bot cmd_event = CommandEvent(text='', cmd_prefix='.', triggered_command='we', hook=MagicMock(), bot=bot, conn=conn, channel='#foo', nick='foobar') cmd_event.hook.required_args = ['db'] cmd_event.hook.doc = "- foobar" cmd_event.prepare_threaded() assert wrap_hook_response(weather.weather, cmd_event) == [ ('notice', ('foobar', '.we - foobar'), {}) ] weather.location_cache.append(('foobar', 'test location')) mock_requests.add(mock_requests.GET, re.compile(r'^https://api\.darksky\.net/forecast/.*'), **FIO_DATA) assert wrap_hook_response(weather.weather, cmd_event) == [( 'message', ( '#foo', '(foobar) \x02Current\x02: foobar, 68F/20C\x0f; \x02High\x02: 64F/18C\x0f; ' '\x02Low\x02: 57F/14C\x0f; \x02Humidity\x02: 45%\x0f; ' '\x02Wind\x02: 12MPH/20KPH SE\x0f ' '-- 123 Test St, Example City, CA - ' '\x1fhttps://darksky.net/forecast/30.123,123.456\x0f ' '(\x1dTo get a forecast, use .fc\x1d)', ), {}, )] assert wrap_hook_response(weather.forecast, cmd_event) == [( 'message', ( '#foo', '(foobar) \x02Today\x02: foobar; High: 64F/18C; Low: 57F/14C; ' 'Humidity: 45%; Wind: 15MPH/24KPH SE | ' '\x02Tomorrow\x02: foobar; High: 64F/18C; ' 'Low: 57F/14C; Humidity: 45%; Wind: 15MPH/24KPH SE ' '-- 123 Test St, Example City, CA - ' '\x1fhttps://darksky.net/forecast/30.123,123.456\x0f', ), {}, )] mock_requests.reset() mock_requests.add(mock_requests.GET, 'https://maps.googleapis.com/maps/api/geocode/json', json={'status': 'foobar'}) response = [] with pytest.raises(ApiError): wrap_hook_response(weather.weather, cmd_event, response) assert response == [('message', ('#foo', '(foobar) API Error occurred.'), {})] bot.config['api_keys']['google_dev_key'] = None bot.config.load_config() weather.create_maps_api(bot) assert wrap_hook_response(weather.weather, cmd_event) == [ ('return', 'This command requires a Google Developers Console API key.') ] assert wrap_hook_response(weather.forecast, cmd_event) == [ ('return', 'This command requires a Google Developers Console API key.') ] bot.config['api_keys']['darksky'] = None bot.config.load_config() weather.create_maps_api(bot) assert wrap_hook_response(weather.weather, cmd_event) == [ ('return', 'This command requires a DarkSky API key.') ] assert wrap_hook_response(weather.forecast, cmd_event) == [ ('return', 'This command requires a DarkSky API key.') ] # Test DB storage bot.config.update({ 'api_keys': { 'google_dev_key': 'AIzatestapikey', 'darksky': 'abc12345' * 4, } }) bot.config.load_config() weather.create_maps_api(bot) weather.table.create(mock_db.engine, checkfirst=True) cmd_event.db = mock_db.session() cmd_event.text = 'my location' weather.load_cache(mock_db.session()) mock_requests.reset() setup_api(mock_requests, mock_db) mock_requests.add(mock_requests.GET, re.compile(r'^https://api\.darksky\.net/forecast/.*'), **FIO_DATA) (loc, data), err = call_with_args(weather.check_and_parse, cmd_event) assert loc == { 'address': '123 Test St, Example City, CA', 'lat': 30.123, 'lng': 123.456 } assert data is not None assert err is None assert weather.location_cache == [(cmd_event.nick, cmd_event.text)] db_data = mock_db.session().execute(weather.table.select()).fetchall() assert len(db_data) == 1 assert list(db_data[0]) == [cmd_event.nick, cmd_event.text]
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 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)