def __init__(self, irc): super().__init__(irc) self.irc = irc self.topic_lock = threading.Lock() self.dbus_service = BTSDBusService(self._email_callback) self.mainloop = None mainloop = GObject.MainLoop() if not mainloop.is_running(): mainloop_thread = threading.Thread(target=mainloop.run) mainloop_thread.start() self.mainloop = mainloop self.dbus_bus = SystemBus() self.dbus_bus.publish(self.dbus_service.interface_name, self.dbus_service) self.dbus_service.start() self.requests_session = requests.Session() self.requests_session.verify = True self.queued_topics = {} self.last_n_messages = [] self.stable_rc_bugs = StableRCBugs(self.requests_session) self.testing_rc_bugs = TestingRCBugs(self.requests_session) self.new_queue = NewQueue(self.requests_session) self.dinstall = Dinstall(self.requests_session) self.rm_queue = RmQueue(self.requests_session) self.apt_archive = AptArchive( self.registryValue('apt_configuration_directory'), self.registryValue('apt_cache_directory')) self.data_sources = (self.stable_rc_bugs, self.testing_rc_bugs, self.new_queue, self.dinstall, self.rm_queue, self.apt_archive) # Schedule datasource updates def wrapper(source): def implementation(): try: source.update() except Exception as e: log.exception('Failed to update {}: {}'.format( source.NAME, e)) self._topic_callback() return implementation for source in self.data_sources: schedule.addPeriodicEvent(wrapper(source), source.INTERVAL, source.NAME, now=False) schedule.addEvent(wrapper(source), time.time() + 1)
class TestDatasourceTestingNewQueue(unittest.TestCase): def setUp(self): fixture = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fixtures', 'new_queue.txt') with io.open(fixture, encoding='utf-8') as f: data = f.read() self.mocker = requests_mock.Mocker() self.mocker.start() self.mocker.register_uri('GET', NewQueue.URL, text=data) session = requests.Session() self.datasource = NewQueue(session) def tearDown(self): self.mocker.stop() def is_new(self, package, version): self.datasource.update() return self.datasource.is_new(package, version) def testURL(self): """ Check we have a sane URL. """ self.assertTrue(len(self.datasource.URL) > 5) self.assertTrue(self.datasource.URL.startswith('http')) def testInterval(self): """ Check we have a sane update interval. """ self.assertTrue(self.datasource.INTERVAL > 60) def testTop(self): self.assertTrue(self.is_new('ezmlm-idx', '6.0.1-1')) def testBottom(self): self.assertTrue(self.is_new('libxml-sax-expatxs-perl', '1.31-1')) def testMultipleVersions(self): self.assertTrue(self.is_new('libgcal', '0.8.1-1')) self.assertTrue(self.is_new('libgcal', '0.8.1-2')) def testInvalidVersion(self): self.assertFalse(self.is_new('rcpp', '0.5.2.invalid')) def testNotInQueue(self): self.assertFalse(self.is_new('package-not-in-new-queue', 'version-foo')) def testByhand(self): self.assertFalse(self.is_new('loadlin', '1.6c.really1.6c.nobin-2')) def testExperimental(self): self.assertTrue(self.is_new('ooo-build', '3.0.0.9+r14588-1'))
def setUp(self): fixture = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fixtures', 'new_queue.txt') with io.open(fixture, encoding='utf-8') as f: data = f.read() self.mocker = requests_mock.Mocker() self.mocker.start() self.mocker.register_uri('GET', NewQueue.URL, text=data) session = requests.Session() self.datasource = NewQueue(session)
class TestDatasourceTestingNewQueue(unittest.TestCase): def setUp(self): self.fixture = os.path.join(os.path.dirname(os.path.abspath(__file__)), \ 'fixtures', 'new_queue.txt') self.datasource = NewQueue() def is_new(self, package, version): fileobj = open(self.fixture) self.datasource.update(fileobj) return self.datasource.is_new(package, version) def testURL(self): """ Check we have a sane URL. """ self.assert_(len(self.datasource.URL) > 5) self.assert_(self.datasource.URL.startswith('http')) def testInterval(self): """ Check we have a sane update interval. """ self.assert_(self.datasource.INTERVAL > 60) def testTop(self): self.assert_(self.is_new('ezmlm-idx', '6.0.1-1')) def testBottom(self): self.assert_(self.is_new('libxml-sax-expatxs-perl', '1.31-1')) def testMultipleVersions(self): self.assert_(self.is_new('libgcal', '0.8.1-1')) self.assert_(self.is_new('libgcal', '0.8.1-2')) def testInvalidVersion(self): self.failIf(self.is_new('rcpp', '0.5.2.invalid')) def testNotInQueue(self): self.failIf(self.is_new('package-not-in-new-queue', 'version-foo')) def testByhand(self): self.failIf(self.is_new('loadlin', '1.6c.really1.6c.nobin-2')) def testExperimental(self): self.assert_(self.is_new('ooo-build', '3.0.0.9+r14588-1'))
def __init__(self, irc): super(DebianDevelChanges, self).__init__(irc) self.irc = irc self.topic_lock = threading.Lock() fr = FifoReader() fifo_loc = '/var/run/debian-devel-changes-bot/fifo' fr.start(self._email_callback, fifo_loc) self.requests_session = requests.Session() self.requests_session.verify = True self.queued_topics = {} self.last_n_messages = [] self.stable_rc_bugs = StableRCBugs(self.requests_session) self.testing_rc_bugs = TestingRCBugs(self.requests_session) self.new_queue = NewQueue(self.requests_session) self.data_sources = (self.stable_rc_bugs, self.testing_rc_bugs, self.new_queue) # Schedule datasource updates for klass, interval, name in get_datasources(): try: schedule.removePeriodicEvent(name) except KeyError: pass def wrapper(klass=klass): klass().update() self._topic_callback() schedule.addPeriodicEvent(wrapper, interval, name, now=False) schedule.addEvent(wrapper, time.time() + 1) def wrapper(source): def implementation(): source.update() self._topic_callback() return implementation for source in self.data_sources: schedule.addPeriodicEvent(wrapper(source), source.INTERVAL, source.NAME, now=False) schedule.addEvent(wrapper(source), time.time() + 1)
class DebianDevelChanges(supybot.callbacks.Plugin): threaded = True def __init__(self, irc): super().__init__(irc) self.irc = irc self.topic_lock = threading.Lock() self.mainloop = None self.mainloop_thread = None mainloop = GObject.MainLoop() if not mainloop.is_running(): log.info('Starting Glib main loop') mainloop_thread = threading.Thread(target=mainloop.run, name='Glib maing loop') mainloop_thread.start() self.mainloop_thread = mainloop_thread self.mainloop = mainloop log.info('Starting D-Bus service') self.dbus_service = BTSDBusService(self._email_callback) self.dbus_bus = SystemBus() self.dbus_bus.publish(self.dbus_service.interface_name, self.dbus_service) self.dbus_service.start() self.requests_session = requests.Session() self.requests_session.verify = True self.queued_topics = {} self.last_n_messages = [] # data sources self.stable_rc_bugs = StableRCBugs(self.requests_session) self.testing_rc_bugs = TestingRCBugs(self.requests_session) self.new_queue = NewQueue(self.requests_session) self.dinstall = Dinstall(self.requests_session) self.rm_queue = RmQueue(self.requests_session) self.apt_archive = AptArchive( self.registryValue('apt_configuration_directory'), self.registryValue('apt_cache_directory')) self.data_sources = (self.stable_rc_bugs, self.testing_rc_bugs, self.new_queue, self.dinstall, self.rm_queue, self.apt_archive) # Schedule datasource updates def wrapper(source): def implementation(): try: source.update() except Exception as e: log.exception('Failed to update {}: {}'.format( source.NAME, e)) self._topic_callback() return implementation for source in self.data_sources: # schedule periodic events schedule.addPeriodicEvent(wrapper(source), source.INTERVAL, source.NAME, now=False) # and run them now once schedule.addEvent(wrapper(source), time.time() + 1) def die(self): log.info('Stopping D-Bus service') self.dbus_service.stop() if self.mainloop is not None: log.info('Stopping Glib main loop') self.mainloop.quit() self.mainloop_thread.join(timeout=1.0) if self.mainloop_thread.is_alive(): log.warn('Glib main loop thread is still alive.') self.mainloop = None self.mainloop_thread = None for source in self.data_sources: try: schedule.removePeriodicEvent(source.NAME) except KeyError: pass super().die() def _email_callback(self, fileobj): try: emailmsg = parse_mail(fileobj) msg = get_message(emailmsg, new_queue=self.new_queue) if not msg: return txt = colourise(msg.for_irc()) # Simple flood/duplicate detection if txt in self.last_n_messages: return self.last_n_messages.insert(0, txt) self.last_n_messages = self.last_n_messages[:20] maintainer_info = None if hasattr(msg, 'maintainer'): maintainer_info = (split_address(msg.maintainer), ) else: maintainer_info = [] for package in msg.package.split(','): package = package.strip() try: maintainer_info.append( self.apt_archive.get_maintainer(package)) except NewDataSource.DataError as e: log.info("Failed to query maintainer for {}.".format( package)) for channel in self.irc.state.channels: package_regex = self.registryValue( 'package_regex', channel, ) or 'a^' # match nothing by default package_match = re.search(package_regex, msg.package) maintainer_match = False maintainer_regex = self.registryValue('maintainer_regex', channel) if maintainer_regex and maintainer_info is not None and len( maintainer_info) >= 0: for mi in maintainer_info: maintainer_match = re.search(maintainer_regex, mi['email']) if maintainer_match: break if not package_match and not maintainer_match: continue distribution_regex = self.registryValue( 'distribution_regex', channel, ) if distribution_regex: if not hasattr(msg, 'distribution'): # If this channel has a distribution regex, don't # bother continuing unless the message actually has a # distribution. This filters security messages, etc. continue if not re.search(distribution_regex, msg.distribution): # Distribution doesn't match regex; don't send this # message. continue send_privmsg = self.registryValue('send_privmsg', channel) # Send NOTICE per default and if 'send_privmsg' is set for the # channel, send PRIVMSG instead. if send_privmsg: ircmsg = supybot.ircmsgs.privmsg(channel, txt) else: ircmsg = supybot.ircmsgs.notice(channel, txt) self.irc.queueMsg(ircmsg) except Exception as e: log.exception('Uncaught exception: %s ' % e) def _topic_callback(self): sections = { self.testing_rc_bugs.get_number_bugs: 'RC bug count', self.stable_rc_bugs.get_number_bugs: 'Stable RC bug count', self.new_queue.get_size: 'NEW queue', self.rm_queue.get_size: 'RM queue', self.dinstall.get_status: 'dinstall' } channels = set() with self.topic_lock: values = {} for callback, prefix in sections.items(): values[prefix] = callback() for channel in self.irc.state.channels: new_topic = topic = self.irc.state.getTopic(channel) for callback, prefix in sections.items(): if values[prefix]: new_topic = rewrite_topic(new_topic, prefix, values[prefix]) if topic != new_topic: self.queued_topics[channel] = new_topic if channel not in channels: log.info("Queueing change of topic in #%s to '%s'" % (channel, new_topic)) channels.add(channel) for channel in channels: event_name = '{}_topic'.format(channel) try: schedule.removeEvent(event_name) except KeyError: pass def update_topic(channel=channel): self._update_topic(channel) schedule.addEvent(update_topic, time.time() + 60, event_name) def _update_topic(self, channel): with self.topic_lock: try: new_topic = self.queued_topics[channel] log.info("Changing topic in #%s to '%s'" % (channel, new_topic)) self.irc.queueMsg(supybot.ircmsgs.topic(channel, new_topic)) except KeyError: pass def rc(self, irc, msg, args): """Link to UDD RC bug overview.""" num_bugs = self.testing_rc_bugs.get_number_bugs() if type(num_bugs) is int: irc.reply( "There are %d release-critical bugs in the testing distribution. " "See https://udd.debian.org/bugs.cgi?release=buster¬main=ign&merged=ign&rc=1" % num_bugs) else: irc.reply("No data at this time.") rc = wrap(rc) bugs = wrap(rc) def update(self, irc, msg, args): """Trigger an update.""" if not ircdb.checkCapability(msg.prefix, 'owner'): irc.reply("You are not authorised to run this command.") return for source in self.data_sources: source.update() irc.reply("Updated %s." % source.NAME) self._topic_callback() update = wrap(update) def madison(self, irc, msg, args, package): """List packages.""" try: lines = madison(package) if not lines: irc.reply( 'Did not get a response -- is "%s" a valid package?' % package) return field_styles = ('package', 'version', 'distribution', 'section') for line in lines: out = [] fields = line.strip().split('|', len(field_styles)) for style, data in zip(field_styles, fields): out.append('[%s]%s' % (style, data)) irc.reply(colourise('[reset]|'.join(out)), prefixNick=False) except Exception as e: irc.reply("Error: %s" % e.message) madison = wrap(madison, ['text']) def get_pool_url(self, package): if package.startswith('lib'): return (package[:4], package) else: return (package[:1], package) def _maintainer(self, irc, msg, args, items): """Get maintainer for package.""" for package in items: info = self.apt_archive.get_maintainer(package) if info: display_name = format_email_address( "%s <%s>" % (info['name'], info['email']), max_domain=18) login = info['email'] if login.endswith('@debian.org'): login = login.replace('@debian.org', '') msg = "[desc]Maintainer for[reset] [package]%s[reset] [desc]is[reset] [by]%s[reset]: " % ( package, display_name) msg += "[url]https://qa.debian.org/developer.php?login=%s[/url]" % login else: msg = 'Unknown source package "%s"' % package irc.reply(colourise(msg), prefixNick=False) maintainer = wrap(_maintainer, [many('anything')]) maint = wrap(_maintainer, [many('anything')]) who_maintains = wrap(_maintainer, [many('anything')]) def _qa(self, irc, msg, args, items): """Get link to QA page.""" for package in items: url = "https://packages.qa.debian.org/%s/%s.html" % self.get_pool_url( package) msg = "[desc]QA page for[reset] [package]%s[reset]: [url]%s[/url]" % ( package, url) irc.reply(colourise(msg), prefixNick=False) qa = wrap(_qa, [many('anything')]) overview = wrap(_qa, [many('anything')]) package = wrap(_qa, [many('anything')]) pkg = wrap(_qa, [many('anything')]) srcpkg = wrap(_qa, [many('anything')]) def _changelog(self, irc, msg, args, items): """Get link to changelog.""" for package in items: url = "https://packages.debian.org/changelogs/pool/main/%s/%s/current/changelog" % self.get_pool_url( package) msg = "[desc]debian/changelog for[reset] [package]%s[reset]: [url]%s[/url]" % ( package, url) irc.reply(colourise(msg), prefixNick=False) changelog = wrap(_changelog, [many('anything')]) changes = wrap(_changelog, [many('anything')]) def _copyright(self, irc, msg, args, items): """Link to copyright files.""" for package in items: url = "https://packages.debian.org/changelogs/pool/main/%s/%s/current/copyright" % self.get_pool_url( package) msg = "[desc]debian/copyright for[reset] [package]%s[reset]: [url]%s[/url]" % ( package, url) irc.reply(colourise(msg), prefixNick=False) copyright = wrap(_copyright, [many('anything')]) def _buggraph(self, irc, msg, args, items): """Link to bug graph.""" for package in items: msg = "[desc]Bug graph for[reset] [package]%s[reset]: [url]https://qa.debian.org/data/bts/graphs/%s/%s.png[/url]" % \ (package, package[0], package) irc.reply(colourise(msg), prefixNick=False) buggraph = wrap(_buggraph, [many('anything')]) bug_graph = wrap(_buggraph, [many('anything')]) def _buildd(self, irc, msg, args, items): """Link to buildd page.""" for package in items: msg = "[desc]buildd status for[reset] [package]%s[reset]: [url]https://buildd.debian.org/pkg.cgi?pkg=%s[/url]" % \ (package, package) irc.reply(colourise(msg), prefixNick=False) buildd = wrap(_buildd, [many('anything')]) def _popcon(self, irc, msg, args, package): """Get popcon data.""" try: msg = popcon(package, self.requests_session) if msg: irc.reply(colourise(msg.for_irc()), prefixNick=False) except Exception as e: irc.reply("Error: unable to obtain popcon data for %s" % package) popcon = wrap(_popcon, ['text']) def _testing(self, irc, msg, args, items): """Check testing migration status.""" for package in items: msg = "[desc]Testing migration status for[reset] [package]%s[reset]: [url]https://qa.debian.org/excuses.php?package=%s[/url]" % \ (package, package) irc.reply(colourise(msg), prefixNick=False) testing = wrap(_testing, [many('anything')]) migration = wrap(_testing, [many('anything')]) def _dehs(self, irc, msg, args, items): """Link to DEHS.""" for package in items: msg = "[desc]Debian External Health Status for[reset] [package]%s[reset]: [url]https://dehs.alioth.debian.org/report.php?package=%s[/url]" % \ (package, urllib.parse.quote(package)) irc.reply(colourise(msg), prefixNick=False) dehs = wrap(_dehs, [many('anything')]) health = wrap(_dehs, [many('anything')]) def _new(self, irc, msg, args): """Link to NEW queue.""" line = "[desc]NEW queue is[reset]: [url]%s[/url]. [desc]Current size is:[reset] %d" % \ ("https://ftp-master.debian.org/new.html", self.new_queue.get_size()) irc.reply(colourise(line)) new = wrap(_new) new_queue = wrap(_new) newqueue = wrap(_new)
def setUp(self): self.fixture = os.path.join(os.path.dirname(os.path.abspath(__file__)), \ 'fixtures', 'new_queue.txt') self.datasource = NewQueue()
def __init__(self, irc): super().__init__(irc) self.irc = irc self.topic_lock = threading.Lock() self.mainloop = None self.mainloop_thread = None mainloop = GObject.MainLoop() if not mainloop.is_running(): log.info('Starting Glib main loop') mainloop_thread = threading.Thread(target=mainloop.run, name='Glib maing loop') mainloop_thread.start() self.mainloop_thread = mainloop_thread self.mainloop = mainloop self.requests_session = requests.Session() self.requests_session.verify = True self.queued_topics = {} self.last_n_messages = [] # data sources pseudo_packages.pp = PseudoPackages(self.requests_session) self.pseudo_packages = pseudo_packages.pp self.stable_rc_bugs = StableRCBugs(self.requests_session) self.testing_rc_bugs = TestingRCBugs(self.requests_session) self.new_queue = NewQueue(self.requests_session) self.dinstall = Dinstall(self.requests_session) self.rm_queue = RmQueue(self.requests_session) self.apt_archive = AptArchive( self.registryValue('apt_configuration_directory'), self.registryValue('apt_cache_directory')) self.data_sources = ( self.pseudo_packages, self.stable_rc_bugs, self.testing_rc_bugs, self.new_queue, self.dinstall, self.rm_queue, self.apt_archive ) # Schedule datasource updates def wrapper(source): def implementation(): try: source.update() except Exception as e: log.exception('Failed to update {}: {}'.format(source.NAME, e)) self._topic_callback() return implementation for source in self.data_sources: # schedule periodic events schedule.addPeriodicEvent(wrapper(source), source.INTERVAL, source.NAME, now=False) # and run them now once schedule.addEvent(wrapper(source), time.time() + 1) log.info('Starting D-Bus service') self.dbus_service = BTSDBusService(self._email_callback) self.dbus_bus = SystemBus() self.dbus_bus.publish(self.dbus_service.interface_name, self.dbus_service) self.dbus_service.start()
class DebianDevelChanges(supybot.callbacks.Plugin): threaded = True def __init__(self, irc): super().__init__(irc) self.irc = irc self.topic_lock = threading.Lock() self.mainloop = None self.mainloop_thread = None mainloop = GObject.MainLoop() if not mainloop.is_running(): log.info('Starting Glib main loop') mainloop_thread = threading.Thread(target=mainloop.run, name='Glib maing loop') mainloop_thread.start() self.mainloop_thread = mainloop_thread self.mainloop = mainloop self.requests_session = requests.Session() self.requests_session.verify = True self.queued_topics = {} self.last_n_messages = [] # data sources pseudo_packages.pp = PseudoPackages(self.requests_session) self.pseudo_packages = pseudo_packages.pp self.stable_rc_bugs = StableRCBugs(self.requests_session) self.testing_rc_bugs = TestingRCBugs(self.requests_session) self.new_queue = NewQueue(self.requests_session) self.dinstall = Dinstall(self.requests_session) self.rm_queue = RmQueue(self.requests_session) self.apt_archive = AptArchive( self.registryValue('apt_configuration_directory'), self.registryValue('apt_cache_directory')) self.data_sources = ( self.pseudo_packages, self.stable_rc_bugs, self.testing_rc_bugs, self.new_queue, self.dinstall, self.rm_queue, self.apt_archive ) # Schedule datasource updates def wrapper(source): def implementation(): try: source.update() except Exception as e: log.exception('Failed to update {}: {}'.format(source.NAME, e)) self._topic_callback() return implementation for source in self.data_sources: # schedule periodic events schedule.addPeriodicEvent(wrapper(source), source.INTERVAL, source.NAME, now=False) # and run them now once schedule.addEvent(wrapper(source), time.time() + 1) log.info('Starting D-Bus service') self.dbus_service = BTSDBusService(self._email_callback) self.dbus_bus = SystemBus() self.dbus_bus.publish(self.dbus_service.interface_name, self.dbus_service) self.dbus_service.start() def die(self): log.info('Stopping D-Bus service') self.dbus_service.stop() if self.mainloop is not None: log.info('Stopping Glib main loop') self.mainloop.quit() self.mainloop_thread.join(timeout=1.0) if self.mainloop_thread.is_alive(): log.warn('Glib main loop thread is still alive.') self.mainloop = None self.mainloop_thread = None for source in self.data_sources: try: schedule.removePeriodicEvent(source.NAME) except KeyError: pass super().die() def _email_callback(self, fileobj): try: emailmsg = parse_mail(fileobj) msg = get_message(emailmsg, new_queue=self.new_queue) if not msg: return txt = colourise(msg.for_irc()) # Simple flood/duplicate detection if txt in self.last_n_messages: return self.last_n_messages.insert(0, txt) self.last_n_messages = self.last_n_messages[:20] maintainer_info = None if hasattr(msg, 'maintainer'): maintainer_info = (split_address(msg.maintainer), ) else: maintainer_info = [] for package in msg.package.split(','): package = package.strip() try: maintainer_info.append(self.apt_archive.get_maintainer(package)) except NewDataSource.DataError as e: log.info("Failed to query maintainer for {}.".format(package)) for channel in self.irc.state.channels: package_regex = self.registryValue( 'package_regex', channel, ) or 'a^' # match nothing by default package_match = re.search(package_regex, msg.package) maintainer_match = False maintainer_regex = self.registryValue( 'maintainer_regex', channel) if maintainer_regex and maintainer_info is not None and len(maintainer_info) >= 0: for mi in maintainer_info: maintainer_match = re.search(maintainer_regex, mi['email']) if maintainer_match: break if not package_match and not maintainer_match: continue distribution_regex = self.registryValue( 'distribution_regex', channel, ) if distribution_regex: if not hasattr(msg, 'distribution'): # If this channel has a distribution regex, don't # bother continuing unless the message actually has a # distribution. This filters security messages, etc. continue if not re.search(distribution_regex, msg.distribution): # Distribution doesn't match regex; don't send this # message. continue send_privmsg = self.registryValue('send_privmsg', channel) # Send NOTICE per default and if 'send_privmsg' is set for the # channel, send PRIVMSG instead. if send_privmsg: ircmsg = supybot.ircmsgs.privmsg(channel, txt) else: ircmsg = supybot.ircmsgs.notice(channel, txt) self.irc.queueMsg(ircmsg) except Exception as e: log.exception('Uncaught exception: %s ' % e) def _topic_callback(self): sections = { self.testing_rc_bugs.get_number_bugs: 'RC bug count', self.stable_rc_bugs.get_number_bugs: 'stable RC bug count', self.new_queue.get_size: 'NEW queue', self.new_queue.get_backports_size: 'backports NEW queue', self.rm_queue.get_size: 'RM queue', self.dinstall.get_status: 'dinstall' } channels = set() with self.topic_lock: values = {} for callback, prefix in sections.items(): new_value = callback() if new_value is not None: values[prefix] = new_value for channel in self.irc.state.channels: new_topic = topic = self.irc.state.getTopic(channel) for prefix, value in values.items(): new_topic = rewrite_topic(new_topic, prefix, value) if topic != new_topic: self.queued_topics[channel] = new_topic if channel not in channels: log.info("Queueing change of topic in #%s to '%s'" % (channel, new_topic)) channels.add(channel) for channel in channels: event_name = '{}_topic'.format(channel) try: schedule.removeEvent(event_name) except KeyError: pass def update_topic(channel=channel): self._update_topic(channel) schedule.addEvent(update_topic, time.time() + 60, event_name) def _update_topic(self, channel): with self.topic_lock: try: new_topic = self.queued_topics[channel] log.info("Changing topic in #%s to '%s'" % (channel, new_topic)) self.irc.queueMsg(supybot.ircmsgs.topic(channel, new_topic)) except KeyError: pass def rc(self, irc, msg, args): """Link to UDD RC bug overview.""" num_bugs = self.testing_rc_bugs.get_number_bugs() if type(num_bugs) is int: irc.reply( "There are %d release-critical bugs in the testing distribution. " "See https://udd.debian.org/bugs.cgi?release=buster¬main=ign&merged=ign&rc=1" % num_bugs) else: irc.reply("No data at this time.") rc = wrap(rc) bugs = wrap(rc) def update(self, irc, msg, args): """Trigger an update.""" if not ircdb.checkCapability(msg.prefix, 'owner'): irc.reply("You are not authorised to run this command.") return for source in self.data_sources: source.update() irc.reply("Updated %s." % source.NAME) self._topic_callback() update = wrap(update) def madison(self, irc, msg, args, package): """List packages.""" try: lines = madison(package) if not lines: irc.reply('Did not get a response -- is "%s" a valid package?' % package) return field_styles = ('package', 'version', 'distribution', 'section') for line in lines: out = [] fields = line.strip().split('|', len(field_styles)) for style, data in zip(field_styles, fields): out.append('[%s]%s' % (style, data)) irc.reply(colourise('[reset]|'.join(out)), prefixNick=False) except Exception as e: irc.reply("Error: %s" % e.message) madison = wrap(madison, ['text']) def get_pool_url(self, package): if package.startswith('lib'): return (package[:4], package) else: return (package[:1], package) def _maintainer(self, irc, msg, args, items): """Get maintainer for package.""" for package in items: info = self.apt_archive.get_maintainer(package) if info: display_name = format_email_address("%s <%s>" % (info['name'], info['email']), max_domain=18) login = info['email'] if login.endswith('@debian.org'): login = login.replace('@debian.org', '') msg = "[desc]Maintainer for[reset] [package]%s[reset] [desc]is[reset] [by]%s[reset]: " % (package, display_name) msg += "[url]https://qa.debian.org/developer.php?login=%s[/url]" % login else: msg = 'Unknown source package "%s"' % package irc.reply(colourise(msg), prefixNick=False) maintainer = wrap(_maintainer, [many('anything')]) maint = wrap(_maintainer, [many('anything')]) who_maintains = wrap(_maintainer, [many('anything')]) def _qa(self, irc, msg, args, items): """Get link to QA page.""" for package in items: url = "https://packages.qa.debian.org/%s/%s.html" % self.get_pool_url(package) msg = "[desc]QA page for[reset] [package]%s[reset]: [url]%s[/url]" % (package, url) irc.reply(colourise(msg), prefixNick=False) qa = wrap(_qa, [many('anything')]) overview = wrap(_qa, [many('anything')]) package = wrap(_qa, [many('anything')]) pkg = wrap(_qa, [many('anything')]) srcpkg = wrap(_qa, [many('anything')]) def _changelog(self, irc, msg, args, items): """Get link to changelog.""" for package in items: url = "https://packages.debian.org/changelogs/pool/main/%s/%s/current/changelog" % self.get_pool_url(package) msg = "[desc]debian/changelog for[reset] [package]%s[reset]: [url]%s[/url]" % (package, url) irc.reply(colourise(msg), prefixNick=False) changelog = wrap(_changelog, [many('anything')]) changes = wrap(_changelog, [many('anything')]) def _copyright(self, irc, msg, args, items): """Link to copyright files.""" for package in items: url = "https://packages.debian.org/changelogs/pool/main/%s/%s/current/copyright" % self.get_pool_url(package) msg = "[desc]debian/copyright for[reset] [package]%s[reset]: [url]%s[/url]" % (package, url) irc.reply(colourise(msg), prefixNick=False) copyright = wrap(_copyright, [many('anything')]) def _buggraph(self, irc, msg, args, items): """Link to bug graph.""" for package in items: msg = "[desc]Bug graph for[reset] [package]%s[reset]: [url]https://qa.debian.org/data/bts/graphs/%s/%s.png[/url]" % \ (package, package[0], package) irc.reply(colourise(msg), prefixNick=False) buggraph = wrap(_buggraph, [many('anything')]) bug_graph = wrap(_buggraph, [many('anything')]) def _buildd(self, irc, msg, args, items): """Link to buildd page.""" for package in items: msg = "[desc]buildd status for[reset] [package]%s[reset]: [url]https://buildd.debian.org/pkg.cgi?pkg=%s[/url]" % \ (package, package) irc.reply(colourise(msg), prefixNick=False) buildd = wrap(_buildd, [many('anything')]) def _popcon(self, irc, msg, args, package): """Get popcon data.""" try: msg = popcon(package, self.requests_session) if msg: irc.reply(colourise(msg.for_irc()), prefixNick=False) except Exception as e: irc.reply("Error: unable to obtain popcon data for %s" % package) popcon = wrap(_popcon, ['text']) def _testing(self, irc, msg, args, items): """Check testing migration status.""" for package in items: msg = "[desc]Testing migration status for[reset] [package]%s[reset]: [url]https://qa.debian.org/excuses.php?package=%s[/url]" % \ (package, package) irc.reply(colourise(msg), prefixNick=False) testing = wrap(_testing, [many('anything')]) migration = wrap(_testing, [many('anything')]) def _new(self, irc, msg, args): """Link to NEW queue.""" line = "[desc]NEW queue is[reset]: [url]%s[/url]. [desc]Current size is:[reset] %d" % \ ("https://ftp-master.debian.org/new.html", self.new_queue.get_size()) irc.reply(colourise(line)) new = wrap(_new) new_queue = wrap(_new) newqueue = wrap(_new)