def handle_names(bot, trigger): """Handle NAMES response, happens when joining to channels.""" names = trigger.split() #TODO specific to one channel type. See issue 281. channels = re.search('(#\S*)', trigger.raw) if not channels: return channel = Identifier(channels.group(1)) if channel not in bot.privileges: bot.privileges[channel] = dict() # This could probably be made flexible in the future, but I don't think # it'd be worth it. mapping = {'+': sopel.module.VOICE, '%': sopel.module.HALFOP, '@': sopel.module.OP, '&': sopel.module.ADMIN, '~': sopel.module.OWNER} for name in names: priv = 0 for prefix, value in iteritems(mapping): if prefix in name: priv = priv | value nick = Identifier(name.lstrip(''.join(mapping.keys()))) bot.privileges[channel][nick] = priv
def search_url_callbacks(self, url): """Yield callbacks found for ``url`` matching their regex pattern :param str url: URL found in a trigger :return: yield 2-value tuples of ``(callback, match)`` For each pattern that matches the ``url`` parameter, it yields a 2-value tuple of ``(callable, match)`` for that pattern. The ``callable`` is the one registered with :meth:`register_url_callback`, and the ``match`` is the result of the regex pattern's ``search`` method. .. versionadded:: 7.0 .. seealso:: The Python documentation for the `re.search`__ function and the `match object`__. .. __: https://docs.python.org/3.6/library/re.html#re.search .. __: https://docs.python.org/3.6/library/re.html#match-objects """ for regex, function in tools.iteritems(self.memory['url_callbacks']): match = regex.search(url) if match: yield function, match
def _modules(self): home = os.getcwd() modules_dir = os.path.join(home, 'modules') filenames = sopel.loader.enumerate_modules(self) os.sys.path.insert(0, modules_dir) for name, mod_spec in iteritems(filenames): path, type_ = mod_spec try: module, _ = sopel.loader.load_module(name, path, type_) except Exception as e: filename, lineno = sopel.tools.get_raising_file_and_line() rel_path = os.path.relpath(filename, os.path.dirname(__file__)) raising_stmt = "%s:%d" % (rel_path, lineno) stderr("Error loading %s: %s (%s)" % (name, e, raising_stmt)) else: if hasattr(module, 'configure'): prompt = name + ' module' if module.__doc__: doc = module.__doc__.split('\n', 1)[0] if doc: prompt = doc prompt = 'Configure {} (y/n)? [n]'.format(prompt) do_configure = get_input(prompt) do_configure = do_configure and do_configure.lower() == 'y' if do_configure: module.configure(self) self.save()
def _clean_cache(bot): """Cleans up old entries in URL safety cache.""" if bot.memory['safety_cache_lock'].acquire(False): LOGGER.info('Starting safety cache cleanup...') try: # clean up by age first cutoff = time.time() - (7 * 24 * 60 * 60) # 7 days ago old_keys = [] for key, data in tools.iteritems(bot.memory['safety_cache']): if data['fetched'] <= cutoff: old_keys.append(key) for key in old_keys: bot.memory['safety_cache'].pop(key, None) # clean up more values if the cache is still too big overage = len(bot.memory['safety_cache']) - cache_limit if overage > 0: extra_keys = sorted( (data.fetched, key) for (key, data) in bot.memory['safety_cache'].items())[:overage] for (_, key) in extra_keys: bot.memory['safety_cache'].pop(key, None) finally: # No matter what errors happen (or not), release the lock bot.memory['safety_cache_lock'].release() LOGGER.info('Safety cache cleanup finished.') else: LOGGER.info('Skipping safety cache cleanup: Cache is locked, ' 'cleanup already running.')
def handle_names(bot, trigger): """Handle NAMES response, happens when joining to channels.""" names = trigger.split() #TODO specific to one channel type. See issue 281. channels = re.search('(#\S*)', trigger.raw) if not channels: return channel = Identifier(channels.group(1)) if channel not in bot.privileges: bot.privileges[channel] = dict() # This could probably be made flexible in the future, but I don't think # it'd be worth it. mapping = { '+': sopel.module.VOICE, '%': sopel.module.HALFOP, '@': sopel.module.OP, '&': sopel.module.ADMIN, '~': sopel.module.OWNER } for name in names: priv = 0 for prefix, value in iteritems(mapping): if prefix in name: priv = priv | value nick = Identifier(name.lstrip(''.join(mapping.keys()))) bot.privileges[channel][nick] = priv
def search_url_callbacks(self, url): """Yield callbacks found for ``url`` matching their regex pattern. :param str url: URL found in a trigger :return: yield 2-value tuples of ``(callback, match)`` For each pattern that matches the ``url`` parameter, it yields a 2-value tuple of ``(callable, match)`` for that pattern. The ``callable`` is the one registered with :meth:`register_url_callback`, and the ``match`` is the result of the regex pattern's ``search`` method. .. versionadded:: 7.0 .. seealso:: The Python documentation for the `re.search`__ function and the `match object`__. .. __: https://docs.python.org/3.6/library/re.html#re.search .. __: https://docs.python.org/3.6/library/re.html#match-objects """ if 'url_callbacks' not in self.memory: # nothing to search return for regex, function in tools.iteritems(self.memory['url_callbacks']): match = regex.search(url) if match: yield function, match
def reload_module_tree(bot, name, seen=None, silent=False): from types import ModuleType old_module = sys.modules[name] if seen is None: seen = {} if name not in seen: seen[name] = [] old_callables = {} for obj_name, obj in iteritems(vars(old_module)): if callable(obj): if (getattr(obj, '__name__', None) == 'shutdown' and obj in bot.shutdown_methods): # If this is a shutdown method, call it first. try: stderr( "calling %s.%s" % ( obj.__module__, obj.__name__, ) ) obj(bot) except Exception as e: stderr( "Error calling shutdown method for module %s:%s" % ( obj.__module__, e ) ) bot.unregister(obj) elif (type(obj) is ModuleType and obj.__name__.startswith(name + '.') and obj.__name__ not in sys.builtin_module_names): # recurse into submodules, see issue 1056 if obj not in seen[name]: seen[name].append(obj) reload(obj) reload_module_tree(bot, obj.__name__, seen, silent) modules = sopel.loader.enumerate_modules(bot.config) if name not in modules: return # Only reload the top-level module, once recursion is finished # Also remove all references to sopel callables from top level of the # module, so that they will not get loaded again if reloading the # module does not override them. for obj_name in old_callables.keys(): delattr(old_module, obj_name) # Also delete the setup function # Sub-modules shouldn't have setup functions, so do after the recursion check if hasattr(old_module, "setup"): delattr(old_module, "setup") path, type_ = modules[name] load_module(bot, name, path, type_, silent)
def recieve_cap_ls_reply(bot, trigger): if bot.server_capabilities: # We've already seen the results, so someone sent CAP LS from a module. # We're too late to do SASL, and we don't want to send CAP END before # the module has done what it needs to, so just return return for cap in trigger.split(): c = cap.split('=') if len(c) == 2: batched_caps[c[0]] = c[1] else: batched_caps[c[0]] = None # Not the last in a multi-line reply. First two args are * and LS. if trigger.args[2] == '*': return bot.server_capabilities = batched_caps # If some other module requests it, we don't need to add another request. # If some other module prohibits it, we shouldn't request it. if 'multi-prefix' not in bot._cap_reqs: # Whether or not the server supports multi-prefix doesn't change how we # parse it, so we don't need to worry if it fails. bot._cap_reqs['multi-prefix'] = (['', 'coretasks', None, None],) for cap, reqs in iteritems(bot._cap_reqs): # At this point, we know mandatory and prohibited don't co-exist, but # we need to call back for optionals if they're also prohibited prefix = '' for entry in reqs: if prefix == '-' and entry[0] != '-': entry[2](bot, entry[0] + cap) continue if entry[0]: prefix = entry[0] # It's not required, or it's supported, so we can request it if prefix != '=' or cap in bot.server_capabilities: # REQs fail as a whole, so we send them one capability at a time bot.write(('CAP', 'REQ', entry[0] + cap)) # If it's required but not in server caps, we need to call all the # callbacks else: for entry in reqs: if entry[2] and entry[0] == '=': entry[2](bot, entry[0] + cap) # If we want to do SASL, we have to wait before we can send CAP END. So if # we are, wait on 903 (SASL successful) to send it. if bot.config.core.auth_method == 'sasl': bot.write(('CAP', 'REQ', 'sasl')) else: bot.write(('CAP', 'END'))
def recieve_cap_ls_reply(bot, trigger): if bot.server_capabilities: # We've already seen the results, so someone sent CAP LS from a module. # We're too late to do SASL, and we don't want to send CAP END before # the module has done what it needs to, so just return return for cap in trigger.split(): c = cap.split('=') if len(c) == 2: batched_caps[c[0]] = c[1] else: batched_caps[c[0]] = None # Not the last in a multi-line reply. First two args are * and LS. if trigger.args[2] == '*': return bot.server_capabilities = batched_caps # If some other module requests it, we don't need to add another request. # If some other module prohibits it, we shouldn't request it. if 'multi-prefix' not in bot._cap_reqs: # Whether or not the server supports multi-prefix doesn't change how we # parse it, so we don't need to worry if it fails. bot._cap_reqs['multi-prefix'] = (['', 'coretasks', None, None], ) for cap, reqs in iteritems(bot._cap_reqs): # At this point, we know mandatory and prohibited don't co-exist, but # we need to call back for optionals if they're also prohibited prefix = '' for entry in reqs: if prefix == '-' and entry[0] != '-': entry[2](bot, entry[0] + cap) continue if entry[0]: prefix = entry[0] # It's not required, or it's supported, so we can request it if prefix != '=' or cap in bot.server_capabilities: # REQs fail as a whole, so we send them one capability at a time bot.write(('CAP', 'REQ', entry[0] + cap)) # If it's required but not in server caps, we need to call all the # callbacks else: for entry in reqs: if entry[2] and entry[0] == '=': entry[2](bot, entry[0] + cap) # If we want to do SASL, we have to wait before we can send CAP END. So if # we are, wait on 903 (SASL successful) to send it. if bot.config.core.auth_method == 'sasl': bot.write(('CAP', 'REQ', 'sasl')) else: bot.write(('CAP', 'END'))
def reload(bot, trigger): """Reloads a module, for use by admins only.""" if not trigger.owner or not trigger.is_privmsg: return name = trigger.group(2) if not name or name == '*' or name.upper() == 'ALL THE THINGS': for module in bot.config.core.enable: try: module = sys.modules[module] if hasattr(module, 'unload'): module.unload(bot) except: pass bot._callables = { 'high': collections.defaultdict(list), 'medium': collections.defaultdict(list), 'low': collections.defaultdict(list) } bot._command_groups = collections.defaultdict(list) bot.setup() return bot.notice('Modules reloaded', trigger.nick) if name not in sys.modules: return bot.reply('{}: not loaded, try the `load` command'.format(name), trigger.nick) old_module = sys.modules[name] if hasattr(old_module, 'unload'): old_module.unload(bot) old_callables = {} for obj_name, obj in iteritems(vars(old_module)): bot.unregister(obj) # Also remove all references to sopel callables from top level of the # module, so that they will not get loaded again if reloading the # module does not override them. for obj_name in old_callables.keys(): delattr(old_module, obj_name) # Also delete the setup function if hasattr(old_module, "setup"): delattr(old_module, "setup") modules = sopel.loader.enumerate_modules(bot.config) path, type_ = modules[name] load_module(bot, name, path, type_, trigger.nick)
def dump_database(filename, data): """Dump the remind database into a file :param str filename: absolute path to the remind database file :param dict data: remind database to dump The remind database is dumped into a plain text file (utf-8 encoded) as tab-separated columns of data: unixtime, channel, nick, and message. If the file does not exist, it is created. """ with codecs.open(filename, 'w', encoding='utf-8') as database: for unixtime, reminders in tools.iteritems(data): for channel, nick, message in reminders: line = '%s\t%s\t%s\t%s\n' % (unixtime, channel, nick, message) database.write(line)
def check_callbacks(bot, trigger, url, run=True): """ Check the given URL against the callbacks list. If it matches, and ``run`` is given as ``True``, run the callback function, otherwise pass. Returns ``True`` if the url matched anything in the callbacks list. """ # Check if it matches the exclusion list first matched = any(regex.search(url) for regex in bot.memory['url_exclude']) # Then, check if there's anything in the callback list for regex, function in tools.iteritems(bot.memory['url_callbacks']): match = regex.search(url) if match: if run: function(bot, trigger, match) matched = True return matched
def handle_names(bot, trigger): """Handle NAMES responses. This function keeps track of users' privileges when Sopel joins channels. """ names = trigger.split() # TODO specific to one channel type. See issue 281. channels = re.search(r'(#\S*)', trigger.raw) if not channels: return channel = Identifier(channels.group(1)) if channel not in bot.privileges: bot.privileges[channel] = {} if channel not in bot.channels: bot.channels[channel] = target.Channel(channel) # This could probably be made flexible in the future, but I don't think # it'd be worth it. # If this ever needs to be updated, remember to change the mode handling in # the WHO-handler functions below, too. mapping = { "+": module.VOICE, "%": module.HALFOP, "@": module.OP, "&": module.ADMIN, "~": module.OWNER, "!": module.OPER, } for name in names: priv = 0 for prefix, value in iteritems(mapping): if prefix in name: priv = priv | value nick = Identifier(name.lstrip(''.join(mapping.keys()))) bot.privileges[channel][nick] = priv user = bot.users.get(nick) if user is None: # It's not possible to set the username/hostname from info received # in a NAMES reply, unfortunately. # Fortunately, the user should already exist in bot.users by the # time this code runs, so this is 99.9% ass-covering. user = target.User(nick, None, None) bot.users[nick] = user bot.channels[channel].add_user(user, privs=priv)
def check_callbacks(bot, trigger, url, run=True): """ Check the given URL against the callbacks list. If it matches, and ``run`` is given as ``True``, run the callback function, otherwise pass. Returns ``True`` if the url matched anything in the callbacks list. """ # Check if it matches the exclusion list first matched = any(regex.search(url) for regex in bot.memory["url_exclude"]) # Then, check if there's anything in the callback list for regex, function in tools.iteritems(bot.memory["url_callbacks"]): match = regex.search(url) if match: # Always run ones from @url; they don't run on their own. if run or hasattr(function, "url_regex"): function(bot, trigger, match) matched = True return matched
def f_reload(bot, trigger): """Reloads a module, for use by admins only.""" if not trigger.admin: return name = trigger.group(2) if name == bot.config.core.owner: return bot.reply('What?') if not name or name == '*' or name.upper() == 'ALL THE THINGS': bot._callables = { 'high': collections.defaultdict(list), 'medium': collections.defaultdict(list), 'low': collections.defaultdict(list) } bot.command_groups = collections.defaultdict(list) bot.setup() # return bot.reply('done') return bot.reply(random.choice(['Good to go!', 'Waiting for orders!', 'Ready!'])) # Custom line if name not in sys.modules: return bot.reply('%s: not loaded, try the `load` command' % name) old_module = sys.modules[name] old_callables = {} for obj_name, obj in iteritems(vars(old_module)): bot.unregister(obj) # Also remove all references to sopel callables from top level of the # module, so that they will not get loaded again if reloading the # module does not override them. for obj_name in old_callables.keys(): delattr(old_module, obj_name) # Also delete the setup function if hasattr(old_module, "setup"): delattr(old_module, "setup") modules = sopel.loader.enumerate_modules(bot.config) path, type_ = modules[name] load_module(bot, name, path, type_)
def f_reload(bot, trigger): """Reloads a module, for use by admins only.""" if not trigger.admin: return name = trigger.group(2) if not name or name == '*' or name.upper() == 'ALL THE THINGS': bot._callables = { 'high': collections.defaultdict(list), 'medium': collections.defaultdict(list), 'low': collections.defaultdict(list) } bot._command_groups = collections.defaultdict(list) bot.setup() return bot.reply('done') if name not in sys.modules: return bot.reply('"%s" not loaded, try the `load` command' % name) old_module = sys.modules[name] old_callables = {} for obj_name, obj in iteritems(vars(old_module)): bot.unregister(obj) # Also remove all references to sopel callables from top level of the # module, so that they will not get loaded again if reloading the # module does not override them. for obj_name in old_callables.keys(): delattr(old_module, obj_name) # Also delete the setup function if hasattr(old_module, "setup"): delattr(old_module, "setup") modules = sopel.loader.enumerate_modules(bot.config) if name not in modules: return bot.reply('"%s" not loaded, try the `load` command' % name) path, type_ = modules[name] load_module(bot, name, path, type_)
def handle_names(bot, trigger): """Handle NAMES response, happens when joining to channels.""" names = trigger.split() # TODO specific to one channel type. See issue 281. channels = re.search(r'(#\S*)', trigger.raw) if not channels: return channel = Identifier(channels.group(1)) if channel not in bot.privileges: bot.privileges[channel] = dict() if channel not in bot.channels: bot.channels[channel] = Channel(channel) # This could probably be made flexible in the future, but I don't think # it'd be worth it. # If this ever needs to be updated, remember to change the mode handling in # the WHO-handler functions below, too. mapping = {'+': sopel.module.VOICE, '%': sopel.module.HALFOP, '@': sopel.module.OP, '&': sopel.module.ADMIN, '~': sopel.module.OWNER} for name in names: priv = 0 for prefix, value in iteritems(mapping): if prefix in name: priv = priv | value nick = Identifier(name.lstrip(''.join(mapping.keys()))) bot.privileges[channel][nick] = priv user = bot.users.get(nick) if user is None: # It's not possible to set the username/hostname from info received # in a NAMES reply, unfortunately. # Fortunately, the user should already exist in bot.users by the # time this code runs, so this is 99.9% ass-covering. user = User(nick, None, None) bot.users[nick] = user bot.channels[channel].add_user(user, privs=priv)
def handle_names(bot, trigger): """Handle NAMES response, happens when joining to channels.""" names = trigger.split() #TODO specific to one channel type. See issue 281. channels = re.search('(#\S*)', trigger.raw) if not channels: return channel = Identifier(channels.group(1)) if channel not in bot.privileges: bot.privileges[channel] = dict() bot.init_ops_list(channel) # This could probably be made flexible in the future, but I don't think # it'd be worth it. mapping = {'+': sopel.module.VOICE, '%': sopel.module.HALFOP, '@': sopel.module.OP, '&': sopel.module.ADMIN, '~': sopel.module.OWNER} for name in names: priv = 0 for prefix, value in iteritems(mapping): if prefix in name: priv = priv | value nick = Identifier(name.lstrip(''.join(mapping.keys()))) bot.privileges[channel][nick] = priv # Old op list maintenance is down here, and should be removed at some # point if '@' in name or '~' in name or '&' in name: bot.add_op(channel, name.lstrip('@&%+~')) bot.add_halfop(channel, name.lstrip('@&%+~')) bot.add_voice(channel, name.lstrip('@&%+~')) elif '%' in name: bot.add_halfop(channel, name.lstrip('@&%+~')) bot.add_voice(channel, name.lstrip('@&%+~')) elif '+' in name: bot.add_voice(channel, name.lstrip('@&%+~'))
def f_reload(bot, trigger): """Reloads a module, for use by admins only.""" if not trigger.admin: return name = trigger.group(2) if not name or name == "*" or name.upper() == "ALL THE THINGS": bot._callables = { "high": collections.defaultdict(list), "medium": collections.defaultdict(list), "low": collections.defaultdict(list), } bot._command_groups = collections.defaultdict(list) bot.setup() return bot.reply("done") if name not in sys.modules: return bot.reply("%s: not loaded, try the `load` command" % name) old_module = sys.modules[name] old_callables = {} for obj_name, obj in iteritems(vars(old_module)): bot.unregister(obj) # Also remove all references to sopel callables from top level of the # module, so that they will not get loaded again if reloading the # module does not override them. for obj_name in old_callables.keys(): delattr(old_module, obj_name) # Also delete the setup function if hasattr(old_module, "setup"): delattr(old_module, "setup") modules = sopel.loader.enumerate_modules(bot.config) path, type_ = modules[name] load_module(bot, name, path, type_)
def process_urls(bot, trigger, urls): results = [] for url in urls: try: url = web.iri_to_uri(url) except: pass title = None for regex, function in tools.iteritems(bot.memory['url_callbacks']): match = regex.search(url) if match is not None: title = function(bot, trigger, match) if title: results.append((title, get_hostname(url))) break if not title: title = find_title(url, verify=bot.config.core.verify_ssl) if title: results.append((title, get_hostname(url))) return results
def f_reload(bot, trigger): """Reloads a module, for use by admins only.""" if not trigger.admin: return name = trigger.group(2) if name == bot.config.core.owner: return bot.reply('What?') if not name or name == '*' or name.upper() == 'ALL THE THINGS': bot.callables = None bot.commands = None bot.setup() return bot.reply('done') if name not in sys.modules: return bot.reply('%s: not loaded, try the `load` command' % name) old_module = sys.modules[name] old_callables = {} for obj_name, obj in iteritems(vars(old_module)): bot.unregister(obj) # Also remove all references to sopel callables from top level of the # module, so that they will not get loaded again if reloading the # module does not override them. for obj_name in old_callables.keys(): delattr(old_module, obj_name) # Also delete the setup function if hasattr(old_module, "setup"): delattr(old_module, "setup") modules = sopel.loader.enumerate_modules(bot.config) path, type_ = modules[name] load_module(bot, name, path, type_)
def recieve_cap_ls_reply(bot, trigger): if bot.server_capabilities: # We've already seen the results, so someone sent CAP LS from a module. # We're too late to do SASL, and we don't want to send CAP END before # the module has done what it needs to, so just return return for cap in trigger.split(): c = cap.split('=') if len(c) == 2: batched_caps[c[0]] = c[1] else: batched_caps[c[0]] = None # Not the last in a multi-line reply. First two args are * and LS. if trigger.args[2] == '*': return bot.server_capabilities = batched_caps # If some other module requests it, we don't need to add another request. # If some other module prohibits it, we shouldn't request it. core_caps = ['multi-prefix', 'away-notify', 'cap-notify', 'server-time'] for cap in core_caps: if cap not in bot._cap_reqs: bot._cap_reqs[cap] = [_CapReq('', 'coretasks')] def acct_warn(bot, cap): LOGGER.info( 'Server does not support %s, or it conflicts with a custom ' 'module. User account validation unavailable or limited.', cap[1:]) if bot.config.core.owner_account or bot.config.core.admin_accounts: LOGGER.warning( 'Owner or admin accounts are configured, but %s is not ' 'supported by the server. This may cause unexpected behavior.', cap[1:]) auth_caps = ['account-notify', 'extended-join', 'account-tag'] for cap in auth_caps: if cap not in bot._cap_reqs: bot._cap_reqs[cap] = [_CapReq('=', 'coretasks', acct_warn)] for cap, reqs in iteritems(bot._cap_reqs): # At this point, we know mandatory and prohibited don't co-exist, but # we need to call back for optionals if they're also prohibited prefix = '' for entry in reqs: if prefix == '-' and entry.prefix != '-': entry.failure(bot, entry.prefix + cap) continue if entry.prefix: prefix = entry.prefix # It's not required, or it's supported, so we can request it if prefix != '=' or cap in bot.server_capabilities: # REQs fail as a whole, so we send them one capability at a time bot.write(('CAP', 'REQ', entry.prefix + cap)) # If it's required but not in server caps, we need to call all the # callbacks else: for entry in reqs: if entry.failure and entry.prefix == '=': entry.failure(bot, entry.prefix + cap) # If we want to do SASL, we have to wait before we can send CAP END. So if # we are, wait on 903 (SASL successful) to send it. if bot.config.core.auth_method == 'sasl': bot.write(('CAP', 'REQ', 'sasl')) else: bot.write(('CAP', 'END'))
def receive_cap_ls_reply(bot, trigger): if bot.server_capabilities: # We've already seen the results, so someone sent CAP LS from a module. # We're too late to do SASL, and we don't want to send CAP END before # the module has done what it needs to, so just return return for cap in trigger.split(): c = cap.split('=') if len(c) == 2: batched_caps[c[0]] = c[1] else: batched_caps[c[0]] = None # Not the last in a multi-line reply. First two args are * and LS. if trigger.args[2] == '*': return bot.server_capabilities = batched_caps # If some other module requests it, we don't need to add another request. # If some other module prohibits it, we shouldn't request it. core_caps = [ 'echo-message', 'multi-prefix', 'away-notify', 'cap-notify', 'server-time', ] for cap in core_caps: if cap not in bot._cap_reqs: bot._cap_reqs[cap] = [_CapReq('', 'coretasks')] def acct_warn(bot, cap): LOGGER.info('Server does not support %s, or it conflicts with a custom ' 'module. User account validation unavailable or limited.', cap[1:]) if bot.config.core.owner_account or bot.config.core.admin_accounts: LOGGER.warning( 'Owner or admin accounts are configured, but %s is not ' 'supported by the server. This may cause unexpected behavior.', cap[1:]) auth_caps = ['account-notify', 'extended-join', 'account-tag'] for cap in auth_caps: if cap not in bot._cap_reqs: bot._cap_reqs[cap] = [_CapReq('', 'coretasks', acct_warn)] for cap, reqs in iteritems(bot._cap_reqs): # At this point, we know mandatory and prohibited don't co-exist, but # we need to call back for optionals if they're also prohibited prefix = '' for entry in reqs: if prefix == '-' and entry.prefix != '-': entry.failure(bot, entry.prefix + cap) continue if entry.prefix: prefix = entry.prefix # It's not required, or it's supported, so we can request it if prefix != '=' or cap in bot.server_capabilities: # REQs fail as a whole, so we send them one capability at a time bot.write(('CAP', 'REQ', entry.prefix + cap)) # If it's required but not in server caps, we need to call all the # callbacks else: for entry in reqs: if entry.failure and entry.prefix == '=': entry.failure(bot, entry.prefix + cap) # If we want to do SASL, we have to wait before we can send CAP END. So if # we are, wait on 903 (SASL successful) to send it. if bot.config.core.auth_method == 'sasl': bot.write(('CAP', 'REQ', 'sasl')) else: bot.write(('CAP', 'END'))
def search_url_callbacks(self, url): for regex, function in tools.iteritems(self.memory['url_callbacks']): match = regex.search(url) if match: yield function, match