3 changed files with 115 additions and 46 deletions
@ -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, |
|||
}) |
@ -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', '<song:str>') |
|||
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', '<url:str>') |
|||
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) |
|||
|
Loading…
Reference in new issue