class Foo(Plugin): def load(self): ''' Called on plugin load/reload ''' # Loads the value of the `bar` configuration key with a default value # if the key were to be absent self.bar = self.config.get('bar', 42) @command(p.string('!foo') + p.bind(p.integer, 'number')) async def foo_command(self, message, number): ''' Called on inputs starting with the string '!foo' (case insensitive) followed by an integer whose value will be bound to the `number` variable ''' await self.send_message( message. channel, # Sends a message in the same channel as the input '{} * {} = {}'.format(number, self.bar, number * self.bar)) @command(p.string('!id') + p.bind(p.mention, 'user_id'), master_only) async def id_command(self, message, user_id): ''' Called on inputs starting with the string '!id' (case insensitive) followed by a discord user mention (usually displayed '@SomeUser' on discord's client) We have access to SomeUser's discord's ID in the `user_id` variable The command is marked as `master_only` and will execute only if the input was sent by someone who was registered as 'master' in the configuration file ''' await self.send_message( message.channel, '{}: the id is {}'.format(message.author.mention, user_id), delete_after=30 # Message deletes itself after 30 seconds )
class ChatBot(Plugin): def load(self): self.chat_bot = Cleverbot() self.dm_bots = defaultdict(Cleverbot) @command( p.bind(p.mention, 'user') + p.bind((p.many(p.any_type >> p.to_s)), 'words'), master_only ) async def answer(self, message, user, words): if str(user) != self.bot.user.id: return await self.bot.send_typing(message.channel) response = self.chat_bot.ask(' '.join(words)) if response: await self.send_message( message.channel, response ) @command(p.bind((p.many(p.any_type >> p.to_s)), 'words')) async def answer_dm(self, message, words): if message.server: return await self.bot.send_typing(message.channel) bot = self.dm_bots[message.author.id] response = bot.ask(' '.join(words)) if response: await self.send_message( message.channel, response )
class Dnd(Plugin): @command( p.string('!roll') + p.bind(p.oneplus(dice_roll_parser), 'dice') + p.eof, # restrictions user_cooldown(30), global_cooldown(15)) async def roll_dice_command(self, message, dice): grouped_dice = group_dice(dice) enforce_dnd_invariants(grouped_dice) results = map(roll_die, grouped_dice) response = ('{} 🎲 rolling your dice\n' '{}'.format(message.author.mention, f.code_block('\n'.join(results)))) await self.send_message(message.channel, response, delete_after=30)
class Instagram(Plugin): def load(self): access_token = self.config.get(c.ACCESS_TOKEN_KEY) client_secret = self.config.get(c.CLIENT_SECRET_KEY) self.api = InstagramAPI(access_token=access_token, client_secret=client_secret) @command(p.string('!instagram') + p.bind(p.word, 'channel'), master_only) async def last_pic(self, message, channel): users = self.api.user_search(channel) if users: user = users[0] media, _ = self.api.user_recent_media(user_id=user.id, count=10) medium = random.choice(media) await self.send_message( message.channel, 'A media from `{}`\'s instagram\n{}'.format( user.full_name, medium.images['standard_resolution'].url), delete_after=30)
class Eval(Plugin): def load(self): self.docker = docker.from_env() self.container_logs = defaultdict(list) context = dict(plugin=self) self.add_web_handlers((r'/eval/logs/(?P<container_id>\S+)', ContainerLogHandler, context), ) ''' Commands ''' @command(p.string('!eval') + p.string('langs')) async def eval_langs(self, message): await self.send_message( message.channel, f.code_block('\n'.join( f'{lang: <10} -> {spec.image: <15} {spec.command("file.ext")}' for lang, spec in EVALSPEC_PER_LANGUAGES.items()))) @command(p.bind(p.sparsed(p.snippet), 'snippet')) async def on_snippet(self, message, snippet): try: spec = EVALSPEC_PER_ALIASES[snippet.language] except KeyError: pass else: query = self.query_yes_no( f'{message.author.mention} Run this code with `{spec.image}` ?', channel=message.channel, author=message.author, timeout=20) if await query: await self.eval(send_message=partial(self.send_message, message.channel), user_key=message.author.id, code=snippet.code, spec=spec) ''' Detail ''' async def eval(self, send_message, user_key, code, spec): host_dir = os.path.join(C.HOST_MOUNT_DIR, user_key) mount_dir = os.path.join(C.DIND_MOUNT_DIR, user_key) user_volume_name = C.USER_STORAGE_VOLUME_NAME(user_key) file_name = await spec.prepare(host_dir, code) file_path = os.path.join(mount_dir, file_name) await self.create_volume(user_volume_name) try: container = self.docker.containers.run( image=spec.image, command=spec.command(file_path), **C.CONTAINER_OPTS(mount_dir, user_volume_name)) except docker.errors.APIError as e: await send_message( f'Error while creating container: {f.code_block(e)}\n', delete_after=20) else: logs = self.container_logs[container.id] stream = container.logs(**C.CONTAINER_LOG_OPTS) logs_url = f'https://globibot.com/bot/eval/logs/{container.id}' await send_message(f'Running... Full logs available at {logs_url}') streaming_message = await send_message('Waiting for output...') self.run_async(self.stream_logs(streaming_message, logs, stream)) status = await self.wait_for_container(container) container.remove(force=True) final_notice = (f'Container exited with status {status}' if status is not None else f'Container killed after {C.MAX_RUN_TIME} seconds') await send_message(final_notice, delete_after=20) async def create_volume(self, volume_name): try: self.docker.volumes.get(volume_name) except docker.errors.NotFound: self.info(f'Creating volume: {volume_name}') self.docker.volumes.create(volume_name, driver_opts=C.VOLUME_DRIVER_OPTS) async def stream_logs(self, message, logs, stream): async def send_update(): if logs: tail = ''.join(logs[-C.MAX_STREAM_LINES:]) await self.bot.edit_message(message, f.code_block(tail)) def accumulate_logs(): try: for line in stream: logs.append(line.decode()) except docker.errors.APIError as e: pass async def update_logs(): try: while True: await send_update() await asyncio.sleep(C.STREAM_REFRESH_IMTERVAL) except asyncio.CancelledError: await send_update() if not logs: await self.bot.edit_message(message, 'No output') loop = asyncio.get_event_loop() update_task = asyncio.ensure_future(update_logs(), loop=loop) await loop.run_in_executor(None, accumulate_logs) update_task.cancel() async def wait_for_container(self, container): loop = asyncio.get_event_loop() call = partial(container.wait, timeout=C.MAX_RUN_TIME) try: return await loop.run_in_executor(None, call) except: return None async def query_yes_no(self, question, channel, **kwargs): question_message = await self.send_message(channel, f'{question}\n[y]es/[n]o') answer_message = await self.bot.wait_for_message(channel=channel, **kwargs) await self.bot.delete_message(question_message) if answer_message is not None: self.run_async(self.delete_message_after(answer_message, 1)) return answer_message.content.lower().startswith('y') else: return False
class Stats(Plugin): GAME_DUMP_INTERVAL = 60 * 10 def load(self): self.add_web_handlers( (r'/stats/game', StatsGameHandler), (r'/stats/user', StatsUserHandler), (r'/stats/api/games/top/(?P<server_id>\w+)', StatsGamesTopHandler, dict(plugin=self)), (r'/stats/api/games/user/(?P<user_id>\w+)', StatsGamesUserHandler, dict(plugin=self)), (r'/stats/(.*)', StatsStaticHandler), ) self.game_times_by_id = dict() now = time() for server in self.bot.servers: for member in server.members: if member.game and member.game.name: game_timer = (member.game.name.lower(), now) self.game_times_by_id[member.id] = game_timer self.alive = True self.run_async(self.dump_periodically()) def unload(self): self.alive = False self.dump_all() async def on_member_update(self, before, after): if before.game != after.game: self.update_game(after.id, after.game) ''' Commands ''' stats_prefix = p.string('!stats') stats_games_prefix = stats_prefix + p.string('games') @command(stats_games_prefix + p.bind(p.mention, 'user_id'), master_only) async def stats_games(self, message, user_id): member = message.server.get_member(str(user_id)) if member is None: return # Flush current tracking self.update_game(member.id, member.game) games = self.top_user_games(user_id) top = [(game.name, game.duration) for game in games[:10]] if games: since = min(map(lambda g: g.created_at, games)) since_days = int((time() - since.timestamp()) / (3600 * 24)) response = ( '{} has played **{}** different games in the last **{}** days ' 'for a total of **{}** seconds\ntop 10:\n{}').format( f.mention(user_id), len(games), since_days, sum(map(lambda g: g.duration, games)), f.code_block(f.format_sql_rows(top))) else: response = '{} has not played any games yet'.format( f.mention(user_id)) await self.send_message(message.channel, response, delete_after=25) @command(stats_games_prefix + p.string('top') + p.bind(p.maybe(p.integer), 'count'), master_only) async def stats_games_top(self, message, count=10): data = self.top_games(message.server, count) if data: await self.send_message( message.channel, 'Most **{}** games played on this server\n{}'.format( len(data), f.code_block(f.format_sql_rows(data))), delete_after=25) @command(stats_games_prefix + p.string('server'), master_only) async def top_games_link(self, message): await self.send_message( message.channel, 'top games played on this server: https://globibot.com/stats/game?id={}' .format(message.server.id), delete_after=45) ''' Details ''' def top_games(self, server, count): user_ids = tuple(member.id for member in server.members) with self.transaction() as trans: trans.execute(q.top_games, dict(limit=count, authors_id=user_ids)) return trans.fetchall() def top_user_games(self, user_id): with self.transaction() as trans: trans.execute(q.author_games, dict(author_id=user_id)) return [GamePlayed(*row) for row in trans.fetchall()] def update_game(self, user_id, new_game): try: game_name, start = self.game_times_by_id[user_id] self.add_game_time(user_id, game_name, start) except KeyError: pass if new_game and new_game.name: self.game_times_by_id[user_id] = (new_game.name.lower(), time()) else: self.game_times_by_id.pop(user_id, None) def add_game_time(self, user_id, game_name, start): duration = int(time() - start) with self.transaction() as trans: trans.execute(q.add_game_times(1), [(user_id, game_name, duration)]) def dump_all(self): now = time() data = [(user_id, game_name, now - start) for user_id, (game_name, start) in self.game_times_by_id.items()] with self.transaction() as trans: trans.execute(q.add_game_times(len(data)), data) for user_id, (game_name, start) in self.game_times_by_id.items(): self.game_times_by_id[user_id] = (game_name, now) async def dump_periodically(self): while True: await asyncio.sleep(Stats.GAME_DUMP_INTERVAL) if not self.alive: break self.dump_all()
class Twitch(Plugin): def load(self): self.client_id = self.config.get(c.CLIENT_ID_KEY) if not self.client_id: self.warning('Missing client id: API calls might not work') self.api = TwitchAPI(self.client_id, self.debug) self.pubsub = PubSub(self.debug, self.run_async) self.restore_monitored() def unload(self): asyncio.ensure_future(self.pubsub.shutdown()) ''' Commands ''' twitch_prefix = p.string('!twitch') @command(twitch_prefix + p.string('status') + p.bind(p.word, 'name')) async def twitch_channel_status(self, message, name): stream, channel = await asyncio.gather(self.api.stream(name), self.api.channel(name)) response = '`{name}` is __{status}__' info = dict(name=channel.display_name, status='online' if stream else 'offline') if stream: response += ': `{title}`\n\n🕹 {game}\n👀 {viewers:,}\n' online_info = dict(title=stream.channel.status, game=stream.game, viewers=stream.viewers) info = {**info, **online_info} await self.send_message(message.channel, response.format(**info), delete_after=15) twitch_top_prefix = twitch_prefix + p.string('top') @command(twitch_top_prefix + p.string('games') + p.bind(p.maybe(p.integer), 'count')) async def twitch_top_games(self, message, count=10): top_games = await self.api.top_games(count) info = [(top_game.game.name, '📺 {:,}'.format(top_game.channels), '👀 {:,}'.format(top_game.viewers)) for top_game in top_games] await self.send_message(message.channel, 'Top {} games on Twitch right now\n{}'.format( len(top_games), f.code_block(f.pad_rows(info, ' | '))), delete_after=15) @command(twitch_top_prefix + p.string('channels') + p.bind(p.maybe(p.integer), 'count')) async def twitch_top_channels(self, message, count=10): streams = await self.api.top_channels(count) info = [(stream.channel.display_name, '🕹 {}'.format(stream.game), '👀 {:,}'.format(stream.viewers), stream.channel.status) for stream in streams] await self.send_message(message.channel, 'Top {} games on Twitch right now\n{}'.format( len(streams), f.code_block(f.pad_rows(info, ' | '))), delete_after=15) @command(twitch_prefix + p.string('monitor') + p.bind(p.word, 'name'), master_only) async def twitch_monitor(self, message, name): channel = await self.api.channel(name) with self.transaction() as trans: trans.execute(q.add_monitored, dict(name=channel.name, server_id=message.server.id)) self.run_async(self.monitor_forever(channel.name, message.server)) await self.send_message(message.channel, 'Now monitoring `{}`'.format( channel.display_name), delete_after=15) @command(twitch_prefix + p.string('unmonitor') + p.bind(p.word, 'name'), master_only) async def twitch_unmonitor(self, message, name): channel = await self.api.channel(name) await self.pubsub.unsubscribe( PubSub.Topics.VIDEO_PLAYBACK(channel.name), message.server.id) with self.transaction() as trans: trans.execute(q.remove_monitored, dict(name=channel.name, server_id=message.server.id)) await self.send_message(message.channel, 'Stopped monitoring `{}`'.format( channel.display_name), delete_after=15) @command(twitch_prefix + p.string('monitored'), master_only) async def monitored(self, message): with self.transaction() as trans: trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] channels = [ channel.name for channel in monitored if str(channel.server_id) == message.server.id ] await self.send_message( message.channel, 'I\'m currently monitoring the following channels:\n{}'.format( f.code_block(channels)), delete_after=15) ''' Details ''' async def monitor_forever(self, name, server): self.info('Monitoring: {}'.format(name)) channel = await self.api.channel(name) events = await self.pubsub.subscribe( PubSub.Topics.VIDEO_PLAYBACK(channel.name), server.id) async for event in events: self.debug(event) if event['type'] == 'stream-up': await self.send_message( server.default_channel, '`{}` just went live!\n{}'.format(channel.display_name, channel.url)) if event['type'] == 'stream-down': await self.send_message( server.default_channel, '`{}` just went offline 😢'.format(channel.display_name)) self.info('Stopped monitoring: {}'.format(name)) def restore_monitored(self): with self.transaction() as trans: trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] for channel in monitored: server = next(serv for serv in self.bot.servers if serv.id == str(channel.server_id)) self.run_async(self.monitor_forever(channel.name, server))
class Dj(Plugin): def load(self): context = dict(plugin=self, bot=self.bot) self.add_web_handlers( (r'/voice', VoiceHandler, context), (r'/ws/voice', VoiceWebSocketHandler, context), ) self.players = dict() self.volumes = defaultdict(lambda: 1.0) self.tts = TTSManager() self.ws_consumers = defaultdict(set) self.ongoing_skips = dict() async def on_reaction_add(self, reaction, user): if user == self.bot.user: return if reaction.emoji == '🗳': await self.check_skips(reaction.message, reaction.count - 1) ''' Commands ''' @command(p.string('!tts')) async def say_command(self, message): content = message.clean_content[5:].strip() await self.queue_tts(message.server, message.author, content) await self.delete_message_after(message, 5) @command(p.string('!voteskip')) async def voteskip(self, message): if message.server.id in self.ongoing_skips: return player = self.get_player(message.server) embed = Embed(title='Voteskip', description='Skip __{}__ ?'.format( player.items[0].resource)) embed.set_footer(text='Click the reaction below to cast your vote') m = await self.send_message(message.channel, '', embed=embed) await self.bot.add_reaction(m, '🗳') self.ongoing_skips[message.server.id] = m self.run_async(self.skip_timeout(message.server.id, m)) @command(p.string('!tts-lang')) async def tts_lang_command(self, message): lang = message.clean_content[10:].strip() self.tts.set_server_language(message.server, lang) await self.delete_message_after(message, 5) @command(p.string('!play')) async def play(self, message): await self.queue_item(message.server, message.author, ItemType.YTDLLink, message.clean_content[6:].strip()) await self.delete_message_after(message, 5) @command(p.string('!skip'), master_only) async def skip(self, message): player = self.get_player(message.server) player.skip() await self.delete_message_after(message, 5) @command(p.string('!volume') + p.bind(p.integer, 'vol'), master_only) async def volume(self, message, vol): if vol < 0 or vol > 100: return vol = float(vol) / 100 self.volumes[message.server.id] = vol player = self.get_player(message.server) player.player.volume = vol ''' Helpers ''' async def check_skips(self, message, count): try: skip_m = self.ongoing_skips[message.server.id] except KeyError: pass else: if skip_m.id == message.id: user_count = len( self.bot.voice_client_in( skip_m.server).channel.voice_members) - 1 if count >= required_skips_for(user_count): player = self.get_player(skip_m.server) player.skip() del self.ongoing_skips[message.server.id] await self.bot.delete_message(message) async def skip_timeout(self, server_id, m): await asyncio.sleep(30) try: skip_m = self.ongoing_skips[server_id] except: pass else: if skip_m.id == m.id: await self.bot.delete_message(m) del self.ongoing_skips[server_id] async def queue_tts(self, server, user, content, lang=None): sound_file = self.tts.talk_in(server, content, lang) await self.queue_item(server, user, ItemType.LocalFile, sound_file) async def join_voice(self, channel): voice = self.bot.voice_client_in(channel.server) if voice is None: await self.bot.join_voice_channel(channel) else: await voice.move_to(channel) async def leave_voice(self, server): voice = self.bot.voice_client_in(server) if voice is not None: await voice.disconnect() async def queue_item(self, server, user, type, resource): try: item = PlayerItem(type, resource, user) except Exception as e: self.notify_ws_error(server, str(e), resource) else: player = self.get_player(server) await player.enqueue(item) self.notify_ws_queue(server, item) def get_player(self, server): try: player = self.players[server.id] except KeyError: get_volume = lambda: self.volumes[server.id] player = ServerPlayer(server, self.bot.voice_client_in, get_volume) self.players[server.id] = player on_next = lambda item: self.notify_ws_next(server, item) self.run_async(player.start(self.error, on_next)) return player def notify_ws_next(self, server, item): data = dict(type='next', playing=item_data(item)) self.notify_ws(server, data) def server_queue_data(self, server): player = self.get_player(server) return dict(type='queue_data', items=[item_data(item) for item in player.items], server_id=server.id) def notify_ws_error(self, server, error, resource): data = dict(type='error', error=error, resource=resource) self.notify_ws(server, data) def notify_ws_queue(self, server, item): data = dict(type='queue', item=item_data(item)) self.notify_ws(server, data) def notify_ws(self, server, data): data['server_id'] = server.id for consumer in self.ws_consumers[server.id]: consumer.write_message(data)
class Meta(Plugin): @command( p.string('!permissions') + p.bind(p.maybe(p.channel), 'channel_id'), master_only ) async def permissions(self, message, channel_id=None): if channel_id is None: channel_id = message.channel.id channel = next( channel for channel in message.server.channels if channel.id == str(channel_id) ) perms = [' - {}'.format(perm) for perm in self.permissions_in(channel)] await self.send_message( message.channel, 'In {}, I can:\n{}' .format(f.channel(channel_id), f.code_block(perms)), delete_after = 30 ) @command(p.string('!channels')) async def channels(self, message): channels = [ (channel.name, channel.id) for channel in message.server.channels ] await self.send_message( message.channel, f.code_block(f.format_sql_rows(channels)), delete_after = 30 ) @command( p.string('!where-can-you') + p.bind(p.word, 'what'), master_only ) async def where_can_you(self, message, what): perm_name = PERMISSION_NAMES[what] permissions = [ (channel, self.permissions_in(channel)) for channel in message.server.channels ] where = [ channel.id for channel, perms in permissions if perm_name in perms and channel.type == ChannelType.text ] await self.send_message( message.channel, 'On this server, I can `{}` {}' .format( PERMISSION_NAMES[what], ('in ' + ' '.join(map(f.channel, where))) if where else '`nowhere`' ), delete_after = 30 ) @command( p.string('!test'), master_only ) async def test(self, message): await self.bot.delete_message(message) def permissions_in(self, channel): member = channel.server.get_member(self.bot.user.id) standard_perms = channel.permissions_for(member) overwrites = [channel.overwrites_for(role) for role in member.roles] sets = [permission_names(perms) for perms in [standard_perms] + overwrites] return reduce(set.union, sets)
class Help(Plugin): def load(self): self.enabled_plugins_names = self.config.get(c.ENABLED_PLUGINS_KEY, []) ''' Commands ''' help_prefix = p.string('!help') @command(help_prefix + p.eof) async def general_help(self, message): help_message = f.code_block( ['!help {}'.format(name) for name in self.enabled_plugins_names], language='css' # Kinda look ok ) await self.send_message(message.channel, '{}\n{}'.format(message.author.mention, help_message), delete_after=30) @command(help_prefix + p.bind(p.word, 'plugin_name')) async def plugin_help(self, message, plugin_name): plugin_name = plugin_name.lower() if plugin_name not in self.enabled_plugins_names: return plugin = self.get_plugin(plugin_name) raw_components = [ '{}{}'.format( validator.parser.name, ' /* master only */' if validator.pre_hook == master_only else '') for validator, _ in plugin.actions ] components = sorted( re.sub(' +', ' ', re.sub(r'[(,)]', '', name)).split(' ') for name in raw_components) lines = [' '.join(comp) for comp in components] help_message = f.code_block(lines, language='css') await self.send_message(message.channel, '{} `{}` plugin commands:\n\n{}'.format( message.author.mention, plugin_name, help_message), delete_after=30) ''' Internals ''' def get_plugin(self, name): return next(plugin for plugin in [ reloader.plugin for reloader in self.bot.plugin_collection.plugin_reloaders if reloader.name == name ])
class Twitch(Plugin): def load(self): self.client_id = self.config.get(c.CLIENT_ID_KEY) self.client_secret = self.config.get(c.CLIENT_SECRET_KEY) if not self.client_id: self.warning('Missing client id: API calls might not work') self.api = TwitchAPI(self.client_id, self.debug) self.pubsub = PubSub(self.debug, self.run_async) self.channels_info = dict() self.restore_monitored() context = dict(plugin=self, bot=self.bot) self.add_web_handlers( (r'/twitch/oauth', OAuthRequestTokenHandler, context), (r'/twitch/authorize', OAuthAuthorizeHandler, context), (r'/twitch/status', TwitchStatusHandler, context), (r'/twitch/followed', TwitchFollowedHandler, context), (r'/twitch/disconnect', TwitchDisconnectHandler, context), (r'/twitch/mention', TwitchMentionHandler, context), (r'/twitch/whisper', TwitchWhisperHandler, context), ) self.token_states = dict() self.run_async(self.refresh_channels_info_forever()) def unload(self): asyncio.ensure_future(self.pubsub.shutdown()) ''' Commands ''' twitch_prefix = p.string('!twitch') @command(twitch_prefix + p.string('status') + p.bind(p.word, 'name')) async def twitch_channel_status(self, message, name): stream, channel = await asyncio.gather( self.api.stream(name), self.api.channel(name) ) response = '`{name}` is __{status}__' info = dict( name = channel.display_name, status = 'online' if stream else 'offline' ) if stream: response += ': `{title}`\n\n🕹 {game}\n👀 {viewers:,}\n' online_info = dict( title = stream.channel.status, game = stream.game, viewers = stream.viewers ) info = {**info, **online_info} await self.send_message( message.channel, response.format(**info), delete_after=15 ) twitch_top_prefix = twitch_prefix + p.string('top') @command(twitch_top_prefix + p.string('games') + p.bind(p.maybe(p.integer), 'count')) async def twitch_top_games(self, message, count=10): top_games = await self.api.top_games(count) info = [ (top_game.game.name, '📺 {:,}'.format(top_game.channels), '👀 {:,}'.format(top_game.viewers)) for top_game in top_games ] await self.send_message( message.channel, 'Top {} games on Twitch right now\n{}' .format(len(top_games), f.code_block(f.pad_rows(info, ' | '))), delete_after=15 ) @command(twitch_top_prefix + p.string('channels') + p.bind(p.maybe(p.integer), 'count')) async def twitch_top_channels(self, message, count=10): streams = await self.api.top_channels(count) info = [ (stream.channel.display_name, '🕹 {}'.format(stream.game), '👀 {:,}'.format(stream.viewers), stream.channel.status) for stream in streams ] await self.send_message( message.channel, 'Top {} games on Twitch right now\n{}' .format(len(streams), f.code_block(f.pad_rows(info, ' | '))), delete_after=15 ) @command( twitch_prefix + p.string('monitor') + p.bind(p.word, 'name'), master_only ) async def twitch_monitor(self, message, name): channel = await self.api.channel(name) with self.transaction() as trans: trans.execute(q.add_monitored, dict( name = channel.name, server_id = message.server.id )) self.run_async(self.monitor_forever(channel.name, message.server)) await self.send_message( message.channel, 'Now monitoring `{}`'.format(channel.display_name), delete_after=15 ) @command( twitch_prefix + p.string('unmonitor') + p.bind(p.word, 'name'), master_only ) async def twitch_unmonitor(self, message, name): channel = await self.api.channel(name) await self.pubsub.unsubscribe( PubSub.Topics.VIDEO_PLAYBACK(channel.name), message.server.id ) with self.transaction() as trans: trans.execute(q.remove_monitored, dict( name = channel.name, server_id = message.server.id )) await self.send_message( message.channel, 'Stopped monitoring `{}`'.format(channel.display_name), delete_after=15 ) @command(twitch_prefix + p.string('monitored'), master_only) async def monitored(self, message): with self.transaction() as trans: trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] channels = [ channel.name for channel in monitored if str(channel.server_id) == message.server.id ] await self.send_message( message.channel, 'I\'m currently monitoring the following channels:\n{}' .format(f.code_block(channels)), delete_after=15 ) ''' Details ''' CHANNELS_INFO_REFRESH_INTERVAL = 60 * 10 async def refresh_channels_info_forever(self): while True: await asyncio.sleep(Twitch.CHANNELS_INFO_REFRESH_INTERVAL) for channel_name in self.channels_info: await self.refresh_channel_info(channel_name) async def refresh_channel_info(self, channel_name): channel = await self.api.channel(channel_name) self.channels_info[channel_name] = channel async def monitor_forever(self, channel_name, server): self.info('Monitoring: {}'.format(channel_name)) await self.refresh_channel_info(channel_name) events = await self.pubsub.subscribe( PubSub.Topics.VIDEO_PLAYBACK(channel_name), server.id ) async for event in events: channel = self.channels_info[channel_name] if event['type'] == 'stream-up': users = self.users_to_mention(channel.name, server) mentions = ' '.join(f.mention(user_id) for user_id in users) await self.send_message( server.default_channel, '{}\nWake up!'.format(mentions), embed = twitch_alert_embed(channel, True) ) if event['type'] == 'stream-down': await self.send_message( server.default_channel, '`{}` just went offline 😢'.format(channel.display_name) ) self.info('Stopped monitoring: {}'.format(channel_name)) async def whisper_monitor_forever(self, channel_name, user): await self.refresh_channel_info(channel_name) events = await self.pubsub.subscribe( PubSub.Topics.VIDEO_PLAYBACK(channel_name), user.id ) async for event in events: if event['type'] == 'stream-up': await self.send_message( user, 'Just a heads up', embed = twitch_alert_embed(self.channels_info[channel_name]) ) def users_to_mention(self, channel_name, server): with self.transaction() as trans: trans.execute(q.get_subscribed_users, dict( channel = channel_name, method = 'mention' )) return [ user_id for user_id, in trans.fetchall() if server in self.bot.servers_of( self.bot.find_user(str(user_id)) ) ] return [] def users_to_whisper(self, channel_name): with self.transaction() as trans: trans.execute(q.get_subscribed_users, dict( channel = channel_name, method = 'whisper' )) return [user_id for user_id, in trans.fetchall()] return [] def restore_monitored(self): with self.transaction() as trans: # Server monitors trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] for channel in monitored: server = next( serv for serv in self.bot.servers if serv.id == str(channel.server_id) ) self.run_async(self.monitor_forever(channel.name, server)) # Whispers trans.execute(q.get_all_notify_whispers) for user_id, channel_name in trans.fetchall(): user = self.bot.find_user(str(user_id)) if user: self.run_async(self.whisper_monitor_forever(channel_name, user)) def request_token_state(self, user): state = ''.join( random.choice(string.ascii_uppercase + string.digits) for _ in range(32) ) self.token_states[user.id] = state return state async def save_user(self, user, code, state): try: reference_state = self.token_states[user.id] except KeyError: return else: if state != reference_state: return token = await self.access_token(code, state) with self.transaction() as trans: trans.execute(q.add_user, dict( id = user.id, token = token )) TOKEN_URL = 'https://api.twitch.tv/kraken/oauth2/token' REDIRECT_URI = 'https://globibot.com/bot/twitch/authorize' async def access_token(self, code, state): client = AsyncHTTPClient() payload = ( ('client_id', self.client_id), ('client_secret', self.client_secret), ('grant_type', 'authorization_code'), ('redirect_uri', Twitch.REDIRECT_URI), ('code', code), ('state', state), ) url = Twitch.TOKEN_URL request = HTTPRequest( url = url, method = 'POST', body = urlencode(payload) ) tornado_future = client.fetch(request) future = to_asyncio_future(tornado_future) response = await future data = json_decode(response.body) return data['access_token'] def disconnect_user(self, user): with self.transaction() as trans: trans.execute(q.delete_user, dict( id = user.id )) def user_connected(self, user): with self.transaction() as trans: trans.execute(q.get_user, dict(id=user.id)) if trans.fetchone(): return True return False async def user_followed(self, user): token = self.get_user_token(user) followed = await self.api.user_followed(token) user_servers = self.bot.servers_of(user) with self.transaction() as trans: trans.execute(q.get_monitored_names, dict( server_ids = tuple(server.id for server in user_servers) )) monitored_names = set(name for name, in trans.fetchall()) trans.execute(q.get_notified, dict( id = user.id, )) notifieds = [NotifiedChannel(*row) for row in trans.fetchall()] whipered = [ notified.name for notified in notifieds if notified.method == 'whisper' ] mentionned = [ notified.name for notified in notifieds if notified.method == 'mention' ] followed_channels = [ChannelState(f.channel.name, f.channel.name in whipered) for f in followed] monitored_channels = [ChannelState(name, name in mentionned) for name in monitored_names] return ( followed_channels, monitored_channels ) return ([], []) def get_user_token(self, user): with self.transaction() as trans: trans.execute(q.get_user, dict(id=user.id)) return trans.fetchone()[0] def user_notify_mention(self, user, channel, state): payload = dict( id = user.id, channel = channel, method = 'mention' ) query = q.user_notify_add if state else q.user_notify_remove with self.transaction() as trans: trans.execute(query, payload) async def user_notify_whisper(self, user, channel, state): payload = dict( id = user.id, channel = channel, method = 'whisper' ) query = q.user_notify_add if state else q.user_notify_remove with self.transaction() as trans: trans.execute(query, payload) if state: self.run_async(self.whisper_monitor_forever(channel, user)) else: await self.pubsub.unsubscribe( PubSub.Topics.VIDEO_PLAYBACK(channel), user.id )
class CryptoCurrency(Plugin): def load(self): self.api = CryptoApi() self.currencies = dict() self.markets_per_coin_pairs = dict() self.run_async(self.fetch_all_currencies()) self.run_async(self.fetch_market_coin_pairs()) async def fetch_all_currencies(self): all_tickers = await self.api.all_tickers() for ticker in all_tickers: id_, name, symbol = ticker['id'], ticker['name'], ticker['symbol'] self.currencies[name.lower()] = id_ self.currencies[symbol.lower()] = id_ self.currencies[id_.lower()] = id_ async def fetch_market_coin_pairs(self): while True: self.markets_per_coin_pairs = await self.api.markets_per_coin_pairs( ) await asyncio.sleep(12 * 3600) @command( p.string('!price-old') + p.bind(p.word, 'currency') + p.bind(p.maybe(p.word), 'exchange')) async def crypto(self, message, currency, exchange=None): if currency.lower() not in self.currencies: await self.send_message( message.channel, '{} I did not find any cryptocurrency named **{}**'.format( message.author.mention, currency)) elif exchange is not None and exchange.lower() not in EXCHANGES: await self.send_message( message.channel, '{} I cannot convert to **{}**'.format(message.author.mention, exchange)) else: currency_id = self.currencies[currency.lower()] exchange = exchange.lower() if exchange is not None else None try: tickers = await self.api.ticker(currency_id, exchange) ticker = tickers[0] except: await self.send_message( message.channel, '{} there was an error contacting __coinmarketcap__'. format(message.author.mention)) else: await self.send_message(message.channel, format_price(ticker, exchange)) @command( p.string('!price') + p.bind(p.word, 'currency') + p.bind(p.maybe(p.word), 'exchange')) async def crypto(self, message, currency, exchange='EUR'): if currency.lower() not in self.currencies: await self.send_message( message.channel, '{} I did not find any cryptocurrency named **{}**'.format( message.author.mention, currency)) elif exchange is not None and exchange.lower() not in EXCHANGES: await self.send_message( message.channel, '{} I cannot convert to **{}**'.format(message.author.mention, exchange)) else: currency_id = self.currencies[currency.lower()] exchange = exchange.lower() if exchange is not None else None try: tickers = await self.api.ticker(currency_id, exchange) ticker = tickers[0] except: await self.send_message( message.channel, '{} there was an error contacting __coinmarketcap__'. format(message.author.mention)) else: await self.send_message(message.channel, '', embed=price_embed(ticker, exchange)) @command( p.string('!chart') + p.bind(p.word, 'quote') + p.bind(p.word, 'base') + p.bind(p.integer, 'span_factor') + p.bind(SPAN_UNIT, 'span_unit')) async def chart(self, message, quote, base, span_factor, span_unit): quote = quote.upper() base = base.upper() span = span_factor * UNIT_TIMES[span_unit] m = await self.send_message(message.channel, 'Generating the chart...') try: true_base = 'BTC' if base == 'USD' else base market = self.markets_per_coin_pairs[(quote, true_base)] except KeyError: await self.send_message( message.channel, 'I don\'t aggregate any market that has that data') else: try: data = await self.api.candle_data(market, quote, true_base, span) except Exception as e: self.error(format_exc(e)) await self.send_message( message.channel, 'Error fetching data from {}'.format(market.name)) else: try: if base == 'USD': tickers = await self.api.ticker( self.currencies['btc'], None) ticker = tickers[0] price = float(ticker['price_usd']) data = [[i[0], *(x * price for x in i[1:])] for i in data] except Exception as e: self.error(format_exc(e)) await self.send_message(message.channel, 'Error converting BTC to USD') chart = make_chart(quote, base, market, span_factor, span_unit, data) await self.send_file(message.channel, chart) finally: await self.bot.delete_message(m)
class Images(Plugin): # def load(self): GUN_URL = 'https://dl.dropboxusercontent.com/u/3427186/3071dbc60204c84ca0cf423b8b08a204.png' @command(p.bind(p.mention, 'user_id') + p.string('🔫'), master_only) async def kill_user(self, message, user_id): member = message.server.get_member(str(user_id)) if member is None: return client = AsyncHTTPClient() tornado_future = client.fetch(member.avatar_url) future = to_asyncio_future(tornado_future) response = await future with open('/tmp/plop.png', 'wb') as f: f.write(response.body) tornado_future = client.fetch(Images.GUN_URL) future = to_asyncio_future(tornado_future) response = await future with open('/tmp/gun.svg', 'wb') as f: f.write(response.body) avatar = Image.open('/tmp/plop.png') gun = Image.open('/tmp/gun.svg') gun.resize(avatar.size) total = Image.new('RGB', (avatar.width * 2, avatar.height)) total.paste(avatar, (0, 0)) total.paste(gun, (avatar.width, 0)) total.save('/tmp/lol.png') await self.send_file( message.channel, '/tmp/lol.png', delete_after = 30, content = '{} is now dead 👍'.format(member.mention) ) MISTAKE_URL = 'http://i.imgur.com/3nR5fDf.png' @command(p.string('!mistake') + p.bind(p.mention, 'user_id'), master_only) async def mistake_user(self, message, user_id): member = message.server.get_member(str(user_id)) if member is None: return client = AsyncHTTPClient() tornado_future = client.fetch(member.avatar_url) future = to_asyncio_future(tornado_future) response = await future with open('/tmp/plop.png', 'wb') as f: f.write(response.body) tornado_future = client.fetch(Images.MISTAKE_URL) future = to_asyncio_future(tornado_future) response = await future with open('/tmp/mistake.png', 'wb') as f: f.write(response.body) avatar = Image.open('/tmp/plop.png') mistake = Image.open('/tmp/mistake.png') avatar = avatar.resize((505, 557)) mistake.paste(avatar, (mistake.width - 505, mistake.height - 557)) mistake.save('/tmp/lol.png') await self.send_file( message.channel, '/tmp/lol.png', delete_after = 30, ) @command(p.string('!mistake') + p.bind(p.word, 'url')) async def mistake_url(self, message, url): client = AsyncHTTPClient() try: tornado_future = client.fetch(url) future = to_asyncio_future(tornado_future) response = await future with open('/tmp/plop.png', 'wb') as f: f.write(response.body) except: return tornado_future = client.fetch(Images.MISTAKE_URL) future = to_asyncio_future(tornado_future) response = await future with open('/tmp/mistake.png', 'wb') as f: f.write(response.body) img = Image.open('/tmp/plop.png') mistake = Image.open('/tmp/mistake.png') img = img.resize((505, 557)) mistake.paste(img, (mistake.width - 505, mistake.height - 557)) mistake.save('/tmp/lol.png') await self.send_file( message.channel, '/tmp/lol.png', delete_after = 30, )
class Utils(Plugin): PREFIXES = ['!', '-', '/'] prefixed = lambda s: p.one_of(p.string, *[ '{}{}'.format(prefix, s) for prefix in Utils.PREFIXES ]).named('[{}]{}'.format(''.join(Utils.PREFIXES), s)) >> p.to_s @command(p.bind(p.oneplus(p.sparsed(unit_value_parser)), 'unit_values'), master_only) async def convert(self, message, unit_values): converted = [(uv, system_convert(uv)) for uv in unit_values] output = ['{} = {}'.format(uv, conv) for uv, conv in converted] for t, uvs in groupby(converted, key=lambda uvs: type(uvs[0].unit)): values = list(map(lambda x: x[0], uvs)) if len(values) >= 2: summed = sum_units(*values) converted_summed = system_convert(summed) output.append( '{} total: {} = {}' .format(t.__name__.lower(), summed, converted_summed) ) await self.send_message( message.channel, 'Converted units\n{}' .format(f.code_block(output)), delete_after = 60 ) @command(p.string('!user') + p.bind(p.mention, 'user')) async def user_id(self, message, user): await self.send_message( message.channel, '{}'.format(user), delete_after=15 ) @command( p.string('!master') + p.string('add') + p.bind(p.mention, 'user'), master_only ) async def add_master(self, message, user): self.bot.masters.append(str(user)) await self.send_message( message.channel, '{} can now use master commands'.format(f.mention(user)), delete_after=20 ) @command(p.string('!master') + p.string('remove') + p.bind(p.mention, 'user')) async def remove_master(self, message, user): if message.author.id != '89108411861467136': return self.bot.masters.remove(str(user)) await self.send_message( message.channel, '{} can no longer use master commands'.format(f.mention(user)), delete_after=20 ) @command(p.string('!user') + p.bind(p.integer, 'user_id')) async def user(self, message, user_id): await self.send_message( message.channel, f.mention(user_id), delete_after=15 ) @command(p.string('!channel') + p.bind(p.channel, 'channel')) async def channel_id(self, message, channel): await self.send_message( message.channel, '{}'.format(channel), delete_after=15 ) @command(p.string('!channel') + p.bind(p.integer, 'channel_id')) async def channel(self, message, channel_id): await self.send_message( message.channel, f.channel(channel_id), delete_after=15 ) @command(p.string('!server') + p.eof) async def server_id(self, message): await self.send_message( message.channel, '{}'.format(message.server.id), delete_after=15 ) @command( p.string('!block') + p.bind(p.mention, 'who'), master_only ) async def block(self, message, who): # TODO: Actually block that bitch 😂 await self.send_message( message.channel, '{} is now blocked 👍'.format(f.mention(who)) ) @command( p.string('!unsafe') + p.bind(p.snippet, 'snippet'), master_only ) async def sql_exec(self, message, snippet): if snippet.language.lower() != 'sql': return try: with self.transaction() as trans: trans.execute(snippet.code) rows = trans.fetchall() self.debug(rows) if rows: text = f.code_block(f.format_sql_rows(rows)) else: text = 'Request executed successfully\n`no output`' await self.send_message(message.channel, text) except Exception as e: await self.send_message(message.channel, '```\n{}\n```'.format(e))
class Twitter(Plugin): def load(self): self.oauth = OAuth( self.config.get(c.ACCESS_TOKEN_KEY), self.config.get(c.ACCESS_TOKEN_SECRET_KEY), self.config.get(c.CONSUMER_KEY_KEY), self.config.get(c.CONSUMER_SECRET_KEY), ) self.client = TwitterAPI(auth=self.oauth) context = dict(plugin=self, bot=self.bot) self.add_web_handlers( (r'/twitter/oauth_token', OAuthTokenHandler, context), (r'/twitter/authorize', OAuthAuthorizeHandler, context), (r'/twitter/status', TwitterStatusHandler, context), (r'/twitter/disconnect', TwitterDisconnectHandler, context), ) self.monitored = set() self.restore_monitored() self.request_tokens = dict() self.last_tweets = dict() self.interactive_tweets = dict() def unload(self): self.monitored.clear() async def on_reaction_add(self, reaction, user): try: tweet = self.interactive_tweets[reaction.message.id] except KeyError: pass else: if reaction.emoji == '❤': await self.like_tweet(tweet, reaction.message.channel, user) elif reaction.emoji == '🔄': await self.rt_tweet(tweet, reaction.message.channel, user) async def on_reaction_remove(self, reaction, user): try: tweet = self.interactive_tweets[reaction.message.id] except KeyError: pass else: if reaction.emoji == '❤': await self.unlike_tweet(tweet, reaction.message.channel, user) elif reaction.emoji == '🔄': await self.unrt_tweet(tweet, reaction.message.channel, user) async def on_new(self, message): for match in TWITTER_STATUS_PATTERN.finditer(message.clean_content): status_id = match.group('status_id') tweet = self.client.statuses.show(id=status_id) if tweet: await asyncio.sleep(2) await self.set_interactive_tweet(tweet, message) ''' Commands ''' twitter_prefix = p.string('!twitter') @command(twitter_prefix + p.string('last') + p.bind(p.word, 'screen_name'), master_only) async def last_tweet(self, message, screen_name): user = self.get_user(screen_name=screen_name) tweets = self.get_tweets(user['id'], count=1) if tweets: tweet = tweets[0] m = await self.send_interactive_tweet(message.channel, tweet, delete_after=150) self.last_tweets[message.channel.id] = tweet self.run_async(self.update_tweet(tweet, m)) @command(twitter_prefix + p.string('monitor') + p.bind(p.word, 'screen_name'), master_only) async def monitor(self, message, screen_name): user = self.get_user(screen_name=screen_name) user_id = user['id'] with self.transaction() as trans: trans.execute( q.add_monitored, dict(user_id=user_id, server_id=message.server.id, channel_id=message.channel.id)) self.run_async(self.monitor_forever(user_id, message.channel)) await self.send_message( message.channel, 'Now monitoring `{}` tweets in this channel'.format( screen_name), delete_after=15) @command(twitter_prefix + p.string('unmonitor') + p.bind(p.word, 'screen_name'), master_only) async def unmonitor(self, message, screen_name): user = self.get_user(screen_name=screen_name) user_id = user['id'] if user_id not in self.monitored: return self.monitored.discard(user_id) with self.transaction() as trans: trans.execute(q.remove_monitored, dict(user_id=user_id, server_id=message.server.id)) await self.send_message(message.channel, 'Stopped monitoring `{}`'.format(screen_name), delete_after=15) @command(twitter_prefix + p.string('monitored'), master_only) async def monitored(self, message): with self.transaction() as trans: trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] users = [ self.get_user_by_id(channel.user_id)['screen_name'] for channel in monitored if str(channel.server_id) == message.server.id ] await self.send_message( message.channel, 'I\'m currently monitoring the following channels:\n{}'.format( f.code_block(users))) @command(p.string('!like')) async def like_tweet_command(self, message): try: tweet = self.last_tweets[message.channel.id] except KeyError: pass else: await self.like_tweet(tweet, message.channel, message.author) @command(p.string('!unlike')) async def unlike_tweet_command(self, message): try: tweet = self.last_tweets[message.channel.id] except KeyError: pass else: await self.unlike_tweet(tweet, message.channel, message.author) @command(p.string('!rt')) async def rt_tweet_command(self, message): try: tweet = self.last_tweets[message.channel.id] except KeyError: pass else: await self.rt_tweet(tweet, message.channel, message.author) @command(p.string('!unrt')) async def unrt_tweet_command(self, message): try: tweet = self.last_tweets[message.channel.id] except KeyError: pass else: await self.unrt_tweet(tweet, message.channel, message.author) @command(p.string('!reply')) async def reply_tweet(self, message): try: tweet = self.last_tweets[message.channel.id] except KeyError: pass else: # Replace emojis to avoid weird moon runes reply = re.sub(r'<:(.*):[0-9]+>', r'\1', message.clean_content[len('!reply'):].strip()) await self.twitter_three_legged_action( tweet, message.channel, message.author, lambda api, tweet: api.statuses.update(status='@{} {}'.format( tweet['user']['screen_name'], reply), in_reply_to_status_id= tweet['id'], _method='POST'), 'reply to', ) ''' Details ''' def get_user(self, screen_name): try: return self.client.users.lookup(screen_name=screen_name)[0] except Exception: raise e.UserNotFound(screen_name) def get_user_by_id(self, user_id): return self.client.users.lookup(user_id=user_id)[0] def get_tweets(self, user_id, count): try: return self.client.statuses.user_timeline(user_id=user_id, count=count, exclude_replies=True) except: return None def restore_monitored(self): with self.transaction() as trans: trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] for channel in monitored: server = next(serv for serv in self.bot.servers if serv.id == str(channel.server_id)) self.run_async( self.monitor_forever( channel.user_id, server.get_channel(str(channel.channel_id)))) async def monitor_forever(self, user_id, channel): self.monitored.add(user_id) tweets = self.get_tweets(user_id, count=1) if not tweets: return last_tweet = tweets[0] self.debug('Monitoring new tweets from {}'.format(user_id)) while user_id in self.monitored: await asyncio.sleep(45) tweets = self.get_tweets(user_id, count=1) if tweets: latest = tweets[0] if latest['id'] != last_tweet['id']: if tweet_time(latest) > tweet_time(last_tweet): last_tweet = latest m = await self.send_interactive_tweet(channel, latest) self.last_tweets[channel.id] = latest self.run_async(self.update_tweet(latest, m)) self.debug('No longer monitoring {}'.format(user_id)) async def send_interactive_tweet(self, channel, tweet, **kwargs): message = await self.send_message(channel, '', embed=self.tweet_embed(tweet), **kwargs) await self.set_interactive_tweet(tweet, message) return message async def set_interactive_tweet(self, tweet, message): await self.bot.add_reaction(message, '🔄') await self.bot.add_reaction(message, '❤') self.interactive_tweets[message.id] = tweet async def update_tweet(self, tweet, message): for _ in range(10): await asyncio.sleep(20) try: tweet = self.client.statuses.show(id=tweet['id']) await self.bot.edit_message(message, '', embed=self.tweet_embed(tweet)) except Exception as e: self.error(e) pass AUTHORIZE_CALLBACK = 'https://globibot.com/bot/twitter/authorize' def request_token(self, user): tweaked_twitter_client = TwitterAPI(auth=self.oauth, format='', api_version=None) try: response = tweaked_twitter_client.oauth.request_token( oauth_callback=Twitter.AUTHORIZE_CALLBACK) except Exception as e: self.error('Error while generating oauth token: {}'.format(e)) else: params = dict( (key, value) for key, value in (component.split('=') for component in response.split('&'))) try: token = params['oauth_token'] secret = params['oauth_token_secret'] except: return else: self.info('Generated request token for {}'.format(user.name)) self.request_tokens[token] = secret return token def save_user(self, user, oauth_token, oauth_verifier): oauth = OAuth( oauth_token, self.request_tokens[oauth_token], self.config.get(c.CONSUMER_KEY_KEY), self.config.get(c.CONSUMER_SECRET_KEY), ) tweaked_twitter_client = TwitterAPI(auth=oauth, format='', api_version=None) try: response = tweaked_twitter_client.oauth.access_token( oauth_verifier=oauth_verifier) except Exception as e: self.error(e) pass else: params = dict( (key, value) for key, value in (component.split('=') for component in response.split('&'))) with self.transaction() as trans: trans.execute( q.add_user, dict(id=user.id, token=params['oauth_token'], secret=params['oauth_token_secret'])) def disconnect_user(self, user): with self.transaction() as trans: trans.execute(q.delete_user, dict(id=user.id)) def user_connected(self, user): with self.transaction() as trans: trans.execute(q.get_user, dict(id=user.id)) if trans.fetchone(): return True return False def get_user_oauth(self, user): with self.transaction() as trans: trans.execute(q.get_user, dict(id=user.id)) data = trans.fetchone() if data: return OAuthUser(*data) def get_user_api(self, oauth_user): oauth = OAuth( oauth_user.token, oauth_user.secret, self.config.get(c.CONSUMER_KEY_KEY), self.config.get(c.CONSUMER_SECRET_KEY), ) return TwitterAPI(auth=oauth) async def like_tweet(self, tweet, channel, user): await self.twitter_three_legged_action( tweet, channel, user, lambda api, tweet: api.favorites.create(_id=tweet['id']), 'like', ) async def unlike_tweet(self, tweet, channel, user): await self.twitter_three_legged_action( tweet, channel, user, lambda api, tweet: api.favorites.destroy(_id=tweet['id']), 'unlike', ) async def rt_tweet(self, tweet, channel, user): await self.twitter_three_legged_action( tweet, channel, user, lambda api, tweet: api.statuses.retweet(id=tweet['id'], _method='POST'), 'retweet', ) async def unrt_tweet(self, tweet, channel, user): await self.twitter_three_legged_action( tweet, channel, user, lambda api, tweet: api.statuses.unretweet(id=tweet['id'], _method='POST'), 'unretweet', ) async def inform_user_about_connections(self, user): await self.send_message( user, 'Psst\nI noticed that you tried using a special Twitter command\n' 'This command requires me to manipulate a limited scope of ' 'your Twitter account\n' 'If you trust me enough to do so, please go to ' 'https://globibot.com/#connections to connect your Twitter acount') async def twitter_three_legged_action(self, tweet, channel, user, action, description): oauth_user = self.get_user_oauth(user) if oauth_user is None: await self.inform_user_about_connections(user) return user_api = self.get_user_api(oauth_user) try: action(user_api, tweet) except Exception as e: await self.send_message( user, 'I couldn\'t {} `{}`\'s tweet for you\n' 'Twitter said:\n{}'.format(description, tweet['user']['screen_name'], f.code_block(str(e.response_data)))) else: await self.send_message(channel, '{} I {} `{}`\'s tweet for you 👍'.format( user.mention, PAST_FORMS[description], tweet['user']['screen_name']), delete_after=5) def replies_to_tweet(self, tweet): screen_name = tweet['user']['screen_name'] query = '@{}'.format(screen_name) try: result = self.client.search.tweets(q=query, since_id=tweet['id']) statuses = result['statuses'] except Exception as e: self.debug('Error fetching replies: {}'.format(e)) return [] else: return [ status for status in statuses if status['in_reply_to_status_id'] == tweet['id'] ] def tweet_embed(self, tweet): name = tweet['user']['name'] screen_name = tweet['user']['screen_name'] body = '{text}\n\n🔄 **{rts}** ❤ **{likes}**'.format( text=tweet['text'], rts=tweet['retweet_count'], likes=tweet_favorite_count(tweet)) embed = Embed( title='Latest tweet', description=body, url='https://twitter.com/{}/status/{}'.format( screen_name, tweet['id']), ) embed.set_author(name=name, icon_url='https://twitter.com/favicon.ico', url='https://twitter.com/{}'.format(screen_name)) embed.set_thumbnail(url=tweet['user']['profile_image_url_https']) embed.colour = 0x1da1f2 embed.set_footer(text='Click the reactions below to like or retweet') embed.timestamp = tweet_time(tweet) try: entities = tweet['extended_entities']['media'] media_urls = [entity['media_url_https'] for entity in entities] except: pass else: embed.set_image(url=media_urls[0]) replies = self.replies_to_tweet(tweet) if replies: embed.add_field(name='Latest replies', value='\n'.join( format_reply(reply, tweet) for reply in replies[:3])) return embed
class Reactions(Plugin): def load(self): self.votes = dict() async def on_new(self, message): if URL_PATTERN.findall(message.content) or message.attachments: await self.bot.add_reaction(message, '👍') await self.bot.add_reaction(message, '👎') async def on_reaction_add(self, reaction, user): if reaction.emoji == '❌': await self.check_vote_delete(reaction) elif reaction.emoji == '👎': await self.check_downvote(reaction) @command( p.string('!react') + p.bind(p.mention, 'who') + p.bind(p.word, 'emoji'), master_only) async def react_last_user(self, message, who, emoji): try: last_message = next(msg for msg in list(self.bot.messages)[-2::-1] if msg.channel.id == message.channel.id and msg.author.id == str(who)) except StopIteration: self.debug('No Message') else: await self.bot.add_reaction(last_message, emoji) await self.bot.delete_message(message) @command( p.string('!react') + p.bind(p.mention, 'who') + p.bind(p.emoji, 'emoji_id'), master_only) async def react_last_user_emoji(self, message, who, emoji_id): try: last_message = next(msg for msg in list(self.bot.messages)[-2::-1] if msg.channel.id == message.channel.id and msg.author.id == str(who)) except StopIteration: self.debug('No Message') else: for server in self.bot.servers: for emoji in server.emojis: if emoji.id == str(emoji_id): try: await self.bot.add_reaction(last_message, emoji) await self.bot.delete_message(message) return except: break await self.send_message(message.channel, "I can't use that emoji 😢", delete_after=5) await self.bot.add_reaction(last_message, '❓') @command( p.string('!votedel') + p.bind(p.mention, 'who') + p.bind(p.maybe(p.integer), 'count'), master_only) async def votedel(self, message, who, count=10): try: last_message = next(msg for msg in list(self.bot.messages)[-2::-1] if msg.channel.id == message.channel.id and msg.author.id == str(who)) except StopIteration: self.debug('No Message') else: self.votes[last_message.id] = (last_message, count) await self.bot.add_reaction(last_message, '❌') await self.send_message( message.channel, 'Deletion vote started: Cast your vote by clicking the ❌ reaction in order to delete {}\'s message ({} votes needed)' .format(last_message.author.mention, count), delete_after=10) async def check_vote_delete(self, reaction): try: message, count = self.votes[reaction.message.id] except KeyError: pass else: if reaction.count >= count: del self.votes[reaction.message.id] await self.bot.delete_message(message) await self.send_message(reaction.message.channel, 'Deletion vote passed', delete_after=5) async def check_downvote(self, reaction): if reaction.count >= 10: await self.bot.delete_message(reaction.message) await self.send_message( reaction.message.channel, '{}, I deleted your post since it was disliked too many times'. format(reaction.message.author.mention), delete_after=10)
class Twitter(Plugin): def load(self): self.oauth = OAuth( self.config.get(c.ACCESS_TOKEN_KEY), self.config.get(c.ACCESS_TOKEN_SECRET_KEY), self.config.get(c.CONSUMER_KEY_KEY), self.config.get(c.CONSUMER_SECRET_KEY), ) self.client = TwitterAPI(auth=self.oauth) self.monitored = set() self.restore_monitored() def unload(self): self.monitored.clear() ''' Commands ''' twitter_prefix = p.string('!twitter') @command(twitter_prefix + p.string('last') + p.bind(p.word, 'screen_name'), master_only) async def last_tweet(self, message, screen_name): user = self.get_user(screen_name=screen_name) tweets = self.get_tweets(user['id'], count=1) if tweets: tweet = tweets[0] m = await self.send_message(message.channel, format_tweet(tweet), delete_after=60) self.run_async(self.update_tweet(tweet, m)) @command(twitter_prefix + p.string('monitor') + p.bind(p.word, 'screen_name'), master_only) async def monitor(self, message, screen_name): user = self.get_user(screen_name=screen_name) user_id = user['id'] with self.transaction() as trans: trans.execute(q.add_monitored, dict(user_id=user_id, server_id=message.server.id)) self.run_async(self.monitor_forever(user_id, message.server)) await self.send_message( message.channel, 'Now monitoring `{}` tweets'.format(screen_name), delete_after=15) @command(twitter_prefix + p.string('unmonitor') + p.bind(p.word, 'screen_name'), master_only) async def unmonitor(self, message, screen_name): user = self.get_user(screen_name=screen_name) user_id = user['id'] if user_id not in self.monitored: return del self.monitored[user_id] with self.transaction() as trans: trans.execute(q.remove_monitored, dict(user_id=user_id, server_id=message.server.id)) await self.send_message(message.channel, 'Stopped monitoring `{}`'.format(screen_name), delete_after=15) @command(twitter_prefix + p.string('monitored'), master_only) async def monitored(self, message): with self.transaction() as trans: trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] users = [ self.get_user_by_id(channel.user_id)['screen_name'] for channel in monitored if str(channel.server_id) == message.server.id ] await self.send_message( message.channel, 'I\'m currently monitoring the following channels:\n{}'.format( f.code_block(users))) ''' Details ''' def get_user(self, screen_name): try: return self.client.users.lookup(screen_name=screen_name)[0] except Exception: raise e.UserNotFound(screen_name) def get_user_by_id(self, user_id): return self.client.users.lookup(user_id=user_id)[0] def get_tweets(self, user_id, count): try: return self.client.statuses.user_timeline(user_id=user_id, count=count, exclude_replies=True) except: return None def restore_monitored(self): with self.transaction() as trans: trans.execute(q.get_monitored) monitored = [MonitoredChannel(*row) for row in trans.fetchall()] for channel in monitored: server = next(serv for serv in self.bot.servers if serv.id == str(channel.server_id)) self.run_async(self.monitor_forever(channel.user_id, server)) async def monitor_forever(self, user_id, server): self.monitored.add(user_id) tweets = self.get_tweets(user_id, count=1) if not tweets: return last_tweet = tweets[0] self.debug('Monitoring new tweets from {}'.format(user_id)) while user_id in self.monitored: await asyncio.sleep(45) tweets = self.get_tweets(user_id, count=1) if tweets: latest = tweets[0] if latest['id'] != last_tweet['id']: if tweet_time(latest) > tweet_time(last_tweet): last_tweet = latest m = await self.send_message(server.default_channel, format_tweet(latest)) self.run_async(self.update_tweet(latest, m)) self.debug('No longer monitoring {}'.format(user_id)) async def update_tweet(self, tweet, message): for i in range(24): await asyncio.sleep(5) try: tweet = self.client.statuses.show(id=tweet['id']) await self.edit_message(message, format_tweet(tweet)) except Exception as e: self.error(e) pass
class Logger(Plugin): def load(self): self.ws_consumers = defaultdict(set) self.add_web_handlers( (r'/ws/logs', LoggerWebSocketHandler, dict(plugin=self)), (r'/logs/top', LogsApiTopHandler, dict(plugin=self, bot=self.bot)), (r'/logs/user/(?P<user_id>\d+)', LogsApiUserHandler, dict(plugin=self, bot=self.bot)), ) ''' Raw events ''' async def on_new(self, message): if message.server: self.save_log(message, message.timestamp) self.notify_ws(message, 'original') async def on_delete(self, message): with self.transaction() as trans: trans.execute(q.mark_deleted, dict(id=message.id)) self.notify_ws(message, 'deleted') async def on_edit(self, before, after): self.save_log(after, after.edited_timestamp) self.notify_ws(after, 'edit') ''' Commands ''' @command( p.string('!deleted') + p.bind(p.mention, 'user_id') + p.bind(p.maybe(p.integer), 'count'), master_only) async def deleted_messages(self, message, user_id, count=5): with self.transaction() as trans: trans.execute(q.last_deleted_logs, dict(author_id=user_id, limit=count)) results = trans.fetchall() messages = [ '{}{}'.format(row[0], ' '.join(row[1])) for row in results ] await self.send_message( message.channel, 'last **{}** deleted messages from <@{}>:\n{}'.format( len(results), user_id, f.code_block(messages)), delete_after=30) @command( p.string('!edited') + p.bind(p.mention, 'user_id') + p.bind(p.maybe(p.integer), 'count'), master_only) async def edited_messages(self, message, user_id, count=10): with self.transaction() as trans: trans.execute(q.last_edited_logs, dict(author_id=user_id, limit=count)) results = trans.fetchall() grouped = groupby(results, key=lambda row: row[0]) messages = [ ' ➡ '.join([ '{}{}'.format(c[1], ' '.join(c[2])) for c in reversed(list(contents)) ]) for _, contents in grouped ] await self.send_message( message.channel, 'last **{}** edited messages from <@{}>:\n{}'.format( len(messages), user_id, '\n'.join(messages)), delete_after=30) @command( p.string('!logs') + p.string('find') + p.bind(p.mention, 'user_id') + p.bind(p.word, 'what') + p.bind(p.maybe(p.integer), 'count'), master_only) async def logs_find(self, message, user_id, what, count=10): with self.transaction() as trans: trans.execute( q.find_logs, dict(author_id=user_id, str='%%{}%%'.format(what), limit=count)) results = trans.fetchall() messages = [r[0] for r in results] await self.send_message(message.channel, 'Found **{}** messages\n{}'.format( len(results), f.code_block(messages)), delete_after=30) @command( p.string('!logs') + p.string('top') + p.bind(p.maybe(p.integer), 'count'), master_only) async def most_active(self, message, count=10): with self.transaction() as trans: trans.execute(q.most_logs, dict(server_id=message.server.id, limit=count)) results = [ '{}: {:,} messages'.format(f.mention(r[0]), r[1]) for r in trans.fetchall() ] await self.send_message( message.channel, 'most **{}** active users on this server\n{}'.format( len(results), '\n'.join(results)), delete_after=30) ''' Details ''' def save_log(self, message, stamp): attachments = [ attachment['proxy_url'] for attachment in message.attachments ] with self.transaction() as trans: trans.execute( q.create_log, dict(id=message.id, server_id=message.server.id, channel_id=message.channel.id, author_id=message.author.id, content=message.content, stamp=stamp, attachments=attachments)) def notify_ws(self, message, t): message_ = dict(id=message.id, tts=message.tts, type=t, pinned=message.pinned, content=message.content, clean_content=message.clean_content) author = dict(id=message.author.id, name=message.author.name, nick=message.author.nick, joindate=message.author.joined_at.timestamp(), color=message.author.color.to_tuple()) channel = dict(id=message.channel.id, name=message.channel.name) server = dict(id=message.server.id, name=message.server.name) # TODO: Serialize mentions # mentions = dict( # users = message.mentions, # channels = message.channel_mentions, # roles = message.role_mentions # ) data = dict( server=server, channel=channel, author=author, # mentions = mentions, message=message_) for consumer in self.ws_consumers[message.server.id]: consumer.write_message(data)
class Trivia(Plugin): def load(self): self.trivias = dict() for name, Behavior in ALL_TRIVAS: try: self.trivias[name] = Behavior() except Exception as e: self.warning('Failed to initialize trivia {}: {}'.format( name, e)) @command( p.string('!trivia') + p.bind(p.word, 'topic') + p.eof, global_cooldown(20, True)) async def trivia(self, message, topic): try: trivia_gen = self.trivias[topic.lower()] except KeyError: topics_string = ' '.join('__{}__'.format(name) for name in self.trivias.keys()) await self.send_message( message.channel, 'There is no trivia about __{}__, available topics are: {}\n'. format(topic, topics_string)) else: await self.run_trivia(message.channel, trivia_gen(), topic) async def send(self, channel, message): sender = self.send_file if 'file_path' in message else self.send_message return await sender(channel=channel, **message) async def get_answers(self, channel, trivia_message): answers = self.bot.logs_from(channel, after=trivia_message, reverse=True) users = set() unique_answers = list() async for answer in answers: if answer.author.id in users: continue if answer.edited_timestamp is not None: continue unique_answers.append(answer) users.add(answer.author.id) return sorted(unique_answers, key=lambda m: m.timestamp) async def run_trivia(self, channel, trivia, topic): premise = next(trivia) message = await premise trivia_message = await self.send(channel, message) query = next(trivia) await query() answers = await self.get_answers(channel, trivia_message) self.debug( ['{}: {}'.format(a.author.name, a.clean_content) for a in answers]) winner, conclusion = trivia.send(answers) await self.send(channel, conclusion) if winner: if len(answers) >= 3: self.register_winner(winner, channel.server, topic) points = self.get_points(winner.id, channel.server.id) await self.send_message( channel, '{} now has {} points!'.format(winner.mention, points)) else: await self.send_message( channel, 'Not enough participants to make this one count') @command( p.string('!trivia') + p.string('stats') + p.bind(p.mention, 'user_id') + p.bind(p.maybe(p.word), 'topic')) async def trivia_stats(self, message, user_id, topic=None): ranks = self.get_ranks(message.server.id, topic) for_string = '' if topic is None else ' for `{}`'.format(topic) try: rank, points = next((i + 1, points) for i, (points, id) in enumerate(ranks) if id == user_id) except StopIteration: await self.send_message( message.channel, '{} has no points on this server{}'.format( f.mention(user_id), for_string)) else: await self.send_message( message.channel, '{} has {} points and is ranked #{} on this server{}'.format( f.mention(user_id), points, rank, for_string)) @command( p.string('!trivia') + p.string('stats') + p.string('all') + p.bind(p.maybe(p.word), 'topic')) async def trivia_stats_all(self, message, topic=None): ranks = self.get_ranks(message.server.id, topic) ranks = ranks[:10] def user_name(u_id): user = self.bot.find_user(str(u_id)) return user.name if user else '???' leaderboard = '\n'.join( '#{:>2} {:<30} ({:>3} pts)'.format(i + 1, user_name(user_id), points) for i, (points, user_id) in enumerate(ranks)) for_string = '' if topic is None else ' for `{}`'.format(topic) await self.send_message( message.channel, 'Top {} on this server{}:\n{}'.format(len(ranks), for_string, f.code_block(leaderboard))) def register_winner(self, winner, server, topic): with self.transaction() as trans: trans.execute( q.create_trivia_result, dict(user_id=winner.id, topic=topic, answer='', stamp=datetime.now(), server_id=server.id)) def get_points(self, user_id, server_id): with self.transaction() as trans: trans.execute(q.get_count, dict( user_id=user_id, server_id=server_id, )) return trans.fetchone()[0] def get_ranks(self, server_id, topic): query = q.get_ranks if topic is None else q.get_ranks_topic params = dict(server_id=server_id) if topic is not None: params['topic'] = topic with self.transaction() as trans: trans.execute(query, params) data = trans.fetchall() return data
class Eval(Plugin): def load(self): self.docker = AsyncDockerClient() with self.transaction() as trans: trans.execute(q.fetch_behaviors) self.behaviors = [row[0] for row in trans.fetchall()] self.default_behavior = self.behaviors[0] self.last_snippets = dict() ''' Commands ''' eval_prefix = p.string('!eval') @command(p.bind(p.snippet, 'snippet')) async def eval_code(self, message, snippet): behavior = self.get_behavior(message.author.id) if behavior is None: await self.send_message( message.author, 'Psst\nYou seem to have posted a `code snippet`\n' 'I can evaluate it if you want\n' 'Since your behavior was not defined, I set it to `manual`\n' 'type `!eval help` for more information') self.last_snippets[message.author.id] = snippet if behavior != 'auto': return await self.run_snippet(message, snippet, "") @command(eval_prefix + p.string('run')) async def eval_run(self, message): behavior = self.get_behavior(message.author.id) if behavior == 'off': return try: snippet = self.last_snippets[message.author.id] run_idx = message.content.index('run') await self.run_snippet(message, snippet, message.content[run_idx + len('run'):]) except KeyError: pass @command(eval_prefix + p.string('save') + p.bind(p.word, 'name')) async def eval_save(self, message, name): try: snippet = self.last_snippets[message.author.id] snippets = self.get_snippets(message.author.id) try: next(s for s in snippets if s.name == name) await self.send_message( message.channel, '{} You already have a snippet named `{}`'.format( message.author.mention, name), delete_after=30) except StopIteration: if self.save_snippet(message.author.id, name, snippet): await self.send_message( message.channel, '{} your last snippet was saved as `{}`\n use `/{}` to run it' .format(message.author.mention, name, name), delete_after=30) except KeyError: pass @command(p.bind(p.word, 'name')) async def command_run(self, message, name): if not name.startswith('/'): return name = name[1:] snippet = self.get_snippet(message.author.id, name) if snippet: name_idx = message.content.index(name) args = message.content[name_idx + len(name):] await self.run_snippet(message, snippet, args) eval_env_prefix = eval_prefix + p.string('env') @command(eval_env_prefix + p.string('inspect') + p.bind(p.word, 'env_name')) async def eval_env_inspect(self, message, env_name): environments = self.get_environments(message.author.id) try: env = next(e for e in environments if e.name == env_name) except StopIteration: await self.send_message( message.channel, '{} You don\'t have any environment saved under the name `{}`'. format(message.author.mention, env_name), delete_after=10) await self.send_message( message.channel, '{} `{}` environment was built from:\n{}'.format( message.author.mention, env_name, f.code_block(env.dockerfile, language='dockerfile'))) @command(eval_env_prefix + p.string('list')) async def eval_env_list(self, message): environments = [ env.name for env in self.get_environments(message.author.id) if env.author_id ] await self.send_message( message.channel, '{} You have `{}` saved environments\n{}'.format( message.author.mention, len(environments), f.code_block(environments))) @command(eval_env_prefix + p.string('map')) async def eval_env_map(self, message): environments = self.get_environments(message.author.id) padding = max(map(lambda env: len(env.language), environments), default=0) mapping = [ '{:{}} ➡ {}'.format(env.language, padding, env.name) for env in environments ] await self.send_message( message.channel, '{} Here is your eval mapping\n{}'.format(message.author.mention, f.code_block(mapping))) @command(eval_env_prefix + p.string('set') + p.bind(p.word, 'language') + p.bind(p.word, 'env_name')) async def eval_set(self, message, language, env_name): environments = self.get_environments(message.author.id) try: env = next(e for e in environments if e.name == env_name and e.author_id) except StopIteration: await self.send_message( message.channel, '{} You don\'t have any environment saved under the name `{}`'. format(message.author.mention, env_name), delete_after=10) return # Removing old mappings for env in environments: if env.language == language: self.set_environment_language(env.id, 'none') # Flagging the env with the language self.set_environment_language(env.id, language) await self.send_message( message.channel, '{} Your `{}` snippets will now be evaluated with your `{}` environment' .format(message.author.mention, language, env_name)) @command(eval_env_prefix + p.string('library') + p.string('build') + p.bind(p.word, 'env_name') + p.bind(p.word, 'language') + p.bind(p.snippet, 'snippet'), master_only) async def build_env_library(self, message, env_name, language, snippet): if snippet.language != 'dockerfile': return response_stream = await self.send_message(message.channel, 'Building...') dockerfile = BytesIO(snippet.code.encode('utf8')) flags = dict(errored=False) image = self.tag_name(env_name) build_stream = self.docker.async_build(fileobj=dockerfile, tag=image, decode=True, rm=True) def format_data(data): flags['errored'] = ('error' in data) return '\n'.join([str(v) for v in data.values()]) errored = flags['errored'] if not errored: self.save_environment(None, 'library/{}'.format(env_name), image, snippet.code, language) await self.stream_data(response_stream, build_stream, format_data) notice = 'Build errored' if errored else 'Build succeeded' await self.send_message(message.channel, notice) await self.delete_message_after(response_stream, 10) @command(eval_env_prefix + p.string('build') + p.bind(p.word, 'env_name') + p.bind(p.snippet, 'snippet'), master_only) async def build_env(self, message, env_name, snippet): if snippet.language != 'dockerfile': return response_stream = await self.send_message(message.channel, 'Building...') dockerfile = BytesIO(snippet.code.encode('utf8')) flags = dict(errored=False) image = self.tag_name(env_name, message.author.id) build_stream = self.docker.async_build(fileobj=dockerfile, tag=image, decode=True, rm=True) def format_data(data): flags['errored'] = ('error' in data) return '\n'.join([str(v) for v in data.values()]) errored = flags['errored'] if not errored: self.save_environment(message.author.id, env_name, image, snippet.code) await self.stream_data(response_stream, build_stream, format_data) notice = 'Build errored' if errored else 'Build succeeded' await self.send_message(message.channel, notice) await self.delete_message_after(response_stream, 10) @command(p.string('!eval') + p.string('behavior') + p.eof) async def user_behavior(self, message): behavior = self.get_behavior(message.author.id) if behavior is None: behavior = self.default_behavior await self.send_message(message.channel, '{} your eval behavior is `{}`'.format( message.author.mention, behavior), delete_after=5) @command( p.string('!eval') + p.string('behavior') + p.string('set') + p.bind(p.word, 'behavior')) async def set_behavior(self, message, behavior): if behavior not in self.behaviors: return self.set_user_behavior(message.author.id, behavior) await self.send_message( message.channel, '{} your eval behavior has been set to `{}`'.format( message.author.mention, behavior), delete_after=5) ''' details ''' async def run_snippet(self, message, snippet, args): environment = self.get_environment(snippet.language, message.author.id) if environment is None: await self.send_message( message.channel, '{} You have no environment associated with the language `{}`'. format(message.author.mention, snippet.language), delete_after=10) else: directory = '/tmp/globibot/{}'.format(message.author.id) os.makedirs(directory, exist_ok=True) with open('{}/{}'.format(directory, 'code.snippet'), 'w') as f: f.write(snippet.code) await self.send_message( message.channel, '{} Running your `{}` snippet in `{}`'.format( message.author.mention, snippet.language, environment.name), delete_after=5) response_stream = await self.send_message(message.channel, '`Waiting for output`') run_stream = self.docker.run_async(args, directory, environment.image, message.author.id) format_data = lambda line: line.decode('utf8') try: await self.stream_data(response_stream, run_stream, format_data) await self.send_message(message.channel, '`Exited`', delete_after=10) except AsyncDockerClient.Timeout: await self.send_message(message.channel, '`Evaluation timed out`') rmtree(directory) TAG_PREFIX = 'globibot_build' def tag_name(self, name, user_id=None): return '{}_{}:{}'.format(Eval.TAG_PREFIX, name, user_id if user_id else 'library'), def get_behavior(self, user_id): with self.transaction() as trans: trans.execute(q.get_behavior, dict(author_id=user_id)) row = trans.fetchone() if row: return row[0] else: trans.execute(q.set_default_behavior, dict(author_id=user_id, )) def set_user_behavior(self, user_id, behavior): with self.transaction() as trans: trans.execute(q.set_behavior, dict(author_id=user_id, behavior=behavior)) def get_environment(self, language, user_id): with self.transaction() as trans: trans.execute(q.get_environment, dict(author_id=user_id, language=language)) row = trans.fetchone() if row: return Environment(*row) def get_environments(self, user_id): with self.transaction() as trans: trans.execute(q.get_environments, dict(author_id=user_id, )) return [Environment(*row) for row in trans.fetchall()] def save_environment(self, user_id, env_name, image, dockerfile, language=None): with self.transaction() as trans: trans.execute( q.save_environment, dict(author_id=user_id, name=env_name, image=image, dockerfile=dockerfile, language=language)) def set_environment_language(self, env_id, language): with self.transaction() as trans: trans.execute(q.set_language, dict(id=env_id, language=language)) def get_snippet(self, user_id, name): with self.transaction() as trans: trans.execute(q.get_snippet, dict(author_id=user_id, name=name)) data = trans.fetchone() if data: return Snippet(*data) def get_snippets(self, user_id): with self.transaction() as trans: trans.execute(q.get_snippets, dict(author_id=user_id)) return [Snippet(*row) for row in trans.fetchall()] def save_snippet(self, user_id, name, snippet): with self.transaction() as trans: trans.execute( q.save_snippet, dict(author_id=user_id, name=name, language=snippet.language, code=snippet.code)) return True