diff --git a/cogs/audio.py b/cogs/audio.py index 1b23a4551..791573ab1 100644 --- a/cogs/audio.py +++ b/cogs/audio.py @@ -60,9 +60,10 @@ class Audio: if await self.check_voice(msg.author, msg): if self.is_playlist_valid([link]): # reusing a function if await self.is_alone_or_admin(msg.author): - self.queue = [] + self.queue = [] self.current = -1 - self.playlist = [link] + self.playlist = [] + self.queue.append(link) self.music_player.stop() else: self.playlist = [] @@ -140,7 +141,7 @@ class Audio: votes_needed = int((len(current_users)-1) / 2) - if len(self.skip_votes)-1 >= votes_needed: + if len(self.skip_votes)-1 >= votes_needed: self.music_player.stop() self.skip_votes = [] return @@ -245,7 +246,16 @@ class Audio: if await self.check_voice(ctx.message.author, ctx.message): if not self.playlist: self.queue.append(link) - await self.bot.say("Link added to queue.") + msg = ctx.message + result = await self.get_song_metadata(link) + try: # In case of invalid SOUNDCLOUD ID + if result["title"] != []: + await self.bot.say("{} has been put into the queue by {}.".format(result["title"], msg.author)) + else: + await self.bot.say("The song has been put into the queue by {}, however it may error.".format(msg.author)) + except: + await self.bot.say("A song has been put into the queue by {}.".format(msg.author)) + else: await self.bot.say("I'm already playing a playlist.") @@ -318,6 +328,29 @@ class Audio: else: await self.bot.say("There are no local playlists.") + @_list.command(name="queue", pass_context=True) + async def list_queue(self, ctx): + message = ctx.message + cmdmsg = message + song_names = [] + song_names.append(self.downloader["TITLE"]) + if len(self.queue) > 0: + for song_url in self.queue: + try: + result = await self.get_song_metadata(song_url) + if result["title"] != []: + song_names.append(result["title"]) + else: + song_names.append("Could not get song title") + except: + song_names.append("Could not get song title") + song_list = '\n'.join('{}: {}'.format(*k) for k in enumerate(song_names)) + elif self.music_player.is_playing(): + song_list = "0: {}".format(song_names) + else: + song_list = "None" + await self.bot.say("Videos in queue: \n" + song_list) + @commands.group(pass_context=True) @checks.mod_or_permissions() async def audioset(self, ctx): @@ -336,7 +369,7 @@ class Audio: if status == "on" or status == "true": self.settings["QUEUE_MODE"] = True await self.bot.say("Queue mode is now on.") - elif status == "off" or status == "false": + elif status == "off" or status == "false": self.settings["QUEUE_MODE"] = False await self.bot.say("Queue mode is now off.") else: @@ -374,6 +407,17 @@ class Audio: else: await self.bot.say("Maximum audio cache size has been set to " + str(size) + "MB.") + @audioset.command() + @checks.is_owner() + async def soundcloud(self, ID : str=None): + """Sets the SoundCloud Client ID + """ + self.settings["SOUNDCLOUD_CLIENT_ID"] = ID + fileIO("data/audio/settings.json", "save", self.settings) + if not ID: + await self.bot.say("SoundCloud API intergration has been disabled") + else: + await self.bot.say("SoundCloud Client ID has been set") @commands.group(pass_context=True) @checks.is_owner() @@ -402,7 +446,7 @@ class Audio: for f in total: size += f return int(size / (1024*1024.0)) - + async def play_video(self, link): self.downloader = {"DONE" : False, "TITLE" : False, "ID" : False, "URL": False, "DURATION" : False, "DOWNLOADING" : False} if "https://" in link or "http://" in link: @@ -505,7 +549,7 @@ class Audio: print("Cache emptied.") - + if msg.channel.is_private and msg.attachments != []: await self.transfer_playlist(msg) if not msg.channel.is_private: @@ -608,6 +652,39 @@ class Audio: except: return False + async def get_json(self, url): + """ + Returns the JSON from an URL. + Expects the url to be valid and return a JSON object. + """ + async with aiohttp.get(url) as r: + result = await r.json() + return result + + async def get_song_metadata(self, song_url): + """ + Returns JSON object containing metadata about the song. + Expects song_url to be valid url and in acepted_list + """ + + youtube_regex = ( + r'(https?://)?(www\.)?' + '(youtube|youtu|youtube-nocookie)\.(com|be)/' + '(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})') + soundcloud_regex = "^(https:\\/\\/soundcloud\\.com\\/.*)" + is_youtube_link = re.match(youtube_regex, song_url) + is_soundcloud_link = re.match(soundcloud_regex, song_url) + + if is_youtube_link: + url = "http://www.youtube.com/oembed?url={0}&format=json".format(song_url) + result = await self.get_json(url) + elif is_soundcloud_link and (self.settings["SOUNDCLOUD_CLIENT_ID"] is not None): + url = "http://api.soundcloud.com/resolve.json?url={0}&client_id={1}".format(song_url, self.settings["SOUNDCLOUD_CLIENT_ID"]) + result = await self.get_json(url) + else: + result = {"title": "A song "} + return result + class EmptyPlayer(): #dummy player def __init__(self): pass @@ -632,8 +709,8 @@ def check_folders(): os.makedirs(folder) def check_files(): - - default = {"VOLUME" : 0.5, "MAX_LENGTH" : 3700, "QUEUE_MODE" : True, "MAX_CACHE" : 0} + + default = {"VOLUME" : 0.5, "MAX_LENGTH" : 3700, "QUEUE_MODE" : True, "MAX_CACHE" : 0, "SOUNDCLOUD_CLIENT_ID": None} settings_path = "data/audio/settings.json" if not os.path.isfile(settings_path): @@ -651,7 +728,7 @@ def check_files(): allowed = ["^(https:\/\/www\\.youtube\\.com\/watch\\?v=...........*)", "^(https:\/\/youtu.be\/...........*)", "^(https:\/\/youtube\\.com\/watch\\?v=...........*)", "^(https:\/\/soundcloud\\.com\/.*)"] - + if not os.path.isfile("data/audio/accepted_links.json"): print("Creating accepted_links.json...") fileIO("data/audio/accepted_links.json", "save", allowed) @@ -663,4 +740,4 @@ def setup(bot): n = Audio(bot) loop.create_task(n.queue_manager()) bot.add_listener(n.incoming_messages, "on_message") - bot.add_cog(n) \ No newline at end of file + bot.add_cog(n) diff --git a/cogs/customcom.py b/cogs/customcom.py index 646f1bdb6..0c06fbbbd 100644 --- a/cogs/customcom.py +++ b/cogs/customcom.py @@ -123,4 +123,4 @@ def setup(bot): check_files() n = CustomCommands(bot) bot.add_listener(n.checkCC, "on_message") - bot.add_cog(n) \ No newline at end of file + bot.add_cog(n) diff --git a/cogs/streams.py b/cogs/streams.py new file mode 100644 index 000000000..fdb143040 --- /dev/null +++ b/cogs/streams.py @@ -0,0 +1,251 @@ +import discord +from discord.ext import commands +from .utils.dataIO import fileIO +from .utils import checks +import os +import time +import aiohttp +import asyncio +from copy import deepcopy +import logging + +class Streams: + """Streams + + Twitch and Hitbox alerts""" + + def __init__(self, bot): + self.bot = bot + self.twitch_streams = fileIO("data/streams/twitch.json", "load") + self.hitbox_streams = fileIO("data/streams/hitbox.json", "load") + self.pattern = {"CHANNELS" : [], "NAME" : "", "ALREADY_ONLINE" : False} + + @commands.command() + async def hitbox(self, stream : str): + """Checks if hitbox stream is online""" + online = await self.hitbox_online(stream) + if online: + await self.bot.say("http://www.hitbox.tv/{}/ is online!".format(stream)) + elif online == False: + await self.bot.say(stream + " is offline.") + elif online == None: + await self.bot.say("That stream doesn't exist.") + else: + await self.bot.say("Error.") + + @commands.command() + async def twitch(self, stream : str): + """Checks if twitch stream is online""" + online = await self.twitch_online(stream) + if online: + await self.bot.say("http://www.twitch.tv/{} is online!".format(stream)) + elif online == False: + await self.bot.say(stream + " is offline.") + elif online == None: + await self.bot.say("That stream doesn't exist.") + else: + await self.bot.say("Error.") + + @commands.group(pass_context=True) + @checks.mod_or_permissions(manage_server=True) + async def streamalert(self, ctx): + """Adds/removes stream alerts from the current channel""" + if ctx.invoked_subcommand is None: + await self.bot.say("Type help streamalert for info.") + + @streamalert.command(name="twitch", pass_context=True) + async def twitch_alert(self, ctx, stream : str): + """Adds/removes twitch alerts from the current channel""" + channel = ctx.message.channel + check = await self.twitch_online(stream) + if check == None: + await self.bot.say("That stream doesn't exist.") + return + elif check == "error": + await self.bot.say("Error.") + return + + done = False + + for i, s in enumerate(self.twitch_streams): + if s["NAME"] == stream: + if channel.id in s["CHANNELS"]: + if len(s["CHANNELS"]) == 1: + self.twitch_streams.remove(s) + await self.bot.say("Alert has been removed from this channel.") + done = True + else: + self.twitch_streams[i]["CHANNELS"].remove(channel.id) + await self.bot.say("Alert has been removed from this channel.") + done = True + else: + self.twitch_streams[i]["CHANNELS"].append(channel.id) + await self.bot.say("Alert activated. I will notify this channel everytime {} is live.".format(stream)) + done = True + + if not done: + self.twitch_streams.append({"CHANNELS" : [channel.id], "NAME" : stream, "ALREADY_ONLINE" : False}) + await self.bot.say("Alert activated. I will notify this channel everytime {} is live.".format(stream)) + + fileIO("data/streams/twitch.json", "save", self.twitch_streams) + + @streamalert.command(name="hitbox", pass_context=True) + async def hitbox_alert(self, ctx, stream : str): + """Adds/removes hitbox alerts from the current channel""" + channel = ctx.message.channel + check = await self.hitbox_online(stream) + if check == None: + await self.bot.say("That stream doesn't exist.") + return + elif check == "error": + await self.bot.say("Error.") + return + + done = False + + for i, s in enumerate(self.hitbox_streams): + if s["NAME"] == stream: + if channel.id in s["CHANNELS"]: + if len(s["CHANNELS"]) == 1: + self.hitbox_streams.remove(s) + await self.bot.say("Alert has been removed from this channel.") + done = True + else: + self.hitbox_streams[i]["CHANNELS"].remove(channel.id) + await self.bot.say("Alert has been removed from this channel.") + done = True + else: + self.hitbox_streams[i]["CHANNELS"].append(channel.id) + await self.bot.say("Alert activated. I will notify this channel everytime {} is live.".format(stream)) + done = True + + if not done: + self.hitbox_streams.append({"CHANNELS" : [channel.id], "NAME" : stream, "ALREADY_ONLINE" : False}) + await self.bot.say("Alert activated. I will notify this channel everytime {} is live.".format(stream)) + + fileIO("data/streams/hitbox.json", "save", self.hitbox_streams) + + @streamalert.command(name="stop", pass_context=True) + async def stop_alert(self, ctx): + """Stops all streams alerts in the current channel""" + channel = ctx.message.channel + + to_delete = [] + + for s in self.hitbox_streams: + if channel.id in s["CHANNELS"]: + if len(s["CHANNELS"]) == 1: + to_delete.append(s) + else: + s["CHANNELS"].remove(channel.id) + + for s in to_delete: + self.hitbox_streams.remove(s) + + to_delete = [] + + for s in self.twitch_streams: + if channel.id in s["CHANNELS"]: + if len(s["CHANNELS"]) == 1: + to_delete.append(s) + else: + s["CHANNELS"].remove(channel.id) + + for s in to_delete: + self.twitch_streams.remove(s) + + fileIO("data/streams/twitch.json", "save", self.twitch_streams) + fileIO("data/streams/hitbox.json", "save", self.hitbox_streams) + + await self.bot.say("There will be no more stream alerts in this channel.") + + + async def hitbox_online(self, stream): + url = "https://api.hitbox.tv/user/" + stream + try: + async with aiohttp.get(url) as r: + data = await r.json() + if data["is_live"] == "0": + return False + elif data["is_live"] == "1": + return True + elif data["is_live"] == None: + return None + except: + return "error" + + async def twitch_online(self, stream): + url = "https://api.twitch.tv/kraken/streams/" + stream + async with aiohttp.get(url) as r: + data = await r.json() + try: + if "stream" in data: + if data["stream"] != None: + return True + else: + return False + elif "error" in data: + return None + except: + return "error" + return False + + async def stream_checker(self): + CHECK_DELAY = 10 + while "Streams" in self.bot.cogs: + + old = (deepcopy(self.twitch_streams), deepcopy(self.hitbox_streams)) + + for stream in self.twitch_streams: + online = await self.twitch_online(stream["NAME"]) + if online and not stream["ALREADY_ONLINE"]: + stream["ALREADY_ONLINE"] = True + for channel in stream["CHANNELS"]: + if self.bot.get_channel(channel): + await self.bot.send_message(self.bot.get_channel(channel), "http://www.twitch.tv/{} is online!".format(stream["NAME"])) + else: + if stream["ALREADY_ONLINE"] and not online: stream["ALREADY_ONLINE"] = False + await asyncio.sleep(1) + + for stream in self.hitbox_streams: + online = await self.hitbox_online(stream["NAME"]) + if online and not stream["ALREADY_ONLINE"]: + stream["ALREADY_ONLINE"] = True + for channel in stream["CHANNELS"]: + if self.bot.get_channel(channel): + await self.bot.send_message(self.bot.get_channel(channel), "http://www.hitbox.tv/{} is online!".format(stream["NAME"])) + else: + if stream["ALREADY_ONLINE"] and not online: stream["ALREADY_ONLINE"] = False + await asyncio.sleep(1) + + if old != (self.twitch_streams, self.hitbox_streams): + fileIO("data/streams/twitch.json", "save", self.twitch_streams) + fileIO("data/streams/hitbox.json", "save", self.hitbox_streams) + + await asyncio.sleep(CHECK_DELAY) + +def check_folders(): + if not os.path.exists("data/streams"): + print("Creating data/streams folder...") + os.makedirs("data/streams") + +def check_files(): + f = "data/streams/twitch.json" + if not fileIO(f, "check"): + print("Creating empty twitch.json...") + fileIO(f, "save", []) + + f = "data/streams/hitbox.json" + if not fileIO(f, "check"): + print("Creating empty hitbox.json...") + fileIO(f, "save", []) + +def setup(bot): + logger = logging.getLogger('aiohttp.client') + logger.setLevel(50) #Stops warning spam + check_folders() + check_files() + n = Streams(bot) + loop = asyncio.get_event_loop() + loop.create_task(n.stream_checker()) + bot.add_cog(n) \ No newline at end of file diff --git a/cogs/trivia.py b/cogs/trivia.py index 04fcb1c87..6d740c3b0 100644 --- a/cogs/trivia.py +++ b/cogs/trivia.py @@ -104,7 +104,7 @@ class TriviaSession(): triviaManager.trivia_sessions.remove(self) def loadList(self, qlist): - with open(qlist, "r", encoding="utf-8") as f: + with open(qlist, "r", encoding="ISO-8859-1") as f: qlist = f.readlines() parsedList = [] for line in qlist: