diff --git a/.env b/.env new file mode 100644 index 0000000..ab489f0 --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +# Discord (you) +MASTER_ID=null # [0-9] + +# Discord (bot) +CLIENT_ID=null # [0-9] +CLIENT_SECRET=null # [0-9a-zA-Z] +TOKEN=null # [0-9a-zA-Z\.] + +# Imgur +imgur_client_id=null # [0-9a-z] +imgur_client_secret=null # [0-9a-z] + +# Config +BOT_PREFIX=; \ No newline at end of file diff --git a/.gitignore b/.gitignore index 94f0ace..a03eb61 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ IO\ Files/* __pycache__ *.lnk *.cmd -var_secrets.py -venv/* \ No newline at end of file +venv/ +.env.local \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..8059dde --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "mikestead.dotenv", + "ms-python.python" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a427185 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.formatting.provider": "black", + "editor.tabSize": 4 +} \ No newline at end of file diff --git a/PokeCord.py b/PokeCord.py index 6eed80d..dee521b 100644 --- a/PokeCord.py +++ b/PokeCord.py @@ -1,61 +1,58 @@ from discord.ext import commands import config as config from Objects.user import * -#from pil import method_sum_check + +# from pil import method_sum_check import random from datetime import time, datetime, timedelta - +import pokemon +import log import pickle import requests from PIL import Image, ImageDraw from io import BytesIO -#Trainer -> [Items, box, ] +# Trainer -> [Items, box, ] + def check_is_pm(ctx): return ctx.message.channel.type == discord.ChannelType.private - #return ctx.message.channel.type == discord.ChannelType.text + # return ctx.message.channel.type == discord.ChannelType.text + class PokeCord(commands.Cog): def __init__(self, bot): self.bot = bot self.channel_bind = {} self.time_to_spawn = None - self.wild_pokemon = None #Spawned Pokemon + self.wild_pokemon = None # Spawned Pokemon self.imgur_results = None self.spawn_msg = None self.trainer_list = {} def __getstate__(self): - return ({ - 'channel_bind' : self.channel_bind, - 'time' : self.time_to_spawn, - 'store' : self.wild_pokemon, - 'imgur' : self.imgur_results, - 'msg' : self.spawn_msg, - 'users' : self.trainer_list - }) + return {"channel_bind": self.channel_bind, "time": self.time_to_spawn, "store": self.wild_pokemon, "imgur": self.imgur_results, "msg": self.spawn_msg, "users": self.trainer_list} pass def __setstate__(self, dictState): - self.channel_bind = dictState['channel_bind'] - self.time_to_spawn = dictState['time'] - self.wild_pokemon = dictState['store'] - if 'imgr' in dictState: - self.imgur_results = dictState['imgr'] + self.channel_bind = dictState["channel_bind"] + self.time_to_spawn = dictState["time"] + self.wild_pokemon = dictState["store"] + if "imgr" in dictState: + self.imgur_results = dictState["imgr"] else: - self.imgur_results = dictState['imgur'] - self.spawn_msg = dictState['msg'] - self.trainer_list = dictState['users'] - # if self.time_to_spawn != None: + self.imgur_results = dictState["imgur"] + self.spawn_msg = dictState["msg"] + self.trainer_list = dictState["users"] + # if self.time_to_spawn != None: # asyncio.create_task(self.timed_spawn()) @property def time_to_spawn(self): return self._time_to_spawn - + @time_to_spawn.setter def time_to_spawn(self, value): self._time_to_spawn = value @@ -73,52 +70,52 @@ def setToSpawn(self): else: return True - @property + @property def appeared(self): - #print("A pokemon appeared: {}".format((self.wild_pokemon != None))) + # print("A pokemon appeared: {}".format((self.wild_pokemon != None))) return self.wild_pokemon != None -############################################################################## -############################################################################## -############################################################################## - - #If last command was restart - #i = 0 - ##async for message in client.logs_from(client.get_channel(CHANNEL_IDs), limit=2): - ## if (datetime.utcnow() - message.timestamp).total_seconds() <= 30: - ## if message.content == "Restarting..." and i == 0: - ## await client.edit_message(message, "Restarted!") - ## if message.content == (BOT_PREFIX + "restart") and i == 1: - ## await client.add_reaction(message, '👌') - ## i = i + 1 - + ############################################################################## + ############################################################################## + ############################################################################## + + # If last command was restart + # i = 0 + ##async for message in client.logs_from(client.get_channel(CHANNEL_IDs), limit=2): + ## if (datetime.utcnow() - message.timestamp).total_seconds() <= 30: + ## if message.content == "Restarting..." and i == 0: + ## await client.edit_message(message, "Restarted!") + ## if message.content == (BOT_PREFIX + "restart") and i == 1: + ## await client.add_reaction(message, '👌') + ## i = i + 1 + def update_pickle(self): - with open("IO Files/PokeCord.pickle", 'wb') as file: + with open("IO Files/PokeCord.pickle", "wb") as file: try: pickle.dump(self, file) pass except Exception as Err: - print(Err) + print(Err) @commands.Cog.listener() async def on_command_completion(self, context): self.update_pickle() - #print(dir(context)) #For checking is a command that changes the bot state + # print(dir(context)) #For checking is a command that changes the bot state #!! Someone get the last message or something @commands.Cog.listener() async def on_ready(self): for guild in self.bot.guilds: - print(f"Access to - {guild.name}") + log.info(f"Access to - {guild.name}") if guild.id not in self.channel_bind: for channel in guild.text_channels: - print(f"{channel.name} - {guild.me.permissions_in(channel)}") # send_messages + log.info(f"{channel.name} - {guild.me.permissions_in(channel)}") # send_messages if guild.me.permissions_in(channel).send_messages: self.channel_bind[guild.id] = channel.id break - print(f"{(guild.me.permissions_in(channel).send_messages)}") - #self.channel_bind[guild.id] = channels[0] - #Check if a pokemon is queued to be spawned + log.info(f"{(guild.me.permissions_in(channel).send_messages)}") + # self.channel_bind[guild.id] = channels[0] + # Check if a pokemon is queued to be spawned if self.appeared: game = discord.Game("Who's That Pokemon?") await self.bot.change_presence(status=discord.Status.online, activity=game) @@ -127,9 +124,10 @@ async def on_ready(self): await asyncio.sleep(self.getSeconds()) await self._spawn() else: - self.time_to_spawn = datetime.now() + timedelta(minutes=random.randint(1,10)) + self.time_to_spawn = datetime.now() + timedelta(minutes=random.randint(1, 10)) await asyncio.sleep(self.getSeconds()) await self._spawn() + # #If time to spawn in the future set in motion # if self.time_to_spawn > datetime.now(): # await asyncio.sleep(self.getSeconds()) @@ -137,26 +135,22 @@ async def on_ready(self): @commands.Cog.listener() async def on_message(self, message): - #Bot or wrong channel do nothing - if ( - message.author == self.bot.user or - message.content[1:].startswith("spawn") or - message.channel.type != discord.ChannelType.text - ): #or message.channel == CHANNEL_IDs: + # Bot or wrong channel do nothing + if message.author == self.bot.user or message.content[1:].startswith("spawn") or message.channel.type != discord.ChannelType.text: # or message.channel == CHANNEL_IDs: return - #print(f"[{datetime.now().strftime('%b-%d %H:%M')}] On Message ({message.channel.type}) {message.content}") - #Pokemon is going to be found + # print(f"[{datetime.now().strftime('%b-%d %H:%M')}] On Message ({message.channel.type}) {message.content}") + # Pokemon is going to be found if self.setToSpawn(): pass else: - #Pokemon is ready for capture + # Pokemon is ready for capture if self.appeared: if not await self.check_capture(message): return if not self.setToSpawn(): - self.time_to_spawn = datetime.now() + timedelta(minutes=random.randint(1,10)) - await message.channel.send('Pokemon set to spawn!') + self.time_to_spawn = datetime.now() + timedelta(minutes=random.randint(1, 10)) + await message.channel.send("Pokemon set to spawn!") await asyncio.sleep(self.getSeconds()) await self._spawn() self.update_pickle() @@ -169,18 +163,19 @@ async def on_message(self, message): # await asyncio.sleep(self.getSeconds()) # await self.cmd_spawn('spawn', message) - #------------------------------------------------- + # ------------------------------------------------- # Commands - #------------------------------------------------- + # ------------------------------------------------- @commands.is_owner() - @commands.command(name='bind', - usage='[channel_id]', + @commands.command( + name="bind", + usage="[channel_id]", help="""Changes the channel the bot listens to. If no argument is given binds to current channel. optional [channel_id] - will link the channel given, if permissible. - """ - ) - async def cmd_bind(self, ctx, channel:discord.TextChannel = None): + """, + ) + async def cmd_bind(self, ctx, channel: discord.TextChannel = None): if channel == None and ctx.message.channel.type == discord.ChannelType.private: await ctx.channel.send("Unable to bind to *direct messages*. Give *channel id* or give command in channel.") return @@ -191,23 +186,23 @@ async def cmd_bind(self, ctx, channel:discord.TextChannel = None): await ctx.channel.send(f"Unable to send messages in channel ({channel.name}) given") self.channel_bind[channel.guild.id] = channel.id - @commands.command(name='info') - async def cmd_info(self, context, emoji = None): + @commands.command(name="info") + async def cmd_info(self, context, emoji=None): await context.send("\u2716") - foundEmoji = discord.utils.find(lambda m: m.name == 'heavy_multiplication_x', self.bot.emojis) + foundEmoji = discord.utils.find(lambda m: m.name == "heavy_multiplication_x", self.bot.emojis) if emoji == None: print("Info") else: print(emoji) @commands.is_owner() - @commands.command(name='edit_embed') + @commands.command(name="edit_embed") async def cmd_edit_embed(self, cmd, message, content=None): msg_list = [] find_message = await message.channel.send(content) - msg_list.append(await message.channel.send('What in the embed do you want to change?')) + msg_list.append(await message.channel.send("What in the embed do you want to change?")) msg_list.append(await client.wait_for_message(author=message.author)) - msg_list.append(await message.channel.send('What would you like it to change to?')) + msg_list.append(await message.channel.send("What would you like it to change to?")) msg_list.append(await client.wait_for_message(author=message.author)) for msg in msg_list: print("{}{}{}".format(bcolors.WARNING, msg.content, bcolors.ENDC)) @@ -228,16 +223,16 @@ async def cmd_edit_embed(self, cmd, message, content=None): elif msg_list[1].content.strip(" ")[1].lower() == "author": new_embed.set_author(name=msg_list[3].content) else: - await message.channel.send('The requested field is not availiable.') + await message.channel.send("The requested field is not availiable.") await client.edit_message(find_message, embed=new_embed) client.delete_messages(msg_list) - @commands.command(name='spawn') + @commands.command(name="spawn") @commands.is_owner() async def cmd_spawn(self, context, message=None): await self._spawn(context.channel, message) - @commands.command(name='missing') + @commands.command(name="missing") async def cmd_missing(self, context, message=None): if self.spawn_msg != None: new_embed = discord.Embed() @@ -246,15 +241,11 @@ async def cmd_missing(self, context, message=None): new_embed.set_thumbnail(url=imgur_result) await client.edit_message(self.spawn_msg, embed=new_embed) - @commands.command( - name='inventory', - aliases=['backpack'], - help="Displays pokemon and items" - ) - async def cmd_inventory(self, context, member:discord.Member = None): + @commands.command(name="inventory", aliases=["backpack"], help="Displays pokemon and items") + async def cmd_inventory(self, context, member: discord.Member = None): if member is None: if context.author.id in self.trainer_list.keys(): - #print(self.trainer_list[context.author.id]) + # print(self.trainer_list[context.author.id]) await context.channel.send(embed=self.trainer_list[context.author.id].embed_list(context.author)) else: msg = "{} you have caught no Pokemon".format(context.author.name) @@ -266,7 +257,7 @@ async def cmd_inventory(self, context, member:discord.Member = None): else: await context.send(f"{member.name} has caught no Pokemon") - @commands.command(name='bad_gif') + @commands.command(name="bad_gif") @commands.is_owner() async def cmd_bad_gif(self, context, message=None): if message == None: @@ -274,8 +265,8 @@ async def cmd_bad_gif(self, context, message=None): msg = "No pokemon is spawned" await context.channel.send(msg) return - file = open("IO Files/badGIF.txt", 'a+') - file.write("{}#{}\n".format(self.wild_pokemon['name'], self.wild_pokemon['id'])) + file = open("IO Files/badGIF.txt", "a+") + file.write("{}#{}\n".format(self.wild_pokemon["name"], self.wild_pokemon["id"])) file.close() msg = "Pokemon has been added to file" await context.channel.send(msg) @@ -283,29 +274,26 @@ async def cmd_bad_gif(self, context, message=None): self.sort_gif_file() @commands.command( - name='release', - usage=' [pokemon index=0]', + name="release", + usage=" [pokemon index=0]", help="""Removes a pokemon from your inventory. - name of the pokemon you want to release optional [pokemon index] - caught multiple of the same pokemon and want to release a particular one - """ - ) - async def cmd_release(self, ctx, pokemonName : str, pokemon_index:int = 0): + """, + ) + async def cmd_release(self, ctx, pokemonName: str, pokemon_index: int = 0): if ctx.author.id in self.trainer_list: trainer = self.trainer_list[ctx.author.id] if trainer.hasPokemon(pokemonName): trainer.removePokemon(pokemonName) - await ctx.channel.send(f"*{ctx.author.mention}*, {pokemonName} has been released.") + await ctx.channel.send(f"*{ctx.author.mention}*, {pokemonName} has been released.") else: - await ctx.channel.send(f"*{ctx.author.display_name}*, haven't caught any {pokemonName}.") + await ctx.channel.send(f"*{ctx.author.display_name}*, haven't caught any {pokemonName}.") else: await ctx.channel.send(f"*{ctx.author.display_name}*, haven't caught any pokemon!") - @commands.command( - name='trade', - help="Trade a pokemon between two users" - ) - async def cmd_trade(self, ctx, member:discord.Member, give_pokemon:str, get_pokemon:str): + @commands.command(name="trade", help="Trade a pokemon between two users") + async def cmd_trade(self, ctx, member: discord.Member, give_pokemon: str, get_pokemon: str): if member.id in self.trainer_list and ctx.author.id in self.trainer_list: trainer_give = self.trainer_list[ctx.author.id] trainer_get = self.trainer_list[member.id] @@ -317,34 +305,30 @@ async def cmd_trade(self, ctx, member:discord.Member, give_pokemon:str, get_poke return else: sent_message = await ctx.send(f"*{member.display_name}*, *{ctx.author.display_name}* wants to trade {give_pokemon} for {get_pokemon}") - await sent_message.add_reaction('\u2716') # X - await sent_message.add_reaction('\u2714') # ✔ + await sent_message.add_reaction("\u2716") # X + await sent_message.add_reaction("\u2714") # ✔ def check(reaction, user): - #print(f"Reaction = {dir(reaction.emoji)} - {reaction.emoji}") - return ( - user == member and - reaction.message.id == sent_message.id and - (reaction.emoji == '\u2716' or reaction.emoji == '\u2714') - ) + # print(f"Reaction = {dir(reaction.emoji)} - {reaction.emoji}") + return user == member and reaction.message.id == sent_message.id and (reaction.emoji == "\u2716" or reaction.emoji == "\u2714") try: - reaction, user = await self.bot.wait_for('reaction_add', timeout=60.0, check=check) + reaction, user = await self.bot.wait_for("reaction_add", timeout=60.0, check=check) except asyncio.TimeoutError: await ctx.send("No Reponse. Canceling trade.") await sent_message.delete() pass else: - if reaction.emoji == '\u2714': + if reaction.emoji == "\u2714": await ctx.send("Trade Accepted!") else: await ctx.send("Trade Rejected") else: await ctx.send("One of you haven't captured pokemon.") - #------------------------------------------------- + # ------------------------------------------------- # Other Functions - #------------------------------------------------- + # ------------------------------------------------- async def timed_spawn(self, ctx): await asyncio.sleep(self.getSeconds()) @@ -357,29 +341,31 @@ async def _spawn(self, channel=None, message=None): return self.time_to_spawn = None - await channel.send('A Wild Pokémon appears!') + await channel.send("A Wild Pokémon appears!") + poke = pokemon.from_message(message) + if message != None: if message.isdigit(): pokeNum = message url = f"http://pokeapi.co/api/v2/pokemon/{message}/" else: - #print error + # print error msg = "When using spawn must be number if input given" await channel.send(msg) return else: - pokeNum = str(random.randint(1,200)) + pokeNum = str(random.randint(1, 200)) url = "http://pokeapi.co/api/v2/pokemon/" + pokeNum + "/" - #Checks if Pokemon has already been found, if so pulls file + # Checks if Pokemon has already been found, if so pulls file if os.path.isfile("IO Files/Pokemon/Pokemon#{}".format(pokeNum)): - with open('IO Files/Pokemon/Pokemon#{}'.format(pokeNum), 'r') as file: + with open("IO Files/Pokemon/Pokemon#{}".format(pokeNum), "r") as file: self.wild_pokemon = json.load(file) else: t0 = datetime.now() self.wild_pokemon = requests.post(url).json() - print(f"Obtained Pokemon - {datetime.now() - t0}") - with open(f"IO Files/Pokemon/Pokemon#{self.wild_pokemon['id']}", 'w') as file: + log.info(f"Obtained Pokemon in {datetime.now() - t0}") + with open(f"IO Files/Pokemon/Pokemon#{self.wild_pokemon['id']}", "w") as file: json.dump(self.wild_pokemon, file) embed = discord.Embed() @@ -392,23 +378,23 @@ async def _spawn(self, channel=None, message=None): await self.bot.change_presence(status=discord.Status.online, activity=game) def sort_gif_file(self): - with open("IO Files/badGIF.txt", 'r') as file: + with open("IO Files/badGIF.txt", "r") as file: docText = file.read().strip() - #print(docText.split("\n")) + # print(docText.split("\n")) docText = docText.split("\n") - with open("IO Files/sorted_badGIF.txt", 'w') as file: - file.write("\n".join(sorted(set(docText), key=lambda item: int(item.split('#')[-1])))) + with open("IO Files/sorted_badGIF.txt", "w") as file: + file.write("\n".join(sorted(set(docText), key=lambda item: int(item.split("#")[-1])))) async def check_capture(self, message): if self.wild_pokemon is None: return False - if self.wild_pokemon['name'] == message.content.lower(): + if self.wild_pokemon["name"] == message.content.lower(): await self.bot.change_presence(status=discord.Status.invisible) - print("[{}]: '{}' caught by {}".format(datetime.now().strftime("%b-%d %H:%M"), self.wild_pokemon['name'], message.author)) + print("[{}]: '{}' caught by {}".format(datetime.now().strftime("%b-%d %H:%M"), self.wild_pokemon["name"], message.author)) embed = discord.Embed(type="rich", title="Gotcha!", color=0xEEE8AA) - embed.description = "{} was caught by {}".format(self.wild_pokemon['name'].upper(), message.author.mention) - embed.set_thumbnail(url="https://play.pokemonshowdown.com/sprites/xyani/{}.gif".format(self.wild_pokemon['name'])) + embed.description = "{} was caught by {}".format(self.wild_pokemon["name"].upper(), message.author.mention) + embed.set_thumbnail(url="https://play.pokemonshowdown.com/sprites/xyani/{}.gif".format(self.wild_pokemon["name"])) # await client.send_message(message.channel, "Gotcha!\n{} was caught by {}".format(self.wild_pokemon['name'].upper(), message.author.mention)) await message.channel.send(embed=embed) if message.author.id in self.trainer_list.keys(): @@ -420,64 +406,54 @@ async def check_capture(self, message): return False async def convert_bw(self, poke): - response = requests.get(poke['sprites']['front_default']) + response = requests.get(poke["sprites"]["front_default"]) print("Obtained Image") img = Image.open(BytesIO(response.content)) - shape_img = img.convert('RGBA') + shape_img = img.convert("RGBA") pixdata = shape_img.load() width, height = shape_img.size for x in range(0, width - 1): for y in range(0, height - 1): - if pixdata[x,y] != (0, 0, 0, 0): - pixdata[x,y] = black - shape_img.save('Images/bw_image.png') + if pixdata[x, y] != (0, 0, 0, 0): + pixdata[x, y] = black + shape_img.save("Images/bw_image.png") async def convert_gif_bw(self, poke): txtfile_name = "Images/PokeGIF.gif" - #Get the gif file - response = requests.get("https://play.pokemonshowdown.com/sprites/xyani/" + poke['name'] + ".gif") + # Get the gif file + response = requests.get("https://play.pokemonshowdown.com/sprites/xyani/" + poke["name"] + ".gif") print("Obtained image: {}".format(response)) #'Download' the file frames = Image.open(BytesIO(response.content)) all_frames = [] width, height = frames.size - #Creates a viewing picture - compilation = Image.new('RGBA', size=(width * 5, height * 10)) + # Creates a viewing picture + compilation = Image.new("RGBA", size=(width * 5, height * 10)) - prev_frame = Image.new('RGBA', size=frames.size, color=(255,255,255,0)) + prev_frame = Image.new("RGBA", size=frames.size, color=(255, 255, 255, 0)) for i in range(frames.n_frames): frames.seek(i) if len(all_frames) <= 50: - compilation.paste(frames, box=(width * int(i % 5), height * int(i / 5))) - + compilation.paste(frames, box=(width * int(i % 5), height * int(i / 5))) + disp_frame, prev_frame = method_dispose(frames, prev_frame) frames.seek(0) - disp_frame = disp_frame.convert('RGB') + disp_frame = disp_frame.convert("RGB") pixdata = disp_frame.load() - #Change Colors + # Change Colors for x in range(width): for y in range(height): - if pixdata[x,y] != white: - pixdata[x,y] = black + if pixdata[x, y] != white: + pixdata[x, y] = black all_frames.append(disp_frame) compilation.save("Images/GIFcollage.png") - #Save the gif - all_frames[0].save( - txtfile_name, - save_all=True, - append_images=all_frames[1:], - optimize=False, - duration=frames.info['duration'], - loop=0, - disposal=1, - transparency=255, - background=0 - ) - #Upload to Imgur + # Save the gif + all_frames[0].save(txtfile_name, save_all=True, append_images=all_frames[1:], optimize=False, duration=frames.info["duration"], loop=0, disposal=1, transparency=255, background=0) + # Upload to Imgur try: - self.imgur_result = config.imgur_client.upload_from_path(txtfile_name, config=None, anon=True)['link'] + self.imgur_result = config.imgur_client.upload_from_path(txtfile_name, config=None, anon=True)["link"] except Exception as Err: print(Err) raise Err @@ -487,7 +463,7 @@ async def wait_msg_delete(self, msg, after): await asyncio.sleep(after) await client.delete_message(msg) - @commands.command(name='clean', aliases=['clear'], help='Deletes # of messages') + @commands.command(name="clean", aliases=["clear"], help="Deletes # of messages") @commands.is_owner() async def cmd_clean(self, context, numMessages: int = 0): if numMessages == 0: @@ -496,7 +472,7 @@ async def cmd_clean(self, context, numMessages: int = 0): else: eleted = await context.channel.purge(limit=numMessages + 1) - @commands.command(name='debug', help='Admin testing bot')# , hidden=True) + @commands.command(name="debug", help="Admin testing bot") # , hidden=True) @commands.is_owner() async def cmd_debug(self, context, *, message): embed = discord.Embed(type="rich", title="__Debug__", color=0x7F0000) @@ -512,6 +488,7 @@ async def cmd_debug(self, context, *, message): await context.message.delete() return + def method_dispose(frames, previous_frame): # 0 PIL = Overlay and pass # 1 PIL = Overlay and return previous @@ -520,7 +497,7 @@ def method_dispose(frames, previous_frame): current_frame = frames.convert() new_frame.alpha_composite(current_frame, dest=frames.dispose_extent[0:2], source=frames.dispose_extent) if frames.disposal_method == 0: - return new_frame, Image.new('RGBA', box=frames.size) + return new_frame, Image.new("RGBA", box=frames.size) elif frames.disposal_method == 1: return new_frame, new_frame.copy() elif frames.disposal_method == 2 or frames.disposal_method == 3: @@ -529,4 +506,4 @@ def method_dispose(frames, previous_frame): return new_frame, previous_frame.copy() else: print("UNKNOWN disposal_method") - return current_frame, current_frame.copy() \ No newline at end of file + return current_frame, current_frame.copy() diff --git a/README.md b/README.md index 1051ab5..d559944 100644 --- a/README.md +++ b/README.md @@ -10,26 +10,45 @@ Is a bot where every so often a pokemon spawns and has to be guessed to be caugh ### Installing -Create your API secrets and input them into the '[var_secrets.py](var_secrets.py)' file, with the values enclosed in quotes. +1. Create virtual environment `py -m venv venv && venv/Scripts/activate` + +2. Install dependencies: `pip install -r requirements.txt` + +3. Create a copy of `.env` and name it `.env.local` + +4. Create your API secrets and put them in `.env.local` + - [imgur](https://api.imgur.com/oauth2/addclient) + - Application name: `PokeCord` (can be whatever you want) + - Authorization type: `OAuth 2 authorization with a callback URL` + - Authorization callback URL: `https://imgur.com` + - On submit, edit `imgr_client_id` and `imgr_client_secret` - [discord](https://discordpy.readthedocs.io/en/latest/discord.html) -To install the dependencies for the project run `pip install -r requirements.txt`. +Example `.env.local`: -* **Note: the bot will still have to be added to the discord channel** +> MASTER_ID = "abcd#1234" +> +> CLIENT_ID = abcd1234 +> ... + +- **Note: the bot will still have to be added to the discord channel** ### Running Run '[runbot.bat](runbot.bat)' after installing to start the bot. +- If you're using virtualenv: `py main.py` + ## Built with -* Windows 10 (Untested on Linux) -* [discord.ext](https://discordpy.readthedocs.io/en/latest/ext/commands/index.html) - Interactions with Discord -* [Pillow](https://pillow.readthedocs.io/en/stable/reference/Image.html) - Image Editing -* [imgurpython](https://github.com/Imgur/imgurpython) - Image Uploading +- Windows 10 (Untested on Linux) +- [discord.ext](https://discordpy.readthedocs.io/en/latest/ext/commands/index.html) - Interactions with Discord +- [Pillow](https://pillow.readthedocs.io/en/stable/reference/Image.html) - Image Editing +- [imgurpython](https://github.com/Imgur/imgurpython) - Image Uploading ## To-do + - [x] Catch-able Pokemon - [ ] Admin Change spawn time - [ ] Limit which region is spawned @@ -37,4 +56,4 @@ Run '[runbot.bat](runbot.bat)' after installing to start the bot. - [ ] Fix Re-coloring - [ ] Add items - [ ] Add leveling and battling -- [ ] Add variabled spawn rate based off rarity (maybe) \ No newline at end of file +- [ ] Add variabled spawn rate based off rarity (maybe) diff --git a/config.py b/config.py index 494ad02..95f5ffe 100644 --- a/config.py +++ b/config.py @@ -1,4 +1,4 @@ -from var_secrets import imgur_client_id, imgur_client_secret +import env import os import json @@ -11,7 +11,7 @@ # DISCORD VARS # #client = discord.Client() -BOT_PREFIX = ";" +BOT_PREFIX = env.get("BOT_PREFIX") CHANNEL_IDs = ("449281327988998156") # PYTHON VALS # @@ -27,7 +27,7 @@ class bcolors: # Globals # -imgur_client = ImgurClient(imgur_client_id, imgur_client_secret) +imgur_client = ImgurClient(env.get("imgur_client_id"), env.get("imgur_client_secret")) # Image Vars # white = (255,255,255) diff --git a/env.py b/env.py new file mode 100644 index 0000000..182653e --- /dev/null +++ b/env.py @@ -0,0 +1,11 @@ +import os +from dotenv import dotenv_values + +var = { + **dotenv_values(".env"), + **dotenv_values(".env.local"), + **os.environ +} + +def get(key): + return var[key] \ No newline at end of file diff --git a/log.py b/log.py new file mode 100644 index 0000000..119653b --- /dev/null +++ b/log.py @@ -0,0 +1,50 @@ +from datetime import datetime +from enum import Enum + + +class LEVEL(Enum): + INFO = 1 + ERROR = 2 + WARN = 3 + DEBUG = 4 + + def __ge__(self, other): + if self.__class__ is other.__class__: + return self.value >= other.value + return NotImplemented + + def __gt__(self, other): + if self.__class__ is other.__class__: + return self.value > other.value + return NotImplemented + + def __le__(self, other): + if self.__class__ is other.__class__: + return self.value <= other.value + return NotImplemented + + def __lt__(self, other): + if self.__class__ is other.__class__: + return self.value < other.value + return NotImplemented + + +level = LEVEL.DEBUG + + +def separator(): + print("~~~~~~~~~~~~") + + +def info(*args, **kwargs): + if level >= LEVEL.INFO: + print(f"[{datetime.now().strftime('%b-%d %H:%M')} INFO]", *args, **kwargs) + + +def debug(*args, **kwargs): + if level >= LEVEL.DEBUG: + print(f"[DEBUG]", *args, **kwargs) + + +# def error() +# def warn() diff --git a/main.py b/main.py index 826495a..fa3a933 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,7 @@ -import sys +import env, log, sys, config, pickle from pathlib import Path -import config -from var_secrets import TOKEN from PokeCord import * -import pickle - -from datetime import timedelta, datetime - # Checking folders exist if not os.path.isdir("IO Files/"): print("Creating IO Files folder...") @@ -25,8 +19,8 @@ @bot.event async def on_connect(): - print('~~~~~~~~~~~~') - print(f"[{datetime.now().strftime('%b-%d %H:%M')}] Logged in as {bot.user.name}(ID: {bot.user.id})") + log.separator() + log.info(f"Logged in as {bot.user.name}(ID: {bot.user.id})") if os.path.isfile("IO Files/PokeCord.pickle"): with open("IO Files/PokeCord.pickle", 'rb') as file: try: @@ -48,7 +42,7 @@ async def on_connect(): @bot.event async def on_command(context): - print(f"[{datetime.now().strftime('%b-%d %H:%M')}]: '{context.command}' by {context.author}") + log.info(f"'{context.command}' by {context.author}") pass @bot.event @@ -75,6 +69,5 @@ async def cmd_restart(ctx): async def cmd_shutdown(context): await bot.close() - -print(f"[{datetime.now().strftime('%b-%d %H:%M')}] Start Bot") -bot.run(TOKEN) \ No newline at end of file +log.info("Start Bot") +bot.run(env.get("TOKEN")) \ No newline at end of file diff --git a/pil.py b/pil.py index 3975b17..d55ebda 100644 --- a/pil.py +++ b/pil.py @@ -6,10 +6,278 @@ import time import math import sys +import log + +from requests.models import Response + +white = (255, 255, 255) # 255 +black = (0, 0, 0) # 255 +transparent = (255, 255, 255) # 0 + + +class PilPokemon: + def __init__(self): + self.response: requests.Response = None + self.original_image: Image.Image = None + self.normal_image: list(Image.Image) = [] + self.grayscale_image: list(Image.Image) = [] + + def getGrayscalePokemon(self, pokemon_id): + self.getGIF(pokemon_id) + self.getGrayscaleGIF() + + def requestPokemon(self, pokemon_id) -> str: + # If specific number wanted + if type(pokemon_id) == int: + url = f"http://pokeapi.co/api/v2/pokemon/{pokemon_id}/" + t0 = time.time() + response = requests.get(url) + if response.status_code == 404: + return None + else: + self.pokemon = response.json() + return self.pokemon["name"] + # If pokemon name is known + elif type(pokemon_id) == str: + return pokemon_id + else: + return None + + def _getFrames(self) -> Image.Image: + self.original_image = Image.open(BytesIO(self.response.content)) + + def getGIF(self, pokemon_id) -> Image.Image: + name = self.requestPokemon(pokemon_id) + if not name: + return None + self.response = requests.get( + f"https://play.pokemonshowdown.com/sprites/xyani/{name.lower()}.gif" + ) + if self.response.status_code == 404: + return None + else: + self.original_image = None + self.normal_image = None + self.grayscale_image = None + self._getFrames() + return self.original_image + + """ + Rebuilds an image based off of dispose methods using two frames + """ + + def _method_dispose(self, i, frames, previous_frame): + # 0 PIL = Overlay and pass + # 1 PIL = Overlay and return previous + # 2 PIL = Erase Overlay + new_frame: Image.Image = previous_frame.copy() + current_frame = frames.convert() + + new_frame.alpha_composite( + current_frame, dest=frames.dispose_extent[0:2], source=frames.dispose_extent + ) + if frames.disposal_method == 0: + return new_frame, Image.new("RGBA", box=frames.size) + elif frames.disposal_method == 1: + return new_frame, new_frame.copy() + elif frames.disposal_method == 2 or frames.disposal_method == 3: + draw = ImageDraw.Draw(previous_frame) + draw.rectangle(frames.dispose_extent, fill=(white + (0,))) + return new_frame, previous_frame.copy() + else: + print("UNKNOWN disposal_method") + exit() + + def getNormalizedImage(self): + if not self.original_image: + return None + + self.normal_image.clear() + frames = self.original_image + width, height = frames.size + + corrected_prev_frame = None + pass_frame = Image.new("RGBA", size=frames.size, color=(255, 255, 255, 0)) + for i in range(frames.n_frames): + frames.seek(i) + disp_frame, pass_frame = self._method_dispose(i, frames, pass_frame) + frames.seek(0) + disp_frame = disp_frame.convert("RGB") + + self.normal_image.append(disp_frame) + + def getGrayscaleGIF(self): + if not self.normal_image: # Is Empty + self.getNormalizedImage() + if not self.normal_image: + return None + + self.grayscale_image.clear() + + for frame in self.normal_image: + copy_frame: Image.Image = frame.copy() + pixdata = copy_frame.load() + # Change Colors + for x in range(copy_frame.width): + for y in range(copy_frame.height): + if pixdata[x, y] != white: + pixdata[x, y] = black + copy_frame = copy_frame.convert("P") + self.grayscale_image.append(copy_frame) + + # Assumption for now is images will be list + def showCollageGIF(self, images: list, num_frame_columns: int = 8): + width, height = images[0].size + num_frame_rows = math.ceil(len(images) / num_frame_columns) + compilation = Image.new( + "RGBA", size=(width * num_frame_columns, height * num_frame_rows) + ) + for i in range(len(images)): + image = images[i] + the_frame = image.convert() + draw = ImageDraw.Draw(the_frame) + draw.rectangle((0, 0, *image.size), outline=(0, 0, 0, 255)) + + dest = ( + width * int(i % num_frame_columns), + height * int(i / num_frame_columns), + ) + compilation.paste(the_frame, dest) + compilation.show() + + def saveGIF(self, images: list, filename: str = "test.gif"): + images[0].save( + fp=filename, + format="GIF", + save_all=True, + append_images=images[1:], + optimize=False, + duration=self.original_image.info["duration"], + loop=0, + disposal=1, + transparency=255, + background=0, + ) + + +### +def _getFrames(response: requests.Response) -> Image.Image: + return Image.open(BytesIO(response.content)) + + +def getPokemonGIF(pokemon_name: str) -> Image.Image: + """Given a pokemon's name will retrieve the gif of that pokemon + + Args: + pokemon_name (str): Pokemon name string + + Returns: + Image.Image: Original GIF of Pokemon + """ + if type(pokemon_name) is not str: + return None + + response: requests.Response = requests.get( + f"https://play.pokemonshowdown.com/sprites/xyani/{name.lower()}.gif" + ) + if response.status_code == 404: + return None + else: + return _getFrames(response) + + +def _method_dispose(i, frames, previous_frame): + """ + Rebuilds an image based off of dispose methods using two frames + """ + # 0 PIL = Overlay and pass + # 1 PIL = Overlay and return previous + # 2 PIL = Erase Overlay + new_frame: Image.Image = previous_frame.copy() + current_frame = frames.convert() + + new_frame.alpha_composite( + current_frame, dest=frames.dispose_extent[0:2], source=frames.dispose_extent + ) + if frames.disposal_method == 0: + return new_frame, Image.new("RGBA", box=frames.size) + elif frames.disposal_method == 1: + return new_frame, new_frame.copy() + elif frames.disposal_method == 2 or frames.disposal_method == 3: + draw = ImageDraw.Draw(previous_frame) + draw.rectangle(frames.dispose_extent, fill=(white + (0,))) + return new_frame, previous_frame.copy() + else: + print("UNKNOWN disposal_method") + exit() + + +def getNormalizedImage(frames: Image.Image) -> list: + """Input is a GIF, taking each frame and rebuilding the image + according to the disposal method of the frame + + Args: + frames (Image.Image): GIF + + Returns: + list(Image.Image): Reconstructed gif with each item in the list + being an image in the gif + """ + normal_image = [] + width, height = frames.size + + corrected_prev_frame = None + pass_frame = Image.new("RGBA", size=frames.size, color=(255, 255, 255, 0)) + for i in range(frames.n_frames): + frames.seek(i) + disp_frame, pass_frame = _method_dispose(i, frames, pass_frame) + frames.seek(0) + disp_frame = disp_frame.convert("RGB") + + normal_image.append(disp_frame) + return normal_image + + +def getGrayscaleGIF(frames: list) -> list: + """Given a list of images will remove color from all. Assumption is + background is white + + Args: + frames (list[Image.Image]): List of images + + Returns: + list: Images with colors removed + """ + grayscale_image = [] + + for frame in frames: + pixdata = frame.load() + # Change Colors + for x in range(frame.width): + for y in range(frame.height): + if pixdata[x, y] != white: + pixdata[x, y] = black + frame = frame.convert("P") + grayscale_image.append(frame) + return grayscale_image + + +def getGrayscalePokemon(pokemon_id: str) -> list: + """Takes the pokemon string and outputs the grayscale image list + + Args: + pokemon_id (str): Pokemon name + + Returns: + list(Image.Image): Grayscale image list of the pokemon + """ + original_image = getPokemonGIF(pokemon_id) + normal_image = getNormalizedImage(original_image) + return getGrayscaleGIF(normal_image) + + +### -white = (255,255,255) #255 -black = (0, 0, 0) #255 -transparent = (255, 255, 255) #0 def getPokemon(val): url = "http://pokeapi.co/api/v2/pokemon/" + val + "/" @@ -18,147 +286,187 @@ def getPokemon(val): print(f"Obtained Pokemon - {time.time() - t0}") return poke + def getGIFimage(name): - response = requests.get(f"https://play.pokemonshowdown.com/sprites/xyani/{name.lower()}.gif") + response = requests.get( + f"https://play.pokemonshowdown.com/sprites/xyani/{name.lower()}.gif" + ) print(f"Obtained Image: {response} : {response.url}") frames = Image.open(BytesIO(response.content)) return frames -def simpleCollage(frames, num_images_width : int = 5, num_images_height : int = 10, fit : bool = False): + +def simpleCollage( + frames: Image.Image, + num_images_width: int = 5, + num_images_height: int = 10, + fit: bool = False, +): + """Prints each frame with the frame number and disposal method in the corner. + + Args: + frames (Image.Image): Image to be collaged + num_images_width (int, optional): Number of columns in collage. Defaults to 5. + num_images_height (int, optional): Number of rows in collage. Defaults to 10. + fit (bool, optional): Will rewrite rows to fit frames in image. Defaults to False. + """ width, height = frames.size if fit: num_images_height = math.ceil(frames.n_frames / num_images_width) - print(f"Frames in image ({num_images_width}x{num_images_height}): {frames.n_frames} - {frames.filename if frames.filename else 'N/A'}") - compilation = Image.new('RGBA', size=(width * num_images_width, height * num_images_height)) + print( + f"Frames in image ({num_images_width}x{num_images_height}): {frames.n_frames} - {frames.filename if frames.filename else 'N/A'}" + ) + compilation = Image.new( + "RGBA", size=(width * num_images_width, height * num_images_height) + ) fnt = ImageFont.load_default().font last_dispose_method_2 = 0 for i in range(frames.n_frames): frames.seek(i) the_frame = frames.convert() draw = ImageDraw.Draw(the_frame) - draw.rectangle(frames.dispose_extent, outline=(0,0,0,255)) - draw.text((0,0), f"F{i}-M{frames.disposal_method}", font=fnt, fill=(255, 0, 0)) + draw.rectangle(frames.dispose_extent, outline=(0, 0, 0, 255)) + draw.text((0, 0), f"F{i}-M{frames.disposal_method}", font=fnt, fill=(255, 0, 0)) compilation.alpha_composite( - the_frame, + the_frame, dest=( - width * int(i % num_images_width), - height * int(i / num_images_width) - ) - ) + width * int(i % num_images_width), + height * int(i / num_images_width), + ), + ) if frames.disposal_method == 2: last_dispose_method_2 = i frames.seek(0) if i == (num_images_width * num_images_height): - break; + break compilation.show() - compilation.save("compilation.png") - -def reformCollage(frames, num_images_width : int = 5, num_images_height : int = 10, fit : bool = False): - # frames.seek(27) + compilation.save("compilation.png") + + +def reformCollage( + frames, num_images_width: int = 5, num_images_height: int = 10, fit: bool = False +): + """ + Pastes each frame not going back to the first frame + Details: + Image background yellow + If pixel is transparency color change to transparent teal + If pixel is background color change to solid red + """ # frames.show() width, height = frames.size if fit: num_images_height = math.ceil(frames.n_frames / num_images_width) - print(f"Frames in image ({num_images_width}x{num_images_height}): {frames.n_frames} - {frames.filename}") + print( + f"Frames in image ({num_images_width}x{num_images_height}): {frames.n_frames} - {frames.filename}" + ) frames.seek(0) - backgroundColor = frames.info['background'] - compilation = Image.new('RGBA', size=(width * num_images_width, height * num_images_height), color=(255, 255, 0))#, color=(255,0,0)) + backgroundColor = frames.info["background"] + compilation = Image.new( + "RGBA", + size=(width * num_images_width, height * num_images_height), + color=(255, 255, 0), + ) # , color=(255,0,0)) fnt = ImageFont.load_default().font - #print(f"Disposal Method - {frames.disposal_method}, Disposal Extend - {frames.dispose_extent}, Info - {frames.info}") + # print(f"Disposal Method - {frames.disposal_method}, Disposal Extend - {frames.dispose_extent}, Info - {frames.info}") for i in range(frames.n_frames): frames.seek(i) - the_frame = Image.new('RGBA', size=frames.size) + the_frame = Image.new("RGBA", size=frames.size) pixdata = the_frame.load() for x in range(width): for y in range(height): palette = frames.getpalette() - a_pixel = frames.getpixel((x,y)) - if a_pixel == frames.info['transparency']: + a_pixel = frames.getpixel((x, y)) + if a_pixel == frames.info["transparency"]: pixdata[x, y] = (0, 255, 255, 0) - #pixdata[x,y] = tuple(palette[a_pixel:a_pixel+3] + [100]) - elif a_pixel == frames.info['background']: - pixdata[x, y] = (255, 0 , 0, 255) + # pixdata[x,y] = tuple(palette[a_pixel:a_pixel+3] + [100]) + elif a_pixel == frames.info["background"]: + pixdata[x, y] = (255, 0, 0, 255) else: - pixdata[x,y] = tuple(palette[a_pixel:a_pixel+3])# + [255]) - #the_frame.alpha_composite(frames, dest=frames.dispose_extent[0:2], source=frames.dispose_extent) - #the_frame = frames.convert() + pixdata[x, y] = tuple(palette[a_pixel : a_pixel + 3]) # + [255]) + # the_frame.alpha_composite(frames, dest=frames.dispose_extent[0:2], source=frames.dispose_extent) + # the_frame = frames.convert() draw = ImageDraw.Draw(the_frame) - draw.rectangle(frames.dispose_extent, outline=(255,173,0,255)) + draw.rectangle(frames.dispose_extent, outline=(255, 173, 0, 255)) # draw.text((0,0), f"F{i}-M{frames.disposal_method}", font=fnt, fill=(255, 0, 0)) compilation.alpha_composite( - the_frame, + the_frame, dest=( - width * int(i % num_images_width), - height * int(i / num_images_width) - ) - ) + width * int(i % num_images_width), + height * int(i / num_images_width), + ), + ) if i == (num_images_width * num_images_height): - break; + break compilation.show() - compilation.save("compilation.png") + compilation.save("compilation.png") + def GIFconvertBW(frames): width, height = frames.size all_frames = [] corrected_prev_frame = None - pass_frame = Image.new('RGBA', size=frames.size, color=(255,255,255,0)) - #disposal_method_list = [] + pass_frame = Image.new("RGBA", size=frames.size, color=(255, 255, 255, 0)) + # disposal_method_list = [] for i in range(frames.n_frames): frames.seek(i) if i == 4: frames.save(f"frames_{i}.png") disp_frame, pass_frame = method_dispose(i, frames, pass_frame) frames.seek(0) - disp_frame = disp_frame.convert('RGB') + disp_frame = disp_frame.convert("RGB") pixdata = disp_frame.load() - #Change Colors + # Change Colors for x in range(width): for y in range(height): - if pixdata[x,y] != white: - pixdata[x,y] = black + if pixdata[x, y] != white: + pixdata[x, y] = black - disp_frame = disp_frame.convert('P') + disp_frame = disp_frame.convert("P") all_frames.append(disp_frame) - print(f"Save {len(all_frames)} frames in 'test.gif'") all_frames[0].save( - fp="test.gif", - format='GIF', - save_all=True, - append_images=all_frames[1:], + fp="test.gif", + format="GIF", + save_all=True, + append_images=all_frames[1:], optimize=False, - duration=frames.info['duration'], + duration=frames.info["duration"], loop=0, disposal=1, transparency=255, - background=0 - ) + background=0, + ) simpleCollage(Image.open("test.gif"), 12, fit=True) + ########################################## # Frame Process Methods # ########################################## -#crop_frame = current_frame.crop(frames.dispose_extent) -#new_frame.alpha_composite(crop_frame, dest=frames.dispose_extent[0:2]) +# crop_frame = current_frame.crop(frames.dispose_extent) +# new_frame.alpha_composite(crop_frame, dest=frames.dispose_extent[0:2]) + def method_dispose(i, frames, previous_frame): # 0 PIL = Overlay and pass # 1 PIL = Overlay and return previous # 2 PIL = Erase Overlay - new_frame = previous_frame.copy() + new_frame: Image.Image = previous_frame.copy() current_frame = frames.convert() - new_frame.alpha_composite(current_frame, dest=frames.dispose_extent[0:2], source=frames.dispose_extent) + new_frame.alpha_composite( + current_frame, dest=frames.dispose_extent[0:2], source=frames.dispose_extent + ) if frames.disposal_method == 0: - return new_frame, Image.new('RGBA', box=frames.size) + return new_frame, Image.new("RGBA", box=frames.size) elif frames.disposal_method == 1: return new_frame, new_frame.copy() elif frames.disposal_method == 2 or frames.disposal_method == 3: @@ -169,19 +477,20 @@ def method_dispose(i, frames, previous_frame): print("UNKNOWN disposal_method") exit() -def method_sum_check(i, current_frame : Image, previous_frame : Image, size_opt_color): + +def method_sum_check(i, current_frame: Image, previous_frame: Image, size_opt_color): width, height = current_frame.size if previous_frame is None: - disp_frame = current_frame.copy() + disp_frame = current_frame.copy() else: - #Remove borders + # Remove borders pixdata = current_frame.load() if size_opt_color is not None: if pixdata[0, 0] == size_opt_color: for x in range(width): for y in range(height): if pixdata[x, y] == size_opt_color: - pixdata[x,y] = white + (0, ) + pixdata[x, y] = white + (0,) # elif pixdata[x, y][3] == 255: # break; @@ -200,109 +509,110 @@ def method_sum_check(i, current_frame : Image, previous_frame : Image, size_opt if pixdata[x, y][3] == 0: curr_alpha_sum += 1 - percent_diff = (curr_alpha_sum / prev_alpha_sum) * 100.0 - if percent_diff > 109.0: # or percent_diff > 100.0: #percent - #print(f"{i} = {percent_diff:6.2f}%: Check Sum (Prev-{prev_alpha_sum} vs Curr-{curr_alpha_sum})") + if percent_diff > 109.0: # or percent_diff > 100.0: #percent + # print(f"{i} = {percent_diff:6.2f}%: Check Sum (Prev-{prev_alpha_sum} vs Curr-{curr_alpha_sum})") disp_frame = Image.alpha_composite(previous_frame, current_frame) else: - #print(f"\t{i} = {percent_diff:6.2f}%: Check Sum (Prev-{prev_alpha_sum} vs Curr-{curr_alpha_sum})") + # print(f"\t{i} = {percent_diff:6.2f}%: Check Sum (Prev-{prev_alpha_sum} vs Curr-{curr_alpha_sum})") disp_frame = current_frame.copy() - + pre_recolor = disp_frame.copy() return disp_frame, pre_recolor -def method_simple_recolor(current_frame : Image): + +def method_simple_recolor(current_frame: Image): width, height = current_frame.size pixdata = current_frame.load() for x in range(width): for y in range(height): - if pixdata[x,y][3] is 255: - pixdata[x,y] = black + (255,) + if pixdata[x, y][3] == 255: + pixdata[x, y] = black + (255,) else: - pixdata[x,y] = white + (255,) + pixdata[x, y] = white + (255,) return current_frame -def method_gather_colors(current_frame : Image, hitBlackLine = False, colorsToCheck = [transparent]): - current_frame = frames.convert('RGB') + +def method_gather_colors( + current_frame: Image, hitBlackLine=False, colorsToCheck=[transparent] +): + current_frame = frames.convert("RGB") pixdata = current_frame.load() width, height = current_frame.size for x in range(0, width - 1): for y in range(0, height - 1): if hitBlackLine: - if pixdata[x,y] not in colorsToCheck: - pixdata[x,y] = black + if pixdata[x, y] not in colorsToCheck: + pixdata[x, y] = black else: - pixdata[x,y] = white + pixdata[x, y] = white else: - if pixdata[x,y] <= (60,60,60): + if pixdata[x, y] <= (60, 60, 60): hitBlackLine = True - elif pixdata[x,y] not in colorsToCheck: - colorsToCheck.append(pixdata[x,y]) + elif pixdata[x, y] not in colorsToCheck: + colorsToCheck.append(pixdata[x, y]) + ########################################## def main(): - print("Args Entered: {}".format(sys.argv)) + log.debug("Args Entered: {}".format(sys.argv)) # If random pokemon wanted if len(sys.argv) == 1: - poke = getPokemon(str(random.randint(1,200))) - frames = getGIFimage(poke['name']) + poke = getPokemon(str(random.randint(1, 200))) + frames = getGIFimage(poke["name"]) # If specific number wanted elif sys.argv[1].isdigit(): poke = getPokemon(sys.argv[1]) - frames = getGIFimage(poke['name']) + frames = getGIFimage(poke["name"]) # If pokemon name is known else: frames = getGIFimage(sys.argv[1]) - print(frames.info) + log.debug(frames.info) simpleCollage(frames, 12, fit=True) GIFconvertBW(frames) - #import pdb; pdb.set_trace() - - - - -def getStillImg(pokeObj): - response = requests.get(poke['sprites']['front_default']) - print("Obtained Image") - img = Image.open(BytesIO(response.content)) - img.save('original.png') - shape_img = img.convert('RGBA') - pixdata = shape_img.load() - width, height = shape_img.size - hit_pixel = False - for x in range(0, width - 1): - for y in range(0, height - 1): - #Check if color is transparent - if pixdata[x,y] == (0, 0, 0, 0): - if hit_pixel == True: - pixdata[x - 1,y] = white - hit_pixel = False - else: - continue - #If not then is part of image - else: - if hit_pixel == True: - pixdata[x,y] = black - else: - pixdata[x,y] = white - hit_pixel = True - - # if pixdata[x,y] != (0, 0, 0, 0) and hit_pixel == False: - # pixdata[x,y] = (255, 255, 255, 255) - # hit_pixel = True - # elif pixdata[x,y] != (0, 0, 0, 0) and hit_pixel == True: - # pixdata[x,y] = (0, 0, 0, 255) - # elif pixdata[x,y] == (0, 0, 0, 0) and hit_pixel == True: - # pixdata[x-1, y] = (255,255,255,255) - # hit_pixel = False - - - shape_img.save('test_image.png') - - -main() \ No newline at end of file + # import pdb; pdb.set_trace() + + +# def getStillImg(pokeObj): +# response = requests.get(poke['sprites']['front_default']) +# print("Obtained Image") +# img = Image.open(BytesIO(response.content)) +# img.save('original.png') +# shape_img = img.convert('RGBA') +# pixdata = shape_img.load() +# width, height = shape_img.size +# hit_pixel = False +# for x in range(0, width - 1): +# for y in range(0, height - 1): +# #Check if color is transparent +# if pixdata[x,y] == (0, 0, 0, 0): +# if hit_pixel == True: +# pixdata[x - 1,y] = white +# hit_pixel = False +# else: +# continue +# #If not then is part of image +# else: +# if hit_pixel == True: +# pixdata[x,y] = black +# else: +# pixdata[x,y] = white +# hit_pixel = True + +# # if pixdata[x,y] != (0, 0, 0, 0) and hit_pixel == False: +# # pixdata[x,y] = (255, 255, 255, 255) +# # hit_pixel = True +# # elif pixdata[x,y] != (0, 0, 0, 0) and hit_pixel == True: +# # pixdata[x,y] = (0, 0, 0, 255) +# # elif pixdata[x,y] == (0, 0, 0, 0) and hit_pixel == True: +# # pixdata[x-1, y] = (255,255,255,255) +# # hit_pixel = False + +# shape_img.save('test_image.png') + +if __name__ == "__main__": + main() diff --git a/pokemon.py b/pokemon.py new file mode 100644 index 0000000..8f36a17 --- /dev/null +++ b/pokemon.py @@ -0,0 +1,38 @@ +import datetime +import json +import os +import random +import log +import requests + + +class Pokemon: + def __init__(self, id): + self.id = id + self.json = None + + self._fetch() + + def _fetch(self): + url = f"http://pokeapi.co/api/v2/pokemon/{id}/" + # Checks if Pokemon has already been found, if so pulls file + if os.path.isfile("IO Files/Pokemon/Pokemon#{}".format(self.id)): + with open("IO Files/Pokemon/Pokemon#{}".format(self.id), "r") as file: + self.wild_pokemon = json.load(file) + else: + t0 = datetime.now() + self.wild_pokemon = requests.post(url).json() + log.info(f"Obtained Pokemon in {datetime.now() - t0}") + with open(f"IO Files/Pokemon/Pokemon#{self.wild_pokemon['id']}", "w") as file: + json.dump(self.wild_pokemon, file) + + +def from_message(msg: str): + if msg != None: + if msg.isdigit(): + return Pokemon(msg) + else: + return Pokemon(msg.lower()) + else: + poke_num = str(random.randint(1, 200)) + return Pokemon(poke_num) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..57ce6b2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 200 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 84eaf9e..259a40e 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/sql/main.py b/sql/main.py new file mode 100644 index 0000000..088d5bc --- /dev/null +++ b/sql/main.py @@ -0,0 +1,11 @@ +# Built in Python Libraries +import json + +import requests + + +def getPokemonInfo(pokemonValue): + url = f"http://pokeapi.co/api/v2/pokemon/{pokemonValue}/" + wild_pokemon = requests.post(url).json() + with open(f"JSON Pokemon/Pokemon#{wild_pokemon['id']}", "w") as file: + json.dump(wild_pokemon, file, indent=4) diff --git a/sql/tables.py b/sql/tables.py new file mode 100644 index 0000000..3e39702 --- /dev/null +++ b/sql/tables.py @@ -0,0 +1,71 @@ +from datetime import datetime +from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, create_engine, exc +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship, sessionmaker + +Base = declarative_base() + +class Trainer(Base): + __tablename__ = 'trainer' + id = Column(String, primary_key=True) # Discord ID of User + pokemon = relationship('PokemonMapping', back_populates='pokemon') + +class Moves(Base): + __tablename__ = 'moves' + id = Column(Integer, primary_key=True) + name = Column(String) + +class Pokemon(Base): + __tablename__ = 'pokemon' + id = Column(int, primary_key=True) + pokemon = Column(String(length=32)) + type = Column(String) + type2 = Column(String, default=None) + image_path = Column(String) # Path to image + + def fromJson(self, requestJson): + pokeObj = Pokemon( + id=requestJson['id'], + pokemon=requestJson['name'], + type=requestJson['types'][0]['type']['name'], + type2=requestJson['types'][1]['type']['name'] if len(requestJson['types'])==2 else None, + image_path=None + ) + session.add(pokeObj) + session.commit() + +class PokeCord(Base): + __tablename__ = 'pokecord' + discord_server = Column(String, primary_key=True) + channel_bind = Column(String) # Channel to post in + message_id = Column(String) # Message with the wild pokemon + wild_pokemon = Column(id, ForeignKey(Pokemon.id)) # Pokemon that appeared + appear_time = Column(DateTime) # Time to appear + +class PokemonMapping(Base): + __tablename__ = 'pokemon_mapping' + id = Column(int, primary_key=True, autoincrement=True) + obtained = Column(DateTime, default=datetime.today()) + trainer = Column(String, ForeignKey(Trainer.id)) + pokemon = Column(Integer, ForeignKey(Pokemon.id)) + level = Column(Integer, default=5) + exp = Column(Integer, default=0) + nickname = Column(String, default=None) + move1 = Column(Integer, ForeignKey(Moves.id)) + move2 = Column(Integer, ForeignKey(Moves.id)) + move3 = Column(Integer, ForeignKey(Moves.id)) + move4 = Column(Integer, ForeignKey(Moves.id)) + +class ItemMapping(Base): + __tablename__ = 'item_mapping' + id = Column(Integer, primary_key=True, autoincrement=True) + trainer = Column(String, ForeignKey(Trainer.id)) + item = Column(String) + +engine = create_engine('sqlite:///test.db') +#Base.metadata.drop_all(bind=engine) +Base.metadata.create_all(engine) +Base.metadata.bind = engine + +DBSession = sessionmaker(bind=engine) +session = DBSession() \ No newline at end of file diff --git a/var_secrets.py b/var_secrets.py deleted file mode 100644 index 04466c6..0000000 --- a/var_secrets.py +++ /dev/null @@ -1,8 +0,0 @@ -MASTER_ID = None #Your Discord ID (Replace with numbers) - -CLIENT_ID = None #Discord given (Replace with numbers) -CLIENT_SECRET = None #Discord given (Replace with string wrapped in quotes, eg. "[value]") -TOKEN = None #Discord given (Replace with string wrapped in quotes, eg. "[value]") - -imgur_client_id = None #IMGR Given (Replace with string wrapped in quotes, eg. "[value]") -imgur_client_secret = None #IMGR Given (Replace with string wrapped in quotes, eg. "[value]")