From 0f89d687c89f7e1b2fde1fa70e6a909b0e8537dd Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 1 May 2019 15:35:26 -0700 Subject: [PATCH] Telecom voice v1 --- disco/voice.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++ examples/music.py | 64 ++++++++++--------------------- setup.py | 2 +- 3 files changed, 115 insertions(+), 46 deletions(-) create mode 100644 disco/voice.py diff --git a/disco/voice.py b/disco/voice.py new file mode 100644 index 0000000..c7df412 --- /dev/null +++ b/disco/voice.py @@ -0,0 +1,95 @@ +from disco.gateway.packets import OPCode +from disco.types.channel import Channel +from telecom import TelecomConnection, AvConvPlayable + + +class VoiceConnection(object): + def __init__(self, client, guild_id): + self.client = client + self.guild_id = guild_id + self.channel_id = None + self._conn = None + self._voice_server_update_listener = self.client.events.on( + 'VoiceServerUpdate', + self._on_voice_server_update, + ) + + self._mute = False + self._deaf = False + + @property + def mute(self): + return self._mute + + @property + def deaf(self): + return self._deaf + + @mute.setter + def mute(self, value): + if value is self._mute: + return + + self._mute = value + self._send_voice_state_update() + + @deaf.setter + def deaf(self, value): + if value is self._deaf: + return + + self._deaf = value + self._send_voice_state_update() + + @classmethod + def from_channel(self, channel): + assert channel.is_voice, 'Cannot connect to a non voice channel' + conn = VoiceConnection(channel.client, channel.guild_id) + conn.connect(channel.id) + return conn + + def set_channel(self, channel_or_id): + if channel_or_id and isinstance(channel_or_id, Channel): + channel_or_id = channel_or_id.id + + self.channel_id = channel_or_id + self._send_voice_state_update() + + def connect(self, channel_id): + assert self._conn is None, 'Already connected' + + self.set_channel(channel_id) + + self._conn = TelecomConnection( + self.client.state.me.id, + self.guild_id, + self.client.gw.session_id, + ) + + def disconnect(self): + assert self._conn is not None, 'Not connected' + + # Send disconnection + self.set_channel(None) + + # Delete our connection so it will get GC'd + del self._conn + self._conn = None + + def play_file(self, url): + self._conn.play(AvConvPlayable(url)) + + def _on_voice_server_update(self, event): + if not self._conn or event.guild_id != self.guild_id: + return + + self._conn.update_server_info(event.endpoint, event.token) + + def _send_voice_state_update(self): + self.client.gw.send(OPCode.VOICE_STATE_UPDATE, { + 'self_mute': self._mute, + 'self_deaf': self._deaf, + 'self_video': False, + 'guild_id': self.guild_id, + 'channel_id': self.channel_id, + }) diff --git a/examples/music.py b/examples/music.py index 8ed006f..a70b506 100644 --- a/examples/music.py +++ b/examples/music.py @@ -1,56 +1,30 @@ from disco.bot import Plugin -from disco.bot.command import CommandError -from disco.voice.player import Player -from disco.voice.playable import YoutubeDLInput, BufferedOpusEncoderPlayable -from disco.voice.client import VoiceException +from disco.voice import VoiceConnection class MusicPlugin(Plugin): - def load(self, ctx): - super(MusicPlugin, self).load(ctx) - self.guilds = {} + def load(self, data): + super(MusicPlugin, self).load(data) + self._connections = {} @Plugin.command('join') def on_join(self, event): - if event.guild.id in self.guilds: - return event.msg.reply("I'm already playing music here.") + vs = event.guild.get_member(event.author).get_voice_state() + if not vs: + return event.msg.reply('you are not in a voice channel') - state = event.guild.get_member(event.author).get_voice_state() - if not state: - return event.msg.reply('You must be connected to voice to use that command.') + if event.guild.id in self._connections: + if self._connections[event.guild.id].channel_id == vs.channel_id: + return event.msg.reply('already in that channel') + else: + self._connections[event.guild.id].set_channel(vs.channel) + return - try: - client = state.channel.connect() - except VoiceException as e: - return event.msg.reply('Failed to connect to voice: `{}`'.format(e)) + self._connections[event.guild.id] = VoiceConnection.from_channel(vs.channel) - self.guilds[event.guild.id] = Player(client) - self.guilds[event.guild.id].complete.wait() - del self.guilds[event.guild.id] + @Plugin.command('play', '') + def on_play(self, event, song=None): + if event.guild.id not in self._connections: + return event.msg.reply('not in voice here') - def get_player(self, guild_id): - if guild_id not in self.guilds: - raise CommandError("I'm not currently playing music here.") - return self.guilds.get(guild_id) - - @Plugin.command('leave') - def on_leave(self, event): - player = self.get_player(event.guild.id) - player.disconnect() - - @Plugin.command('play', '') - def on_play(self, event, url): - item = YoutubeDLInput(url).pipe(BufferedOpusEncoderPlayable) - self.get_player(event.guild.id).queue.append(item) - - @Plugin.command('pause') - def on_pause(self, event): - self.get_player(event.guild.id).pause() - - @Plugin.command('resume') - def on_resume(self, event): - self.get_player(event.guild.id).resume() - - @Plugin.command('kill') - def on_kill(self, event): - self.get_player(event.guild.id).client.ws.sock.shutdown() + self._connections[event.guild.id].play_file(song) diff --git a/setup.py b/setup.py index eae8156..cb0615c 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ with open('README.md') as f: readme = f.read() extras_require = { - 'voice': ['pynacl==1.2.1'], + 'voice': ['telecom==0.0.2'], 'http': ['flask==0.12.2'], 'yaml': ['pyyaml==3.12'], 'music': ['youtube_dl>=2018.1.21'],