diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 2ec42fd..0000000 --- a/.drone.yml +++ /dev/null @@ -1,12 +0,0 @@ - -pipeline: - build: - image: plugins/docker - repo: rnorge/music - secrets: [ docker_username, docker_password ] - tags: - - latest - - ${DRONE_TAG} - when: - event: - - tag \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 0cbc142..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,16 +0,0 @@ - build: - image: docker:stable - services: - - docker:dind - variables: - DOCKER_HOST: tcp://docker:2375 - DOCKER_DRIVER: overlay2 - IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - before_script: - - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com - stage: build - script: - - docker build -t $IMAGE_TAG . - - docker push $IMAGE_TAG - only: - - master \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 440fc0f..f6d6c30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,6 @@ RUN \ WORKDIR /app -CMD ln -sf /app/data /config && /app/localization /config && python3 /app/bot.py +CMD cp -u /app/data/config.yaml.example /config/config.yaml.example && python3 /app/bot.py --data-directory /config VOLUME /config diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..09eea56 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,57 @@ +pipeline { + agent none + environment { + GIT_REPO = 'Ev-1/ShiteMusicBot' + DOCKER_REPO = 'rnorge/music' + TAG="" + FLAKE_FILES = "bot.py cogs/*.py cogs/utils/*.py" + } + stages { + stage('Flake8') { + agent { label 'amd64'} + steps { + sh """ + python3.7 -m venv venv && venv/bin/pip install flake8 && venv/bin/python -m flake8 --max-line-length 120 ${FLAKE_FILES} + rm -rf venv/ + """ + script { + TAG = sh(returnStdout: true, script: 'grep -i bot_version cogs/utils/bot_version.py | cut -d" " -f3 | tr -d \\"').trim() + SBranch = sh(returnStdout: true, script: 'echo ${GIT_BRANCH} | sed "s#/#_#"').trim() + } + } + } + stage('Docker Builds') { + parallel { + stage('branch') { + agent { label 'amd64'} + when { + not { + changeRequest() + } + } + steps { + script { + if (BRANCH_NAME == 'master') { + def image = docker.build("${DOCKER_REPO}:${TAG}-amd64") + image.push() + } + def image = docker.build("${DOCKER_REPO}:${SBranch}-amd64") + image.push() + } + } + } + stage('pr') { + agent { label 'amd64'} + when { + changeRequest() + } + steps { + script { + def image = docker.build("${DOCKER_REPO}:PR_$GIT_BRANCH-amd64") + } + } + } + } + } + } +} diff --git a/bot.py b/bot.py index 6aeb5d3..ed12397 100644 --- a/bot.py +++ b/bot.py @@ -1,13 +1,13 @@ import discord import codecs import time -import sys +import os import traceback import yaml import aiohttp from discord.ext import commands -from discord.ext.commands.view import StringView +from argparse import ArgumentParser, RawTextHelpFormatter from cogs.utils.settingsmanager import Settings from cogs.utils.localizer import Localizer from cogs.utils.localizer import LocalizerWrapper @@ -17,11 +17,6 @@ from cogs.utils.logger import BotLogger from cogs.helpformatter import commandhelper from cogs.utils.paginator import Scroller - -with codecs.open("data/config.yaml", 'r', encoding='utf8') as f: - conf = yaml.load(f, Loader=yaml.SafeLoader) - - initial_extensions = [ 'cogs.cogs', 'cogs.settings', @@ -29,7 +24,6 @@ initial_extensions = [ 'cogs.helpformatter' ] - on_ready_extensions = [ 'cogs.music', 'cogs.musicevents' @@ -45,11 +39,11 @@ def _get_prefix(bot, message): class Bot(commands.Bot): - def __init__(self, debug: bool=False): + def __init__(self, datadir, debug: bool = False): super().__init__(command_prefix=_get_prefix, description=conf["bot"]["description"]) - self.settings = Settings(**conf['default server settings']) + self.settings = Settings(datadir, **conf['default server settings']) self.APIkeys = conf.get('APIkeys', {}) self.localizer = Localizer(conf.get('locale path', "./localization"), conf.get('locale', 'en_en')) @@ -57,6 +51,7 @@ class Bot(commands.Bot): self.session = aiohttp.ClientSession(loop=self.loop) + self.datadir = datadir self.debug = debug self.main_logger = logger self.logger = self.main_logger.bot_logger.getChild("Bot") @@ -77,27 +72,23 @@ class Bot(commands.Bot): await scroller.start_scrolling() if isinstance(err, commands.CommandInvokeError): - self.logger.debug("Error running command: %s\n Traceback: %s" - % (ctx.command, err)) + self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) pass elif isinstance(err, commands.NoPrivateMessage): - self.logger.debug("Error running command: %s\n Traceback: %s" - % (ctx.command, err)) + self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) await ctx.send('That command is not available in DMs') - + elif isinstance(err, commands.CommandOnCooldown): await ctx.send(f"{ctx.message.author.mention} Command is on cooldown. " f"Try again in `{err.retry_after:.1f}` seconds.") elif isinstance(err, RuntimeError): - self.logger.debug("Error running command: %s\n Traceback: %s" - % (ctx.command, err)) + self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) pass elif isinstance(err, commands.CheckFailure): - self.logger.debug("Error running command: %s\n Traceback: %s" - % (ctx.command, err)) + self.logger.debug("Error running command: %s\n Traceback: %s" % (ctx.command, err)) pass elif isinstance(err, commands.CommandNotFound): @@ -134,7 +125,7 @@ class Bot(commands.Bot): try: self.logger.debug("Loading extension %s" % extension) self.load_extension(extension) - except Exception as e: + except Exception: self.logger.exception("Loading of extension %s failed" % extension) print(f'\nLogged in as: {self.user.name}' + @@ -155,14 +146,34 @@ class Bot(commands.Bot): print(e) -def run_bot(debug: bool=False): - bot = Bot(debug=debug) +def run_bot(datadir, debug: bool = False): + bot = Bot(datadir, debug=debug) bot.run() if __name__ == '__main__': - state = False - if 'debug' in sys.argv: - state = True - logger = BotLogger(state, conf.get('log_path', './data/logs')) - run_bot(debug=state) + parser = ArgumentParser(prog='Shite Music Bot', + description='Discord music bot utilizing discord.py, lavalink and lavalink.py', + formatter_class=RawTextHelpFormatter) + + parser.add_argument("-D", "--debug", action='store_true', help='Sets debug to true') + parser.add_argument("-d", "--data-directory", help='Define an alternate data directory location') + + args = parser.parse_args() + if args.debug or os.environ.get('debug'): + is_debug = True + else: + is_debug = False + + if args.data_folder: + datadir = str(args.data_folder) + else: + datadir = "data" + + print(f"Data folder: {datadir}") + + with codecs.open(f"{datadir}/config.yaml", 'r', encoding='utf8') as f: + conf = yaml.load(f, Loader=yaml.SafeLoader) + + logger = BotLogger(is_debug, conf.get('log_path', f'{datadir}/logs')) + run_bot(debug=is_debug, datadir=datadir) diff --git a/cogs/cogs.py b/cogs/cogs.py index 65e8640..0001530 100644 --- a/cogs/cogs.py +++ b/cogs/cogs.py @@ -1,10 +1,8 @@ -import discord -import os -import codecs import traceback from discord.ext import commands from cogs.utils import checks + class Cogs(commands.Cog): def __init__(self, bot): self.bot = bot @@ -34,7 +32,7 @@ class Cogs(commands.Cog): try: self.bot.unload_extension(f'cogs.{module}') await ctx.send(f'{module} unloaded') - except Exception as e: + except Exception: await ctx.send(f'```py\n{traceback.format_exc()}\n```') @_cogs.command(name='reload') @@ -45,7 +43,7 @@ class Cogs(commands.Cog): self.bot.unload_extension(f'cogs.{module}') self.bot.load_extension(f'cogs.{module}') await ctx.send(f'{module} reloaded') - except Exception as e: + except Exception: await ctx.send(f'```py\n{traceback.format_exc()}\n```') @_cogs.command(name='reloadall') @@ -59,7 +57,7 @@ class Cogs(commands.Cog): self.bot.unload_extension(f'{extension}') self.bot.load_extension(f'{extension}') await ctx.send('Extensions reloaded') - except Exception as e: + except Exception: await ctx.send(f'```py\n{traceback.format_exc()}\n```') @_cogs.command(name='shutdown') diff --git a/cogs/helpformatter.py b/cogs/helpformatter.py index a2317b7..8347125 100644 --- a/cogs/helpformatter.py +++ b/cogs/helpformatter.py @@ -1,6 +1,4 @@ -import discord from discord.ext import commands -import asyncio import re from cogs.utils.paginator import HelpPaginator, Scroller @@ -18,10 +16,11 @@ def get_cmd_dict(ctx, qualified_name): cmd_dict = ctx.bot.aliaser.get_cmd_help("en_en", split[-1], split[:-1]) try: cmd_dict["aliases"] = [split[-1]] - except: + except KeyError: cmd_dict = None return cmd_dict + async def helper(ctx): bot = ctx.bot # Display music commands first @@ -29,7 +28,7 @@ async def helper(ctx): paginator = await coghelper(ctx, music) for cog in bot.cogs.copy().values(): - if cog.__cog_name__ is 'Music': + if cog.__cog_name__ == 'Music': continue cogpaignator = await coghelper(ctx, cog) paginator.append_paginator(cogpaignator) @@ -48,7 +47,7 @@ async def coghelper(ctx, cog, ignore_subcommands=True): continue try: can_run = await cmd.can_run(ctx) - except CommandError: + except commands.CommandError: # Was 'CommandError' guessed it was supposed to be what it is now can_run = False if not can_run: continue @@ -107,13 +106,14 @@ def prefix_cleaner(ctx): ctx.prefix = pref return ctx + class Help(commands.Cog): """Help command""" def __init__(self, bot): self.bot = bot @commands.command(hidden=True) - async def help(self, ctx): # Takes no args because reasons(using the view directly) + async def help(self, ctx): # Takes no args because reasons(using the view directly) ctx.view.skip_ws() v = ctx.view invoker = v.buffer[v.index:v.end] diff --git a/cogs/misc.py b/cogs/misc.py index 7336c0a..8274b27 100644 --- a/cogs/misc.py +++ b/cogs/misc.py @@ -1,12 +1,9 @@ import discord -import os -import asyncio import time -import random import platform from discord.ext import commands -from cogs.utils import checks +from cogs.utils import checks, bot_version from lavalink import __version__ as LavalinkVersion @@ -16,13 +13,13 @@ class Misc(commands.Cog): @commands.command(name='ping', hidden=True) async def _ping(self, ctx): - start = time.perf_counter() - message = await ctx.send('Ping...') - end = time.perf_counter() - duration = int((end - start) * 1000) - edit = f'Pong!\nPing: {duration}ms' \ - + f' | websocket: {int(self.bot.latency * 1000)}ms' - await message.edit(content=edit) + start = time.perf_counter() + message = await ctx.send('Ping...') + end = time.perf_counter() + duration = int((end - start) * 1000) + edit = f'Pong!\nPing: {duration}ms' \ + + f' | websocket: {int(self.bot.latency * 1000)}ms' + await message.edit(content=edit) @commands.command(name='uptime', hidden=True) async def _uptime(self, ctx): @@ -57,7 +54,6 @@ class Misc(commands.Cog): embed.add_field(name='{music.listeners}', value=f'{listeners}') embed = ctx.localizer.format_embed(embed) await ctx.send(embed=embed) - @commands.command(name="reloadlocale") @checks.is_owner() @@ -92,19 +88,18 @@ class Misc(commands.Cog): days, remainder = divmod(diff, 24 * 60 * 60) hours, remainder = divmod(remainder, 60 * 60) minutes, seconds = divmod(remainder, 60) - avatar = self.bot.user.avatar_url_as(format=None, - static_format='png', - size=1024) + avatar = self.bot.user.avatar_url_as(format=None, static_format='png', size=1024) uptimetext = f'{days}d {hours}t {minutes}m {seconds}s' embed = discord.Embed(color=ctx.me.color) embed.set_author(name=self.bot.user.name, icon_url=avatar) embed.set_thumbnail(url=avatar) - embed.set_image(url='https://cdn.discordapp.com/attachments/298524946454282250/368118192251469835/vintage1turntable.png') + embed.set_image(url='https://cdn.discordapp.com/attachments/298524946454282250/' + '368118192251469835/vintage1turntable.png') embed.add_field(name="{bot.what}", value='{bot.infotext}', inline=False) - embed.set_footer(icon_url="https://cdn.discordapp.com/icons/532176350019321917/92f43a1f67308a99a30c169db4b671dd.png?size=64", - text="{bot.footer_text}") + embed.set_footer(icon_url="https://cdn.discordapp.com/icons/532176350019321917/" + "92f43a1f67308a99a30c169db4b671dd.png?size=64", text="{bot.footer_text}") embed.add_field(name="{bot.how}", value='{bot.spectext}') embed.add_field(name="{bot.how_many}", @@ -112,13 +107,9 @@ class Misc(commands.Cog): embed.add_field(name="{bot.how_long}", value=uptimetext) - embed = ctx.localizer.format_embed(embed, - _python_v=platform.python_version(), - _discord_v=discord.__version__, - _lavalink_v=LavalinkVersion, - _guilds=guilds, - _members=members - ) + embed = ctx.localizer.format_embed(embed, _python_v=platform.python_version(), _discord_v=discord.__version__, + _lavalink_v=LavalinkVersion, _guilds=guilds, _members=members, + _bot_v=bot_version.bot_version) await ctx.send(embed=embed) diff --git a/cogs/music.py b/cogs/music.py index be9fdbf..3e570c5 100644 --- a/cogs/music.py +++ b/cogs/music.py @@ -4,7 +4,6 @@ Music commands import math import re import asyncio -import time import yaml import codecs @@ -33,7 +32,7 @@ class Music(commands.Cog): if not hasattr(bot, 'lavalink'): # This ensures the client isn't overwritten during cog reloads. bot.lavalink = lavalink.Client(bot.user.id, player=MixPlayer) - with codecs.open("data/config.yaml", 'r', encoding='utf8') as f: + with codecs.open(f"{self.bot.datadir}/config.yaml", 'r', encoding='utf8') as f: conf = yaml.load(f, Loader=yaml.SafeLoader) bot.lavalink.add_node(**conf['lavalink nodes']['main']) @@ -106,7 +105,7 @@ class Music(commands.Cog): async def _seek(self, ctx, *, time: str): """ Seeks to a given position in a track. """ player = self.bot.lavalink.players.get(ctx.guild.id) - + if not player.is_playing: return await ctx.send(ctx.localizer.format_str("{not_playing}")) @@ -150,7 +149,7 @@ class Music(commands.Cog): @commands.command(name='skipto') @checks.DJ_or(alone=True) - async def _skip_to(self, ctx, pos: int=1): + async def _skip_to(self, ctx, pos: int = 1): """ Plays the queue from a specific point. Disregards tracks before the pos. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -212,7 +211,7 @@ class Music(commands.Cog): await ctx.send(embed=embed) @commands.command(name='queue') - async def _queue(self, ctx, user: discord.Member=None): + async def _queue(self, ctx, user: discord.Member = None): """ Shows the player's queue. """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -220,7 +219,7 @@ class Music(commands.Cog): embed = discord.Embed(description='{queue.empty}', color=ctx.me.color) embed = ctx.localizer.format_embed(embed) return await ctx.send(embed=embed) - + if user is None: queue = player.global_queue() pagified_queue = QueuePaginator(ctx.localizer, queue, color=ctx.me.color) @@ -228,7 +227,7 @@ class Music(commands.Cog): else: user_queue = player.user_queue(user.id, dual=True) if not user_queue: - return await ctx.send(ctx.localizer.format_str("{queue.empty}", _user=user.name)) + return await ctx.send(ctx.localizer.format_str("{queue.empty}", _user=user.name)) pagified_queue = QueuePaginator(ctx.localizer, user_queue, color=ctx.me.color, user_name=user.name) scroller = Scroller(ctx, pagified_queue) @@ -285,13 +284,13 @@ class Music(commands.Cog): if not user_queue: return await ctx.send(ctx.localizer.format_str("{my_queue}")) - if not all(x in range(1,len(user_queue) + 1) for x in [from_pos, to_pos]): + if not all(x in range(1, len(user_queue) + 1) for x in [from_pos, to_pos]): return await ctx.send(ctx.localizer.format_str("{out_of_range}", _len=len(user_queue))) moved = player.move_user_track(ctx.author.id, from_pos - 1, to_pos - 1) if moved is None: return await ctx.send(ctx.localizer.format_str("{move.not_in_queue}")) - + msg = ctx.localizer.format_str("{move.moved_to}", _title=moved.title, _from=from_pos, _to=to_pos) await ctx.send(msg) @@ -316,7 +315,7 @@ class Music(commands.Cog): @commands.command(name="DJremove") @checks.DJ_or() - async def _djremove(self, ctx, pos: int, user: discord.Member=None): + async def _djremove(self, ctx, pos: int, user: discord.Member = None): """ Remove a song from either the global queue or a users queue""" player = self.bot.lavalink.players.get(ctx.guild.id) @@ -436,13 +435,11 @@ class Music(commands.Cog): await result_msg.edit(embed=embed) try: - reaction, user = await self.bot.wait_for('reaction_add', - timeout=15.0, - check=check) + reaction, user = await self.bot.wait_for('reaction_add', timeout=15.0, check=check) except asyncio.TimeoutError: await result_msg.clear_reactions() embed.title = '' - embed.description='{time_expired}' + embed.description = '{time_expired}' embed = ctx.localizer.format_embed(embed) await result_msg.edit(embed=embed) await asyncio.sleep(5) @@ -488,7 +485,7 @@ class Music(commands.Cog): return await ctx.send(f'🔈 | {player.volume}%') is_dj = checks.is_DJ(ctx) - is_admin = getattr(ctx.author.guild_permissions, 'administrator', None) == True + is_admin = getattr(ctx.author.guild_permissions, 'administrator', None) is True if int(player.current.requester) == ctx.author.id and not is_dj and not is_admin: if not 50 <= volume <= 125: return await ctx.send(ctx.localizer.format_str("{volume.out_of_range}")) @@ -516,7 +513,7 @@ class Music(commands.Cog): @commands.command(name='boost') @checks.DJ_or(alone=True) - async def _boost(self, ctx, boost: bool=None): + async def _boost(self, ctx, boost: bool = None): """ Set the equalizer to bass boost the music """ player = self.bot.lavalink.players.get(ctx.guild.id) @@ -529,7 +526,7 @@ class Music(commands.Cog): embed.description = '{boost.on}' else: embed.description = '{boost.off}' - + embed = ctx.localizer.format_embed(embed) await ctx.send(embed=embed) @@ -543,15 +540,16 @@ class Music(commands.Cog): embed = ctx.localizer.format_embed(embed) return await ctx.send(embed=embed) track = history[0] - description = ctx.localizer.format_str("{history.current}", _title=track.title, _uri=track.uri,_id=track.requester) + '\n\n' + description = ctx.localizer.format_str("{history.current}", _title=track.title, _uri=track.uri, + _id=track.requester) + '\n\n' description += ctx.localizer.format_str("{history.previous}", _len=len(history)-1) + '\n' - thumb_url = await RoxUtils.ThumbNailer.identify(self, - track.identifier, - track.uri) + thumb_url = await RoxUtils.ThumbNailer.identify(self, track.identifier, track.uri) for index, track in enumerate(history[1:], start=1): - description += ctx.localizer.format_str("{history.track}",_index=-index, _title=track.title, _uri=track.uri, _id=track.requester) + '\n' + description += ctx.localizer.format_str("{history.track}", _index=-index, _title=track.title, + _uri=track.uri, _id=track.requester) + '\n' - embed = discord.Embed(title=ctx.localizer.format_str('{history.title}'), color=ctx.me.color, description=description) + embed = discord.Embed(title=ctx.localizer.format_str('{history.title}'), color=ctx.me.color, + description=description) if thumb_url: embed.set_thumbnail(url=thumb_url) @@ -575,7 +573,7 @@ class Music(commands.Cog): if not query and player.is_playing: query = player.current.title - query = re.sub('[-()_\[\]]', '', query) + query = re.sub(r'[-()_\[\]]', '', query) filtered_words = [word for word in query.split() if word.lower() not in excluded_words] query = ' '.join(filtered_words) @@ -593,7 +591,7 @@ class Music(commands.Cog): result = await get_site_content(url) response = json.loads(result) song_id = response['response']['hits'][0]['result']['id'] - except: + except Exception: embed = discord.Embed(description=':x:', color=0xFF0000) return await status_msg.edit(embed=embed) @@ -620,8 +618,7 @@ class Music(commands.Cog): await ctx.send(embed=page) else: paginator.add_page_indicator(ctx.localizer) - scroller = Scroller(ctx, paginator) - await scroller.start_scrolling() + await Scroller(ctx, paginator).start_scrolling() @commands.command(name='scrub') @checks.DJ_or(alone=True) @@ -677,22 +674,22 @@ class Music(commands.Cog): scrubbing = True while scrubbing: try: - reaction, user = await self.bot.wait_for('reaction_add', - timeout=15.0, - check=check) + reaction, user = await self.bot.wait_for('reaction_add', timeout=15.0, check=check) except asyncio.TimeoutError: - scrolling = False + # PEP8EDIT scrolling = False try: await msg.delete() await ctx.message.delete() - except: + except discord.Forbidden: + pass + except discord.HTTPException: pass finally: break try: await msg.remove_reaction(reaction, user) - except: + except discord.Forbidden: pass if selection is not None: if not isinstance(arg, bool): @@ -704,8 +701,11 @@ class Music(commands.Cog): player = self.bot.lavalink.players.create(ctx.guild.id, endpoint=ctx.guild.region.value) # Create returns a player if one exists, otherwise creates. - should_connect = ctx.command.callback.__name__ in ('_play', '_find', '_search') # Add commands that require joining voice to work. - without_connect = ctx.command.callback.__name__ in ('_queue', '_history', '_now', '_lyrics') # Add commands that don't require the you being in voice. + # Add commands that require joining voice to work. + should_connect = ctx.command.callback.__name__ in ('_play', '_find', '_search') + + # Add commands that don't require the you being in voice. + without_connect = ctx.command.callback.__name__ in ('_queue', '_history', '_now', '_lyrics') if without_connect: return @@ -750,8 +750,8 @@ class Music(commands.Cog): maxlength = self.max_track_length(ctx.guild, player) if maxlength and track['info']['length'] > maxlength: embed.description = ctx.localizer.format_str("{enqueue.toolong}", - _length=timeformatter.format(track['info']['length']), - _max=timeformatter.format(maxlength)) + _length=timeformatter.format(track['info']['length']), + _max=timeformatter.format(maxlength)) return track, pos_global, pos_local = player.add(requester=ctx.author.id, track=track) @@ -769,9 +769,7 @@ class Music(commands.Cog): embed.add_field(name="{enqueue.playing_in}", value=f"`{until_play} ({{enqueue.estimated}})`", inline=True) embed.title = '{enqueue.enqueued}' - thumb_url = await RoxUtils.ThumbNailer.identify(self, - track.identifier, - track.uri) + thumb_url = await RoxUtils.ThumbNailer.identify(self, track.identifier, track.uri) if thumb_url: embed.set_thumbnail(url=thumb_url) @@ -784,7 +782,7 @@ class Music(commands.Cog): maxlength = self.bot.settings.get(guild, 'duration.max', None) if maxlength is None: return None - if len(player.listeners): # Avoid division by 0. + if len(player.listeners): # Avoid division by 0. listeners = len(player.listeners) else: listeners = 1 diff --git a/cogs/musicevents.py b/cogs/musicevents.py index c3cb74b..30bc624 100644 --- a/cogs/musicevents.py +++ b/cogs/musicevents.py @@ -2,15 +2,13 @@ A cog to separate events from regular music commands """ -import asyncio +import codecs import yaml -import discord import lavalink from discord.ext import commands -import time from .utils.mixplayer import MixPlayer -from lavalink.events import * +import lavalink.events class MusicEvents(commands.Cog): @@ -20,7 +18,7 @@ class MusicEvents(commands.Cog): if not hasattr(bot, 'lavalink'): # This ensures the client isn't overwritten during cog reloads. bot.lavalink = lavalink.Client(bot.user.id, player=MixPlayer) - with codecs.open("data/config.yaml", 'r', encoding='utf8') as f: + with codecs.open(f"{self.bot.datadir}/config.yaml", 'r', encoding='utf8') as f: conf = yaml.load(f, Loader=yaml.SafeLoader) bot.lavalink.add_node(**conf['lavalink nodes']['main']) @@ -32,20 +30,20 @@ class MusicEvents(commands.Cog): self.bot.lavalink._event_hooks.clear() async def track_hook(self, event): - if isinstance(event, TrackEndEvent): + if isinstance(event, lavalink.events.TrackEndEvent): pass # Send track ended message to channel. - if isinstance(event, TrackStartEvent): + if isinstance(event, lavalink.events.TrackStartEvent): pass - if isinstance(event, QueueEndEvent): + if isinstance(event, lavalink.events.QueueEndEvent): channel = self.bot.get_channel(event.player.fetch('channel')) await self.check_leave_voice(channel.guild) - if isinstance(event, PlayerUpdateEvent): + if isinstance(event, lavalink.events.PlayerUpdateEvent): pass - if isinstance(event, NodeDisconnectedEvent): + if isinstance(event, lavalink.events.NodeDisconnectedEvent): pass - if isinstance(event, NodeConnectedEvent): + if isinstance(event, lavalink.events.NodeConnectedEvent): pass - if isinstance(event, NodeChangedEvent): + if isinstance(event, lavalink.events.NodeChangedEvent): pass async def connect_to(self, guild_id: int, channel_id: str): diff --git a/cogs/settings.py b/cogs/settings.py index 0344694..fd6669b 100644 --- a/cogs/settings.py +++ b/cogs/settings.py @@ -1,10 +1,9 @@ import discord -import os -import codecs from discord.ext import commands from cogs.utils import checks + class Settings(commands.Cog): def __init__(self, bot): self.bot = bot @@ -54,7 +53,7 @@ class Settings(commands.Cog): @commands.guild_only() @_set.command(name='threshold') - async def set_vote_threshold(self, ctx, threshold: int=50): + async def set_vote_threshold(self, ctx, threshold: int = 50): if not 0 <= threshold <= 100: return await ctx.send('Must be between 0 and 100') @@ -148,7 +147,7 @@ class Settings(commands.Cog): @commands.guild_only() @_set.command(name='maxduration') - async def set_max_track_duration(self, ctx, duration: int=None): + async def set_max_track_duration(self, ctx, duration: int = None): self.bot.settings.set(ctx.guild, 'duration.max', duration) duration = self.bot.settings.get(ctx.guild, 'duration.max') @@ -163,7 +162,7 @@ class Settings(commands.Cog): @commands.guild_only() @_set.command(name='dynamicmax') - async def set_track_duration_type(self, ctx, dynamic: bool=False): + async def set_track_duration_type(self, ctx, dynamic: bool = False): self.bot.settings.set(ctx.guild, 'duration.is_dynamic', dynamic) is_dynamic = self.bot.settings.get(ctx.guild, 'duration.is_dynamic') diff --git a/cogs/utils/RoxUtils.py b/cogs/utils/RoxUtils.py index 6df46da..f656abf 100644 --- a/cogs/utils/RoxUtils.py +++ b/cogs/utils/RoxUtils.py @@ -1,5 +1,4 @@ import aiohttp -import asyncio from bs4 import BeautifulSoup as bs4 session = aiohttp.ClientSession() @@ -8,10 +7,10 @@ session = aiohttp.ClientSession() class ThumbNailer(object): async def get_img(self, url): - async with session.get(url, timeout=30) as response: - assert response.status == 200 - html = await response.read() - return await ThumbNailer.__parse_result(html) + async with session.get(url, timeout=30) as response: + assert response.status == 200 + html = await response.read() + return await ThumbNailer.__parse_result(html) async def __parse_result(html): try: diff --git a/cogs/utils/alias.py b/cogs/utils/alias.py index 7004382..0bbdc25 100644 --- a/cogs/utils/alias.py +++ b/cogs/utils/alias.py @@ -1,4 +1,3 @@ -import discord import yaml from glob import glob @@ -12,6 +11,7 @@ Allows per guild aliases of subcommands without doing anything to discord.py its ways of doing this appreciated. """ + class Aliaser: def __init__(self, localization_folder, default_lang): self.localization_folder = path.realpath(localization_folder) @@ -100,7 +100,7 @@ class Aliaser: ctx.command = None return ctx ctx.view.undo() - ctx.invoker = invoker = ctx.view.buffer[ctx.view.index:ctx.view.end] + ctx.invoker = ctx.view.buffer[ctx.view.index:ctx.view.end] alias = ctx.view.get_word() command = self.convert_alias(ctx.locale, alias, alias) ctx.invoked_with = command @@ -142,11 +142,11 @@ class Aliaser: else: sub = ctx.bot.all_commands.get(subcmd, None) - # Translate any subsubcommands + # Translate any subsubcommands if sub and isinstance(sub, commands.GroupMixin): self.get_subcommand(ctx, sub, parents) - # Reset the view indexes + # Reset the view indexes view.index = idx view.previous = prev - return ctx \ No newline at end of file + return ctx diff --git a/cogs/utils/bot_version.py b/cogs/utils/bot_version.py new file mode 100644 index 0000000..ded25ec --- /dev/null +++ b/cogs/utils/bot_version.py @@ -0,0 +1 @@ +bot_version = "0.1.2" diff --git a/cogs/utils/checks.py b/cogs/utils/checks.py index 7f2a4cb..70506a5 100644 --- a/cogs/utils/checks.py +++ b/cogs/utils/checks.py @@ -37,8 +37,9 @@ def is_even(): def is_owner(): def predicate(ctx): is_owner = (ctx.message.author.id == 120970603556503552 or - ctx.message.author.id == 142212883512557569 or ctx.message.author.id == 212635519706726410 or - ctx.message.author.id == 170506717140877312) + ctx.message.author.id == 142212883512557569 or + ctx.message.author.id == 212635519706726410 or + ctx.message.author.id == 170506717140877312) return is_owner return commands.check(predicate) @@ -72,7 +73,7 @@ def is_DJ(ctx): return any([has_role_id(ctx, role_id) for role_id in dj_role_ids]) -def DJ_or(alone: bool=False, current: bool=False): +def DJ_or(alone: bool = False, current: bool = False): async def predicate(ctx): try: player = ctx.bot.lavalink.players.get(ctx.guild.id) diff --git a/cogs/utils/context.py b/cogs/utils/context.py index 632ba0c..61da689 100644 --- a/cogs/utils/context.py +++ b/cogs/utils/context.py @@ -1,9 +1,10 @@ from discord.ext import commands + class Context(commands.Context): def __init__(self, **kwargs): super().__init__(**kwargs) if self.message.guild: self.locale = self.bot.settings.get(self.message.guild, 'locale', 'default_locale') else: - self.locale = self.bot.settings.default_locale \ No newline at end of file + self.locale = self.bot.settings.default_locale diff --git a/cogs/utils/dict_utils.py b/cogs/utils/dict_utils.py index b3e6439..eab05c4 100644 --- a/cogs/utils/dict_utils.py +++ b/cogs/utils/dict_utils.py @@ -1,5 +1,6 @@ import collections + class SafeDict(dict): def __missing__(self, key): return '{' + key + '}' diff --git a/cogs/utils/localizer.py b/cogs/utils/localizer.py index a14e5b0..fcbe318 100644 --- a/cogs/utils/localizer.py +++ b/cogs/utils/localizer.py @@ -16,6 +16,7 @@ Localizer for bot kmatch = re.compile('({(?!_)([^{}]+)})') + class Localizer: def __init__(self, localization_folder, default_lang): self.localization_folder = path.realpath(localization_folder) @@ -31,7 +32,6 @@ class Localizer: for folder in glob(path.join(self.localization_folder, "*/")): folder_base = path.basename(path.dirname(folder)) self.localization_table[folder_base] = False - # loads all localizations def load_localizations(self): @@ -41,7 +41,8 @@ class Localizer: self.all_localizations = flatten(self.localization_table) for lang, d in self.localization_table.items(): - self.localization_table[lang] = Localizer._parse_localization_dictionary(self.localization_table[lang], self.all_localizations) + self.localization_table[lang] = Localizer._parse_localization_dictionary(self.localization_table[lang], + self.all_localizations) # internal function for loading a localization def _load_localization(self, lang): @@ -57,7 +58,7 @@ class Localizer: file_base = path.basename(file).split(".")[0] with open(file, "r", encoding='utf-8') as f: data = yaml.load(f, Loader=yaml.SafeLoader) - + l_table[file_base] = data for file in glob(path.join(self.localization_folder, lang, "*.txt")): @@ -66,14 +67,13 @@ class Localizer: content = f.read() l_table[file_base] = content - l_table = flatten(l_table) # parsing a few times to resolve all values for i in range(0, 5): l_table = Localizer._parse_localization_dictionary(l_table, l_table) - + self.localization_table[lang] = Localizer._parse_localization_dictionary(l_table, l_table) - + # parses and interpolates translation dictionary @staticmethod def _parse_localization_dictionary(d, lookup, prefix=None): @@ -89,7 +89,7 @@ class Localizer: def _replace_keys(value, prefix=None): for outer, inner in kmatch.findall(value): nstr = inner - if prefix is not None: + if prefix is not None: nstr = f'{prefix}.{inner}' nstr = f'{{{nstr}}}' nstr = nstr.replace(".", "/") @@ -101,11 +101,11 @@ class Localizer: d = SafeDict(d) value = Localizer._replace_keys(value, prefix) return value.format_map(d) - + # returns true if localization is currently loaded def isLoaded(self, lang): return self.localization_table.get(lang, False) - + def getAvaliableLocalizations(self): return self.localization_table.keys() @@ -114,25 +114,25 @@ class Localizer: lang = lang or self.default_lang if not self.isLoaded(lang): self._load_localization(lang) - + return self.localization_table.get(lang, {}).get(key.replace(".", "/")) - + # inserts translations into a string def format_str(self, s, lang=None, prefix=None, **kvpairs): lang = lang or self.default_lang if not self.isLoaded(lang): self._load_localization(lang) - + ns = Localizer._parse_localization_string(s, self.localization_table.get(lang, {}), prefix) ns = Localizer._parse_localization_string(ns, self.all_localizations, prefix) return ns.format_map(SafeDict(kvpairs)) - + # inserts translations into a values of a dictionary def format_dict(self, d, lang=None, prefix=None, **kvpairs): lang = lang or self.default_lang if not self.isLoaded(lang): self._load_localization(lang) - + nd = copy.deepcopy(d) cursorQueue = [nd] while cursorQueue: @@ -157,12 +157,12 @@ class LocalizerWrapper: self.localizer = localizer self.lang = lang self.prefix = prefix - + def format_str(self, s, **kvpairs): return self.localizer.format_str(s, self.lang, self.prefix, **kvpairs) def format_dict(self, d, **kvpairs): return self.localizer.format_dict(d, self.lang, self.prefix, **kvpairs) - + def format_embed(self, embed, **kvpairs): - return self.localizer.format_embed(embed, self.lang, self.prefix, **kvpairs) \ No newline at end of file + return self.localizer.format_embed(embed, self.lang, self.prefix, **kvpairs) diff --git a/cogs/utils/logger.py b/cogs/utils/logger.py index cbf1907..207f7ff 100644 --- a/cogs/utils/logger.py +++ b/cogs/utils/logger.py @@ -2,6 +2,7 @@ import logging.handlers import logging import os + class BotLogger(object): def __init__(self, debug=None, data_location=None): self.log_location = data_location @@ -23,9 +24,9 @@ class BotLogger(object): os.makedirs(self.log_location) print(f"Creating logging directory: {self.log_location}") if self.log_level: - print(os.path.abspath(self.log_location)) - file_logger = logging.handlers.RotatingFileHandler(f'{self.log_location}/bot.log', - mode='a', maxBytes=5000, encoding="UTF-8", delay=0, backupCount=5) + print(f"Logging directory: {self.log_location}") + file_logger = logging.handlers.RotatingFileHandler(f'{self.log_location}/bot.log', mode='a', + maxBytes=5000, encoding="UTF-8", delay=0, backupCount=5) file_logger.setLevel(self.log_level) file_logger.setFormatter(log_formatter) self.bot_logger.addHandler(file_logger) @@ -35,4 +36,3 @@ class BotLogger(object): console_handler.setFormatter(log_formatter) self.bot_logger.addHandler(console_handler) - diff --git a/cogs/utils/mixplayer.py b/cogs/utils/mixplayer.py index 4643add..0fbf9bb 100644 --- a/cogs/utils/mixplayer.py +++ b/cogs/utils/mixplayer.py @@ -1,14 +1,10 @@ -import time import lavalink from collections import OrderedDict, deque from itertools import cycle, islice, chain from random import shuffle -from lavalink.events import (TrackStartEvent, TrackStuckEvent, - TrackExceptionEvent, TrackEndEvent, - QueueEndEvent, PlayerUpdateEvent, - NodeChangedEvent) +from lavalink.events import TrackStartEvent, TrackStuckEvent, TrackExceptionEvent, TrackEndEvent, QueueEndEvent from lavalink import Node, DefaultPlayer, AudioTrack @@ -22,11 +18,9 @@ class MixPlayer(DefaultPlayer): self.skip_voters = set() self.boosted = False - def add(self, requester: int, track: dict, pos: int=None): + def add(self, requester: int, track: dict, pos: int = None): """ Adds a track to the queue. """ - return self.queue.add_track(requester, - AudioTrack.build(track, requester), - pos) + return self.queue.add_track(requester, AudioTrack.build(track, requester), pos) def add_next(self, requester: int, track: dict): """ Adds a track to beginning of the queue """ @@ -51,7 +45,7 @@ class MixPlayer(DefaultPlayer): """ Randomly reorders the queue of requester """ self.queue.shuffle_user_queue(requester) - def user_queue(self, user: int, dual: bool=False): + def user_queue(self, user: int, dual: bool = False): return self.queue.get_user_queue(user, dual) def global_queue(self): @@ -60,7 +54,7 @@ class MixPlayer(DefaultPlayer): def get_history(self): return self.queue.history - def queue_duration(self, include_current: bool=True): + def queue_duration(self, include_current: bool = True): duration = 0 for track in self.queue: duration += track.duration @@ -69,7 +63,7 @@ class MixPlayer(DefaultPlayer): return lavalink.Utils.format_time(duration + remaining) return lavalink.Utils.format_time(duration) - async def play(self, track: AudioTrack=None, start_time: int=0): + async def play(self, track: AudioTrack = None, start_time: int = 0): self.current = None self.last_update = 0 @@ -91,7 +85,7 @@ class MixPlayer(DefaultPlayer): track=track.track, startTime=start_time) await self.node._dispatch_event(TrackStartEvent(self, track)) - async def skip(self, pos: int=0): + async def skip(self, pos: int = 0): """ Plays the next track in the queue, if any. """ for i in range(pos): _ = self.queue.pop_first() @@ -125,7 +119,7 @@ class MixPlayer(DefaultPlayer): self.skip_voters.clear() await self.play() - async def bassboost(self, boost: bool=False): + async def bassboost(self, boost: bool = False): if boost: gains = [ (0, 0.15), @@ -161,7 +155,7 @@ class MixQueue: def __init__(self): self.queues = OrderedDict() self.priority_queue = [] - self._history = deque(maxlen=11) # 10 + current + self._history = deque(maxlen=11) # 10 + current def __str__(self): tmp = '' @@ -193,7 +187,7 @@ class MixQueue: self.priority_queue = [] # if dual is true also returns global positions of tracks - def get_user_queue(self, requester: int, dual: bool=False): + def get_user_queue(self, requester: int, dual: bool = False): queue = self.queues.get(requester, []) if dual and queue: pos = [self._loc_to_glob(requester, i) for i in range(len(queue))] @@ -215,7 +209,7 @@ class MixQueue: except KeyError: pass - def add_track(self, requester: int, track: AudioTrack, pos: int=None): + def add_track(self, requester: int, track: AudioTrack, pos: int = None): user_queue = self.queues.get(requester) if user_queue is None: self.queues[requester] = [track] @@ -323,4 +317,3 @@ class MixQueue: @property def history(self): return list(reversed(self._history)) - diff --git a/cogs/utils/paginator.py b/cogs/utils/paginator.py index 25b4df3..4300ff1 100644 --- a/cogs/utils/paginator.py +++ b/cogs/utils/paginator.py @@ -21,10 +21,8 @@ class BasePaginator: self.close_page() if localizer_str: for i, page in enumerate(self._pages, start=1): - page.set_footer(text=localizer.format_str(localizer_str, - _current=i, - _total=len(self._pages), - **kvpairs)) + page.set_footer(text=localizer.format_str(localizer_str, _current=i, + _total=len(self._pages), **kvpairs)) else: for i, page in enumerate(self._pages, start=1): page.set_footer(text=f"{i}/{len(self._pages)}") @@ -110,31 +108,26 @@ class FieldPaginator(BasePaginator): class QueuePaginator(TextPaginator): - def __init__(self, localizer, queue, color, user_name: str=None): + def __init__(self, localizer, queue, color, user_name: str = None): self.localizer = localizer self.queue = queue if user_name is None: - title = localizer.format_str("{queue.length}", - _length=len(queue)) + title = localizer.format_str("{queue.length}", _length=len(queue)) else: - title = localizer.format_str("{queue.userqueue}", - _user=user_name, - _length=len(queue)) - + title = localizer.format_str("{queue.userqueue}", _user=user_name, _length=len(queue)) + super().__init__(max_lines=10, **{"color": color, "title": title}) for index, temp in enumerate(queue): if user_name is None: track = temp - queued_track = localizer.format_str("{queue.globaltrack}", _index=index+1, - _title=track.title, _uri=track.uri, - _user_id=track.requester) + queued_track = localizer.format_str("{queue.globaltrack}", _index=index+1, _title=track.title, + _uri=track.uri, _user_id=track.requester) else: track, globpos = temp - queued_track = localizer.format_str("{queue.usertrack}", _index=index+1, - _globalindex=globpos+1, _title=track.title, - _uri=track.uri) + queued_track = localizer.format_str("{queue.usertrack}", _index=index+1, _globalindex=globpos+1, + _title=track.title, _uri=track.uri) self.add_line(queued_track) self.add_page_indicator(self.localizer, "{queue.pageindicator}") @@ -286,14 +279,14 @@ class Scroller: await self.message.delete() if self.clear_command: await self.cmdmsg.delete() - except: + except discord.Forbidden: pass finally: break try: await self.message.remove_reaction(reaction, user) - except: + except discord.Forbidden: pass - await self.match() \ No newline at end of file + await self.match() diff --git a/cogs/utils/settingsmanager.py b/cogs/utils/settingsmanager.py index a6eed76..2a25a22 100644 --- a/cogs/utils/settingsmanager.py +++ b/cogs/utils/settingsmanager.py @@ -1,11 +1,12 @@ import os import codecs -import locale +import locale as localee import yaml + class Settings: - def __init__(self, **default_settings): - self._DATA_PATH = 'data/bot/' + def __init__(self, datadir, **default_settings): + self._DATA_PATH = f"{datadir}/bot/" self._SETTINGS_PATH = self._DATA_PATH + 'settings.yaml' self.default_prefix = default_settings["prefix"] @@ -15,7 +16,7 @@ class Settings: self.default_is_dynamic = default_settings["dynamic max duration"] if not self.default_locale: - locale, codepage = locale.getlocale() + locale, codepage = localee.getlocale() default_locale, default_codepage = locale.getdefaultlocale() self.default_locale = locale or default_locale @@ -35,7 +36,7 @@ class Settings: if val is None: try: d.pop(key) - except: + except KeyError: pass else: d[key] = val diff --git a/localization/en_en/misc.yaml b/localization/en_en/misc.yaml index ac183c7..221cfac 100644 --- a/localization/en_en/misc.yaml +++ b/localization/en_en/misc.yaml @@ -6,6 +6,7 @@ bot: **Python:** [{_python_v}](https://www.python.org/) **Discord.py:** [{_discord_v}](https://github.com/Rapptz/discord.py/tree/rewrite) **Lavalink:** [{_lavalink_v}](https://github.com/Devoxin/Lavalink.py) + **ShiteMusicBot:** {_bot_v} stattext: |- **Guilds:** {_guilds} **Users:** {_members} diff --git a/localization/nb_no/misc.yaml b/localization/nb_no/misc.yaml index e17b8d4..73b0765 100644 --- a/localization/nb_no/misc.yaml +++ b/localization/nb_no/misc.yaml @@ -6,6 +6,7 @@ bot: **Python:** [{_python_v}](https://www.python.org/) **Discord.py:** [{_discord_v}](https://github.com/Rapptz/discord.py/tree/rewrite) **Lavalink:** [{_lavalink_v}](https://github.com/Devoxin/Lavalink.py) + **ShiteMusicBot:** {_bot_v} stattext: |- **Tenarar:** {_guilds} **Brukere:** {_members}