Skip to content

Commit

Permalink
ops: Dockerize + Database support
Browse files Browse the repository at this point in the history
Merge pull request #5 from We-Talk-a-Lot/dockerize
  • Loading branch information
Seniru authored May 17, 2023
2 parents 2aa7d38 + ef1e25c commit 7f01ac8
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 75 deletions.
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ COPY . .

RUN pip install --no-cache-dir -r requirements.txt

ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh

CMD ["./script.bash"]
8 changes: 8 additions & 0 deletions database/CustomCommands.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

CREATE TABLE IF NOT EXISTS `CustomCommands` (
`name` varchar(15) NOT NULL,
`runner` varchar(20) NOT NULL,
`source` varchar(40) NOT NULL,
`author` bigint(20),
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
9 changes: 9 additions & 0 deletions database/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM mysql:5.7

WORKDIR /database

ADD *.sql /docker-entrypoint-initdb.d/

EXPOSE 3306
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld"]
4 changes: 4 additions & 0 deletions database/ModData.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS `Warnings` (
`member` bigint(20) NOT NULL,
`reason` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
16 changes: 16 additions & 0 deletions database/Qotd.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS `QOTD` (
`id` int NOT NULL,
`question` VARCHAR(200) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `QOTData` (
`kind` CHAR(6) NOT NULL,
`value` VARCHAR(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `QOTData`
VALUES('latest', '$current_time');

INSERT INTO `QOTData`
VALUES('index', '0');
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
version: "3.3"
services:
db:
container_name: db
build: ./database
restart: always
ports:
- 3306:3306
env_file: ./.env
volumes:
- my-db:/var/lib/mysql

bot:
container_name: bot
depends_on:
- db
build: .
env_file: ./.env
working_dir: /src
volumes:
- ./:/src
volumes:
my-db:
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
aiohttp==3.8.4
aiomysql==0.1.1
aiosignal==1.3.1
aiotfm==1.4.8
async-timeout==4.0.2
Expand All @@ -10,7 +11,9 @@ docstring-parser==0.15
frozenlist==1.3.3
idna==3.4
multidict==6.0.4
mysqlclient==2.1.1
PyMySQL==1.0.3
requests==2.30.0
urllib3<2
urllib3==2.0.2
w3lib==2.1.1
yarl==1.9.2
2 changes: 2 additions & 0 deletions script.bash
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

bash /wait-for-it.sh "db:3306" --timeout=60

function run() {
python -u src/main.py || run
}
Expand Down
77 changes: 41 additions & 36 deletions src/bots/Discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import discord
import requests
import utils
import aiomysql
from data import data

from bots.cmd_handler import commands
Expand Down Expand Up @@ -45,8 +46,13 @@ async def on_ready(self):
self.mod_data = await self.data_channel.fetch_message(data["data"]["mod"])
self.mod_data = json.loads(self.mod_data.content[7:-3])

self.slash = {}#slash.Manager(self)
#DiscordComponents(self)
self.db = await aiomysql.create_pool(
host='db',
port=int(os.getenv("MYSQL_PORT")),
user=os.getenv("MYSQL_USER"),
password=os.getenv("MYSQL_PASSWORD"),
db=os.getenv("MYSQL_DATABASE")
)

await self.start_period_tasks()

Expand All @@ -71,36 +77,46 @@ async def on_message(self, message):
}))

await cmd["f"](args[1:], message, self)
elif args[0] in self.ccmds:
else:

ccmd = self.ccmds[args[0]]
code = requests.get(ccmd["source"]).content.decode("utf-8")
async with self.db.acquire() as conn:
cur = await conn.cursor(aiomysql.DictCursor)
await cur.execute("SELECT * FROM CustomCommands \
WHERE name LIKE %s;",
[ args[0] ]
)

stdin = content[len(args[0]) + 1:]
ccmd = await cur.fetchone()
if not ccmd:
return

code = requests.get(ccmd["source"]).content.decode("utf-8")

res = requests.post(WANDBOX_ENDPOINT + "/compile.json",
data = json.dumps({ "compiler": ccmd["runner"], "code": code, "stdin": "{}\n{}".format(message.author.id, f"'{stdin}'" if stdin else "''") }),
headers = { "content-Type": "application/json" }
)
stdin = content[len(args[0]) + 1:]

if res.status_code != 200:
return await message.reply(":x: | We encountered an internal error. Please try again soon!")
res = requests.post(WANDBOX_ENDPOINT + "/compile.json",
data = json.dumps({ "compiler": ccmd["runner"], "code": code, "stdin": "{}\n{}".format(message.author.id, f"'{stdin}'" if stdin else "''") }),
headers = { "content-Type": "application/json" }
)

res = json.loads(res.content.decode("utf-8"))
if res.status_code != 200:
return await message.reply(":x: | We encountered an internal error. Please try again soon!")

if res["status"] != "0":
return await message.reply(embed = discord.Embed.from_dict({
"title": ":x: Error in the command",
"description": "Contact the author of this command to fix it\n\n**Error log**:\n ```\n{}```".format((res["program_error"] or "")),
"color": 0xcc0000
res = json.loads(res.content.decode("utf-8"))

}))
if res["status"] != "0":
return await message.reply(embed = discord.Embed.from_dict({
"title": ":x: Error in the command",
"description": "Contact the author of this command to fix it\n\n**Error log**:\n ```\n{}```".format((res["program_error"] or "")),
"color": 0xcc0000

}))

res = json.loads(res["program_output"])
if not res:
return await message.reply(":x: | An error occured while running the command. Please ask the author of this command to fix the output format.")
res = json.loads(res["program_output"])
if not res:
return await message.reply(":x: | An error occured while running the command. Please ask the author of this command to fix the output format.")

await message.reply(embed = discord.Embed.from_dict(res))
await message.reply(embed = discord.Embed.from_dict(res))

elif self.user.id in message.raw_mentions:
fact = requests.get("https://uselessfacts.jsph.pl/random.md?language=en", headers = { "User-Agent": "Seniru" }).json()
Expand All @@ -119,17 +135,6 @@ async def on_message(self, message):
}))

async def on_interaction(self, interaction: discord.Interaction):
#await interaction.end(content = "** **")
#if cmd_name in commands and commands[cmd_name]["discord"]:
# cmd = commands[cmd_name]
# interaction.reply = self.main_guild.get_channel(interaction.channel.id).send
# interaction.send = self.main_guild.get_channel(interaction.channel.id).send
# interaction.options = list(map(lambda o: o.value, interaction.command.options))
# interaction.mentions = list(
# map(
# lambda m: self.main_guild.get_member(int(re.match(r".*?(\d+).*", m)[1])),
# filter(lambda o: re.match(r"^<@!?(\d+)>$", o), interaction.options)
# ))
interaction = utils.MockInteraction(interaction, self)

if interaction.type == discord.InteractionType.application_command:
Expand Down Expand Up @@ -207,7 +212,7 @@ async def on_error(self, evt, *args, **kwargs):
if str(r) == "Cannot send a packet to a closed Connection.":
return await commands["restart"]["f"]([], None, self)

await self.get_channel(data["channels"]["staff"]).send(f"`[ERR][DISCORD@evt_{evt}]` ```py\n{traceback.format_exc()}```")
await self.get_channel(data["channels"]["logs"]).send(f"`[ERR][DISCORD@evt_{evt}]` ```py\n{traceback.format_exc()}```")

async def send_verification_key(self, member):
key = utils.generate_random_key(member.id)
Expand Down Expand Up @@ -252,7 +257,7 @@ async def start_period_tasks(self):
await commands[task[0]]["f"](task[1], None, self)
except Exception as e:
import traceback
await self.main_guild.get_channel(data["channels"]["staff"]).send(
await self.main_guild.get_channel(data["channels"]["logs"]).send(
"**`[DAILY TASK FAILURE|{0}]`** \n```py\n{1}```"
.format(task[0].upper(), traceback.format_exc()))
await last_daily_data.edit(content=str(now.timestamp()))
Expand Down
110 changes: 74 additions & 36 deletions src/bots/commands/ccmds.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import aiomysql
import utils

from discord import Embed
Expand All @@ -6,30 +7,40 @@
async def cmds(args, msg, client):
"""Lists all the custom commands
"""
await msg.reply(embed = Embed.from_dict({
"title": "Available custom commands",
"description": "• " + "\n• ".join(client.ccmds.keys()),
"color": 0x2987ba
}))
async with client.db.acquire() as conn:
cur = await conn.cursor()
await cur.execute("SELECT name FROM CustomCommands;")
r = await cur.fetchall()
await msg.reply(embed = Embed.from_dict({
"title": "Available custom commands",
"description": "• " + "\n• ".join(map(lambda c: c[0], r)),
"color": 0x2987ba
}))

async def cmd(args, msg, client):
"""Displays information about a custom command
Args:
command (string): The command
"""
cmd_name = utils.get(args, 0, "")
cmd = client.ccmds.get(cmd_name)
if not cmd:
return await msg.reply(":x: | Cannot find that command")
await msg.reply(embed = Embed.from_dict({
"title": "Custom command info",
"fields": [
{ "name": "Author", "value": "<@!{}>".format(cmd["author"]) },
{ "name": "Source", "value": "[{source}]({source})".format(source = cmd["source"])}
],
"color": 0x2987ba
}))
async with client.db.acquire() as conn:
cur = await conn.cursor(aiomysql.DictCursor)
cmd_name = utils.get(args, 0, "")
await cur.execute(
"SELECT * FROM CustomCommands \
WHERE name LIKE %s;"
, [cmd_name])
cmd = await cur.fetchone()
if not cmd:
return await msg.reply(":x: | Cannot find that command")
await msg.reply(embed = Embed.from_dict({
"title": "Custom command info",
"fields": [
{ "name": "Author", "value": "<@!{}>".format(cmd["author"]) },
{ "name": "Source", "value": "[{source}]({source})".format(source = cmd["source"])}
],
"color": 0x2987ba
}))

async def ccmd(args, msg, client):
"""Creates a new custom command
Expand All @@ -44,12 +55,19 @@ async def ccmd(args, msg, client):
name, compiler, source = args[0], args[1], args[2]
if len(name) > 10:
return await msg.reply(":x: Command name should be less than or equal to 10 characters")
if client.ccmds.get(name):
return await msg.reply("Command **{}** already exists! Please use `> ecmd` to overwrite it!".format(name))

client.ccmds[name] = { "runner": compiler, "source": source, "author": str(msg.author.id) } # stringified id for backward compatibility
await client.update_ccmds()
await msg.reply(":white_check_mark: | Created the command")
async with client.db.acquire() as conn:
cur = await conn.cursor(aiomysql.DictCursor)
try:
await cur.execute(
"INSERT INTO CustomCommands \
VALUES (%s, %s, %s, %s);",
[name, compiler, source, msg.author.id]
)
await conn.commit()
await msg.reply(":white_check_mark: | Created the command")
except Exception as e:
if e.args[0] == 1062:
await msg.reply("Command **{}** already exists! Please use `> ecmd` to overwrite it!".format(name))


async def dcmd(args, msg, client):
Expand All @@ -59,13 +77,23 @@ async def dcmd(args, msg, client):
command (string): Command name
"""
cmd_name = utils.get(args, 0, "")
cmd = client.ccmds.get(cmd_name)
admin_role = client.main_guild.get_role(data["roles"]["admin"])
if not (cmd and (cmd["author"] == str(msg.author.id) or (admin_role in msg.author.roles))):
return await msg.reply(":x: You are not the author of the specified command or the command doesn't exist!")
client.ccmds.pop(cmd_name)
await client.update_ccmds()
await msg.reply(":white_check_mark: | Deleted the command")

async with client.db.acquire() as conn:
cur = await conn.cursor(aiomysql.DictCursor)
await cur.execute("SELECT * FROM CustomCommands \
WHERE name LIKE %s;",
[ cmd_name ]
)
cmd = await cur.fetchone()
if not (cmd and (cmd["author"] == str(msg.author.id) or (admin_role in msg.author.roles))):
return await msg.reply(":x: You are not the author of the specified command or the command doesn't exist!")
await cur.execute("DELETE FROM CustomCommands \
WHERE name LIKE %s;",
[ cmd_name ]
)
await conn.commit()
await msg.reply(":white_check_mark: | Deleted the command")

async def ecmd(args, msg, client):
"""Edits a custom command
Expand All @@ -78,13 +106,23 @@ async def ecmd(args, msg, client):
if len(args) < 3:
return await msg.reply(":x: Failed to edit the command. Please supply all the arguments\nFormat: `> ecmd <name> <compiler> <source>`")
name, compiler, source = args[0], args[1], args[2]
cmd = client.ccmds.get(name)

admin_role = client.main_guild.get_role(data["roles"]["admin"])
if not (cmd and (cmd["author"] == str(msg.author.id) or (admin_role in msg.author.roles))):
return await msg.reply(":x: You are not the author of the specified command or the command doesn't exist!")

async with client.db.acquire() as conn:
cur = await conn.cursor(aiomysql.DictCursor)
await cur.execute("SELECT * FROM CustomCommands \
WHERE name LIKE %s;",
[ name ]
)
cmd = await cur.fetchone()
if not (cmd and (cmd["author"] == msg.author.id or (admin_role in msg.author.roles))):
return await msg.reply(":x: You are not the author of the specified command or the command doesn't exist!")

await cur.execute("UPDATE CustomCommands \
SET runner=%s, source=%s \
WHERE name LIKE %s",
[ compiler, source, name ]
)

client.ccmds[name] = { "runner": compiler, "source": source, "author": str(msg.author.id) } # stringified id for backward compatibility
await client.update_ccmds()
await msg.reply(":white_check_mark: | Editted the command")
await conn.commit()
await msg.reply(":white_check_mark: | Editted the command")
3 changes: 2 additions & 1 deletion src/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"bday": 592742600058994688,
"admin": 592341953547337739,
"stats": 575328568008114176,
"staff": 905746669725962270
"staff": 905746669725962270,
"logs": 760004760048107530
},
"roles": {
"admin": 655909026101723147,
Expand Down
2 changes: 1 addition & 1 deletion src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#sys.path.append("discordslashcommands")
from bots.Discord import Discord

print("[INFO][SYSTME] Starting...")
print("[INFO][SYSTEM] Starting...")

loop = asyncio.get_event_loop()

Expand Down

0 comments on commit 7f01ac8

Please sign in to comment.