diff --git a/local_config.ini.spec b/local_config.ini.spec index 0d1c2a6..de1059a 100644 --- a/local_config.ini.spec +++ b/local_config.ini.spec @@ -24,7 +24,6 @@ moin-disabled-user = string_list(default=list()) tea_steep_time = integer(default=220) image_preview = boolean(default=true) -dsa_watcher_interval = integer(default=900) loglevel = option('ERROR', WARN', 'INFO', 'DEBUG', default='INFO') debug_mode = boolean(default=false) diff --git a/persistent_config.ini.spec b/persistent_config.ini.spec index a30e320..88156e3 100644 --- a/persistent_config.ini.spec +++ b/persistent_config.ini.spec @@ -6,7 +6,9 @@ start_time = integer(default=0) [plugins] [[info]] enabled = boolean(default=true) - last_dsa = integer(default=0) # TODO broken + [[dsa-watcher]] + last_dsa = integer(default=0) + interval = integer(default=900) [user_pref] diff --git a/plugins/__init__.py b/plugins/__init__.py index eaff684..87791ff 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -26,7 +26,6 @@ def plugin_enabled_set(plugin, enabled): log.warn("couldn't get exclusive lock") config.conf_set('persistent_locked', True) - # blob = conf_load() if plugin.plugin_name not in config.runtime_config_store['plugins']: config.runtime_config_store['plugins'][plugin.plugin_name] = {} @@ -36,6 +35,25 @@ def plugin_enabled_set(plugin, enabled): config.conf_set('persistent_locked', False) +def register_active_event(t, callback, args, action_runner, plugin, msg_obj): + """ + Execute a callback at a given time and react on the output + + :param t: when to execute the job + :param callback: the function to execute + :param args: parameters for said function + :param action_runner: bots action dict parser + :param plugin: pass-through object for action parser + :param msg_obj: pass-through object for action parser + :return: + """ + def func(func_args): + action = callback(*func_args) + if action: + action_runner(action=action, plugin=plugin, msg_obj=msg_obj) + joblist.append((t, func, args)) + + def register_event(t, callback, args): joblist.append((t, callback, args)) diff --git a/plugins/commands.py b/plugins/commands.py index 394a20b..a917fcc 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -6,13 +6,14 @@ import traceback import unicodedata import requests +from lxml import etree import config from common import ( VERSION, RATE_FUN, RATE_GLOBAL, RATE_INTERACTIVE, RATE_NO_LIMIT, giphy, pluginfunction, - ptypes_COMMAND -) + ptypes_COMMAND, + RATE_NO_SILENCE) from string_constants import cakes, excuses, moin_strings_hi, moin_strings_bye log = logging.getLogger(__name__) @@ -28,7 +29,6 @@ def command_version(argv, **args): @pluginfunction('uptime', 'prints uptime', ptypes_COMMAND) def command_uptime(argv, **args): - u = int(config.runtimeconf_get('start_time') + time.time()) plural_uptime = 's' plural_request = 's' @@ -47,7 +47,6 @@ def command_uptime(argv, **args): @pluginfunction('info', 'prints info message', ptypes_COMMAND) def command_info(argv, **args): - log.info('sent long info') return { 'msg': args['reply_user'] + ( @@ -61,7 +60,6 @@ def command_info(argv, **args): @pluginfunction('ping', 'sends pong', ptypes_COMMAND, ratelimit_class=RATE_INTERACTIVE) def command_ping(argv, **args): - rnd = random.randint(0, 3) # 1:4 if 0 == rnd: msg = args['reply_user'] + ''': peng (You're dead now.)''' @@ -338,7 +336,6 @@ def usersetting_get(argv, args): @pluginfunction('set', 'modify a user setting', ptypes_COMMAND, ratelimit_class=RATE_NO_LIMIT) def command_usersetting(argv, **args): - settings = ['spoiler'] arg_user = args['reply_user'] arg_key = argv[0] if len(argv) > 0 else None @@ -529,90 +526,67 @@ def command_show_recordlist(argv, **args): ) } -# TODO: disabled until rewrite -# @pluginfunction('dsa-watcher', 'automatically crawls for newly published Debian Security Announces', ptypes_COMMAND, -# ratelimit_class=RATE_NO_SILENCE) -# def command_dsa_watcher(argv, **_): -# """ -# TODO: rewrite so that a last_dsa_date is used instead, then all DSAs since then printed and the date set to now() -# """ -# -# if 2 != len(argv): -# msg = 'wrong number of arguments' -# log.warn(msg) -# return {'msg': msg} -# -# if 'crawl' == argv[1]: -# out = [] -# # TODO: this is broken... the default should neither be part of the code, -# # but rather be determined at runtime (like "latest" or similar) -# dsa = config.runtime_config_store.deepget('plugins.last_dsa', 1000) -# -# url = 'https://security-tracker.debian.org/tracker/DSA-%d-1' % dsa -# -# try: -# request = urllib.request.Request(url) -# request.add_header('User-Agent', USER_AGENT) -# response = urllib.request.urlopen(request) -# html_text = response.read(BUFSIZ) # ignore more than BUFSIZ -# except Exception as e: -# err = e -# if '404' not in str(err): -# msg = 'error for %s: %s' % (url, err) -# log.warn(msg) -# out.append(msg) -# else: -# if str != type(html_text): -# html_text = str(html_text) -# -# result = re.match(r'.*?Description(.*?).*?', html_text, re.S | re.M | re.IGNORECASE) -# -# package = 'error extracting package name' -# if result: -# package = result.groups()[0] -# -# if config.get('persistent_locked'): -# msg = "couldn't get exclusive lock" -# log.warn(msg) -# out.append(msg) -# else: -# config.set('persistent_locked', True) -# blob = conf_load() -# -# if 'plugin_conf' not in blob: -# blob['plugin_conf'] = {} -# -# if 'last_dsa' not in blob['plugin_conf']: -# blob['plugin_conf']['last_dsa'] = 3308 # FIXME: fixed value -# -# blob['plugin_conf']['last_dsa'] += 1 -# -# runtimeconf_save(blob) -# config.set('persistent_locked', False) -# -# msg = ( -# 'new Debian Security Announce found (%s): %s' % (str(package).replace(' - security update', ''), url)) -# out.append(msg) -# -# log.info('no dsa for %d, trying again...' % dsa) -# # that's good, no error, just 404 -> DSA not released yet -# -# crawl_at = time.time() + config.get('dsa_watcher_interval') -# # register_event(crawl_at, command_dsa_watcher, (['dsa-watcher', 'crawl'],)) -# -# msg = 'next crawl set to %s' % time.strftime('%Y-%m-%d %H:%M', time.localtime(crawl_at)) -# out.append(msg) -# return { -# 'msg': out, -# 'event': { -# 'time': crawl_at, -# 'command': (command_dsa_watcher, (['dsa-watcher', 'crawl'],)) -# } -# } -# else: -# msg = 'wrong argument' -# log.warn(msg) -# return {'msg': msg} + +@pluginfunction( + 'dsa-watcher', + 'automatically crawls for newly published Debian Security Announces', ptypes_COMMAND, + ratelimit_class=RATE_NO_SILENCE, enabled=True) +def command_dsa_watcher(argv=None, **_): + """ + TODO: rewrite so that a last_dsa_date is used instead, + then all DSAs since then printed and the date set to now() + :param argv: + :param _: + """ + log.debug("Called command_dsa_watcher") + + def get_id_from_about_string(about): + return int(about.split('/')[-1].split('-')[1]) + + def get_dsa_list(after): + """ + Get a list of dsa items in form of id and package, retrieved from the RSS feed + :param after: optional integer to filter on (only DSA's after that will be returned) + :returns list of id, package (with DSA prefix) + """ + nsmap = { + "purl": "http://purl.org/rss/1.0/", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + } + dsa_response = requests.get("https://www.debian.org/security/dsa-long") + xmldoc = etree.fromstring(dsa_response.content) + dsa_about_list = xmldoc.xpath('//purl:item/@rdf:about', namespaces=nsmap) + for dsa_about in reversed(dsa_about_list): + dsa_id = get_id_from_about_string(dsa_about) + if after and dsa_id <= after: + continue + else: + yield dsa_id, str(dsa_about).replace(' - security update', '') + + out = [] + last_dsa = config.runtimeconf_deepget('plugins.dsa-watcher.last_dsa') + log.debug('Searching for DSA after ID {}'.format(last_dsa)) + for dsa, package in get_dsa_list(after=last_dsa): + url = 'https://security-tracker.debian.org/tracker/DSA-%d-1' % dsa + + msg = 'new Debian Security Announce found ({}): {}'.format(package, url) + out.append(msg) + + last_dsa = dsa + + config.runtime_config_store['plugins']['dsa-watcher']['last_dsa'] = last_dsa + config.runtimeconf_persist() + crawl_at = time.time() + config.runtimeconf_deepget('plugins.dsa-watcher.interval') + + msg = 'next crawl set to %s' % time.strftime('%Y-%m-%d %H:%M', time.localtime(crawl_at)) + out.append(msg) + return { + 'msg': out, + 'event': { + 'time': crawl_at, + 'command': (command_dsa_watcher, ([],)) + } + } @pluginfunction("provoke-bots", "search for other bots", ptypes_COMMAND) @@ -667,7 +641,8 @@ def set_status(argv, **args): return { 'presence': { 'status': 'xa', - 'msg': 'I\'m muted now. You can unmute me with "%s: set_status unmute"' % config.conf_get("bot_nickname") + 'msg': 'I\'m muted now. You can unmute me with "%s: set_status unmute"' % config.conf_get( + "bot_nickname") } } elif command == 'unmute' and args['reply_user'] == config.conf_get('bot_owner'): @@ -720,7 +695,7 @@ def ignore_user(argv, **args): if not argv: return {'msg': 'syntax: "{}: snitch username"'.format(config.conf_get("bot_nickname"))} - then = time.time() + 15*60 + then = time.time() + 15 * 60 spammer = argv[0] if spammer == config.conf_get("bot_owner"): diff --git a/urlbot.py b/urlbot.py index ae4aad7..41c6c26 100755 --- a/urlbot.py +++ b/urlbot.py @@ -26,7 +26,8 @@ from plugins import ( plugin_enabled_get, ptypes_PARSE, register_event, - else_command + register_active_event, + else_command, ) import config @@ -355,7 +356,16 @@ class UrlBot(IdleBot): elif 'command' in event: command = event["command"] if rate_limit(RATE_EVENT): - register_event(event["time"], command[0], command[1]) + register_event(t=event["time"], callback=command[0], args=command[1]) + # kind of ugly.. + register_active_event( + t=event['time'], + callback=command[0], + args=command[1], + action_runner=self._run_action, + plugin=plugin, + msg_obj=msg_obj + ) if 'msg' in action and rate_limit(RATE_CHAT | plugin.ratelimit_class): self.send_reply(action['msg'], msg_obj)