diff options
| author | Nathan Perry <nathan@meta.sc> | 2016-04-10 03:59:24 -0400 |
|---|---|---|
| committer | Nathan Perry <nathan@meta.sc> | 2016-04-10 03:59:24 -0400 |
| commit | 38b1e8ffb033ba7bcbf4eab3a2f19dba51de4e77 (patch) | |
| tree | 16b9937829708dc6ea4faeed0a4c7d29273416f1 | |
| parent | 188b2be4076b674ed4718e2055ee5bd505574368 (diff) | |
reworked significantly
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | config.example.yml | 11 | ||||
| -rw-r--r-- | srv.py | 253 |
3 files changed, 202 insertions, 64 deletions
@@ -60,3 +60,5 @@ target/ #Ipython Notebook .ipynb_checkpoints + +config.yml diff --git a/config.example.yml b/config.example.yml new file mode 100644 index 0000000..b6e4533 --- /dev/null +++ b/config.example.yml @@ -0,0 +1,11 @@ +trigger: sample + +queue_size: 5 +admin: 12345678901234567 +op_role: sample-controller + +server: sample +voice_channel: General + +username: username@example.com +password: testpass123 @@ -1,121 +1,246 @@ import discord -import asyncio import logging import re +import yaml +from asyncio import coroutine, Queue, ensure_future as async, QueueEmpty, sleep from urllib.parse import urlsplit +from functools import partial logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('yt-bot') +logger.setLevel(logging.DEBUG) + +with open('config.yml') as f: + config = yaml.load(f) + client = discord.Client() -client.queue = asyncio.Queue(maxsize=5) +client.queue = Queue(maxsize=config.get('queue_size', 0)) client.current_player = None +reg = re.compile(r'^(?:!|\/){} (.*)$'.format(config['trigger'])) + + @client.event -@asyncio.coroutine +@coroutine def on_ready(): - logging.info('Logged in as\n\t{}\n\t{}'.format(client.user.name, client.user.id)) + logger.info('Logged in as {} ({})'.format(client.user.name, client.user.id)) -bot_name = '' -server_name = '' -reg = re.compile(r'^(?:!|\/){} (.*)$'.format(bot_name)) -main_player_id = 0 - @client.event -@asyncio.coroutine +@coroutine def on_message(message): - global reg - global main_player_id - - logging.debug('received message %s' % message) + logger.debug('received message \'{}\' from {}#{}, ({})'.format(message.content, + message.server, + message.channel, + 'private' if message.channel.is_private else 'public')) + if message.channel.is_private: + logger.debug('ignoring private message.') return - if message.server.name != server_name: - logging.info('wrong server %s' % message.server.name) + comp = message.server.id if type(config['server']) is int else message.server.name + + if comp != config['server']: + logger.debug('message from wrong server ({})'.format(comp)) return match = reg.search(message.content) if not match: - logging.info('match failed') + logger.debug('no match.') return - command = match.group(1).split()[0] - if command == 'stop' and int(message.author.id) == main_player_id: - if client.current_player and client.current_player.is_playing(): - client.current_player.stop() - return + commands = match.group(1).split() + command = commands[0] + author_id = int(message.author.id) - if command == 'pause' and int(message.author.id) == main_player_id: - if client.current_player and client.current_player.is_playing(): - client.current_player.pause() - return + cmd_map = { + 'skip': stop_player, + 'die': stop_client, + 'sudoku': stop_client, + 'pause': pause, + 'resume': resume, + 'list': partial(list_queued, channel=message.channel), + 'queue': partial(list_queued, channel=message.channel), + } - if command == 'resume' and int(message.author.id) == main_player_id: - if client.current_player and not client.current_player.is_playing(): - client.current_player.resume() - return + if command in cmd_map: + if author_id == config['admin'] or config['op_role'] in [role.name for role in message.author.roles]: + logger.info('running command \'{}\''.format(command)) + async(cmd_map[command](client)) + return + logger.info('unauthorized command \'{}\' from member \'{}\' ({})'.format(command, message.author.name, message.author.id)) + async(client.send_message(message.channel, 'fuck you. you\'re not allowed to do that.', tts=message.tts)) + return url = urlsplit(command, scheme='https') if not (url.netloc and (url.path or (url.path is '/watch' and not url.query))): - yield from client.send_message(message.channel, 'format your commands right. fuck you.', tts=message.tts) + logger.info('syntax error: invalid url \'{}\''.format(command)) + async(client.send_message(message.channel, 'format your commands right. fuck you.', tts=message.tts)) return url = url.geturl() - logging.info(url) + logger.debug('playing video from url \'{}\''.format(url)) - if not client.is_voice_connected(): - logging.info('connecting') - voice_chan = discord.utils.find(lambda x: x.name == 'General' and x.type is discord.ChannelType.voice, - message.server.channels) - if not voice_chan: - logging.error('no voice channel') + async(enqueue_video((url, message), client, message.channel)) - yield from client.join_voice_channel(voice_chan) - if client.current_player and client.current_player.is_playing(): - client.current_player.stop() +@coroutine +def pause(client): + if not client.current_player: + return - client.current_player = yield from client.voice.create_ytdl_player(url) - client.current_player.start() + client.current_player.pause() - # enqueue_video(url, client, message.channel) +@coroutine +def resume(client): + if not client.current_player: + return + + client.current_player.resume() + + +@coroutine +def stop_player(client): + if not client.current_player: + return + + client.current_player.stop() + client.current_player = None + + +@coroutine +def stop_client(client): + if not client.current_player: + return + + while True: + try: + client.queue.get_nowait() + except QueueEmpty: + break + + yield from async(stop_player(client)) -def enqueue_video(url, client, channel): - if not client.is_voice_connected(): - logging.info - voice_chan = discord.utils.find(lambda x: x.name == 'General' and x.type is discord.ChannelType.voice, - channel.server.channels) - yield from client.join_voice_channel(voice_chan) + +@coroutine +def enqueue_video(pair, client, channel): + global config + + yield from async(connect_voice(client)) if not client.is_voice_connected(): - yield from client.send_message(channel, 'go fuck yourself. voice isn\'t working.', tts=True) + async(client.send_message(channel, 'go fuck yourself. voice isn\'t working.', tts=True)) return if client.queue.full(): - yield from client.send_message(channel, 'fuck you. wait for the other videos.', tts=True) + async(client.send_message(channel, 'fuck you. wait for the other videos.', tts=True)) return - elem = yield from client.voice.create_ytdl_player(url) - client.queue.put(elem) + async(client.queue.put(pair)) + + +@coroutine +def connect_voice(client): + if not client.is_voice_connected(): + server = discord.utils.find(lambda x: x.name == config['server'], client.servers) + voice_chan = discord.utils.find(lambda x: x.name == config['voice_channel'] and x.type is discord.ChannelType.voice, + server.channels) + yield from client.join_voice_channel(voice_chan) + + +@coroutine +def list_queued(client, channel): + s = '' + count = 0 + + def list_resp(s, count): + if len(s.strip()) == 0: + s = 'Queue empty\n' + + slots = config.get('queue_size', 0) + if slots is not 0: + s += '{} slots remaining in the queue.'.format(slots - count) + + async(client.send_message(channel, s.strip())) + + + if client.current_player and not client.current_player.is_done(): + s += '**{}**: {}\n\n'.format('playing' if client.current_player.is_playing() else 'paused', client.current_player.title) + + pairs = [] + while True: + try: + pairs.append(client.queue.get_nowait()) + + except QueueEmpty: + break + + if len(pairs) == 0: + list_resp(s, count) + return + yield from async(connect_voice(client)) -def run_video(client, loop): - logging.info('looping') - if client.current_player and client.current_player.is_playing(): + if not client.is_voice_connected(): + async(client.send_message(channel, 'go fuck yourself. couldn\'t check stored videos', tts=True)) + logger.error('unable to connect to voice!') + for pair in pairs: + yield from client.queue.put(pair) return - client.current_player = yield from client.queue.get() + for (url, msg) in pairs: + if len(msg.embeds) == 0: + logger.debug('got non-embedded link. creating player to find title.') + player = yield from client.voice.create_ytdl_player(url) + name = player.title + else: + name = msg.embeds[0].get('title', None) + + s += '{}\n'.format(name if name and name != '' else '(Unknown)') + count += 1 + + yield from client.queue.put((url, msg)) + + list_resp(s, count) + + +@coroutine +def run_video(client): + vid_logger = logger.getChild('video_scheduler') + vid_logger.debug('entering run_video') + + (url, _) = yield from client.queue.get() + yield from connect_voice(client) + + if not client.is_voice_connected(): + raise Exception('unable to connect to voice!') + else: + vid_logger.info('voice reconnected') + + vid_logger.debug('starting playback') + client.current_player = yield from client.voice.create_ytdl_player(url) client.current_player.start() + vid_logger.debug('playback started') + + has_slept = False + while True: + if not client.current_player or client.current_player.is_done(): + break + + if not has_slept: + vid_logger.debug('sleeping') + has_slept = True - loop.call_later(1, run_video, client, loop) + yield from sleep(2) + + if has_slept: + vid_logger.debug('awoken') + async(run_video(client)) -loop = asyncio.get_event_loop() -loop.call_soon(run_video, client, loop) -username = '' -password = '' -client.run(username, password) +async(run_video(client)) +client.run(config['username'], config['password']) |
