# -*- coding: utf-8 -*- from __future__ import print_function import willie import json import shelve import time import urllib2 from ffpb import pretty_date from batcave import BatcaveClient __batcave = None highscores = None def setup(bot): """Called by willie upon loading this plugin.""" global __batcave, highscores __batcave = BatcaveClient(bot.config.ffpb.batcave_url) # load highscores from disk highscores = shelve.open('highscoredata', writeback=True) if not 'nodes' in highscores: highscores['nodes'] = 0 highscores['nodes_ts'] = time.time() if not 'clients' in highscores: highscores['clients'] = 0 highscores['clients_ts'] = time.time() def shutdown(bot): """Called by willie upon loading this plugin.""" global highscores # store highscores if not highscores is None: highscores.sync() highscores.close() highscores = None @willie.module.interval(15) def ffpb_get_stats(bot): """Fetch current statistics, if the highscore changes signal this.""" status = __batcave.get_status() if status is None: print('Failed to fetch BATCAVE status.') return bot.memory['ffpb_stats'] = status (nodes_active, clients_count) = \ (status['nodes_active'], status['clients_unique']) nodes_active += status.get('gateways_active', 0) highscore_changed = False if nodes_active > highscores['nodes']: highscores['nodes'] = nodes_active highscores['nodes_ts'] = time.time() highscore_changed = True if clients_count > highscores['clients']: highscores['clients'] = clients_count highscores['clients_ts'] = time.time() highscore_changed = True if highscore_changed: print('HIGHSCORE changed: {0} nodes ({1}), {2} clients ({3})'.format( highscores['nodes'], highscores['nodes_ts'], highscores['clients'], highscores['clients_ts'], )) if not bot.config.ffpb.msg_target is None: action_msg = 'notiert sich den neuen Highscore: {0} Knoten ({1}), {2} Clients ({3})' action_target = bot.config.ffpb.msg_target if not bot.config.ffpb.msg_target_public is None: action_target = bot.config.ffpb.msg_target_public bot.msg(action_target, '\x01ACTION %s\x01' % action_msg.format( highscores['nodes'], pretty_date(int(highscores['nodes_ts'])), highscores['clients'], pretty_date(int(highscores['clients_ts'])), )) @willie.module.commands('status') def ffpb_status(bot, trigger): """State of the network: count of nodes + clients""" stats = bot.memory.get('ffpb_stats') if stats is None: bot.say('Uff, kein Plan wo der Zettel ist. Fragst du später nochmal?') return gwactive = stats.get('gateways_active', 0) bot.say('Es sind {0} Knoten (inkl. {1} Gateways) und ca. {2} Clients online.'.format( stats["nodes_active"] + gwactive, gwactive, stats["clients_unique"])) @willie.module.commands('raw-status') def ffpb_batcave_status(bot, trigger): """State as given by BATCAVE.""" status = __batcave.get_status() bot.say('Status: ' + str(json.dumps(status))[1:-1]) @willie.module.commands('highscore') def ffpb_highscore(bot, trigger): """Print current highscores (nodes + clients).""" bot.say('Highscore: {0} Knoten ({1}), {2} Clients ({3})'.format( highscores['nodes'], pretty_date(int(highscores['nodes_ts'])), highscores['clients'], pretty_date(int(highscores['clients_ts'])))) MAX_ROLLOUTSTATUS_LIST = 42 @willie.module.commands('rollout-status') def ffpb_rolloutstatus(bot, trigger): """Display statistic on how many nodes have installed which firmware.""" # initialize results dictionary result = {} skipped = 0 arg = trigger.group(3) # inform users about changed command parameters if arg is not None and arg not in ['all', 'list']: bot.reply('Hm? !rollout-status [all|list ]') return # get all nodes nodes = __batcave.get_nodes() if nodes is None: bot.reply('Hmpf, ich kriege gerade keine Infos. Das ist doch Mist so.') return offlinenodes = 0 count_offline = (arg == "all") if arg == 'list': list_nodes = trigger.group(4) if list_nodes is None or len(list_nodes) == 0: bot.reply('!rollout-status list ') return list_inactive = 0 result = {'stable': [], 'testing': []} for item in nodes: release = item.get('firmware') if release != list_nodes: continue if item.get('status') not in ['active', 'stale']: list_inactive += 1 continue name = item.get('name', item.get('node_id')) branch = item.get('autoupdater') if branch in result: result[branch].append(name) else: result[branch] = [name] total = sum([len(result[x]) for x in result]) if total == 0: bot.reply('Niemand benutzt derzeit die Version "{version}".'.format(version=list_nodes)) if list_inactive > 0: bot.say('Aber es wurden {count} inaktive/offline Knoten mit Version {version} gezählt.'.format( count=list_inactive, version=list_nodes, )) return if total > MAX_ROLLOUTSTATUS_LIST: bot.reply('Das betrifft {total} Knoten, mehr als {max} werte ich als IRC-Spam.'.format(total=total, max=MAX_ROLLOUTSTATUS_LIST)) else: for branch, nodes in result.items(): bot.say('{count} Knoten auf {version} "{branch}": {nodes}'.format( branch=branch, version=list_nodes, count=len(nodes), nodes=','.join(nodes), )) if list_inactive > 0: bot.say('Zudem wurden {count} inaktive/offline Knoten mit Version {version} gezählt.'.format( count=list_inactive, version=list_nodes, )) # respond to the user return # check each node in ALFRED data for item in nodes: if (not count_offline) and (item.get('status') not in ['active', 'stale']): offlinenodes += 1 continue release = item.get('firmware') branch = item.get('autoupdater') enabled = branch != 'off' if release is None or branch is None: skipped += 1 continue if not release in result or result[release] is None: result[release] = {'stable': None, 'testing': None, } if not branch in result[release] or result[release][branch] is None: result[release][branch] = {'auto': 0, 'manual': 0, 'total': 0, } result[release][branch]['total'] += 1 mode = 'auto' if enabled else 'manual' result[release][branch][mode] += 1 # respond to user releases = sorted([x for x in result]) for release in releases: output = 'Rollout von \'{0}\':'.format(release) branches = sorted([x for x in result[release]]) first = True for branch in branches: item = result[release][branch] if item is None: continue if not first: output += ',' first = False auto_count = item['auto'] manual_count = item['manual'] output += ' {1} {0}'.format(branch, auto_count) if manual_count > 0: output += ' (+{0} manuell)'.format(manual_count) bot.say(output) # output count of nodes for which the autoupdater's branch and/or # firmware version could not be retrieved if skipped > 0: bot.say('plus {0} Knoten mit unklarem Status'.format(skipped)) if not count_offline and offlinenodes > 0: bot.say('zudem wurden {0} Knoten ignoriert da sie nicht online sind'.format(offlinenodes)) @willie.module.commands('providers') def ffpb_providers(bot, trigger): """Fetch the top 5 providers from BATCAVE.""" providers = __batcave.get_providers() providers.sort(key=lambda x: x['count'], reverse=True) top5 = providers[:5] top5 = ['{0} ({1:.0f}%)'.format(x['name'], x['percentage']) for x in top5] bot.say('Unsere Top 5 Provider: ' + ', '.join(top5))