update music test

parent 44e3e8dd
......@@ -6,14 +6,6 @@ config.json
cogs/__pycache__/
cogs/__init__.py
cogs/utils/__pycache__/
cogs/utils/__init__.py
cogs/utils/tag_manager/__pycache__/
changes.diff
cogs/_command_misc.py
cogs/_command_music.py
cogs/_command_personal.py
cogs/_command_tags.py
shard_snake.py
cogs/utils/tag_manager/_parser.py
avatars/
youtube_cache/
\ No newline at end of file
import discord, asyncio, math, traceback
from discord.ext import commands
from .utils import checks
opus_loaded = discord.opus.is_loaded()
class VoicePlayer:
def __init__(self, message, player, client):
self.requester = message.author
self.channel = message.channel
self.player = player
self.client = client
def __repr__(self):
duration = self.player.duration
fmt = "**{0.title}** by **{0.uploader}**"
if duration:
fmt += " ({0[0]}m {0[1]}s)".format(divmod(duration, 60))
return fmt.format(self.player)
def __str__(self):
duration = self.player.duration
fmt = "**{0.title}** by **{0.uploader}** in #{1.name}\n{0.likes} \N{THUMBS UP SIGN}\N{EMOJI MODIFIER FITZPATRICK TYPE-1-2} - {0.views} views - {0.dislikes} \N{THUMBS DOWN SIGN}\N{EMOJI MODIFIER FITZPATRICK TYPE-1-2}\nRequested by **{2.display_name}**"
if duration:
fmt += " ({0[0]}m {0[1]}s)".format(divmod(duration, 60))
return fmt.format(self.player, self.client.channel, 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.song_list = []
self.skip_votes = set()
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):
if self.is_playing():
self.player.stop()
self.skip_votes.clear()
def toggle_next(self):
self.bot.loop.call_soon_threadsafe(self.play_next_song.set)
async def audio_player_task(self):
while True:
old_volume = self.current.player.volume if self.current else 0.5
self.play_next_song.clear()
if self.current:
self.bot.log.info("Finishing {} in {} #{}".format(repr(self.current), self.current.client.channel.server.name, self.current.client.channel.name))
self.current = await self.songs.get()
self.song_list.pop()
if self.current:
self.bot.log.info("Playing {} in {} #{}".format(repr(self.current), self.current.client.channel.server.name, self.current.client.channel.name))
await self.bot.send_message(self.current.channel, "Playing " + str(self.current))
self.current.player.volume = old_volume
try:
self.current.player.start()
except Exception as e:
print(type(e).__name__, e)
await self.play_next_song.wait()
class Music:
def __init__(self, bot):
self.bot = bot
self.voice_states = {}
async def on_voice_state_update(self, original_member, new_member):
server = new_member.server
state = self.get_voice_state(server)
if state.voice is not None:
listeners = sum(1 for m in state.voice.channel.voice_members if not m.bot)
if listeners == 0:
if state.is_playing():
state.player.stop()
try:
state.audio_player.cancel()
del self.voice_states[server.id]
await state.voice.disconnect()
except Exception as e:
print(type(e).__name__, e)
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_client(self, channel):
voice = await self.bot.join_voice_channel(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
def required_votes(self, channel):
member_count = len([m.id for m in channel.voice_members if not m.bot])
user_limit = channel.user_limit or member_count
required_votes = math.ceil(min(user_limit, member_count) / 2)
return required_votes
@commands.command(pass_context=True, no_pm=True, brief="join a voice channel")
@checks.is_owner()
async def join(self, ctx, *, channel : discord.Channel):
try:
await self.create_client(channel)
except discord.ClientException:
await self.bot.say("\N{CROSS MARK} already in a voice channel")
except discord.InvalidArgument:
await self.bot.say("\N{CROSS MARK} no a valid voice channel")
else:
await self.bot.say("Joined **{}** in **{}**".format(channel.name, channel.server.name))
@commands.command(pass_context=True, no_pm=True, brief="join your voice channel")
@checks.is_owner()
async def summon(self, ctx):
summon_channel = ctx.message.author.voice_channel
if summon_channel is None:
await self.bot.say("\N{CROSS MARK} no voice channel available")
return False
state = self.get_voice_state(ctx.message.server)
if state.voice is None:
state.voice = await self.bot.join_voice_channel(summon_channel)
else:
await state.voice.move_to(summon_channel)
return True
@commands.command(pass_context=True, no_pm=True, name="play", brief="play music")
@checks.is_owner()
async def play_music(self, ctx, *, song : str):
state = self.get_voice_state(ctx.message.server)
opts = {
"default_search": "auto",
"quiet": True
}
if state.voice is None:
success = await ctx.invoke(self.summon)
if not success:
return
try:
player = await state.voice.create_ytdl_player(song, ytdl_options=opts, after=state.toggle_next)
except Exception as e:
traceback.print_tb(e.__traceback__)
await self.bot.send_message(ctx.message.channel, "Could not play song: [{}]: {}".format(type(e).__name__, e))
else:
player.volume = 0.5
player_state = VoicePlayer(ctx.message, player, state.voice)
await self.bot.say("Queued **{}**".format(player.title))
await state.songs.put(player_state)
state.song_list.append(player_state)
@commands.command(pass_context=True, no_pm=True, aliases=["vol"], brief="adjust music volume")
async def volume(self, ctx, value : int = None):
state = self.get_voice_state(ctx.message.server)
if state.is_playing():
if value is not None:
player = state.player
player.volume = value / 100
await self.bot.say("Changed volume to {:.0%}".format(player.volume))
else:
await self.bot.say("Volume is {:.0%}".format(player.volume))
@commands.command(pass_context=True, no_pm=True, brief="pause music")
async def pause(self, ctx):
state = self.get_voice_state(ctx.message.server)
if state.is_playing():
state.player.pause()
@commands.command(pass_context=True, no_pm=True, brief="resume music")
async def resume(self, ctx):
state = self.get_voice_state(ctx.message.server)
if state.is_playing():
state.player.resume()
@commands.command(pass_context=True, no_pm=True, brief="leave a voice channel", help="also clears the song queue")
async def leave(self, ctx):
author = ctx.message.author
server = ctx.message.server
state = self.get_voice_state(server)
print((author.id not in [server.owner.id, state.current.requester.id, "163521874872107009"]) or (author.id not in [server.owner.id, state.current.requester.id, "163521874872107009"] and ctx.message.channel.permissions_for(author).administrator is False))
if state.is_playing():
if (author.id not in [server.owner.id, state.current.requester.id, "163521874872107009"]) or (author.id not in [server.owner.id, state.current.requester.id, "163521874872107009"] and ctx.message.channel.permissions_for(author).administrator is False):
await self.bot.say("\N{CROSS MARK} only the song requester can exit the channel while the song is playing")
return
if state.is_playing():
state.player.stop()
try:
state.audio_player.cancel()
del self.voice_states[server.id]
await state.voice.disconnect()
except:
pass
@commands.command(pass_context=True, no_pm=True, brief="stop music")
async def stop(self, ctx):
server = ctx.message.server
voter = ctx.message.author
state = self.get_voice_state(server)
if (voter.id in [server.owner.id, state.current.requester.id, "163521874872107009"]) or (voter.id not in [server.owner.id, state.current.requester.id, "163521874872107009"] and ctx.message.channel.permissions_for(voter).administrator):
if state.is_playing():
state.player.stop()
else:
await self.bot.say("\N{CROSS MARK} only the song requester can stop the song")
@commands.command(pass_context=True, no_pm=True, brief="skip a song")
async def skip(self, ctx):
server = ctx.message.server
state = self.get_voice_state(ctx.message.server)
if not state.is_playing():
await self.bot.say("\N{CROSS MARK} no song to skip")
return
voter = ctx.message.author
if not voter.voice_channel == state.voice.channel:
await self.bot.say("\N{CROSS MARK} you must be listening to the song to skip it")
return
if (voter.id in [server.owner.id, state.current.requester.id, "163521874872107009"]) or (voter.id not in [server.owner.id, state.current.requester.id, "163521874872107009"] and ctx.message.channel.permissions_for(voter).administrator):
await self.bot.say("Skipping **{}**".format(state.current.player.title))
state.skip()
return
if voter.id not in state.skip_votes:
state.skip_votes.add(voter.id)
required_votes = self.required_votes(state.voice.channel)
total_votes = len(state.skip_votes)
if total_votes >= required_votes:
await self.bot.say("Skip vote passed ({}/{}), skipping **{}**".format(total_votes, required_votes, state.current.player.title))
state.skip()
return
else:
await self.bot.say("{} voted to skip. ({}/{})".format(voter.display_name, total_votes, required_votes))
return
else:
await self.bot.say("\N{CROSS MARK} you have already voted to skip this song")
@commands.command(pass_context=True, no_pm=True, brief="see what is playing")
async def playing(self, ctx):
state = self.get_voice_state(ctx.message.server)
if state.current is None:
await self.bot.say("Nothing is playing")
elif state.is_playing():
total_votes = len(state.skip_votes)
required_votes = self.required_votes(state.voice.channel)
await self.bot.say("Now playing {}\n\n{} skips out of {} required".format(str(state.current), total_votes, required_votes))
else:
await self.bot.say("Nothing is playing")
@commands.command(no_pm=True, alises=["songs"], pass_context=True, brief="see the song queue")
async def queue(self, ctx):
state = self.get_voice_state(ctx.message.server)
song_queue = state.song_list
if len(song_queue) > 0:
result = ["Songs Currently Queued In **{}**".format(state.voice.channel.name), '']
for count, song in enumerate(song_queue, start=1):
result.append("{}. {}".format(count, repr(song)))
result = "\n".join(result)
else:
result = "No songs queued in **{}**".format(state.voice.channel.name)
await self.bot.say(result)
def setup(bot):
bot.add_cog(Music(bot))
\ No newline at end of file
......@@ -152,14 +152,17 @@ class Personal:
return
print(voice_channel)
voice_client = await self.bot.join_voice_channel(voice_channel)
voice_client = self.bot.voice_client_in(voice_channel.server)
if voice_client is None:
voice_client = await self.bot.join_voice_channel(voice_channel)
print(voice_client)
download_client = Downloader()
stream_file = await download_client.download(url)
print(stream_file)
player = voice_client.create_stream_player(stream_file, after=lambda: stream_file.close)
stream_filename = await download_client.download(url)
print(stream_filename)
player = voice_client.create_ffmpeg_player(stream_filename, before_options="-nostdin", options="-vn -b:a 128k", after=lambda: print(f"Done playing {url}"))
player.volume = 0.6
player.start()
def setup(bot):
bot.add_cog(Personal(bot))
\ No newline at end of file
import discord, re
from discord.ext import commands
FULL_ID = re.compile(r"^(?P<id>[0-9]{,23})$")
ROLE_ID = re.compile(r"^<@&(?P<id>[0-9]{,23})>$")
CHANNEL_ID = re.compile(r"^<#(?P<id>[0-9]{,23})>$")
MEMBER_ID = re.compile(r"^<@!?(?P<id>[0-9]{,23})>$")
class MultiMention(commands.Converter):
def __init__(self, ctx, argument):
super().__init__(ctx, argument)
def convert(self): # its never going to be a private channel
message = self.ctx.message
channel = message.channel
server = channel.server
author = message.author
full_id = FULL_ID.match(self.argument)
role_mention = ROLE_ID.match(self.argument)
channel_mention = CHANNEL_ID.match(self.argument)
user_mention = MEMBER_ID.match(self.argument)
if full_id is not None:
obj = discord.utils.get(list(server.members) + list(server.channels) + server.roles, id=full_id.group("id"))
if obj is None:
raise commands.BadArgument(f"{self.argument} could not be found")
else:
return obj
elif role_mention is not None:
role = discord.utils.get(server.roles, id=role_mention.group("id"))
if role is None:
raise commands.BadArgument(f"Role {self.argument} could not be found")
else:
return role
elif channel_mention is not None:
channel = discord.utils.get(server.channels, id=channel_mention.group("id"))
if channel is None:
raise commands.BadArgument(f"Channel {self.argument} could not be found")
else:
return channel
elif user_mention is not None:
user = discord.utils.get(server.members, id=user_mention.group("id"))
if user is None:
raise commands.BadArgument(f"Member {self.argument} could not be found")
else:
return user
elif self.argument.lower() == "server":
return server
elif self.argument.lower() == "channel":
return channel
elif self.argument.lower() == "role":
return author.top_role
else:
raise commands.BadArgument(f"Unrecognized input '{self.argument}'")
\ No newline at end of file
""" Discord snake music downloader """
import logging, asyncio, youtube_dl, os
import asyncio, youtube_dl, os, json
from functools import partial
from concurrent.futures import ThreadPoolExecutor
ytdl_options = {
"format": "bestaudio/best",
"outtmpl": "%(extractor)s-%(id)s-%(title)s.%(ext)s",
"outtmpl": "%(extractor)s-%(id)s.%(ext)s",
"restrictfilenames": True,
"noplaylist": True,
"nocheckcertificate": True,
"ignoreerrors": False,
"logtostderr": False,
"quiet": False,
"no_warnings": False,
#"ignoreerrors": False,
#"logtostderr": False,
#"quiet": False,
#"no_warnings": False,
"default_search": "auto",
"source_address": "0.0.0.0",
}
youtube_dl.utils.bug_reports_message = lambda: ""
#youtube_dl.utils.bug_reports_message = lambda: ""
class Downloader:
def __init__(self, download_folder="youtube_cache"):
......@@ -65,16 +65,29 @@ class Downloader:
print(f"Starting download of {url}")
video_info = await self.extract_info(url, download=False)
filename = self.unsafe_ytdl.prepare_filename(video_info)
with open("info.json", 'w') as f:
f.write(json.dumps(video_info, sort_keys=True, indent=2))
if "_type" in video_info:
filename = self.unsafe_ytdl.params["outtmpl"] % video_info.get("entries")[0]
else:
filename = self.unsafe_ytdl.params["outtmpl"] % video_info
if os.path.basename(filename) in os.listdir(self.download_folder):
print(f"{url} is cached")
return open(filename)
return filename
else:
return await self._real_download(url)
async def _real_download(self, url):
video_info = await self.extract_info(url, download=False)
filename = self.unsafe_ytdl.prepare_filename(video_info)
result = self.unsafe_ytdl.download([url])
if "_type" in video_info:
filename = self.unsafe_ytdl.params["outtmpl"] % video_info.get("entries")[0]
else:
filename = self.unsafe_ytdl.params["outtmpl"] % video_info
result = await self.extract_info(url, download=True, extra_info=video_info)
if result is not None:
return open(filename)
return filename
......@@ -50,8 +50,6 @@ else:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
print("Using uvloop")
discord_logger = logging.getLogger("discord")
discord_logger.setLevel(logging.INFO)
# weird fix for single-process sharding issue
# maybe when rewrite is stable we can stop using this
......@@ -159,6 +157,12 @@ class ShareManager:
"shard_count": self.shard_count
})
self.log = logging.getLogger()
self.log.setLevel(logging.DEBUG)
self.log.addHandler(
logging.FileHandler(filename="snake.log", encoding="utf-8", mode='w')
)
def set_shard_count(self, shard_count):
self.shard_count = shard_count
self.kwargs["shard_count"] = shard_count
......@@ -288,7 +292,9 @@ class ShareManager:
print(f"Closing {paint(self.bot_class.__name__, 'cyan')}<{shard_id}>")
self.shard_tasks[shard_id].cancel()
await shard.logout()
shard.loop.stop()
print(f"Closed {shard_id + 1} shards, exiting..")
self.loop.stop()
sys.exit(0)
def start_shard(self, shard_id, future=None):
......@@ -352,6 +358,15 @@ class ShareManager:
class SnakeBot(commands.Bot):
def log_socket(self, data, send=False):
if isinstance(data, str):
json_data = json.loads(data)
op_type = json_data.get("t")
if op_type is not None:
if op_type.lower() == "voice_state_update":
if json_data["d"]["guild_id"] == "180133909923758080":
print(f"[Shard {self.shard_id}] client {paint('>>' if send else '<<', 'green')} server {paint(op_type.upper(), 'blue')} -> {json_data}")
@contextmanager
def db_scope(self): # context manager for database sessions
session = self.db.Session()
......@@ -388,12 +403,6 @@ class SnakeBot(commands.Bot):
self.db = sql.SQL(db_username=os.getenv("SNAKE_DB_USERNAME"), db_password=os.getenv("SNAKE_DB_PASSWORD"), db_name=os.getenv("SNAKE_DB_NAME"))
self.boot_time = datetime.now()
self.log = logging.getLogger()
self.log.setLevel(logging.INFO)
self.log.addHandler(
logging.FileHandler(filename="snake.log", encoding="utf-8", mode='w')
)
async def log_message(self, message, action): # log the messages
author = message.author
channel = message.channel
......@@ -584,7 +593,7 @@ class SnakeBot(commands.Bot):
success = True
return chat_result
else:
self.log.warning(f"Could not fetch chat response. Retrying.. [{chat_result}]")
self.shared.log.warning(f"Could not fetch chat response. Retrying.. [{chat_result}]")
async def update_servers(self):
server_count = len(self.shared.servers)
......@@ -605,11 +614,11 @@ class SnakeBot(commands.Bot):
async with self.aio_session.post(f"https://bots.discord.pw/api/bots/{self.user.id}/stats", headers=bots_headers, data=bots_data) as response:
if response.status != 200:
self.log.error(f"Could not post to bots.discord.pw ({response.status}")
self.shared.log.error(f"Could not post to bots.discord.pw ({response.status}")
async with self.aio_session.post("https://www.carbonitex.net/discord/data/botdata.php", data=carbon_data) as response:
if response.status != 200:
self.log.error(f"Could not post to carbonitex.net ({response.status})")
self.shared.log.error(f"Could not post to carbonitex.net ({response.status})")
async def post_server_update(self, text):
channel = self.get_channel("234512725554888705")
......@@ -650,13 +659,13 @@ class SnakeBot(commands.Bot):
return voice_servers, total_servers
async def on_resume(self):
print(f"Resumed as {paint(self.user.name, 'blue')}#{paint(self.user.discriminator, 'yellow')} Shard #{paint(self.shard_id, 'magenta')} [{paint(self.user.id, 'b_green')}]")
print(f"Resumed as {paint(self.user.name, 'blue')}#{paint(self.user.discriminator, 'yellow')} Shard #{paint(self.shard_id, 'magenta')} [{paint(self.user.id, 'b_green')}] {paint('DEBUG MODE', 'b_cyan') if self._DEBUG else ''}")
self.resume_time = datetime.now()
self.resumed_after = time.get_elapsed_time(self.start_time, self.resume_time)
print(f"Resumed after {self.resumed_after}")
async def on_ready(self):
print(f"Logged in as {paint(self.user.name, 'blue')}#{paint(self.user.discriminator, 'yellow')} Shard #{paint(self.shard_id, 'magenta')} [{paint(self.user.id, 'b_green')}]")
print(f"Logged in as {paint(self.user.name, 'blue')}#{paint(self.user.discriminator, 'yellow')} Shard #{paint(self.shard_id, 'magenta')} [{paint(self.user.id, 'b_green')}] {paint('DEBUG MODE', 'b_cyan') if self._DEBUG else ''}")
self.start_time = datetime.now()
self.boot_duration = time.get_elapsed_time(self.boot_time, self.start_time)
print(f"Loaded in {self.boot_duration}")
......@@ -664,8 +673,10 @@ class SnakeBot(commands.Bot):
async def on_command_error(self, error, ctx):
if isinstance(error, commands.NoPrivateMessage):
await self.send_message(ctx.message.author, "\N{WARNING SIGN} Sorry, you can't use this command in a private message!")
elif isinstance(error, commands.DisabledCommand):
await self.send_message(ctx.message.author, "\N{WARNING SIGN} Sorry, this command is disabled!")
elif isinstance(error, commands.CommandOnCooldown):
await self.send_message(ctx.message.channel, f"{ctx.message.author.mention} slow down! Try again in {error.retry_after:.1f} seconds.")
......@@ -677,6 +688,7 @@ class SnakeBot(commands.Bot):
print(f"In {paint(ctx.command.qualified_name, 'b_red')}:")
traceback.print_tb(error.original.__traceback__)
print(f"{paint(original_name, 'red')}: {error.original}")
else:
print(f"{paint(type(error).__name__, 'b_red')}: {error}")
......@@ -687,7 +699,7 @@ class SnakeBot(commands.Bot):
destination = "Private Message"
else:
destination = f"[{message.server.name} #{message.channel.name}]"
self.log.info(f"SHARD#{self.shard_id} {destination}: {message.author.name}: {message.clean_content}")
self.shared.log.info(f"SHARD#{self.shard_id} {destination}: {message.author.name}: {message.clean_content}")
await self.log_command_use(ctx.command.qualified_name) # log that this command was used
async def on_message(self, message):
......@@ -738,6 +750,11 @@ class SnakeBot(commands.Bot):
await self.shared.post_server_update(f"Left **{server.name}** [{server.id}] (owned by **{server.owner.display_name}**#**{server.owner.discriminator}** [{server.owner.id}]) ({len(self.shared.servers)} total servers)")
await self.update_servers()
async def on_socket_raw_receive(self, payload):
self.log_socket(payload)
async def on_socket_raw_send(self, payload):
self.log_socket(payload, send=True)
shar