diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9818865 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +.idea +errors +__pycache__ +notsobot_3.log \ No newline at end of file diff --git a/.idea/libraries/R_User_Library.xml b/.idea/libraries/R_User_Library.xml new file mode 100644 index 0000000..71f5ff7 --- /dev/null +++ b/.idea/libraries/R_User_Library.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 20ba448..cdfbc5b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ **Bot Invite**: `https://discordapp.com/oauth2/authorize?client_id=170903265565736960&scope=bot&permissions=8` +**Commands**: https://mods.nyc/help/ + I won't mind if you take some code to learn or improve but please don't be a skid. Feel free to ask me questions, NotSoSuper#8800 on Discord. diff --git a/bot.py b/bot.py index 023836e..626a00b 100644 --- a/bot.py +++ b/bot.py @@ -9,10 +9,12 @@ from utils import checks from utils.funcs import Funcs + #Discord Code Block Formats code = "```py\n{0}\n```" diff = "```diff\n{0}\n```" + def init_logging(shard_id, bot): logging.root.setLevel(logging.INFO) logger = logging.getLogger('NotSoBot #{0}'.format(shard_id)) @@ -23,10 +25,13 @@ def init_logging(shard_id, bot): log.addHandler(handler) bot.logger = logger bot.log = log +#END init_logging() + class Object(object): pass + #Bot Utility Functions/Variables def init_funcs(bot): #Globals @@ -38,14 +43,17 @@ def init_funcs(bot): bot.globals.command_spam = {} bot.globals.spam_sent = {} bot.globals.command_deleted_sent = {} + #MySQL global cursor, engine, Session - if bot.dev_mode: - db = 'discord_dev' - elif bot.self_bot: - db = 'discord_self' - else: - db = 'discord' + # if bot.dev_mode: + # db = 'discord_dev' + # elif bot.self_bot: + # db = 'discord_self' + # else: + # db = 'discord' + db = 'discord' + engine = create_engine('mysql+pymysql://{0}:@localhost/{1}?charset=utf8mb4'.format(bot.shard_id if not bot.self_bot else '', db), encoding='utf8') session_factory = sessionmaker(bind=engine) Session = scoped_session(session_factory) @@ -84,44 +92,50 @@ def init_funcs(bot): bot.path = Object() discord_path = bot.path.discord = funcs.discord_path files_path = bot.path.files = funcs.files_path +#END init_funcs() + #Bot Cogs modules = [ - 'mods.Logging', + #'mods.Logging', 'mods.Commands', - 'mods.Moderation', + #'mods.Moderation', 'mods.Utils', 'mods.Info', 'mods.Fun', 'mods.Chan', 'mods.Repl', - 'mods.Stats', + #'mods.Stats', 'mods.Tags', 'mods.Logs', 'mods.Wc', # 'mods.AI', 'mods.Changes', 'mods.Markov', - 'mods.Verification', + #'mods.Verification', 'mods.Nsfw', 'mods.Reminders', 'mods.JoinLeave', 'mods.Afk' ] + #Console Colors def prRed(prt): print("\033[91m {}\033[00m" .format(prt)) def prGreen(prt): print("\033[92m {}\033[00m" .format(prt)) + class NotSoBot(commands.Bot): def __init__(self, *args, **kwargs): self.loop = kwargs.pop('loop', asyncio.get_event_loop()) asyncio.get_child_watcher().attach_loop(self.loop) self.dev_mode = kwargs.pop('dev_mode', False) - self.token = os.getenv('bot_token') if not self.dev_mode else os.getenv('bot_beta_token') + #self.token = os.getenv('bot_token') if not self.dev_mode else os.getenv('bot_beta_token') + self.token = "NTQzNTQyMTQ4NjUwMzY5MDUz.Dz-EmQ.oGb9THEL_e_0vJh3knr_D2lKQH4" # Hardcoded self.self_bot = kwargs.pop('self_bot', False) if self.self_bot: - self.token = os.getenv('notsosuper_token') + self.token = "NTQzNTQyMTQ4NjUwMzY5MDUz.Dz-EmQ.oGb9THEL_e_0vJh3knr_D2lKQH4" # Hardcoded + #self.token = os.getenv('notsosuper_token') shard_id = kwargs.get('shard_id', 0) command_prefix = kwargs.pop('command_prefix', commands.when_mentioned_or('.')) init_logging(shard_id, self) @@ -133,13 +147,19 @@ def __init__(self, *args, **kwargs): self.own_task = None self.last_message = None self.command_messages = {} + #END __init__() + def __del__(self): self.loop.set_exception_handler(lambda *args, **kwargs: None) + #END __del__() + @property def get_cursor(self): return Session() + #END get_cursor() + async def on_ready(self): if not self.self_bot: @@ -171,18 +191,22 @@ async def on_ready(self): else: print('------\n{0}\nShard {1}/{2}{3}------'.format(self.user, self.shard_id, self.shard_count-1, '\nDev Mode: Enabled\n' if self.dev_mode else '')) await self.change_presence(game=discord.Game(name="https://ropestore.org")) + #END on_ready() + async def on_message(self, message): self.last_message = message.timestamp await self.wait_until_ready() if self.globals.on_ready_write: self.write_last_time() + if self.owner is None: if self.self_bot: self.owner = self.user else: application_info = await self.application_info() self.owner = application_info.owner + if self.dev_mode and message.author != self.owner: return elif self.self_bot and message.author != self.owner: @@ -191,33 +215,40 @@ async def on_message(self, message): return elif message.author.bot: return + blacklisted = await self.is_blacklisted(message) if blacklisted: return + prefix_result = await self.get_prefix(message) prefixes = prefix_result[0] check = prefix_result[1] if not self.self_bot else True command = None - invoker = None + #invoker = None pm_only = False for prefix in prefixes: if message.content.lower().startswith(prefix) and check and message.content.lower() != prefix: prefix_escape = re.escape(prefix) message_regex = re.compile(r'('+prefix_escape+r')'+r'[\s]*(\w+)(.*)', re.I|re.X|re.S) match = message_regex.findall(message.content) + if len(match) == 0: return + match = match[0] command = match[1].lower() message.content = match[0].lower()+command+match[2] if command not in self.commands: return + if message.channel.is_private: if command in self.commands and self.commands[command].no_pm: pm_only = True + if pm_only is False: cmd = str(self.commands[command]) command_blacklisted = await self.command_check(message, cmd, prefix) + if command_blacklisted: return try: @@ -257,6 +288,8 @@ async def on_message(self, message): command_spam[message.channel] = {0: [utc+4], 1: [command]} await self.process_commands(message, command, prefix) self.command_messages[message] = [command, prefix] + #END on_message() + async def on_command(self, command, ctx): embed = discord.Embed() @@ -266,11 +299,13 @@ async def on_command(self, command, ctx): embed.add_field(name='Server', value='{0.name} <{0.id}>'.format(ctx.message.server) if ctx.message.server else 'Private Message') embed.add_field(name='Channel', value='`{0.name}`'.format(ctx.message.channel)) embed.add_field(name='Message', value=ctx.message.clean_content+' '.join([x['url'] for x in ctx.message.attachments]), inline=False) - embed.color = self.funcs.get_color()() + #embed.color = self.funcs.get_color() embed.timestamp = ctx.message.timestamp await self.queue_message("178313681786896384", embed) if ctx.message.author.id == self.owner.id: ctx.command.reset_cooldown(ctx) + #END on_command() + async def on_error(self, event, *args, **kwargs): prRed("Error!") @@ -280,6 +315,8 @@ async def on_error(self, event, *args, **kwargs): wrapper = textwrap.TextWrapper(initial_indent='! ', subsequent_indent='- ') fmt = wrapper.fill(str(traceback.format_exc())) await self.queue_message("180073721048989696", diff.format(fmt)) + #END on_error() + async def on_command_error(self, e, ctx): try: @@ -296,10 +333,7 @@ async def on_command_error(self, e, ctx): else: cooldown_sent[ctx.message.channel] = utc+5 await self.send_message(ctx.message.channel, ":no_entry: **Cooldown** `Cannot use again for another {:.2f} seconds.`".format(e.retry_after)) - elif isinstance(e, commands.MissingRequiredArgument): - await self.command_help(ctx) - ctx.command.reset_cooldown(ctx) - elif isinstance(e, commands.BadArgument): + elif isinstance(e, commands.MissingRequiredArgument) or isinstance(e, commands.BadArgument): await self.command_help(ctx) ctx.command.reset_cooldown(ctx) elif isinstance(e, checks.No_Perms): @@ -350,7 +384,7 @@ async def on_command_error(self, e, ctx): embed.add_field(name='File', value=str(e.__traceback__.tb_frame.f_code.co_filename)+'\nLine: **{0}**'.format(e.__traceback__.tb_lineno), inline=False) embed.add_field(name='Traceback', value=code.format(''.join(tb)), inline=False) embed.set_author(name='{0} <{0.id}>'.format(ctx.message.author), icon_url=ctx.message.author.avatar_url) - embed.color = discord.Color.red() + #embed.color = "#FF0000" embed.timestamp = datetime.datetime.now() await self.queue_message("180073721048989696", embed) elif type(e).__name__ == 'NoPrivateMessage': @@ -365,6 +399,8 @@ async def on_command_error(self, e, ctx): return except Exception as e: print(e) + #END on_command_error() + async def on_server_join(self, server): await self.wait_until_ready() @@ -376,9 +412,11 @@ async def on_server_join(self, server): embed.add_field(name='Members', value='**{0}**/{1}'.format(sum(1 for x in server.members if x.status == discord.Status.online or x.status == discord.Status.idle), len(server.members))) embed.add_field(name='Default Channel', value=server.default_channel) embed.add_field(name='Channels', value='Text: `{0}`\nVoice: `{1}`\nTotal: **{2}**'.format(sum(1 for x in server.channels if x.type == discord.ChannelType.text), sum(1 for x in server.channels if x.type == discord.ChannelType.voice), len(server.channels))) - embed.color = discord.Color.green() + #embed.color = "#00FF00" embed.timestamp = datetime.datetime.now() await self.queue_message('211247117816168449', embed) + #END on_server_join() + async def on_server_remove(self, server): await self.wait_until_ready() @@ -393,9 +431,11 @@ async def on_server_remove(self, server): embed.set_author(name='{0} <{0.id}>'.format(server.owner), icon_url=server.owner.avatar_url) embed.add_field(name='Server', value='{0.name} <{0.id}>'.format(server)) embed.add_field(name='Members', value='**{0}**/{1}'.format(sum(1 for x in server.members if x.status == discord.Status.online or x.status == discord.Status.idle), len(server.members))) - embed.color = discord.Color.red() + #embed.color = discord.Color.red() embed.timestamp = datetime.datetime.now() await self.queue_message('211247117816168449', embed) + #END on_server_remove() + async def on_resumed(self): last_time = self.get_last_time() @@ -408,6 +448,8 @@ async def on_resumed(self): downtime = str(utc - last_time) msg = '`[Shard {0}]` {1} has now <@&211727010932719627> after being <@&211727098149076995> since **{2}** for **{3}** second(s) (Current Time: **{4}**)'.format(self.shard_id, self.user.mention, time_msg, downtime, current_time_msg) await self.queue_message('211247117816168449', msg) + #END on_resumed() + async def send_message(self, destination, content=None, *, tts=False, embed=None, replace_mentions=False, replace_everyone=True): if content: @@ -417,15 +459,23 @@ async def send_message(self, destination, content=None, *, tts=False, embed=None if replace_mentions: content = await self.funcs.replace_mentions(content) return await super().send_message(destination, content, tts=tts, embed=embed) + #END send_message() + def get_member(self, id:str): return discord.utils.get(self.get_all_members(), id=id) + #END get_member() + def run(self): super().run(self.token) + #END run() + async def login(self, *args, **kwargs): return await super().login(self.token, bot=False if self.self_bot else True) + #END login() + def die(self): try: @@ -441,3 +491,5 @@ def die(self): self.log.removeHandler(handler) except Exception as e: print(e) + #END die() +#END_CLASS NotSoBot \ No newline at end of file diff --git a/bot0.py b/bot0.py index 779d820..aedcb37 100644 --- a/bot0.py +++ b/bot0.py @@ -3,13 +3,17 @@ import os, sys from bot import NotSoBot + loop = asyncio.get_event_loop() -dev_mode = str(os.getenv('dev_mode', False)) -if dev_mode == '1' or dev_mode.lower() == 'true': - dev_mode = True -else: - dev_mode = False + +# dev_mode = str(os.getenv('dev_mode', False)) +# if dev_mode == '1' or dev_mode.lower() == 'true': +# dev_mode = True +# else: +# dev_mode = False +dev_mode = False + shard_id = [int(s) for s in os.path.realpath(__file__) if s.isdigit()][0] shard_count = len([s for s in os.listdir() if s.startswith('bot') and s[3].isdigit()]) @@ -19,6 +23,7 @@ if len(sys.argv) > 1: shard_count = int(sys.argv[2]) + async def watcher(): await asyncio.sleep(30) while True: @@ -34,9 +39,11 @@ async def watcher(): bot = NotSoBot(loop=loop, shard_id=shard_id, shard_count=shard_count, dev_mode=dev_mode, max_messages=10000) + if __name__ == "__main__": try: task = loop.create_task(bot.run()) + print("NotSoFork is up and running!") task.add_done_callback(functools.partial(main, loop)) bot.own_task = task loop.create_task(watcher()) diff --git a/install-reqs.sh b/install-reqs.sh new file mode 100644 index 0000000..4b51904 --- /dev/null +++ b/install-reqs.sh @@ -0,0 +1,12 @@ +clear +printf "" > errors # Clear out errors from possible previous runs + +while read line; do + pip install $line + + if [ $? -ne 0 ]; then + printf "ERROR INSTALLING: $line\n" >> errors + fi + + printf "##################################################################\n" +done < "./requirements.txt" \ No newline at end of file diff --git a/mods/Fun.py b/mods/Fun.py index 725a670..22de9ca 100644 --- a/mods/Fun.py +++ b/mods/Fun.py @@ -35,11 +35,18 @@ def get_deep_text(element): except: return '' -def posnum(num): - if num < 0 : - return - (num) + +# updated to handle bad input and use abs function +def posnum(num): + # check if number is int + if isinstance(num, int): + return abs(num) + # check if number is float + elif isinstance(num, float): + return abs(num) else: - return num + return False + def find_coeffs(pa, pb): matrix = [] @@ -119,27 +126,35 @@ async def badmeme(self, ctx, direct=None): b = await self.bytes_download(url) await self.bot.upload(b, filename='badmeme.png') + def getImgs(self, scale, imgs): + exif = {} + list_imgs = [] + count = 0 + for img in imgs: + i = wand.image.Image(file=img) + i.format = 'jpg' + i.alpha_channel = True + if i.size >= (3000, 3000): + return ':warning: `Image exceeds maximum resolution >= (3000, 3000).`', None + exif.update({count: (k[5:], v) for k, v in i.metadata.items() if k.startswith('exif:')}) + count += 1 + i.transform(resize='800x800>') + i.liquid_rescale(width=int(i.width * 0.5), height=int(i.height * 0.5), + delta_x=int(0.5 * scale) if scale else 1, rigidity=0) + i.liquid_rescale(width=int(i.width * 1.5), height=int(i.height * 1.5), delta_x=scale if scale else 2, + rigidity=0) + magikd = BytesIO() + i.save(file=magikd) + magikd.seek(0) + list_imgs.append(magikd) + return exif, list_imgs + def do_magik(self, scale, *imgs): try: - list_imgs = [] - exif = {} exif_msg = '' - count = 0 - for img in imgs: - i = wand.image.Image(file=img) - i.format = 'jpg' - i.alpha_channel = True - if i.size >= (3000, 3000): - return ':warning: `Image exceeds maximum resolution >= (3000, 3000).`', None - exif.update({count:(k[5:], v) for k, v in i.metadata.items() if k.startswith('exif:')}) - count += 1 - i.transform(resize='800x800>') - i.liquid_rescale(width=int(i.width * 0.5), height=int(i.height * 0.5), delta_x=int(0.5 * scale) if scale else 1, rigidity=0) - i.liquid_rescale(width=int(i.width * 1.5), height=int(i.height * 1.5), delta_x=scale if scale else 2, rigidity=0) - magikd = BytesIO() - i.save(file=magikd) - magikd.seek(0) - list_imgs.append(magikd) + + exif, list_imgs = self.getImgs(scale, imgs) + if len(list_imgs) > 1: imgs = [PIL.Image.open(i).convert('RGBA') for i in list_imgs] min_shape = sorted([(np.sum(i.size), i.size) for i in imgs])[0][1] @@ -201,6 +216,15 @@ async def magik(self, ctx, *urls:str): except Exception as e: await self.bot.say(e) + def gmagikExcept(self): + exc_type, exc_obj, tb = sys.exc_info() + f = tb.tb_frame + lineno = tb.tb_lineno + filename = f.f_code.co_filename + linecache.checkcache(filename) + line = linecache.getline(filename, lineno, f.f_globals) + print('EXCEPTION IN ({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj)) + def do_gmagik(self, ctx, gif, gif_dir, rand): try: try: @@ -237,13 +261,7 @@ def do_gmagik(self, ctx, gif, gif_dir, rand): i.save(filename=image) return True except Exception as e: - exc_type, exc_obj, tb = sys.exc_info() - f = tb.tb_frame - lineno = tb.tb_lineno - filename = f.f_code.co_filename - linecache.checkcache(filename) - line = linecache.getline(filename, lineno, f.f_globals) - print('EXCEPTION IN ({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj)) + self.gmagikExcept() @commands.command(pass_context=True) @commands.cooldown(1, 20, commands.BucketType.server) diff --git a/mods/JoinLeave.py b/mods/JoinLeave.py index 1850910..ac2acce 100644 --- a/mods/JoinLeave.py +++ b/mods/JoinLeave.py @@ -10,8 +10,13 @@ default_leave = '**{user}#{discrim}** has left the server.' #http://stackoverflow.com/a/16671271 +# updated to check for input and use abs function def number_formating(n): - return str(n)+("th" if 4<=n%100<=20 else {1:"st",2:"nd",3:"rd"}.get(n%10, "th")) + if not isinstance(n, int): + return False + if n < 1: + n = abs(n) + return str(n) + ("th" if 4 <= n % 100 <= 20 else {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")) class Object(): pass diff --git a/mods/Tags.py b/mods/Tags.py index 62f3f93..86d4899 100644 --- a/mods/Tags.py +++ b/mods/Tags.py @@ -18,7 +18,18 @@ cool = "```xl\n{0}\n```" code = "```py\n{0}\n```" + +# updated to check for bad inputs def check_int(k): + # if int, return true + if isinstance(k, int): + return True + + # if float, return false + if isinstance(k, float): + return False + + # if a string, parse and check if int if k[0] in ('-', '+'): return k[1:].isdigit() return k.isdigit() diff --git a/mods/__init__.py b/mods/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pixelsort/sorting.py b/pixelsort/sorting.py index a8f0eb2..d333585 100644 --- a/pixelsort/sorting.py +++ b/pixelsort/sorting.py @@ -5,13 +5,31 @@ def lightness(pixel): return util.lightness(pixel) +# updated to check inputs def intensity(pixel): - return pixel[0] + pixel[1] + pixel[2] + if len(pixel) > 3: + return False + for num in pixel: + if not isinstance(num, int): + return False + return pixel[0] + pixel[1] + pixel[2] +# updated to check for bad inputs def maximum(pixel): - return max(pixel[0], pixel[1], pixel[2]) + if len(pixel) > 3: + return False + for num in pixel: + if not isinstance(num, int): + return False + return max(pixel[0], pixel[1], pixel[2]) +# updated to check for bad inputs def minimum(pixel): - return min(pixel[0], pixel[1], pixel[2]) + if len(pixel) > 3: + return False + for num in pixel: + if not isinstance(num, int): + return False + return min(pixel[0], pixel[1], pixel[2]) diff --git a/pixelsort/util.py b/pixelsort/util.py index f38d609..1b3b36d 100644 --- a/pixelsort/util.py +++ b/pixelsort/util.py @@ -11,8 +11,19 @@ def lightness(pixel): return rgb_to_hsv(pixel[0], pixel[1], pixel[2])[2] / 255.0 # For backwards compatibility with python2 +# Updated to check for bad input def random_width(clength): + # check if input is an integer + if not isinstance(clength, int): + return False + # if integer is negative, return false + if clength < 0: + return False + + # get a random number x = random.random() + + # set width equal a value less than or equal to clength width = int(clength * (1 - x)) return width diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..9242569 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1 @@ +# This only exists so that bot.py recognizes "utils" as a module \ No newline at end of file