From 8b0e6cf6610b2495768bddee7b3ba7d18078631e Mon Sep 17 00:00:00 2001 From: Myned Date: Sun, 24 Sep 2017 03:34:08 -0400 Subject: [PATCH] Initial commit --- .gitignore | 110 ++++++++++ __init__.py | 0 src/main/__init__.py | 0 src/main/cogs/__init__.py | 0 src/main/cogs/booru.py | 421 ++++++++++++++++++++++++++++++++++++ src/main/cogs/info.py | 32 +++ src/main/cogs/tools.py | 58 +++++ src/main/misc/__init__.py | 0 src/main/misc/checks.py | 45 ++++ src/main/misc/exceptions.py | 24 ++ src/main/run.py | 81 +++++++ src/main/utils/__init__.py | 0 src/main/utils/formatter.py | 31 +++ src/main/utils/scraper.py | 33 +++ 14 files changed, 835 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 src/main/__init__.py create mode 100644 src/main/cogs/__init__.py create mode 100644 src/main/cogs/booru.py create mode 100644 src/main/cogs/info.py create mode 100644 src/main/cogs/tools.py create mode 100644 src/main/misc/__init__.py create mode 100644 src/main/misc/checks.py create mode 100644 src/main/misc/exceptions.py create mode 100644 src/main/run.py create mode 100644 src/main/utils/__init__.py create mode 100644 src/main/utils/formatter.py create mode 100644 src/main/utils/scraper.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..974375e --- /dev/null +++ b/.gitignore @@ -0,0 +1,110 @@ +# Custom +*.json +*.pyo +*.pyc +*.DS_Store + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/__init__.py b/src/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/cogs/__init__.py b/src/main/cogs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/cogs/booru.py b/src/main/cogs/booru.py new file mode 100644 index 0000000..a9faf42 --- /dev/null +++ b/src/main/cogs/booru.py @@ -0,0 +1,421 @@ +import asyncio +import discord +import json +import requests +import traceback +from discord import reaction +from discord.ext import commands +from discord.ext.commands import errors +from misc import checks +from misc import exceptions as exc +from utils import formatter, scraper + +try: + with open('global_blacklist.json') as infile: + global_blacklist = json.load(infile) + print('\"global_blacklist.json\" loaded.') +except FileNotFoundError: + print('Blacklist file not found: \"global_blacklist.json\" created.') + with open('global_blacklist.json', 'w+') as iofile: + json.dump([], iofile, indent=4, sort_keys=True) + iofile.seek(0) + global_blacklist = json.load(iofile) +try: + with open('guild_blacklist.json') as infile: + guild_blacklist = json.load(infile) + print('\"guild_blacklist.json\" loaded.') +except FileNotFoundError: + print('Blacklist file not found: \"guild_blacklist.json\" created.') + with open('guild_blacklist.json', 'w+') as iofile: + json.dump({}, iofile, indent=4, sort_keys=True) + iofile.seek(0) + guild_blacklist = json.load(iofile) +try: + with open('user_blacklist.json') as infile: + user_blacklist = json.load(infile) + print('\"user_blacklist.json\" loaded.') +except FileNotFoundError: + print('Blacklist file not found: \"user_blacklist.json\" created.') + with open('user_blacklist.json', 'w+') as iofile: + json.dump({}, iofile, indent=4, sort_keys=True) + iofile.seek(0) + user_blacklist = json.load(iofile) + +last_command = {} + +class MsG: + + def __init__(self, bot): + self.bot = bot + + # Creates reaction-based paginator for linked pools + @commands.command(brief='e621/e926 Pool selector', description='e621/e926 | NSFW/SFW\nShow pools in a page format', hidden=True) + @checks.del_ctx() + async def pool(self, ctx, url): + pool_urls = [] + + def check_right(reaction, user): + return user == ctx.message.author and str(reaction.emoji) == '➡️' + def check_left(reaction, user): + return user == ctx.message.author and str(reaction.emoji) == '⬅️' + + try: + pool = scraper.find_pool(url) + for link in pool: + pool_urls.append(scraper.find_image_url('https://e621.net' + link)) + except exc.PostError: + await ctx.send('❌ ' + ctx.message.author.mention + ' **No pool found.**') + except exc.ImageError: + await ctx.send('❌ ' + ctx.message.author.mention + ' **No image found.**') + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) + + # Reverse image searches a linked image using the public iqdb + @commands.command(name='reverse', aliases=['rev', 'ris'], brief='e621 Reverse image search', description='e621 | NSFW\nReverse-search an image with given URL') + @checks.del_ctx() + async def reverse_image_search(self, ctx, url): + try: + await ctx.trigger_typing() + await ctx.send('✅ ' + ctx.message.author.mention + ' **Probable match:**\n' + scraper.check_match('http://iqdb.harry.lu/?url=' + url)) + except exc.MatchError: + await ctx.send('❌ ' + ctx.message.author.mention + ' **No probable match.**') + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) + + # Searches for and returns images from e621.net given tags when not blacklisted + @commands.command(aliases=['e6', '6'], brief='e621 | NSFW', description='e621 | NSFW\nTag-based search for e621.net\n\nYou can only search 5 tags and 6 images at once for now.\ne6 [tags...] ([# of images])') + @checks.del_ctx() + @checks.is_nsfw() + async def e621(self, ctx, *args): + global global_blacklist, guild_blacklist, user_blacklist + args = list(args) + try: + await ctx.trigger_typing() + await self.check_send_urls(ctx, 'e621', args) + except exc.TagBlacklisted as e: + await ctx.send('❌ `' + str(e) + '` **blacklisted.**', delete_after=10) + except exc.BoundsError as e: + await ctx.send('❌ `' + str(e) + '` **out of bounds.**', delete_after=10) + except exc.TagBoundsError as e: + await ctx.send('❌ `' + str(e) + '` **out of bounds.** Tags limited to 5, currently.', delete_after=10) + except Exception: + await ctx.send(exc.base) + traceback.print_exc() + + @e621.error + async def e621_error(self, ctx, error): + if isinstance(error, errors.CheckFailure): + return await ctx.send('❌ <#' + str(ctx.message.channel.id) + '> **is not an NSFW channel.**', delete_after=10) + + # Searches for and returns images from e926.net given tags when not blacklisted + @commands.command(aliases=['e9', '9'], brief='e926 | SFW', description='e926 | SFW\nTag-based search for e926.net\n\nYou can only search 5 tags and 6 images at once for now.\ne9 [tags...] ([# of images])') + @checks.del_ctx() + async def e926(self, ctx, *args): + global global_blacklist, guild_blacklist, user_blacklist + args = list(args) + try: + await ctx.trigger_typing() + await self.check_send_urls(ctx, 'e926', args) + except exc.TagBlacklisted as e: + await ctx.send('❌ `' + str(e) + '` **blacklisted.**', delete_after=10) + except exc.BoundsError as e: + await ctx.send('❌ `' + str(e) + '` **out of bounds.**', delete_after=10) + except exc.TagBoundsError as e: + await ctx.send('❌ `' + str(e) + '` **out of bounds.** Tags limited to 5, currently.', delete_after=10) + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) + + # Messy code that checks image limit and tags in blacklists + async def check_send_urls(self, ctx, booru, args): + global global_blacklist, guild_blacklist, user_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + channel = ctx.message.channel + user = ctx.message.author + urls = [] + limit = 1 + # Checks if tags are in the file blacklists + if args: + for tag in args: + if tag == 'swf' or tag == 'webm' or tag in global_blacklist or tag in guild_blacklist.get(str(guild.id), {}).get(str(channel.id), []) or tag in user_blacklist.get(str(user.id), []): + raise exc.TagBlacklisted(tag) + if len(args) > 5: + raise exc.TagBoundsError(formatter.tostring(args[5:])) + # Checks for, defines, and removes limit from end of args + if args and len(args[-1]) == 1: + if int(args[-1]) <= 6 and int(args[-1]) >= 1: + limit = int(args[-1]) + args.pop() + else: + raise exc.BoundsError(args[-1]) + # Checks for blacklisted tags in endpoint blacklists - try/except is for continuing the parent loop + while len(urls) < limit: + request = requests.get('https://' + booru + '.net/post/index.json?limit=6&tags=order:random' + formatter.tostring_commas(args)).json() + for post in request: + if 'swf' in post['file_ext'] or 'webm' in post['file_ext']: + continue + try: + for tag in global_blacklist: + if tag in post['tags']: + raise exc.Continue + for tag in guild_blacklist.get(str(guild.id), {}).get(str(channel.id), []): + if tag in post['tags']: + raise exc.Continue + for tag in user_blacklist.get(str(user.id), []): + if tag in post['tags']: + raise exc.Continue + except exc.Continue: + continue + if post['file_url'] not in urls: + urls.append(post['file_url']) + if len(urls) == limit: + break + for url in urls: + await ctx.send('`' + formatter.tostring(args) + '`\n' + url) + + # Umbrella command structure to manage global, channel, and user blacklists + @commands.group(aliases=['bl', 'b'], brief='Manage blacklists', description='Blacklist base command for managing blacklists\n\n`bl get [blacklist]` to show a blacklist\n`bl set [blacklist] [tags]` to replace a blacklist\n`bl clear [blacklist]` to clear a blacklist\n`bl add [blacklist] [tags]` to add tags to a blacklist\n`bl remove [blacklist] [tags]` to remove tags from a blacklist', usage='[flag] [blacklist] ([tags])') + @checks.del_ctx() + async def blacklist(self, ctx): + if ctx.invoked_subcommand is None: + await ctx.send('❌ **Use a flag to manage blacklists.**\n*Type* `' + ctx.prefix + 'help bl` *for more info.*', delete_after=10) + + @blacklist.error + async def blacklist_error(self, ctx, error): + if isinstance(error, commands.CheckFailure): + return await ctx.send('❌ **Insufficient permissions.**', delete_after=10) + if isinstance(error, exc.TagExists): + return await ctx.send('❌ `' + str(exc.TagExists) + '` **already in blacklist.**', delete_after=10) + if isinstance(error, exc.TagError): + return await ctx.send('❌ `' + str(exc.TagError) + '` **not in blacklist.**', delete_after=10) + if isinstance(error, KeyError): + return await ctx.send('❌ **Blacklist does not exist.**', delete_after=10) + + @blacklist.command(name='update', aliases=['upd', 'up']) + async def _update_blacklists(self, ctx): + global global_blacklist, guild_blacklist, user_blacklist + with open('global_blacklist.json', 'w') as outfile: + json.dump(global_blacklist, outfile, indent=4, sort_keys=True) + with open('guild_blacklist.json', 'w') as outfile: + json.dump(guild_blacklist, outfile, indent=4, sort_keys=True) + with open('user_blacklist.json', 'w') as outfile: + json.dump(user_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ **Blacklists updated.**') + + @blacklist.group(name='get', aliases=['g']) + async def _get_blacklist(self, ctx): + if ctx.invoked_subcommand is None: + await ctx.send('❌ **Invalid blacklist.**') + + @_get_blacklist.command(name='global', aliases=['gl', 'g']) + async def __get_global_blacklist(self, ctx): + global global_blacklist + await ctx.send('🚫 **Global blacklist:**\n```' + formatter.tostring(global_blacklist) + '```') + @_get_blacklist.command(name='channel', aliases=['ch', 'c']) + async def __get_channel_blacklist(self, ctx): + global guild_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + channel = ctx.message.channel + await ctx.send('🚫 <#' + str(channel.id) + '> **blacklist:**\n```' + formatter.tostring(guild_blacklist.get(str(guild.id), {}).get(str(channel.id), [])) + '```') + @_get_blacklist.command(name='me', aliases=['m']) + async def __get_user_blacklist(self, ctx): + global user_blacklist + user = ctx.message.author + await ctx.send('🚫 ' + user.mention + '**\'s blacklist:**\n```' + formatter.tostring(user_blacklist.get(str(user.id), [])) + '```', delete_after=10) + @_get_blacklist.command(name='here', aliases=['h']) + async def __get_here_blacklists(self, ctx): + global global_blacklist, guild_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + channel = ctx.message.channel + await ctx.send('🚫 **__Blacklisted:__**\n\n**Global:**\n```' + formatter.tostring(global_blacklist) + '```\n**<#' + str(channel.id) + '>:**\n```' + formatter.tostring(guild_blacklist.get(str(guild.id), {}).get(str(channel.id), [])) + '```') + @_get_blacklist.group(name='all', aliases=['a']) + async def __get_all_blacklists(self, ctx): + if ctx.invoked_subcommand is None: + await ctx.send('❌ **Invalid blacklist.**') + + @__get_all_blacklists.command(name='guild', aliases=['g']) + @commands.has_permissions(manage_channels=True) + async def ___get_all_guild_blacklists(self, ctx): + global guild_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + await ctx.send('🚫 **__' + guild.name + ' blacklists:__**\n\n' + formatter.dict_tostring(guild_blacklist.get(str(guild.id), {}))) + @__get_all_blacklists.command(name='user', aliases=['u', 'member', 'm']) + @commands.is_owner() + async def ___get_all_user_blacklists(self, ctx): + global user_blacklist + await ctx.send('🚫 **__User blacklists:__**\n\n' + formatter.dict_tostring(user_blacklist)) + + @blacklist.group(name='add', aliases=['a']) + async def _add_tags(self, ctx): + if ctx.invoked_subcommand is None: + await ctx.send('❌ **Invalid blacklist.**') + + @_add_tags.command(name='global', aliases=['gl', 'g']) + @commands.is_owner() + async def __add_global_tags(self, ctx, *tags): + global global_blacklist + for tag in tags: + if tag in global_blacklist: + raise exc.TagExists(tag) + global_blacklist.extend(tags) + with open('global_blacklist.json', 'w') as outfile: + json.dump(global_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ **Added to global blacklist:**\n```' + formatter.tostring(tags) + '```', delete_after=5) + @_add_tags.command(name='channel', aliases=['ch', 'c']) + @commands.has_permissions(manage_channels=True) + async def __add_channel_tags(self, ctx, *tags): + global guild_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + channel = ctx.message.channel + for tag in tags: + if tag in guild_blacklist.get(str(guild.id), {}).get(str(channel.id), []): + raise exc.TagExists(tag) + guild_blacklist.setdefault(str(guild.id), {}).setdefault(str(channel.id), []).extend(tags) + with open('guild_blacklist.json', 'w') as outfile: + json.dump(guild_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ **Added to** <#' + str(channel.id) + '> **blacklist:**\n```' + formatter.tostring(tags) + '```', delete_after=5) + @_add_tags.command(name='me', aliases=['m']) + async def __add_user_tags(self, ctx, *tags): + global user_blacklist + user = ctx.message.author + for tag in tags: + if tag in user_blacklist.get(str(user.id), []): + raise exc.TagExists(tag) + user_blacklist.setdefault(str(user.id), []).extend(tags) + with open('user_blacklist.json', 'w') as outfile: + json.dump(user_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ ' + user.mention + ' **added:**\n```' + formatter.tostring(tags) + '```', delete_after=5) + + @blacklist.group(name='remove', aliases=['rm', 'r']) + async def _remove_tags(self, ctx): + if ctx.invoked_subcommand is None: + await ctx.send('❌ **Invalid blacklist.**') + + @_remove_tags.command(name='global', aliases=['gl', 'g']) + @commands.is_owner() + async def __remove_global_tags(self, ctx, *tags): + global global_blacklist + for tag in tags: + if tag in global_blacklist: + global_blacklist.remove(tag) + else: + raise exc.TagError(tag) + with open('global_blacklist.json', 'w') as outfile: + json.dump(global_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ **Removed from global blacklist:**\n```' + formatter.tostring(tags) + '```', delete_after=5) + @_remove_tags.command(name='channel', aliases=['ch', 'c']) + @commands.has_permissions(manage_channels=True) + async def __remove_channel_tags(self, ctx, *tags): + global guild_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + channel = ctx.message.channel + for tag in tags: + if tag in guild_blacklist.get(str(guild.id), {}).get(str(channel.id), []): + guild_blacklist.get(str(guild.id), {})[str(channel.id)].remove(tag) + else: + raise exc.TagError(tag) + with open('guild_blacklist.json', 'w') as outfile: + json.dump(guild_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ **Removed from** <#' + str(channel.id) + '> **blacklist:**\n```' + formatter.tostring(tags) + '```', delete_after=5) + @_remove_tags.command(name='me', aliases=['m']) + async def __remove_user_tags(self, ctx, *tags): + global user_blacklist + user = ctx.message.author + for tag in tags: + if tag in user_blacklist.get(str(user.id), []): + user_blacklist.get[str(user.id)].remove(tag) + else: + raise exc.TagError(tag) + with open('user_blacklist.json', 'w') as outfile: + json.dump(user_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ ' + user.mention + ' **removed:**\n```' + formatter.tostring(tags) + '```', delete_after=5) + + @blacklist.group(name='set', aliases=['s']) + async def _set_blacklist(self, ctx): + if ctx.invoked_subcommand is None: + await ctx.send('❌ **Invalid blacklist.**') + + @_set_blacklist.command(name='global', aliases=['gl', 'g']) + @commands.is_owner() + async def __set_global_blacklist(self, ctx, *tags): + global global_blacklist + global_blacklist = tags[:] + with open('global_blacklist.json', 'w') as outfile: + json.dump(global_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ **Global blacklist set to:**\n```' + formatter.tostring(global_blacklist) + '```', delete_after=10) + @_set_blacklist.command(name='channel', aliases=['ch', 'c']) + @commands.has_permissions(manage_channels=True) + async def __set_channel_blacklist(self, ctx, *tags): + global guild_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + channel = ctx.message.channel + guild_blacklist.setdefault(str(guild.id), {})[str(channel.id)] = tags[:] + with open('guild_blacklist.json', 'w') as outfile: + json.dump(guild_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ <#' + str(channel.id) + '> **blacklist set to:**\n```' + formatter.tostring(guild_blacklist.get(str(guild.id), {}).get(str(channel.id), [])) + '```', delete_after=10) + @_set_blacklist.command(name='me', aliases=['m']) + async def __set_user_blacklist(self, ctx, *tags): + global user_blacklist + user = ctx.message.author + user_blacklist[str(user.id)] = tags[:] + with open('user_blacklist.json', 'w') as outfile: + json.dump(user_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ ' + user.mention + '**\'s blacklist set to:**\n```' + formatter.tostring(user_blacklist.get(str(user.id), [])) + '```', delete_after=10) + + @blacklist.group(name='clear', aliases=['cl', 'c']) + async def _clear_blacklist(self, ctx): + if ctx.invoked_subcommand is None: + await ctx.send('❌ **Invalid blacklist.**') + + @_clear_blacklist.command(name='global', aliases=['gl', 'g']) + @commands.is_owner() + async def __clear_global_blacklist(self, ctx): + global global_blacklist + global_blacklist = [] + with open('global_blacklist.json', 'w') as outfile: + json.dump(global_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ **Global blacklist cleared.**', delete_after=5) + @_clear_blacklist.command(name='channel', aliases=['ch', 'c']) + @commands.has_permissions(manage_channels=True) + async def __clear_channel_blacklist(self, ctx): + global guild_blacklist + if isinstance(ctx.message.guild, discord.Guild): + guild = ctx.message.guild + else: + guild = ctx.message.channel + channel = ctx.message.channel + guild_blacklist.get(str(guild.id), {})[str(channel.id)] = [] + with open('guild_blacklist.json', 'w') as outfile: + json.dump(guild_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ <#' + str(channel.id) + '> **blacklist cleared.**', delete_after=5) + @_clear_blacklist.command(name='me', aliases=['m']) + async def __clear_user_blacklist(self, ctx): + global user_blacklist + user = ctx.message.author + user_blacklist[str(user.id)] = [] + with open('user_blacklist.json', 'w') as outfile: + json.dump(user_blacklist, outfile, indent=4, sort_keys=True) + await ctx.send('✅ ' + user.mention + '**\'s blacklist cleared.**', delete_after=5) diff --git a/src/main/cogs/info.py b/src/main/cogs/info.py new file mode 100644 index 0000000..d86435b --- /dev/null +++ b/src/main/cogs/info.py @@ -0,0 +1,32 @@ +import asyncio +import discord +import traceback +from discord.ext import commands +from misc import exceptions as exc + + +class Info: + + def __init__(self, bot): + self.bot = bot + + @commands.group(name='info', aliases=['i']) + async def info(self, ctx): + if invoked_subcommand is None: + await ctx.send('BOT INFO') + + @info.command(aliases=['g', 'server', 's'], brief='Provides info about a guild', hidden=True) + async def guild(self, ctx): + try: + guild = '' + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) + + @info.command(aliases=['u', 'member', 'm'], brief='Provides info about a user', hidden=True) + async def user(self, ctx): + try: + user = '' + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) diff --git a/src/main/cogs/tools.py b/src/main/cogs/tools.py new file mode 100644 index 0000000..93ccf0c --- /dev/null +++ b/src/main/cogs/tools.py @@ -0,0 +1,58 @@ +import asyncio +import discord +import traceback +from discord.ext import commands +from cogs import booru +from misc import checks +from misc import exceptions as exc +from utils import formatter + +class Utils: + + def __init__(self, bot): + self.bot = bot + + def last(): + pass + + @commands.command(name='last', aliases=['l', ','], brief='Reinvokes last command', description='Reinvokes previous command executed', hidden=True) + async def last_command(self, ctx): + try: + # await ctx.invoke(command, args) + await ctx.send('`' + booru.last_command[ctx.message.author.id] + '`') + except Exception: + await ctx.send(exceptions.base) + traceback.print_exc(limit=1) + + # [prefix]ping -> Pong! + @commands.command(aliases=['p'], brief='Pong!', description='Returns latency from bot to Discord servers, not to user') + @checks.del_ctx() + async def ping(self, ctx): + try: + await ctx.send(ctx.message.author.mention + ' 🏓 `' + str(int(self.bot.latency * 1000)) + 'ms`', delete_after=5) + except Exception: + await ctx.send(exceptions.base) + traceback.print_exc(limit=1) + + @commands.command(aliases=['pre'], brief='List bot prefixes', description='Shows all used prefixes') + @checks.del_ctx() + async def prefix(self, ctx): + try: + await ctx.send('**Prefix:** `,` or ' + ctx.me.mention) + except Exception: + await ctx.send(exceptions.base) + traceback.print_exc(limit=1) + + @commands.group(name=',send', aliases=[',s'], hidden=True) + @commands.is_owner() + async def send(self, ctx): + pass + + @send.command(name='guild', aliases=['g', 'server', 's']) + @checks.del_ctx() + async def send_guild(self, ctx, guild, channel, *message): + await discord.utils.get(self.bot.get_all_channels(), guild__name=guild, name=channel).send(formatter.tostring(message)) + @send.command(name='user', aliases=['u', 'member', 'm']) + @checks.del_ctx() + async def send_user(self, ctx, user, *message): + await discord.utils.get(self.bot.get_all_members(), id=int(user)).send(formatter.tostring(message)) diff --git a/src/main/misc/__init__.py b/src/main/misc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/misc/checks.py b/src/main/misc/checks.py new file mode 100644 index 0000000..7376cc6 --- /dev/null +++ b/src/main/misc/checks.py @@ -0,0 +1,45 @@ +import asyncio +import discord +import json +import traceback +from discord.ext import commands +from discord.ext.commands import errors + +with open('config.json') as infile: + config = json.load(infile) + +owner_id = config['owner_id'] + +def is_owner(): + async def predicate(ctx): + return ctx.message.author.id == owner_id + return commands.check(predicate) +def is_admin(): + def predicate(ctx): + return ctx.message.author.guild_permissions.administrator + return commands.check(predicate) +def is_mod(): + def predicate(ctx): + return ctx.message.author.guild_permissions.ban_members + return commands.check(predicate) + +def owner(ctx): + return ctx.message.author.id == owner_id +def admin(ctx): + return ctx.message.author.guild_permissions.administrator +def mod(ctx): + return ctx.message.author.guild_permissions.ban_members + +def is_nsfw(): + def predicate(ctx): + if isinstance(ctx.message.channel, discord.TextChannel): + return ctx.message.channel.is_nsfw() + return True + return commands.check(predicate) + +def del_ctx(): + async def predicate(ctx): + if isinstance(ctx.message.channel, discord.TextChannel): + await ctx.message.delete() + return True + return commands.check(predicate) diff --git a/src/main/misc/exceptions.py b/src/main/misc/exceptions.py new file mode 100644 index 0000000..a56802a --- /dev/null +++ b/src/main/misc/exceptions.py @@ -0,0 +1,24 @@ +base = '‼️ **An internal error has occurred.** Please notify my master! 🐺' + +class PostError(Exception): + pass +class ImageError(Exception): + pass +class MatchError(Exception): + pass +class TagBlacklisted(Exception): + pass +class BoundsError(Exception): + pass +class TagBoundsError(Exception): + pass +class TagExists(Exception): + pass +class TagError(Exception): + pass +class FlagError(Exception): + pass +class BlacklistError(Exception): + pass +class Continue(Exception): + pass diff --git a/src/main/run.py b/src/main/run.py new file mode 100644 index 0000000..388e229 --- /dev/null +++ b/src/main/run.py @@ -0,0 +1,81 @@ +import asyncio +import discord +import json +import traceback +from discord import utils +from discord.ext import commands +from cogs import booru, info, tools +from misc import checks +from misc import exceptions as exc + +try: + with open('config.json') as infile: + config = json.load(infile) + print('\"config.json\" loaded.') +except FileNotFoundError: + with open('config.json', 'w') as outfile: + json.dump({'client_id': 'int', 'owner_id': 'int', 'permissions': 'int', 'shutdown_channel': 'int', 'startup_channel': 'int', + 'token': 'str'}, outfile, indent=4, sort_keys=True) + raise FileNotFoundError('Config file not found: \"config.json\" created with abstract values. Restart \"run.py\" with correct values.') + +bot = commands.Bot(command_prefix=commands.when_mentioned_or(','), description='Experimental booru bot') + +# Send and print ready message to #testing and console after logon +@bot.event +async def on_ready(): + await bot.get_channel(config['startup_channel']).send('Hello how are? **Have day.** 🌈\n[STARTUP-INFO]') + print('Connected.') + print('Username: ' + bot.user.name) + print('-------') + +# Close connection to Discord - immediate offline +@bot.command(name=',die', aliases=[',d', ',close', ',kill'], brief='Kills the bot', description='BOT OWNER ONLY\nCloses the connections to Discord', hidden=True) +@checks.del_ctx() +@commands.is_owner() +async def die(ctx): + try: + await ctx.send('Am go bye. **Have night.** 💤') + # await bot.get_channel(config['shutdown_channel']).send('[SHUTDOWN-INFO]') + await bot.close() + print('-------') + print('Closed.') + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) + +# Invite bot to bot owner's server +@bot.command(name=',invite', aliases=[',inv', ',link'], brief='Invite the bot', description='BOT OWNER ONLY\nInvite the bot to a server (Requires admin)', hidden=True) +@checks.del_ctx() +@commands.is_owner() +async def invite(ctx): + try: + await ctx.send('🔗 ' + utils.oauth_url(config['client_id'], permissions=config['permissions'], guild=ctx.message.guild)) + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) + +@bot.command(brief='[IN TESTING]', description='[IN TESTING]', hidden=True) +async def hi(ctx): + try: + hello = 'Hello, ' + ctx.message.author.mention + '.' + if ctx.message.author.id == checks.owner_id: + hello += '.. ***Master.*** uwu' + elif ctx.message.author.guild_permissions.administrator: + hello = hello[:7] + '**Admin** ' + hello[7:] + elif ctx.message.author.guild_permissions.ban_members: + hello = hello[:7] + '**Mod** ' + hello[7:] + await ctx.send(hello) + except Exception: + await ctx.send(exc.base) + traceback.print_exc(limit=1) + +@bot.command(hidden=True) +@checks.del_ctx() +async def test(ctx): + pass + +bot.add_cog(info.Info(bot)) +bot.add_cog(tools.Utils(bot)) +bot.add_cog(booru.MsG(bot)) + +bot.run(config['token']) diff --git a/src/main/utils/__init__.py b/src/main/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/utils/formatter.py b/src/main/utils/formatter.py new file mode 100644 index 0000000..faa36c6 --- /dev/null +++ b/src/main/utils/formatter.py @@ -0,0 +1,31 @@ +def tostring(i): + o = ' ' + if i: + for v in i: + o += v + ' ' + o = o[1:-1] + return o + +def tostring_commas(i): + if i: + o = ',' + for v in i: + o += v + ',' + return o[:-1] + return '' + +def dict_tostring(i): + o = '' + if i: + for k, v in i.items(): + o += '**' + k + ':** `' + tostring(v) + '`\n' + return o + +def dictelem_tostring(i): + o = '' + if i: + for dic, elem in i.items(): + o += '**__' + dic + '__**\n' + for k, v in elem.items(): + o += '***' + k + ':*** `' + tostring(v) + '`\n' + return o diff --git a/src/main/utils/scraper.py b/src/main/utils/scraper.py new file mode 100644 index 0000000..6e3bb2e --- /dev/null +++ b/src/main/utils/scraper.py @@ -0,0 +1,33 @@ +import requests +from bs4 import BeautifulSoup +from lxml import html +from misc import exceptions as exc + +def check_match(url): + r = requests.get(url) + soup = BeautifulSoup(r.content, 'html.parser') + value = soup.find_all('a')[1].get('href') + if value != '#': + return value + else: + raise exc.MatchError(value) + +def find_pool(url): + r = requests.get(url) + tree = html.fromstring(r.content) + post = tree.xpath('/html/body/div[@id="content"]/div[@id="pool-show"]/div[@style="margin-top: 2em;"]/span/a/@href') + print(post) + if post: + return post + else: + raise exc.PostError(post) + +def find_image_url(url): + r = requests.get(url) + tree = html.fromstring(r.content) + image_url = tree.xpath('/html/body/div[@id="content"]/div[@id="post-view"]/div[@class="content"]/div/img/@src') + print(image_url) + if image_url: + return image_url + else: + raise exc.ImageError(image_url)