Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
chatbot/musci.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
227 lines (196 sloc)
8.08 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import asyncio | |
import discord | |
from discord.voice_client import VoiceClient | |
from discord.ext import commands | |
if not discord.opus.is_loaded(): | |
# the 'opus' library here is opus.dll on windows | |
# or libopus.so on linux in the current directory | |
# you should replace this with the location the | |
# opus library is located in and with the proper filename. | |
# note that on windows this DLL is automatically provided for you | |
discord.opus.load_opus('opus') | |
"""I took this code from https://mega.nz/#!eopkmZrD!6vNS6xitffXDp41JcO4-kjV5gxqrTuHcirgZiN4Vj2M | |
but I had to rewrite the commands from discord.py 0.16 version to discord.py 1.0.0a version . """ | |
def __init__(self, bot): | |
self.bot = bot | |
class VoiceEntry: | |
def __init__(self, message, player): | |
self.requester = message.author | |
self.channel = message.channel | |
self.player = player | |
def __str__(self): | |
fmt = ' {0.title} uploaded by {0.uploader} and requested by {1.display_name}' | |
duration = self.player.duration | |
if duration: | |
fmt = fmt + ' [length: {0[0]}m {0[1]}s]'.format(divmod(duration, 60)) | |
return fmt.format(self.player, self.requester) | |
class VoiceState: | |
def __init__(self, bot): | |
self.current = None | |
self.voice = None | |
self.bot = bot | |
self.play_next_song = asyncio.Event() | |
self.songs = asyncio.Queue() | |
self.skip_votes = set() # a set of user_ids that voted | |
self.audio_player = self.bot.loop.create_task(self.audio_player_task()) | |
def is_playing(self): | |
if self.voice is None or self.current is None: | |
return False | |
player = self.current.player | |
return not player.is_done() | |
@property | |
def player(self): | |
return self.current.player | |
def skip(self): | |
self.skip_votes.clear() | |
if self.is_playing(): | |
self.player.stop() | |
def toggle_next(self): | |
self.bot.loop.call_soon_threadsafe(self.play_next_song.set) | |
async def audio_player_task(self): | |
while True: | |
self.play_next_song.clear() | |
self.current = await self.songs.get() | |
await self.bot.send_message(self.current.channel, 'Now playing' + str(self.current)) | |
self.current.player.start() | |
await self.play_next_song.wait() | |
class Music: | |
"""Voice related commands. | |
Works in multiple servers at once. | |
""" | |
def __init__(self, bot): | |
self.bot = bot | |
self.voice_states = {} | |
def get_voice_state(self, server): | |
state = self.voice_states.get(server.id) | |
if state is None: | |
state = VoiceState(self.bot) | |
self.voice_states[server.id] = state | |
return state | |
async def create_voice_client(self, channel): | |
voice = await self.bot.VoiceChannel.connect(channel) | |
state = self.get_voice_state(channel.server) | |
state.voice = voice | |
def __unload(self): | |
for state in self.voice_states.values(): | |
try: | |
state.audio_player.cancel() | |
if state.voice: | |
self.bot.loop.create_task(state.voice.disconnect()) | |
except: | |
pass | |
@commands.command(pass_context=True, no_pm=True) | |
async def join(self, ctx, *, channel : discord.channel): | |
"""Joins a voice channel.""" | |
try: | |
await self.create_voice_client(channel) | |
except discord.ClientException: | |
await self.ctx.send('Already in a voice channel...') | |
except discord.InvalidArgument: | |
await self.ctx.send('This is not a voice channel...') | |
else: | |
await self.ctx.send('Ready to play audio in **' + channel.name) | |
@commands.command(pass_context=True, no_pm=True) | |
async def summon(self, ctx): | |
"""Summons the bot to join your voice channel.""" | |
summoned_channel = ctx.message.author.voice.channel | |
if summoned_channel is None: | |
await self.ctx.send('Are you sure your in a channel?') | |
return False | |
state = self.get_voice_state(ctx.message.guild) | |
if state.voice is None: | |
state.voice = await self.bot.VoiceChannel.connect(summoned_channel) | |
else: | |
await state.voice.move_to(summoned_channel) | |
return True | |
@commands.command(pass_context=True, no_pm=True) | |
async def play(self, ctx, song : str): | |
"""Plays a song. | |
""" | |
state = self.get_voice_state(ctx.channel.guild) | |
opts = { | |
'default_search': 'auto', | |
'quiet': True, | |
} | |
if state.voice is None: | |
success = await ctx.invoke(self.summon) | |
await self.bot.say("Loading the song please be patient..") | |
if not success: | |
return | |
try: | |
player = await state.voice.create_ytdl_player(song, ytdl_options=opts, after=state.toggle_next) | |
except Exception as e: | |
fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' | |
await self.bot.send_message(ctx.message.channel, fmt.format(type(e).__name__, e)) | |
else: | |
player.volume = 0.6 | |
entry = VoiceEntry(ctx.message, player) | |
await self.ctx.send('Enqueued ' + str(entry)) | |
await state.songs.put(entry) | |
@commands.command(pass_context=True, no_pm=True) | |
async def volume(self, ctx, value : int): | |
"""Sets the volume of the currently playing song.""" | |
state = self.get_voice_state(ctx.message.server) | |
if state.is_playing(): | |
player = state.player | |
player.volume = value / 100 | |
await self.bot.say('Set the volume to {:.0%}'.format(player.volume)) | |
@commands.command(pass_context=True, no_pm=True) | |
async def resume(self, ctx): | |
"""Resumes the currently played song.""" | |
state = self.get_voice_state(ctx.message.guild) | |
if state.is_playing(): | |
player = state.player | |
player.resume() | |
@commands.command(pass_context=True, no_pm=True) | |
async def stop(self, ctx): | |
"""Stops playing audio and leaves the voice channel. | |
This also clears the queue. | |
""" | |
server = ctx.message.guild | |
state = self.get_voice_state(server) | |
if state.is_playing(): | |
player = state.player | |
player.stop() | |
try: | |
state.audio_player.cancel() | |
del self.voice_states[server.id] | |
await state.voice.disconnect() | |
await self.ctx.send("Cleared the queue and disconnected from voice channel ") | |
except: | |
pass | |
@commands.command(pass_context=True, no_pm=True) | |
async def skip(self, ctx): | |
"""Vote to skip a song. The song requester can automatically skip. | |
3 skip votes are needed for the song to be skipped. | |
""" | |
state = self.get_voice_state(ctx.message.guild) | |
if not state.is_playing(): | |
await self.ctx.send('Not playing any music right now...') | |
return | |
voter = ctx.message.author | |
if voter == state.current.requester: | |
await self.ctx.send('Requester requested skipping song...') | |
state.skip() | |
elif voter.id not in state.skip_votes: | |
state.skip_votes.add(voter.id) | |
total_votes = len(state.skip_votes) | |
if total_votes >= 3: | |
await self.ctx.send('Skip vote passed, skipping song...') | |
state.skip() | |
else: | |
await self.ctx.send('Skip vote added, currently at [{}/3]'.format(total_votes)) | |
else: | |
await self.ctx.send('You have already voted to skip this song.') | |
@commands.command(pass_context=True, no_pm=True) | |
async def playing(self, ctx): | |
"""Shows info about the currently played song.""" | |
state = self.get_voice_state(ctx.message.guild) | |
if state.current is None: | |
await self.ctx.send('Not playing anything.') | |
else: | |
skip_count = len(state.skip_votes) | |
await self.ctx.send('Now playing {} [skips: {}/3]'.format(state.current, skip_count)) | |
def setup(bot): | |
bot.add_cog(Music(bot)) | |
print('Music is loaded') |