250 lines
11 KiB
Python
250 lines
11 KiB
Python
import json
|
|
import random
|
|
|
|
import nextcord
|
|
from nextcord.ext import commands
|
|
import re
|
|
import textrace
|
|
import asyncio
|
|
|
|
ongoing_races = {}
|
|
|
|
bot = commands.Bot(intents=nextcord.Intents.all())
|
|
|
|
class TokenError(Exception):
|
|
pass
|
|
|
|
with open("db.json") as f:
|
|
db = json.load(f)
|
|
|
|
tag_db = db["tag_servers"]
|
|
|
|
# Valid tokens
|
|
condition_tokens = {
|
|
"&",
|
|
"|",
|
|
"^"
|
|
}
|
|
variable_tokens = {
|
|
"msg"
|
|
}
|
|
match_tokens = {
|
|
"has",
|
|
"HAS",
|
|
"startswith",
|
|
"STARTSWITH",
|
|
"hasword",
|
|
"HASWORD",
|
|
"regex",
|
|
"=",
|
|
"=="
|
|
}
|
|
action_tokens = {
|
|
"say": 2,
|
|
"reply": 2,
|
|
"react": 2
|
|
}
|
|
setting_tokens = {
|
|
"check_self": 2
|
|
}
|
|
|
|
def tokenize(multimod: str):
|
|
tokenized = {}
|
|
regex_output = re.findall(r"^\s*(.+?)\s*{\s*(.+)\s*}\s*$", multimod)
|
|
if not regex_output:
|
|
raise TokenError("condition-action structure not found")
|
|
tokenized["condition"] = regex_output[0][0]
|
|
tokenized["action"] = regex_output[0][1]
|
|
if tokenized["condition"].count("\"") % 2 or tokenized["condition"].count("'") % 2:
|
|
raise TokenError("condition contains unclosed quotes")
|
|
tokenized["condition"] = re.findall(r"\"[^\"]*\"|'[^\"]*'|[|&^]|[^\s|&^\"]+", tokenized["condition"])
|
|
for i, condition_token in enumerate(tokenized["condition"]):
|
|
tokenized["condition"][i] = condition_token.strip("\"").strip("'")
|
|
grouped = []
|
|
current_group = []
|
|
for token in tokenized["condition"]:
|
|
if token in condition_tokens:
|
|
if current_group:
|
|
grouped.append(current_group)
|
|
current_group = []
|
|
grouped.append(token)
|
|
else:
|
|
current_group.append(token)
|
|
if current_group:
|
|
grouped.append(current_group)
|
|
tokenized["condition"] = grouped
|
|
|
|
if tokenized["action"].count("\"") % 2 or tokenized["action"].count("'") % 2:
|
|
raise TokenError("action contains unclosed quotes")
|
|
tokenized["action"] = re.split(r";\s*", tokenized["action"])
|
|
for i, action in enumerate(tokenized["action"]):
|
|
tokenized["action"][i] = re.findall(r"\"[^\"]*\"|'[^\"]*'|[^\s\"]+", action)
|
|
for i2, action_token in enumerate(tokenized["action"][i]):
|
|
tokenized["action"][i][i2] = action_token.strip("\"").strip("'")
|
|
|
|
# Check that everything is valid
|
|
if type(tokenized["condition"][0]) == str or type(tokenized["condition"][len(tokenized["condition"]) - 1]) == str:
|
|
raise TokenError("unexpected condition token at start or end of condition")
|
|
|
|
for condition_group in tokenized["condition"]:
|
|
if type(condition_group) == list:
|
|
if len(condition_group) != 3:
|
|
raise TokenError(f"3 tokens expected in condition group, got {len(condition_group)}")
|
|
if condition_group[0] not in variable_tokens:
|
|
raise TokenError(f"unknown token where variable token expected: {condition_group[0]}")
|
|
if condition_group[1] not in match_tokens:
|
|
raise TokenError(f"unknown token where match token expected: {condition_group[1]}")
|
|
elif type(condition_group) == str:
|
|
if condition_group not in condition_tokens:
|
|
raise TokenError(f"unknown token where condition token expected: {condition_group}")
|
|
|
|
for action in tokenized["action"]:
|
|
if action[0] not in action_tokens and action[0] not in setting_tokens:
|
|
raise TokenError(f"unknown token where action or setting token expected: {action[0]}")
|
|
if len(action) != action_tokens[action[0]] and len(action) != setting_tokens[action[0]]:
|
|
raise TokenError(f"{action_tokens[action[0]] or setting_tokens[action[0]]} tokens expected in action group, got {len(action)}")
|
|
return tokenized
|
|
|
|
@bot.event
|
|
async def on_ready():
|
|
print(f'We have logged in as {bot.user}')
|
|
await bot.get_channel(1359151976994308219).send("It seems I have woken up in a 2008 Dacia Sandero.")
|
|
|
|
@bot.slash_command()
|
|
async def tag(interaction: nextcord.Interaction):
|
|
pass
|
|
|
|
@tag.subcommand(description="Add a tag")
|
|
async def add(interaction: nextcord.Interaction, tag: str):
|
|
await interaction.response.defer(ephemeral=True)
|
|
try:
|
|
tokenized = tokenize(tag)
|
|
except TokenError as e:
|
|
await interaction.send(f"Malformed tag: {e}")
|
|
else:
|
|
await interaction.send(f"Tag added!\n`{tokenized}`")
|
|
if not str(interaction.guild_id) in tag_db:
|
|
tag_db[str(interaction.guild_id)] = []
|
|
tag_db[str(interaction.guild_id)].append(tokenized)
|
|
with open("db.json", "w") as f:
|
|
json.dump(db, f)
|
|
|
|
@tag.subcommand(description="Remove a tag")
|
|
async def remove(interaction: nextcord.Interaction, tag: int):
|
|
await interaction.response.defer(ephemeral=True)
|
|
if not interaction.user.guild_permissions.manage_guild:
|
|
await interaction.send("You need the Manage Server permission to manage tags")
|
|
return
|
|
if tag_db[str(interaction.guild_id)] and tag_db[str(interaction.guild_id)][tag]:
|
|
await interaction.send(f"Tag removed!\n")
|
|
else:
|
|
await interaction.send(f"No such tag ")
|
|
|
|
@tag.subcommand(name="list", description="List tags")
|
|
async def list_(interaction: nextcord.Interaction):
|
|
await interaction.response.defer(ephemeral=True)
|
|
string = ""
|
|
if tag_db[str(interaction.guild_id)]:
|
|
for i, tag in enumerate(tag_db[str(interaction.guild_id)]):
|
|
string += f"{i}. `{tag}`\n"
|
|
else:
|
|
string = "No tags for this server."
|
|
await interaction.send(embed=nextcord.Embed(color=nextcord.Color.blue(), title="Tag list", description=string))
|
|
|
|
@tag.subcommand(description="Export tags to a txt file")
|
|
async def export(interaction: nextcord.Interaction):
|
|
await interaction.send("Maybe in 2027")
|
|
|
|
@tag.subcommand(name="import", description="Import tags from a txt file")
|
|
async def import_(interaction: nextcord.Interaction, file: nextcord.Attachment):
|
|
await interaction.send("Maybe in 2027")
|
|
|
|
@bot.event
|
|
async def on_message(message: nextcord.Message):
|
|
if message.author != bot.user:
|
|
for guild in tag_db:
|
|
if str(message.guild.id) == guild:
|
|
tags = tag_db[guild]
|
|
for tag in tags:
|
|
for condition_group in tag["condition"]:
|
|
last_operator = None
|
|
condition = None
|
|
if type(condition_group) == list:
|
|
if condition_group[0] == "msg":
|
|
comparing_var = message.content
|
|
|
|
previous_condition = condition
|
|
|
|
if condition_group[1] == "has":
|
|
condition = condition_group[2].lower() in comparing_var.lower()
|
|
if condition_group[1] == "HAS":
|
|
condition = condition_group[2] in comparing_var
|
|
if condition_group[1] == "startswith":
|
|
condition = comparing_var.lower().startswith(condition_group[2].lower())
|
|
if condition_group[1] == "STARTSWITH":
|
|
condition = comparing_var.startswith(condition_group[2])
|
|
if condition_group[1] == "hasword":
|
|
condition = all(elem in comparing_var.lower().split() for elem in condition_group[2].lower().split())
|
|
if condition_group[1] == "HASWORD":
|
|
condition = all(elem in comparing_var.lower().split() for elem in condition_group[2].split())
|
|
if condition_group[1] == "regex":
|
|
condition = bool(re.search(condition_group[2], comparing_var, re.IGNORECASE))
|
|
if condition_group[1] == "REGEX":
|
|
condition = bool(re.search(condition_group[2], comparing_var))
|
|
if condition_group[1] == "=":
|
|
condition = comparing_var.lower() == condition_group[2].lower()
|
|
if condition_group[1] == "==":
|
|
condition = comparing_var == condition_group[2]
|
|
|
|
if last_operator == "|":
|
|
condition = previous_condition or condition
|
|
if last_operator == "&":
|
|
condition = previous_condition and condition
|
|
if last_operator == "^":
|
|
condition = bool(previous_condition) ^ condition
|
|
|
|
elif type(condition_group) == str:
|
|
last_operator = condition_group
|
|
|
|
if condition:
|
|
for action in tag["action"]:
|
|
if action[0] == "say":
|
|
await message.channel.send(action[1])
|
|
if action[0] == "reply":
|
|
await message.reply(action[1])
|
|
if action[0] == "react":
|
|
await message.add_reaction(action[1])
|
|
|
|
if message.channel.id in ongoing_races:
|
|
for race in ongoing_races[message.channel.id]:
|
|
if message.content.lower() == race["answer"].lower():
|
|
ongoing_races.pop(message.channel.id)
|
|
if not "users" in db:
|
|
db["users"] = {}
|
|
if str(message.author.id) not in db["users"]:
|
|
db["users"][str(message.author.id)] = {}
|
|
if "balance" not in db["users"][str(message.author.id)]:
|
|
db["users"][str(message.author.id)]["balance"] = 0
|
|
db["users"][str(message.author.id)]["balance"] = round(db["users"][str(message.author.id)]["balance"] + race["prize"], 2)
|
|
with open("db.json", "w") as f:
|
|
json.dump(db, f)
|
|
|
|
await message.channel.send(f"**{message.author.name}** has won the race! {race["prize"]} internet points have been deposited into **{message.author.name}**'s account.")
|
|
elif random.randint(1, 20) == 1:
|
|
# Start text race
|
|
internet_value, text, image_buffer = textrace.generate_image()
|
|
await message.channel.send(f"**TEXT RACE!**\nType this text in 60 seconds to get {internet_value} internet points!", file=nextcord.File(image_buffer, filename="bumbler.png"))
|
|
if message.channel.id not in ongoing_races:
|
|
ongoing_races[message.channel.id] = []
|
|
ongoing_races[message.channel.id].append({"answer": text, "prize": internet_value})
|
|
await asyncio.sleep(60)
|
|
race_still_going = False
|
|
if message.channel.id in ongoing_races and {"answer": text, "prize": internet_value} in ongoing_races[message.channel.id]:
|
|
ongoing_races.pop(message.channel.id)
|
|
await message.channel.send(f"**Time's up!**\nThe answer was **{text}**.")
|
|
|
|
with open("token.txt") as f:
|
|
token = f.read()
|
|
|
|
bot.run(token)
|