Compare commits

...

23 Commits

Author SHA1 Message Date
18a0090194 Merge pull request 'dev-prodId' (#1) from dev-prodId into main
Reviewed-on: #1
2026-07-29 08:12:56 +00:00
5081b29333 fixed wrong function call in change sheet, again 2026-07-29 10:07:28 +02:00
30e403a14b fixed wrong function call in change sheet 2026-07-29 10:05:38 +02:00
5d01251072 changed channel ID to fix wrong perms. 2026-07-29 09:56:39 +02:00
5f01371866 fuck you 2026-07-28 23:51:59 +02:00
57d5c38a98 fixed bad files path 2026-07-28 23:44:18 +02:00
049ae8f9dd help 2026-07-28 23:42:30 +02:00
c3d574b57c help 2026-07-28 23:39:19 +02:00
9c1f2b8cbc help 2026-07-28 23:37:31 +02:00
cc04cc7a49 help 2026-07-28 23:35:01 +02:00
21563f1aef fixed missing var 2026-07-28 23:29:35 +02:00
c8bb112fc2 added prod channels ID. 2026-07-13 21:38:56 +02:00
9ff9dde701 added safety for carriers name (only allow those in the list in case someone has the funny idea of ignoring the autocomplete. No fingers pointed, of course. Light. 2026-07-04 21:39:47 +02:00
6fdcba7489 removed auto complete for commodities as its simply too slow :( 2026-07-04 21:32:35 +02:00
af336c9785 fixed errored source for still needed commodities. 2026-07-02 00:23:54 +02:00
b204a67eea refactored code into cogs for clarity and ease of use (and more bugs) 2026-07-02 00:19:21 +02:00
d23b49a77e added logger for bot to discord 2026-07-01 21:43:03 +02:00
f175fa90ba removed useless imports 2026-07-01 21:38:58 +02:00
a8d3795fdc changed delivery to only show still needed commodities 2026-07-01 21:36:48 +02:00
744fcb6ce7 carrier state & left to haul state now live in tasks. added bugs. Removed Herobrine. 2026-06-30 21:49:21 +02:00
3415b38e4e tested new way to present left to haul data. 2026-06-29 23:47:18 +02:00
8aa61a109e added bugs, lefttohaul, revamped http get, many more things that will break in fun ways 2026-06-25 00:32:09 +02:00
08e0d41a09 initial commit for automated carrier control (w/ bugs) 2026-06-22 00:23:27 +02:00
4 changed files with 582 additions and 225 deletions

4
.gitignore vendored
View File

@ -2,6 +2,10 @@
*.token *.token
# Last file # Last file
*.last *.last
# name and url files. not dangerous to share, but not needed.
*.name
*.url
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

400
cogs/IdaCogs.py Normal file
View File

@ -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

164
helpers.py Normal file
View File

@ -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()

239
main.py
View File

@ -1,243 +1,32 @@
import discord
from discord.ext import commands 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() bot = commands.Bot()
# various tokens # various tokens
discordToken = readToken("discord.token") discordToken = helpers.readToken("discord.token")
gsheetToken = readToken("sheet.token")
lastCommandFile = "command.last"
# default command to see if the bot has lived # default command to see if the bot has lived
@bot.listen() @bot.listen()
async def on_connect(): async def on_connect():
print("I'm alive, bitch") 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 @bot.event
# guild IDs are both IDA servers async def on_ready():
@bot.slash_command( print("I'm ready, bitch")
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")
#run the damn thing #run the damn thing
bot.run(discordToken)
bot.load_extension("cogs.IdaCogs")
bot.run(discordToken)