diff --git a/.gitignore b/.gitignore index 0031b90..e671378 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ *.token # Last file *.last +# name and url files. not dangerous to share, but not needed. +*.name +*.url + # ---> Python # Byte-compiled / optimized / DLL files diff --git a/cogs/IdaCogs.py b/cogs/IdaCogs.py new file mode 100644 index 0000000..28f27a8 --- /dev/null +++ b/cogs/IdaCogs.py @@ -0,0 +1,400 @@ +import discord +from discord import option +from discord.ext import commands, tasks +import aiohttp +import helpers +from datetime import datetime + +class IdaCogs(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.commodities = [] + self.session = None + self.gsheetToken = helpers.readToken("sheet.token") + self.stationName = helpers.getStationName() + self.stationUrl = helpers.getStationUrl() + self.whatsLeftToHaulChannelId = 1531933188132175943 + self.carrierStatsChannel = 1531775556801269930 + self.debugChannel = 583345534178426911 + + + + print("help") + + @commands.Cog.listener() + async def on_ready(self): + self.session = aiohttp.ClientSession() + self.writeToWhatsLeftToHaul.start() + self.writeTocarrierState.start() + print("I'm ready, cog bitch") + + + + + #auto complete + async def getCarriers(self, ctx: discord.AutocompleteContext): + state = await helpers.getInternalCarrierState(self.session, self.gsheetToken) + carrierList = await helpers.getListOfCarriers(state) + # print(carrierList) + # return [carrier for carriers in carrierList] + return carrierList + + async def getCommodities(self, ctx: discord.AutocompleteContext): + return [commodity for commodity in self.commodities if commodity.lower().startswith(ctx.value.lower())] + + + + # delivery command + # guild IDs are both IDA servers + @discord.slash_command( + name="delivery", + # guild_ids=[401372086746087425], + description= "sends your delivery to the bot!" + ) + + ## sadly the autocomplete is too slow and allow non-authorized values. + #@option("commodity",required=True, + # description="The Commodity you want to deliver", + # autocomplete=getCommodities) + + @option("commodity",required=True, + description = "The Commodity you want to deliver", + choices = [ + "Aluminium", + "Ceramic Composites", + "CMM Composite", + "Computer Components", + "Copper", + "Food Cartridges", + "Fruit and Vegetables", + "Insulating Membrane", + "Liquid Oxygen", + "Medical Diagnostic Equipment", + "Non-Lethal Weapons", + "Polymers", + "Power Generators", + "Semiconductors", + "Steel", + "Superconductors", + "Titanium", + "Water", + "Water Purifiers" + ]) + + @option("quantity",required=True, + description="Please be nice and input a value between 1 and 1326 (new rack update!!!!1!1)", + type=int, min_value=1, max_value=1326) + @option("target", required=True, + description="Are you delivering to a carrier or the target station?", + type=str, choices=['Station', 'Carrier']) + + async def delivery(self, ctx,commodity, quantity,target): + + author = str(ctx.author) + author = author[:author.find(" ")] + + await ctx.defer() + + data = { + "postType":"delivery", + "username":author, + "commodity": commodity, + "quantity": quantity, + "target": target + } + + try: + response = await helpers.postToSheet(self.session, data, self.gsheetToken) + if response == 200: + await ctx.followup.send(f"your delivery of {quantity} of {commodity} to a {target} has been added to the sheet!") + print(f"[{datetime.isoformat(datetime.now())}]{author} delivery of {quantity} of {commodity} to a {target}") + await helpers.writeDeliveryToCommandFile(author, data) + else: + await ctx.followup.send(f"Failed to log delivery (HTTP {response}). Please contact the yellow people if that keeps happening") + except Exception as e: + await ctx.followup.send(f"help : Error: {e}") + + + + # delivery command for misc commodity only + # guild IDs are both IDA servers + @discord.slash_command( + name="misc", + # guild_ids=[401372086746087425], + description= "Used for the commodities we're too lazy to create a sheet for" + ) + async def misc(self, ctx, + quantity: discord.Option(discord.SlashCommandOptionType.integer, description="Please be nice and input a value between 1 and 1326 (new rack update!!!!1!1)", min_value=1, max_value=1326), + target: discord.Option(str, choices=['Station', 'Carrier']) + ): + + author = str(ctx.author) + author = author[:author.find(" ")] + + await ctx.defer() + + data = { + "postType":"delivery", + "username":author, + "commodity": "Misc", + "quantity": quantity, + "target": target + } + + try: + response = await helpers.postToSheet(self.session, data, self.gsheetToken ) + if response == 200: + await ctx.followup.send(f"your delivery of {quantity} of Miscellaneous stuff to a {target} has been added to the sheet!") + print(f"[{datetime.isoformat(datetime.now())}]{author} delivery of {quantity} of Miscellaneous to a {target}") + await helpers.writeDeliveryToCommandFile(author, data) + else: + await ctx.followup.send(f"Failed to log delivery (HTTP {response}). Please contact the yellow people if that keeps happening") + except Exception as e: + await ctx.followup.send(f"help : Error: {e}") + + + + + + @discord.slash_command( + name="last", + # guild_ids=[401372086746087425], + description= "send the last delivery you made again!" + ) + async def last(self, ctx): + + author = str(ctx.author) + author = author[:author.find(" ")] + + data = await helpers.getDataFromDeliveryFile(author) + + await ctx.defer() + + + if data is not None: + try: + response = await helpers.postToSheet(self.session, data, self.gsheetToken) + if response == 200: + await ctx.followup.send(f"your delivery of {data['quantity']} of {data['commodity']} to a {data['target']} has been added to the sheet!") + print(f"[{datetime.isoformat(datetime.now())}] delivery of {data['quantity']} of {data['commodity']} to a {data['target']}") + await helpers.writeDeliveryToCommandFile(author, data) + else: + await ctx.followup.send(f"Failed to log delivery (HTTP {response}). Please contact the yellow people if that keeps happening") + except Exception as e: + await ctx.followup.send(f"help : Error: {e}") + else: + await ctx.followup.send(f"You haven't delivered to this build yet, or I have amnesia.") + + + + + #change url command + @discord.slash_command( + name="change-sheet-url", + # guild_ids=[401372086746087425], + description= "changes the URL of the active sheet, use with caution" + ) + async def changeSheetUrl(self, ctx, + url:discord.Option(discord.SlashCommandOptionType.string, description="the new URL") + ): + + await ctx.defer() + try: + with open("sheet.token", 'w') as f: + print(url) + f.write(url) + + await ctx.followup.send(f"URL has been set to {url}") + self.gsheetToken = helpers.readToken("sheet.token") + await helpers.delLastCommandFile() + print("deleted command.last file content") + + # gets the name and URL from the sheet, then store it in a file so we don't ask the sheet again. + name, url = (await helpers.getFromSheet(self.session, self.gsheetToken, "sheet")).split("|") + + with open("station.name", 'w') as f: + f.write(name) + self.stationName = name + + with open("station.url", 'w') as f: + f.write(url) + self.stationUrl = url + + self.writeToWhatsLeftToHaul.restart() + + except Exception as e: + print(e) + await ctx.followup.send(f"something shat the bed") + + + + + @discord.slash_command( + name="setcarrierstate", + # guild_ids=[401372086746087425], + description= "Sets a carrier state on the sheet" + ) + @option("carrier", description="The carrier you want to edit", autocomplete=getCarriers) + @option("state",required=True, description="The carrier you want to edit", choices=["Loading","Unloading","Full","Empty","Inactive"]) + async def setCarrierState(self, ctx, carrier, state): + response = await helpers.getFromSheet(self.session, self.gsheetToken, "carrier") + + await ctx.defer() + + #transforms the response into a dict (carrier : state) we can use + carrierState = await helpers.parseCarrierState(response) + + if carrier not in carrierState.keys(): + await ctx.followup.send(f"The specified carrier {carrier} is not in the allowed list! Quitting!") + return 0 + + author = str(ctx.author) + + carrierState[carrier] = state + + rawState = await helpers.toRawState(carrierState) + + #print(rawState) + + + i = 1 + text = "Current Carrier State : \n" + + + data = { + "postType":"carrier", + "carrierState": rawState + } + + + try: + response = await helpers.postToSheet(self.session, data, self.gsheetToken) + if response == 200: + await ctx.followup.send(f"The carrier {carrier} has been set to {state}") + print(f"{author} set {carrier} state to {state}") + channel = self.bot.get_channel(self.debugChannel) + await channel.send(f"{author} set {carrier} state to {state}") + + else: + await ctx.followup.send(f"i fucked up ") + except Exception as e: + await ctx.followup.send(f"help : Error: {e}") + + + + + + #writes to the whats left to haul channel ever 60 seconds about.. the stuff left to haul. + #also updates the current commodity List + @tasks.loop(seconds=60) + async def writeToWhatsLeftToHaul(self): + + + response = await helpers.getFromSheet(self.session, self.gsheetToken, "commodity") + + print(response) + commodity, values, initial = response.split("|") + + commodity = commodity.split(",") + values = values.split(",") + initial = initial.split(",") + + + + values = [int(i) for i in values] + initial = [int (i) for i in initial] + + + finalDict = {} + progressDict = {} + neededCommodity = [] + + + for i in range(len(commodity)): + finalDict[commodity[i]] = values[i] + + if initial[i] > 0: + progressDict[commodity[i]] = await helpers.createProgressBar(values[i],initial[i]) + else: + progressDict[commodity[i]] = await helpers.createProgressBar(1,1) + + if values[i] > 0: + neededCommodity.append(commodity[i]) + + #finalDict = dict(sorted(finalDict.items(), key=lambda item: item[1], reverse=True)) + + self.commodities = neededCommodity + + embed = discord.Embed(title="Estimated remaining amounts", + colour=discord.Colour(0xffdd55), + timestamp=datetime.now()) + embed.set_thumbnail(url="https://cdn.discordapp.com/emojis/536647643367997492.png") + + embed2 = discord.Embed(title="Current progress", + colour=discord.Colour(0x002f80), + timestamp=datetime.now()) + embed2.set_thumbnail(url="https://cdn.discordapp.com/emojis/536647643367997492.png") + + + + for key in finalDict: + if finalDict[key] > 0: + embed.add_field(name=key, value=finalDict[key], inline=True) + embed2.add_field(name=key, value=str(progressDict[key]), inline=True) + + + embed2.set_footer(text="This message updates every 60 seconds(ish). Last updated") + embed.set_author(name=u'\U0001F517 ' + self.stationName + u' \U0001F517', + url=self.stationUrl) + embed2.set_author(name=u'\U0001F517 ' + self.stationName + u' \U0001F517', + url=self.stationUrl) + + + channel = self.bot.get_channel(self.whatsLeftToHaulChannelId) + + found = False + async for oldmsg in channel.history(limit=2): + if oldmsg.author == self.bot.user: + found = True + await oldmsg.edit(embeds=[embed, embed2]) + break + if not found: + await channel.send(embeds=[embed, embed2]) + + + + + + #writes the carriers state to... the carrier state channel. Seriously, who is writing this? + @tasks.loop(seconds=60) + async def writeTocarrierState(self): + response = await helpers.getInternalCarrierState(self.session, self.gsheetToken) + carrierState = await helpers.parseCarrierState(response) + + + channel = self.bot.get_channel(self.carrierStatsChannel) + + embed = discord.Embed(title="Carriers State", + colour=discord.Colour(0x29ac08), + timestamp=datetime.now()) + embed.set_thumbnail(url="https://cdn.discordapp.com/emojis/536647643367997492.png") + embed.set_author(name=u'\U0001F517 ' + self.stationName + u' \U0001F517', + url=self.stationUrl) + + embed.set_footer(text="This message updates every 60 seconds(ish). Last updated") + + for carrier, state in carrierState.items(): + embed.add_field(name=carrier, value=state, inline=True) + + + found = False + async for oldmsg in channel.history(limit=2): + if oldmsg.author == self.bot.user: + found = True + await oldmsg.edit(embed=embed) + break + if not found: + await channel.send(embed=embed) + +def setup(bot): # this is called by Pycord to setup the cog + bot.add_cog(IdaCogs(bot)) # add the cog to the bot \ No newline at end of file diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..51ffc5c --- /dev/null +++ b/helpers.py @@ -0,0 +1,164 @@ +import json +import os +import ast +from math import floor + +import discord + + +lastCommandFile = "command.last" + +### reads token files +def readToken(filePath): + with open(filePath, 'r') as f: + return f.readline() + + +def getStationName(): + #there's a small chance the files don't exist. If they don't we create an empty file. + try: + with open("station.name", 'r') as f: + return f.readline() + except FileNotFoundError: + open("station.name", 'w').close() + return "REPLACEME" + +def getStationUrl(): + try: + with open("station.url",'r') as f: + return f.readline() + except FileNotFoundError: + open("station.url", 'w').close() + return "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + +### sends a get request to the sheet +async def getFromSheet(session, gsheetToken, data): + + params = {"type":data} + + async with session.get(gsheetToken, params=params) as response: + #print("Status:", response.status) + text = await response.text() + #print(text) + return text + + +### takes a json, returns a http code +async def postToSheet(session, data, gsheetToken): + async with session.post(gsheetToken, json=data) as response: + text = await response.text() + #print(text) + #print("Status:", response.status) + return response.status + + +### carrier state parser, takes the values from the sheet and turn it into something we can use + +async def parseCarrierState(state): + splitState = state.split(",") + + carrierDict = {} + + for i in range(0,len(splitState),2): + carrier = splitState[i] + state = splitState[i+1] + if carrier != "" and state != "": + carrierDict[carrier] = state + + return carrierDict + +### converts a dict to the sheet syntax (CSV) +async def toRawState(carrierDict :dict[str, str]): + + rawState = [] + + for key,value in carrierDict.items(): + rawState.append([key]) + rawState.append([value]) + + rawLen = len(rawState) + + for i in range(rawLen, 41): + rawState.append([""]) + + return rawState + + +async def getListOfCarriers(state): + splitState = state.split(",") + carrierList = [] + for i in range(0,len(splitState),2): + carrier = splitState[i] + if carrier != "": + carrierList.append(carrier) + + return carrierList + +### used to query the carrier state internally. Why did I make this???? +async def getInternalCarrierState(session, gsheetToken): + response = await getFromSheet(session, gsheetToken, "carrier") + return response + + + + +###creates a nice progress bar from two values +async def createProgressBar(a,b): + #print(a, b) + ratio = floor(((b-a)/b) * 10) + + #print(ratio) + + progressBar = f"{'◼'* ratio}{'◻'*(10-ratio)} {ratio*10}%" + + return progressBar + + + +#creates the file if it doesn't exist. stores last command used by... users on this delivery. +async def writeDeliveryToCommandFile(author, command): + + #shitty hack to see if the file is empty + path = os.getcwd() + "/" + lastCommandFile + empty = os.stat(path).st_size < 1 + + with open(lastCommandFile, 'r') as f: + if empty: + jsonContent = json.loads("{}") + + else: + jsonContent = json.load(f) + + + + with open(lastCommandFile, 'w') as f: + jsonContent[author] = command + json.dump(jsonContent, f) + +async def getDataFromDeliveryFile(author): + path = os.getcwd() + "/" + lastCommandFile + empty = os.stat(path).st_size < 1 + + with open(lastCommandFile, 'r') as f: + if empty: + return None + + else: + jsonContent = json.load(f) + if author in jsonContent.keys(): + return jsonContent[author] + + else: + return None + + + + + +async def delLastCommandFile(): + open(lastCommandFile, 'w').close() + + + + diff --git a/main.py b/main.py index 524ba6e..fb69cf4 100644 --- a/main.py +++ b/main.py @@ -1,243 +1,32 @@ -import discord from discord.ext import commands -import requests -import json -from datetime import datetime as dt -import aiohttp -import os -import asyncio - - -### reads token files -def readToken(filePath): - with open(filePath, 'r') as f: - return f.readline() - - - - -### takes a json, returns a http code -async def postToSheet(data, gsheetToken): - async with aiohttp.ClientSession() as session: - async with session.post(gsheetToken, json=data) as response: - # print("Status:", response.status) - return response.status - - - - -#creates the file if it doesn't exist. stores last command used by... users on this delivery. -async def writeDeliveryToCommandFile(author, command): - - #shitty hack to see if the file is empty - path = os.getcwd() + "/" + lastCommandFile - empty = os.stat(path).st_size < 1 - - with open(lastCommandFile, 'r') as f: - if empty: - jsonContent = json.loads("{}") - - else: - jsonContent = json.load(f) - - - - with open(lastCommandFile, 'w') as f: - jsonContent[author] = command - json.dump(jsonContent, f) - -async def getDataFromDeliveryFile(author): - path = os.getcwd() + "/" + lastCommandFile - empty = os.stat(path).st_size < 1 - - with open(lastCommandFile, 'r') as f: - if empty: - return None - - else: - jsonContent = json.load(f) - if author in jsonContent.keys(): - return jsonContent[author] - - else: - return None - - - - - -async def delLastCommandFile(): - open(lastCommandFile, 'w').close() - +import helpers bot = commands.Bot() # various tokens -discordToken = readToken("discord.token") -gsheetToken = readToken("sheet.token") -lastCommandFile = "command.last" +discordToken = helpers.readToken("discord.token") + + # default command to see if the bot has lived @bot.listen() async def on_connect(): print("I'm alive, bitch") -# delivery command -# guild IDs are both IDA servers -@bot.slash_command( - name="delivery", - # guild_ids=[401372086746087425], - description= "sends your delivery to the bot!" -) -async def delivery(ctx, - commodity:discord.Option(str, choices=[ - "Aluminium", - "Ceramic Composites", - "CMM Composite", - "Computer Components", - "Copper", - "Food Cartridges", - "Fruit and Vegetables", - "Insulating Membrane", - "Liquid Oxygen", - "Medical Diagnostic Equipment", - "Non-Lethal Weapons", - "Polymers", - "Power Generators", - "Semiconductors", - "Steel", - "Superconductors", - "Titanium", - "Water", - "Water Purifiers" - ]), - quantity: discord.Option(discord.SlashCommandOptionType.integer, description="Please be nice and input a value between 1 and 1326 (new rack update!!!!1!1)", min_value=1, max_value=1326), - target: discord.Option(str, choices=['Station', 'Carrier']) - ): - - author = str(ctx.author) - author = author[:author.find(" ")] - - await ctx.defer() - - data = { - "username":author, - "commodity": commodity, - "quantity": quantity, - "target": target - } - - try: - response = await postToSheet(data, gsheetToken) - if response == 200: - await ctx.followup.send(f"your delivery of {quantity} of {commodity} to a {target} has been added to the sheet!") - print(f"[{dt.isoformat(dt.now())}]{author} delivery of {quantity} of {commodity} to a {target}") - await writeDeliveryToCommandFile(author, data) - else: - await ctx.followup.send(f"Failed to log delivery (HTTP {response}). Please contact the yellow people if that keeps happening") - except Exception as e: - await ctx.followup.send(f"help : Error: {e}") -# delivery command for misc commodity only -# guild IDs are both IDA servers -@bot.slash_command( - name="misc", - # guild_ids=[401372086746087425], - description= "Used for the commodities we're too lazy to create a sheet for" -) -async def misc(ctx, - quantity: discord.Option(discord.SlashCommandOptionType.integer, description="Please be nice and input a value between 1 and 1326 (new rack update!!!!1!1)", min_value=1, max_value=1326), - target: discord.Option(str, choices=['Station', 'Carrier']) - ): - - author = str(ctx.author) - author = author[:author.find(" ")] - - await ctx.defer() - - data = { - "username":author, - "commodity": "Miscellaneous", - "quantity": quantity, - "target": target - } - - try: - response = await postToSheet(data, gsheetToken) - if response == 200: - await ctx.followup.send(f"your delivery of {quantity} of Miscellaneous stuff to a {target} has been added to the sheet!") - print(f"[{dt.isoformat(dt.now())}]{author} delivery of {quantity} of Miscellaneous to a {target}") - await writeDeliveryToCommandFile(author, data) - else: - await ctx.followup.send(f"Failed to log delivery (HTTP {response}). Please contact the yellow people if that keeps happening") - except Exception as e: - await ctx.followup.send(f"help : Error: {e}") - - - - -@bot.slash_command( - name="last", - # guild_ids=[401372086746087425], - description= "send the last delivery you made again!" -) -async def last(ctx): - - author = str(ctx.author) - author = author[:author.find(" ")] - - data = await getDataFromDeliveryFile(author) - - await ctx.defer() - - - if data is not None: - try: - response = await postToSheet(data, gsheetToken) - if response == 200: - await ctx.followup.send(f"your delivery of {data['quantity']} of {data['commodity']} to a {data['target']} has been added to the sheet!") - print(f"[{dt.isoformat(dt.now())}] delivery of {data['quantity']} of {data['commodity']} to a {data['target']}") - await writeDeliveryToCommandFile(author, data) - else: - await ctx.followup.send(f"Failed to log delivery (HTTP {response}). Please contact the yellow people if that keeps happening") - except Exception as e: - await ctx.followup.send(f"help : Error: {e}") - else: - await ctx.followup.send(f"You haven't delivered to this build yet, or I have amnesia.") - - - - - - - -#change url command -@bot.slash_command( - name="change-sheet-url", - # guild_ids=[401372086746087425], - description= "changes the URL of the active sheet, use with caution" -) -async def changeSheetUrl(ctx, - url:discord.Option(discord.SlashCommandOptionType.string, description="the new URL") - ): - global gsheetToken - await ctx.defer() - try: - with open("sheet.token",'w') as f: - f.write(url) - - await ctx.followup.send(f"URL has been set to {url}") - gsheetToken = readToken("sheet.token") - await delLastCommandFile() - print("deleted command.last file content") - except Exception as e: - await ctx.followup.send(f"something shat the bed") - - +@bot.event +async def on_ready(): + print("I'm ready, bitch") #run the damn thing -bot.run(discordToken) \ No newline at end of file + + +bot.load_extension("cogs.IdaCogs") +bot.run(discordToken) + + +