Compare commits
12 Commits
e98cb67f5f
...
current
| Author | SHA1 | Date | |
|---|---|---|---|
| d77ff6b216 | |||
| b1fda50d4c | |||
| 05c7b9151c | |||
| f1e0a7cdf8 | |||
| 050277ac8b | |||
| 9c6c324b02 | |||
| d7065cf0d6 | |||
| b0c6eedba9 | |||
| 77690b57c6 | |||
| b273d8035a | |||
| a4ec39e4a2 | |||
| 901cf575c4 |
+3
-2
@@ -3,8 +3,8 @@ target
|
|||||||
/venv
|
/venv
|
||||||
.cargo
|
.cargo
|
||||||
.idea
|
.idea
|
||||||
web/static/index.html
|
static/index.html
|
||||||
web/static/assets
|
static/assets
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
@@ -28,3 +28,4 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
.junie
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "DELETE FROM patreon_link WHERE user_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "0402e16b1ec89a96d893d43f6b40500ccbde3c619116a702c87954df49898e23"
|
||||||
|
}
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "\n UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "09f6269e5df3acc01e8e2660532b5fcab21c0cd4fd126b580176d24578932d7e"
|
|
||||||
}
|
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "\n SELECT IFNULL(timezone, 'UTC') AS timezone\n FROM users\n WHERE timezone IS NOT NULL\n GROUP BY timezone\n ORDER BY COUNT(timezone) DESC\n LIMIT 21\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "timezone",
|
||||||
|
"type_info": {
|
||||||
|
"type": "VarString",
|
||||||
|
"flags": "NOT_NULL",
|
||||||
|
"max_size": 128
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "19bc60a2ff67ce6e169985a76405af51d7d16d4d7b84d1c239de5af79da93268"
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "\n DELETE FROM reminders\n WHERE `utc_time` < NOW() - INTERVAL ? DAY\n AND status != 'pending'\n ORDER BY `utc_time`\n LIMIT 1000\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "2d780695fe98347ea4ab2cb745462f0a9c55cf913c71d4d822b91958f4f8a729"
|
||||||
|
}
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "\n UPDATE reminders\n INNER JOIN `channels`\n ON `channels`.id = reminders.channel_id\n SET reminders.`utc_time` = reminders.`utc_time` + ?\n WHERE channels.`channel` = ?\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "2db489e076c93a5a2baf2dd48eb3278d68296aea93097a642e2bbb5112d51fe8"
|
|
||||||
}
|
|
||||||
-134
@@ -1,134 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n LEFT JOIN\n channels\n ON\n channels.id = reminders.channel_id\n WHERE\n `status` = 'pending' AND\n FIND_IN_SET(channels.channel, ?)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "uid",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
|
||||||
"max_size": 256
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "channel",
|
|
||||||
"type_info": {
|
|
||||||
"type": "LongLong",
|
|
||||||
"flags": "UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
|
||||||
"max_size": 20
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "utc_time",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Datetime",
|
|
||||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
|
||||||
"max_size": 19
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "interval_seconds",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "interval_days",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 6,
|
|
||||||
"name": "interval_months",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "expires",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Datetime",
|
|
||||||
"flags": "BINARY",
|
|
||||||
"max_size": 19
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 8,
|
|
||||||
"name": "enabled",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Tiny",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 9,
|
|
||||||
"name": "content",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 10,
|
|
||||||
"name": "embed_description",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 11,
|
|
||||||
"name": "set_by",
|
|
||||||
"type_info": {
|
|
||||||
"type": "LongLong",
|
|
||||||
"flags": "MULTIPLE_KEY | UNSIGNED",
|
|
||||||
"max_size": 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "3695f95cea95c075b2b3becdf1b5d75bf1ccace3b9a176086faa4ad76c0a0fbd"
|
|
||||||
}
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "\n UPDATE reminders\n INNER JOIN `channels`\n ON `channels`.id = reminders.channel_id\n SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)\n WHERE FIND_IN_SET(channels.`channel`, ?)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "65496ff85dd92b5aaf12519628afdc16ca7d70131744c9c53880dc56b92991d9"
|
|
||||||
}
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "\n DELETE FROM todos WHERE FIND_IN_SET(id, ?)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "6e00a27fa770d1aa8cac48cd8878e712ef536c67eeb4fb9a4a801459ada35715"
|
|
||||||
}
|
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "SELECT user_id FROM patreon_link WHERE guild_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "user_id",
|
||||||
|
"type_info": {
|
||||||
|
"type": "LongLong",
|
||||||
|
"flags": "NOT_NULL | PRIMARY_KEY | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||||
|
"max_size": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "92cdd6af01e398b22112ffe88b9ff63d9cc61faaf0dee9eda974efbc8bf84173"
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "\n DELETE FROM reminders\n WHERE `utc_time` < NOW() - INTERVAL ? DAY\n ORDER BY `utc_time`\n LIMIT 1000\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "93897198be27266cd9de90063ee67594cf65c1216c9b9787fc96cd8ffcc1cdef"
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "\n UPDATE reminders\n INNER JOIN `channels`\n ON `channels`.id = reminders.channel_id\n SET reminders.`utc_time` = reminders.`utc_time` + ?\n WHERE channels.`channel` = ?\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "9b871f08d294555453696808185c6d29d4753619fbee6295a053cefaa9dcc0ae"
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "INSERT INTO patreon_link (user_id, guild_id, linked_at) VALUES (?, ?, NOW())\n ON DUPLICATE KEY UPDATE guild_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "a647934dc5485cfbf430c77b71f7b181f888b0961d5274621e5e1dd76417080e"
|
||||||
|
}
|
||||||
-264
@@ -1,264 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "\n SELECT\n reminders.attachment_name,\n reminders.avatar,\n channels.channel,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n IFNULL(reminders.embed_fields, '[]') AS embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.restartable,\n reminders.tts,\n reminders.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n INNER JOIN channels ON channels.id = reminders.channel_id\n WHERE `status` = 'pending' AND FIND_IN_SET(channels.channel, ?)",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "attachment_name",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 1040
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "avatar",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "channel",
|
|
||||||
"type_info": {
|
|
||||||
"type": "LongLong",
|
|
||||||
"flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
|
||||||
"max_size": 20
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "content",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "embed_author",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1024
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "embed_author_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 6,
|
|
||||||
"name": "embed_color",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "NOT_NULL | UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "embed_description",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 8,
|
|
||||||
"name": "embed_footer",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 9,
|
|
||||||
"name": "embed_footer_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 10,
|
|
||||||
"name": "embed_image_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 11,
|
|
||||||
"name": "embed_thumbnail_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 12,
|
|
||||||
"name": "embed_title",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1024
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 13,
|
|
||||||
"name": "embed_fields",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL | BINARY",
|
|
||||||
"max_size": 4294967292
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 14,
|
|
||||||
"name": "enabled",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Tiny",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 15,
|
|
||||||
"name": "expires",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Datetime",
|
|
||||||
"flags": "BINARY",
|
|
||||||
"max_size": 19
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 16,
|
|
||||||
"name": "interval_seconds",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 17,
|
|
||||||
"name": "interval_days",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 18,
|
|
||||||
"name": "interval_months",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 19,
|
|
||||||
"name": "name",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 20,
|
|
||||||
"name": "restartable",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Tiny",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 21,
|
|
||||||
"name": "tts",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Tiny",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 22,
|
|
||||||
"name": "uid",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
|
||||||
"max_size": 256
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 23,
|
|
||||||
"name": "username",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 128
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 24,
|
|
||||||
"name": "utc_time",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Datetime",
|
|
||||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
|
||||||
"max_size": 19
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "af5bf4c6b30ffd316ecebc2dd53554e41f0d4f40cad63736930d20cb18159b38"
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "\n SELECT IFNULL(timezone, 'UTC') AS timezone\n FROM users\n WHERE timezone IS NOT NULL\n GROUP BY timezone\n ORDER BY COUNT(timezone) DESC\n LIMIT 21\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "timezone",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 128
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 0
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "bbdd4bd7ebffb97efab8ba7e829159e104615260929341ec0e961b4d5cd6ca0c"
|
|
||||||
}
|
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "MySQL",
|
"db_name": "MySQL",
|
||||||
"query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n `status` = 'pending' AND\n channels.channel = ? AND\n FIND_IN_SET(reminders.enabled, ?)\n ORDER BY\n reminders.utc_time\n ",
|
"query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n `status` = 'pending' AND\n channels.channel = ? AND\n reminders.enabled >= ?\n ORDER BY\n reminders.utc_time\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -130,5 +130,5 @@
|
|||||||
true
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "d60ea641070dbd882cd53878fa109d08bd3f65e0da8c263e78fe0b200228bc2b"
|
"hash": "d2921961627fef0e12892dbbcd4b891e58c0e52c20897aab3d95365774c01bda"
|
||||||
}
|
}
|
||||||
-264
@@ -1,264 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "MySQL",
|
|
||||||
"query": "SELECT\n reminders.attachment,\n reminders.attachment_name,\n reminders.avatar,\n CONCAT('#', channels.channel) AS channel,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.restartable,\n reminders.tts,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE FIND_IN_SET(channels.channel, ?) AND status = 'pending'",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "attachment",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Blob",
|
|
||||||
"flags": "BLOB | BINARY",
|
|
||||||
"max_size": 16777215
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "attachment_name",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 1040
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "avatar",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "channel",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 84
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "content",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "embed_author",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1024
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 6,
|
|
||||||
"name": "embed_author_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "embed_color",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "NOT_NULL | UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 8,
|
|
||||||
"name": "embed_description",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 9,
|
|
||||||
"name": "embed_footer",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 8192
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 10,
|
|
||||||
"name": "embed_footer_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 11,
|
|
||||||
"name": "embed_image_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 12,
|
|
||||||
"name": "embed_thumbnail_url",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 2048
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 13,
|
|
||||||
"name": "embed_title",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1024
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 14,
|
|
||||||
"name": "embed_fields",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Json",
|
|
||||||
"flags": "BLOB | BINARY",
|
|
||||||
"max_size": 4294967295
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 15,
|
|
||||||
"name": "enabled",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Tiny",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 16,
|
|
||||||
"name": "expires",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Datetime",
|
|
||||||
"flags": "BINARY",
|
|
||||||
"max_size": 19
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 17,
|
|
||||||
"name": "interval_seconds",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 18,
|
|
||||||
"name": "interval_days",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 19,
|
|
||||||
"name": "interval_months",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Long",
|
|
||||||
"flags": "UNSIGNED",
|
|
||||||
"max_size": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 20,
|
|
||||||
"name": "name",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 21,
|
|
||||||
"name": "restartable",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Tiny",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 22,
|
|
||||||
"name": "tts",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Tiny",
|
|
||||||
"flags": "NOT_NULL",
|
|
||||||
"max_size": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 23,
|
|
||||||
"name": "username",
|
|
||||||
"type_info": {
|
|
||||||
"type": "VarString",
|
|
||||||
"flags": "",
|
|
||||||
"max_size": 128
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 24,
|
|
||||||
"name": "utc_time",
|
|
||||||
"type_info": {
|
|
||||||
"type": "Datetime",
|
|
||||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
|
||||||
"max_size": 19
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "db69def9391283efb9bf915223d7d6b2d169203b7dc45481a509137f7590d9a6"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Caddy v2 configuration for local development (HTTPS on localhost)
|
||||||
|
# Reverse-proxy to the bot service and serve static assets.
|
||||||
|
# Uses Caddy's internal CA to generate a self-signed certificate for localhost.
|
||||||
|
|
||||||
|
# HTTP -> HTTPS redirect for local development
|
||||||
|
:80 {
|
||||||
|
redir https://localhost{uri}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Local HTTPS site with self-signed cert
|
||||||
|
localhost {
|
||||||
|
encode zstd gzip
|
||||||
|
# Issue a locally-trusted certificate via Caddy's internal CA
|
||||||
|
tls internal
|
||||||
|
|
||||||
|
# Serve static files under /static from the mounted volume
|
||||||
|
handle_path /static* {
|
||||||
|
root * /var/www/reminder-rs/static
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy everything else to the bot service inside the Docker network
|
||||||
|
reverse_proxy bot:18920
|
||||||
|
}
|
||||||
Generated
+1
-1
@@ -2623,7 +2623,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reminder-rs"
|
name = "reminder-rs"
|
||||||
version = "1.7.41"
|
version = "1.7.44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reminder-rs"
|
name = "reminder-rs"
|
||||||
version = "1.7.41"
|
version = "1.7.44"
|
||||||
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0 only"
|
license = "AGPL-3.0 only"
|
||||||
|
|||||||
+16
-10
@@ -1,11 +1,10 @@
|
|||||||
FROM ubuntu:24.04
|
FROM alpine:latest AS build
|
||||||
|
|
||||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||||
CARGO_HOME=/usr/local/cargo \
|
CARGO_HOME=/usr/local/cargo \
|
||||||
PATH=/usr/local/cargo/bin:$PATH
|
PATH=/usr/local/cargo/bin:$PATH
|
||||||
|
|
||||||
RUN apt update
|
RUN apk add gcc build-base pkgconfig openssl-dev openssl-libs-static curl mysql-client npm
|
||||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt install -y gcc gcc-multilib pkg-config libssl-dev curl mysql-client-8.0 npm
|
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain nightly
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain nightly
|
||||||
|
|
||||||
WORKDIR /usr/src/reminder-rs
|
WORKDIR /usr/src/reminder-rs
|
||||||
@@ -17,13 +16,7 @@ COPY ./conf ./conf
|
|||||||
COPY ./extract_derive ./extract_derive
|
COPY ./extract_derive ./extract_derive
|
||||||
COPY ./migrations ./migrations
|
COPY ./migrations ./migrations
|
||||||
COPY ./recordable_derive ./recordable_derive
|
COPY ./recordable_derive ./recordable_derive
|
||||||
COPY ./reminder-dashboard/public ./reminder-dashboard/public
|
COPY ./reminder-dashboard ./reminder-dashboard
|
||||||
COPY ./reminder-dashboard/src ./reminder-dashboard/src
|
|
||||||
COPY ./reminder-dashboard/index.html ./reminder-dashboard/
|
|
||||||
COPY ./reminder-dashboard/package.json ./reminder-dashboard/
|
|
||||||
COPY ./reminder-dashboard/package-lock.json ./reminder-dashboard/
|
|
||||||
COPY ./reminder-dashboard/tsconfig.json ./reminder-dashboard/
|
|
||||||
COPY ./reminder-dashboard/vite.config.ts ./reminder-dashboard/
|
|
||||||
COPY ./src ./src
|
COPY ./src ./src
|
||||||
COPY ./static ./static
|
COPY ./static ./static
|
||||||
COPY ./templates ./templates
|
COPY ./templates ./templates
|
||||||
@@ -32,6 +25,19 @@ COPY ./Cargo.lock ./
|
|||||||
COPY ./Cargo.toml ./
|
COPY ./Cargo.toml ./
|
||||||
COPY ./dp.py ./
|
COPY ./dp.py ./
|
||||||
|
|
||||||
|
# Build and install the Rust binary
|
||||||
RUN cargo install --path .
|
RUN cargo install --path .
|
||||||
|
|
||||||
|
FROM alpine:latest AS runtime
|
||||||
|
|
||||||
|
WORKDIR /usr/src/reminder-rs
|
||||||
|
|
||||||
|
COPY --from=build /usr/local/cargo/bin/reminder-rs /usr/local/bin/reminder-rs
|
||||||
|
COPY --from=build /usr/src/reminder-rs/static /usr/src/reminder-rs/static
|
||||||
|
COPY --from=build /usr/src/reminder-rs/templates /usr/src/reminder-rs/templates
|
||||||
|
|
||||||
|
RUN apk add python3 py3-pip
|
||||||
|
RUN pip3 install --no-cache --break-system-packages dateparser
|
||||||
|
|
||||||
|
EXPOSE 18920
|
||||||
CMD ["reminder-rs"]
|
CMD ["reminder-rs"]
|
||||||
|
|||||||
@@ -1,52 +1,78 @@
|
|||||||
# reminder-rs
|
# reminder-rs
|
||||||
|
|
||||||
Reminder Bot for Discord.
|
Reminder Bot for Discord.
|
||||||
|
|
||||||
## How do I use it?
|
## How do I use it?
|
||||||
I offer a hosted version of the bot. You can invite it with: **https://invite.reminder-bot.com**. The catch is that repeating
|
|
||||||
|
I offer a hosted version of the bot. You can invite it with: **https://invite.reminder-bot.com**.
|
||||||
|
The catch is that repeating
|
||||||
reminders are paid on the hosted version of the bot. Keep reading if you want to host it yourself.
|
reminders are paid on the hosted version of the bot. Keep reading if you want to host it yourself.
|
||||||
|
|
||||||
You'll need rustc and cargo for compilation. To run, you'll need Python 3 still (due to no suitable replacement for dateparser in Rust)
|
You'll need rustc and cargo for compilation. To run, you'll need Python 3 still (due to no suitable
|
||||||
|
replacement for dateparser in Rust)
|
||||||
|
|
||||||
### Build APT package
|
### Build APT package
|
||||||
|
|
||||||
Recommended method.
|
Recommended method.
|
||||||
|
|
||||||
By default, this builds targeting Ubuntu 20.04. Modify the Containerfile if you wish to target a different platform. These instructions are written using `podman`, but `docker` should work too.
|
By default, this builds targeting Ubuntu 20.04. Modify the Containerfile if you wish to target a
|
||||||
|
different platform. These instructions are written using `podman`, but `docker` should work too.
|
||||||
|
|
||||||
1. Install container software: `sudo apt install podman`.
|
1. Install container software: `sudo apt install podman`.
|
||||||
2. Install database server: `sudo apt install mysql-server-8.0`. Create a database called `reminders`
|
2. Install database server: `sudo apt install mysql-server-8.0`. Create a database called
|
||||||
|
`reminders`
|
||||||
3. Install SQLx CLI: `cargo install sqlx-cli`
|
3. Install SQLx CLI: `cargo install sqlx-cli`
|
||||||
4. From the source code directory, execute `sqlx migrate run`
|
4. From the source code directory, execute `sqlx migrate run`
|
||||||
5. Build container image: `podman build -t reminder-rs .`
|
5. Build container image: `podman build -t reminder-rs .`
|
||||||
6. Build with podman: `podman run --rm --network=host -v "$PWD":/mnt -w /mnt -e "DATABASE_URL=mysql://user@localhost/reminders" reminder-rs cargo deb`
|
6. Build with podman:
|
||||||
|
`podman run --rm --network=host -v "$PWD":/mnt -w /mnt -e "DATABASE_URL=mysql://user@localhost/reminders" reminder-rs cargo deb`
|
||||||
|
|
||||||
### Compiling for other target
|
### Compiling for other target
|
||||||
|
|
||||||
1. Install requirements:
|
1. Install requirements:
|
||||||
`sudo apt install gcc gcc-multilib cmake libssl-dev build-essential python3-dateparser`
|
`sudo apt install gcc gcc-multilib cmake libssl-dev build-essential python3-dateparser`
|
||||||
2. Install rustup from https://rustup.rs
|
2. Install rustup from https://rustup.rs
|
||||||
3. Install the nightly toolchain: `rustup toolchain default nightly`
|
3. Install the nightly toolchain: `rustup toolchain default nightly`
|
||||||
4. Install database server: `sudo apt install mysql-server-8.0`. Create a database called `reminders`.
|
4. Install database server: `sudo apt install mysql-server-8.0`. Create a database called
|
||||||
|
`reminders`.
|
||||||
5. Install `sqlx-cli`: `cargo install sqlx-cli`.
|
5. Install `sqlx-cli`: `cargo install sqlx-cli`.
|
||||||
6. Run migrations: `sqlx migrate run`.
|
6. Run migrations: `sqlx migrate run`.
|
||||||
7. Set environment variables:
|
7. Set environment variables:
|
||||||
* `DATABASE_URL` - the URL of your MySQL database (`mysql://user[:password]@domain/database`)
|
* `DATABASE_URL` - the URL of your MySQL database (`mysql://user[:password]@domain/database`)
|
||||||
8. Build: `cargo build --release`
|
8. Build: `cargo build --release`
|
||||||
|
|
||||||
|
|
||||||
### Configuring
|
### Configuring
|
||||||
|
|
||||||
Reminder Bot reads a number of environment variables. Some are essential, and others have hardcoded fallbacks. Environment variables can be loaded from a .env file in the working directory.
|
Reminder Bot reads a number of environment variables. Some are essential, and others have hardcoded
|
||||||
|
fallbacks. Environment variables can be loaded from a .env file in the working directory.
|
||||||
|
|
||||||
__Required Variables__
|
__Required Variables__
|
||||||
|
|
||||||
* `DATABASE_URL` - the URL of your MySQL database (`mysql://user[:password]@domain/database`)
|
* `DATABASE_URL` - the URL of your MySQL database (`mysql://user[:password]@domain/database`)
|
||||||
* `DISCORD_TOKEN` - your application's bot user's authorization token
|
* `DISCORD_TOKEN` - your application's bot user's authorization token
|
||||||
|
|
||||||
__Other Variables__
|
__Other Variables__
|
||||||
|
|
||||||
* `MIN_INTERVAL` - default `600`, defines the shortest interval the bot should accept
|
* `MIN_INTERVAL` - default `600`, defines the shortest interval the bot should accept
|
||||||
* `LOCAL_TIMEZONE` - default `UTC`, necessary for calculations in the natural language processor
|
* `LOCAL_TIMEZONE` - default `UTC`, necessary for calculations in the natural language processor
|
||||||
* `SUBSCRIPTION_ROLES` - default `None`, accepts a list of Discord role IDs that are given to subscribed users
|
* `SUBSCRIPTION_ROLES` - default `None`, accepts a list of Discord role IDs that are given to
|
||||||
* `CNC_GUILD` - default `None`, accepts a single Discord guild ID for the server that the subscription roles belong to
|
subscribed users
|
||||||
* `PYTHON_LOCATION` - default `/usr/bin/python3`. Can be changed if your Python executable is located somewhere else
|
* `CNC_GUILD` - default `None`, accepts a single Discord guild ID for the server that the
|
||||||
* `THEME_COLOR` - default `8fb677`. Specifies the hex value of the color to use on info message embeds
|
subscription roles belong to
|
||||||
|
* `PYTHON_LOCATION` - default `/usr/bin/python3`. Can be changed if your Python executable is
|
||||||
|
located somewhere else
|
||||||
|
* `THEME_COLOR` - default `8fb677`. Specifies the hex value of the color to use on info message
|
||||||
|
embeds
|
||||||
|
|
||||||
|
## Running with Docker
|
||||||
|
|
||||||
|
A `compose.yml` file is provided to aid in running the bot agnostically using docker.
|
||||||
|
|
||||||
|
* Populate a `.env` file as in `conf/default.env`
|
||||||
|
* Add the additional variable `ROCKET_SECRET_KEY` with a key generated from
|
||||||
|
`head -c64 /dev/urandom | base64`
|
||||||
|
* Run `docker compose up`
|
||||||
|
|
||||||
|
Please note that this is _not_ production-ready when run via compose. We do not offer a way for
|
||||||
|
backing up of your data, or a way to run the dashboard securely via HTTPS, which is required for
|
||||||
|
OAuth.
|
||||||
|
|||||||
@@ -8,6 +8,26 @@ fn main() {
|
|||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("build")
|
.arg("build")
|
||||||
.current_dir(Path::new("reminder-dashboard"))
|
.current_dir(Path::new("reminder-dashboard"))
|
||||||
|
.env("VITE_VERSION", env!("CARGO_PKG_VERSION"))
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to build NPM");
|
.expect("Failed to build NPM")
|
||||||
|
.wait()
|
||||||
|
.expect("Failed to wait for NPM build");
|
||||||
|
|
||||||
|
Command::new("cp")
|
||||||
|
.arg("reminder-dashboard/dist/index.html")
|
||||||
|
.arg("static/index.html")
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to copy index.html")
|
||||||
|
.wait()
|
||||||
|
.expect("Failed to wait for index.html copy");
|
||||||
|
|
||||||
|
Command::new("cp")
|
||||||
|
.arg("-r")
|
||||||
|
.arg("reminder-dashboard/dist/static/assets")
|
||||||
|
.arg("static/")
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to copy assets")
|
||||||
|
.wait()
|
||||||
|
.expect("Failed to wait for assets copy");
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-14
@@ -1,21 +1,59 @@
|
|||||||
version: '3.3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
bot:
|
|
||||||
build: ./Containerfile.run
|
|
||||||
image: reminder-rs-run
|
|
||||||
restart: always
|
|
||||||
expose:
|
|
||||||
- '80'
|
|
||||||
database:
|
database:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
restart: always
|
restart: always
|
||||||
|
command: --log-bin-trust-function-creators=1
|
||||||
environment:
|
environment:
|
||||||
MYSQL_DATABASE: 'reminders'
|
MYSQL_ROOT_PASSWORD: root
|
||||||
MYSQL_USER: 'reminder-bot'
|
MYSQL_DATABASE: reminders
|
||||||
ports:
|
MYSQL_USER: reminder-bot
|
||||||
- '3306:3306'
|
MYSQL_PASSWORD: password
|
||||||
expose:
|
|
||||||
- '3306'
|
|
||||||
volumes:
|
volumes:
|
||||||
- reminders:/var/lib/mysql
|
- reminders:/var/lib/mysql
|
||||||
|
|
||||||
|
bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile.run
|
||||||
|
image: reminder-rs-run
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: "mysql://reminder-bot:password@database/reminders"
|
||||||
|
DISCORD_TOKEN:
|
||||||
|
PATREON_GUILD_ID:
|
||||||
|
PATREON_ROLE_ID:
|
||||||
|
LOCAL_TIMEZONE:
|
||||||
|
MIN_INTERVAL:
|
||||||
|
ROCKET_SECRET_KEY:
|
||||||
|
ROCKET_ADDRESS: "0.0.0.0"
|
||||||
|
ROCKET_PORT: "18920"
|
||||||
|
REMIND_INTERVAL:
|
||||||
|
OAUTH2_DISCORD_CALLBACK:
|
||||||
|
OAUTH2_CLIENT_ID:
|
||||||
|
OAUTH2_CLIENT_SECRET:
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "18920:18920"
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
image: caddy:2.4.6-alpine
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- bot
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- ./static:/var/www/reminder-rs/static:ro
|
||||||
|
- caddy_data:/data
|
||||||
|
- caddy_config:/config
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
reminders:
|
||||||
|
caddy_data:
|
||||||
|
caddy_config:
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
SET foreign_key_checks = 0;
|
||||||
|
|
||||||
|
-- Drop all old tables
|
||||||
|
DROP TABLE IF EXISTS users_old;
|
||||||
|
DROP TABLE IF EXISTS messages;
|
||||||
|
DROP TABLE IF EXISTS embeds;
|
||||||
|
DROP TABLE IF EXISTS embed_fields;
|
||||||
|
DROP TABLE IF EXISTS command_aliases;
|
||||||
|
DROP TABLE IF EXISTS macro;
|
||||||
|
DROP TABLE IF EXISTS roles;
|
||||||
|
DROP TABLE IF EXISTS command_restrictions;
|
||||||
|
|
||||||
|
SET foreign_key_checks = 1;
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch --mode development",
|
"dev": "vite build --watch --mode development",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"prettier": "prettier -w src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ type Template = {
|
|||||||
embed_fields: EmbedField[] | null;
|
embed_fields: EmbedField[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const USER_INFO_STALE_TIME = 120_000;
|
const USER_INFO_STALE_TIME = 86_400_000;
|
||||||
const GUILD_INFO_STALE_TIME = 300_000;
|
const GUILD_INFO_STALE_TIME = 300_000;
|
||||||
const OTHER_STALE_TIME = 120_000;
|
const OTHER_STALE_TIME = 120_000;
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ export const fetchGuildTodos = (guild: string) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const patchGuildTodo = (guild: string) => ({
|
export const patchGuildTodo = (guild: string) => ({
|
||||||
mutationFn: ({ id, todo }) => axios.patch(`/dashboard/api/guild/${guild}/todos/${id}`, todo),
|
mutationFn: ({id, todo}) => axios.patch(`/dashboard/api/guild/${guild}/todos/${id}`, todo),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const postGuildTodo = (guild: string) => ({
|
export const postGuildTodo = (guild: string) => ({
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { createContext } from "preact";
|
||||||
|
import { useContext } from "preact/compat";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
import { fetchUserInfo } from "../../api";
|
||||||
|
import { useQuery } from "react-query";
|
||||||
|
|
||||||
|
type TColorScheme = "light" | "dark";
|
||||||
|
|
||||||
|
type TColorSchemeContext = {
|
||||||
|
colorScheme: TColorScheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ColorSchemeContext = createContext({ colorScheme: "light" } as TColorSchemeContext);
|
||||||
|
|
||||||
|
export const ColorSchemeProvider = ({ children }) => {
|
||||||
|
const { data } = useQuery({ ...fetchUserInfo() });
|
||||||
|
const [activeScheme, setActiveScheme] = useState<TColorScheme>("light");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const preference = data?.preferences?.dashboard_color_scheme || "system";
|
||||||
|
|
||||||
|
if (preference === "system") {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
const handleChange = () => {
|
||||||
|
setActiveScheme(mediaQuery.matches ? "dark" : "light");
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange();
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
} else {
|
||||||
|
setActiveScheme(preference as TColorScheme);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColorSchemeContext.Provider value={{ colorScheme: activeScheme }}>
|
||||||
|
{children}
|
||||||
|
</ColorSchemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useColorScheme = () => useContext(ColorSchemeContext);
|
||||||
@@ -1,59 +1,66 @@
|
|||||||
import { Sidebar } from "../Sidebar";
|
import {Sidebar} from "../Sidebar";
|
||||||
import { QueryClient, QueryClientProvider } from "react-query";
|
import {QueryClient, QueryClientProvider} from "react-query";
|
||||||
import { Route, Router, Switch } from "wouter";
|
import {Route, Router, Switch} from "wouter";
|
||||||
import { Welcome } from "../Welcome";
|
import {Welcome} from "../Welcome";
|
||||||
import { Guild } from "../Guild";
|
import {Guild} from "../Guild";
|
||||||
import { FlashProvider } from "./FlashProvider";
|
import {FlashProvider} from "./FlashProvider";
|
||||||
import { TimezoneProvider } from "./TimezoneProvider";
|
import {TimezoneProvider} from "./TimezoneProvider";
|
||||||
import { User } from "../User";
|
import {User} from "../User";
|
||||||
import { GuildReminders } from "../Guild/GuildReminders";
|
import {GuildReminders} from "../Guild/GuildReminders";
|
||||||
import { GuildTodos } from "../Guild/GuildTodos";
|
import {GuildTodos} from "../Guild/GuildTodos";
|
||||||
|
import {ColorSchemeProvider, useColorScheme} from "./ColorSchemeProvider";
|
||||||
|
import {useEffect} from "preact/hooks";
|
||||||
|
|
||||||
export function App() {
|
const InnerApp = () => {
|
||||||
const queryClient = new QueryClient();
|
const {colorScheme} = useColorScheme();
|
||||||
|
|
||||||
let scheme = "light";
|
useEffect(() => {
|
||||||
// if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
const body = document.querySelector("body");
|
||||||
// scheme = "dark";
|
body.className = body.className.replace(/scheme-\w+/g, "");
|
||||||
// }
|
body.classList.add(`scheme-${colorScheme}`);
|
||||||
|
}, [colorScheme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<TimezoneProvider>
|
|
||||||
<FlashProvider>
|
|
||||||
<Router base={"/dashboard"}>
|
<Router base={"/dashboard"}>
|
||||||
<div class={`columns is-gapless dashboard-frame scheme-${scheme}`}>
|
<div class={`columns is-gapless dashboard-frame scheme-${colorScheme}`}>
|
||||||
<Sidebar />
|
<Sidebar/>
|
||||||
<div class="column is-main-content">
|
<div class="column is-main-content">
|
||||||
<div style={{ margin: "0 12px 12px 12px" }}>
|
<div style={{margin: "0 12px 12px 12px"}}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={"/@me/reminders"} component={User}></Route>
|
<Route path={"/@me/reminders"} component={User}></Route>
|
||||||
<Route
|
<Route path={"/:guild/reminders"}>
|
||||||
path={"/:guild/reminders"}
|
|
||||||
component={() => (
|
|
||||||
<Guild>
|
<Guild>
|
||||||
<GuildReminders />
|
<GuildReminders/>
|
||||||
</Guild>
|
</Guild>
|
||||||
)}
|
</Route>
|
||||||
></Route>
|
<Route path={"/:guild/todos"}>
|
||||||
<Route
|
|
||||||
path={"/:guild/todos"}
|
|
||||||
component={() => (
|
|
||||||
<Guild>
|
<Guild>
|
||||||
<GuildTodos />
|
<GuildTodos/>
|
||||||
</Guild>
|
</Guild>
|
||||||
)}
|
</Route>
|
||||||
></Route>
|
|
||||||
<Route>
|
<Route>
|
||||||
<Welcome />
|
<Welcome/>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<ColorSchemeProvider>
|
||||||
|
<TimezoneProvider>
|
||||||
|
<FlashProvider>
|
||||||
|
<InnerApp/>
|
||||||
</FlashProvider>
|
</FlashProvider>
|
||||||
</TimezoneProvider>
|
</TimezoneProvider>
|
||||||
|
</ColorSchemeProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {JSX} from "preact";
|
import { JSX } from "preact";
|
||||||
import {createPortal} from "preact/compat";
|
import { createPortal } from "preact/compat";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setModalOpen: (open: boolean) => never;
|
setModalOpen: (open: boolean) => never;
|
||||||
@@ -9,7 +9,7 @@ type Props = {
|
|||||||
children: string | JSX.Element | JSX.Element[] | (() => JSX.Element);
|
children: string | JSX.Element | JSX.Element[] | (() => JSX.Element);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Modal = ({setModalOpen, title, onSubmit, onSubmitText, children}: Props) => {
|
export const Modal = ({ setModalOpen, title, onSubmit, onSubmitText, children }: Props) => {
|
||||||
const body = document.querySelector("body");
|
const body = document.querySelector("body");
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
box-shadow: 0 0 5px 0 rgba(0,0,0,0.75);
|
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.75);
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
background-color: #35373c;
|
background-color: #35373c;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ aside.menu {
|
|||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1023px),print {
|
@media screen and (max-width: 1023px), print {
|
||||||
.columns:not(.is-desktop) {
|
.columns:not(.is-desktop) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ import { useRef, useState } from "preact/hooks";
|
|||||||
import { ICON_FLASH_TIME } from "../../consts";
|
import { ICON_FLASH_TIME } from "../../consts";
|
||||||
import { useFlash } from "../App/FlashContext";
|
import { useFlash } from "../App/FlashContext";
|
||||||
|
|
||||||
|
enum ColorScheme {
|
||||||
|
System = "system",
|
||||||
|
Dark = "dark",
|
||||||
|
Light = "light",
|
||||||
|
}
|
||||||
|
|
||||||
export const UserPreferences = () => {
|
export const UserPreferences = () => {
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
@@ -96,6 +102,40 @@ const PreferencesModal = ({ setModalOpen }) => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<br></br>
|
<br></br>
|
||||||
|
<div style={{ display: "flex", flexDirection: "row", alignContent: "center" }}>
|
||||||
|
<label>
|
||||||
|
<div class={"is-inline-block"} style={{ marginRight: "6px" }}>
|
||||||
|
Dashboard Color Scheme:
|
||||||
|
</div>
|
||||||
|
<div class={"control"}>
|
||||||
|
<div class={"is-inline-block select"}>
|
||||||
|
{isLoading && <i class={"fa fa-spinner"} />}
|
||||||
|
{isSuccess && (
|
||||||
|
<select
|
||||||
|
class={"channel-selector"}
|
||||||
|
value={
|
||||||
|
updatedSettings.dashboard_color_scheme === undefined
|
||||||
|
? data.preferences.dashboard_color_scheme
|
||||||
|
: updatedSettings.dashboard_color_scheme
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
setUpdatedSettings((s) => ({
|
||||||
|
...s,
|
||||||
|
dashboard_color_scheme: (e.target as HTMLSelectElement)
|
||||||
|
.value as ColorScheme,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value={ColorScheme.System}>System default</option>
|
||||||
|
<option value={ColorScheme.Light}>Light</option>
|
||||||
|
<option value={ColorScheme.Dark}>Dark</option>
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<br></br>
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<button
|
<button
|
||||||
class="button is-success is-outlined"
|
class="button is-success is-outlined"
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export const Welcome = () => (
|
|||||||
<p>
|
<p>
|
||||||
<strong>Please report bugs!</strong> I can't fix issues if I am unaware of them.
|
<strong>Please report bugs!</strong> I can't fix issues if I am unaware of them.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Client version: {import.meta.env.VITE_VERSION}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -57,3 +57,225 @@ div.reminderContent.is-collapsed .hide-box i {
|
|||||||
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
|
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@import "vars";
|
||||||
|
|
||||||
|
.scheme-light {
|
||||||
|
background-color: $primary-background-light;
|
||||||
|
color: $primary-text-light;
|
||||||
|
|
||||||
|
.column.is-main-content {
|
||||||
|
background-color: $primary-background-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reminderContent {
|
||||||
|
background-color: $secondary-background-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scheme-dark {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
|
||||||
|
.column.is-main-content {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reminderContent {
|
||||||
|
background-color: $primary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title,
|
||||||
|
.subtitle,
|
||||||
|
.label,
|
||||||
|
.help,
|
||||||
|
strong {
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select,
|
||||||
|
.input,
|
||||||
|
.textarea {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
.button.is-outlined.is-success:not(.is-focused, :hover, :focus) {
|
||||||
|
background-color: $primary-background-dark;
|
||||||
|
color: #48c78e;
|
||||||
|
border-color: #48c78e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-outlined.is-danger:not(.is-focused, :hover, :focus) {
|
||||||
|
background-color: $primary-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-outlined.is-warning:not(.is-focused, :hover, :focus) {
|
||||||
|
background-color: $primary-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:not(.is-success, .is-danger, .is-warning, .is-light) {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-light {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal
|
||||||
|
.modal-card-head,
|
||||||
|
.modal-card-foot {
|
||||||
|
background-color: $contrast-background-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-head .modal-card-title {
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-body {
|
||||||
|
background-color: $primary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
.navbar {
|
||||||
|
background-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item,
|
||||||
|
.navbar-burger {
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select dropdown arrow
|
||||||
|
.select:not(.is-multiple):not(.is-loading)::after {
|
||||||
|
border-color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select select {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interval selector inputs
|
||||||
|
.interval-group input {
|
||||||
|
background-color: $secondary-background-dark !important;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
.notification {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Links
|
||||||
|
a:not(.menu a):not(.button) {
|
||||||
|
color: #7eaef1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content area
|
||||||
|
.content {
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox
|
||||||
|
.checkbox {
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message input
|
||||||
|
.message-input {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Box
|
||||||
|
.box {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panel
|
||||||
|
.panel {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
|
||||||
|
.panel-heading {
|
||||||
|
background-color: $contrast-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-block {
|
||||||
|
color: $primary-text-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal rule
|
||||||
|
hr {
|
||||||
|
background-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag
|
||||||
|
.tag:not(.is-success, .is-danger, .is-warning, .is-info) {
|
||||||
|
background-color: $contrast-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo
|
||||||
|
.todo {
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page links
|
||||||
|
.page-links .button {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading overlay
|
||||||
|
.load-screen {
|
||||||
|
background-color: rgba(36, 36, 36, 0.8);
|
||||||
|
color: $primary-text-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// File/attachment input
|
||||||
|
.file-cta {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
background-color: $secondary-background-dark;
|
||||||
|
color: $primary-text-dark;
|
||||||
|
border-color: $contrast-background-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date/time inputs color scheme
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="datetime-local"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal background overlay
|
||||||
|
.modal-background {
|
||||||
|
background-color: rgba(10, 10, 10, 0.86);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl Recordable for Options {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
let channel_id = if let Some(channel) = ctx.channel_id().to_channel_cached(&ctx.cache()) {
|
let channel_id = if let Some(channel) = ctx.cache().channel(ctx.channel_id()) {
|
||||||
if Some(channel.guild_id) == ctx.guild_id() {
|
if Some(channel.guild_id) == ctx.guild_id() {
|
||||||
flags.channel_id.unwrap_or_else(|| ctx.channel_id())
|
flags.channel_id.unwrap_or_else(|| ctx.channel_id())
|
||||||
} else {
|
} else {
|
||||||
@@ -66,8 +66,7 @@ impl Recordable for Options {
|
|||||||
ctx.channel_id()
|
ctx.channel_id()
|
||||||
};
|
};
|
||||||
|
|
||||||
let channel_name =
|
let channel_name = ctx.cache().channel(channel_id).map(|channel| channel.name.clone());
|
||||||
channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone());
|
|
||||||
|
|
||||||
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
|
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl ComponentDataModel {
|
|||||||
let flags = pager.flags;
|
let flags = pager.flags;
|
||||||
|
|
||||||
let channel_id = {
|
let channel_id = {
|
||||||
let channel_opt = component.channel_id.to_channel_cached(&ctx.cache);
|
let channel_opt = ctx.cache.channel(component.channel_id);
|
||||||
|
|
||||||
if let Some(channel) = channel_opt {
|
if let Some(channel) = channel_opt {
|
||||||
if Some(channel.guild_id) == component.guild_id {
|
if Some(channel.guild_id) == component.guild_id {
|
||||||
@@ -85,7 +85,7 @@ impl ComponentDataModel {
|
|||||||
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
||||||
|
|
||||||
let channel_name =
|
let channel_name =
|
||||||
channel_id.to_channel_cached(&ctx.cache).map(|channel| channel.name.clone());
|
ctx.cache.channel(channel_id).map(|channel| channel.name.clone());
|
||||||
|
|
||||||
let next_page = pager.next_page(pages);
|
let next_page = pager.next_page(pages);
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ use crate::{consts::DEFAULT_AVATAR, Error};
|
|||||||
pub struct ChannelData {
|
pub struct ChannelData {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub channel: u64,
|
pub channel: u64,
|
||||||
pub name: Option<String>,
|
|
||||||
pub nudge: i16,
|
pub nudge: i16,
|
||||||
pub blacklisted: bool,
|
|
||||||
pub webhook_id: Option<u64>,
|
pub webhook_id: Option<u64>,
|
||||||
pub webhook_token: Option<String>,
|
pub webhook_token: Option<String>,
|
||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
@@ -27,7 +25,7 @@ impl ChannelData {
|
|||||||
if let Ok(c) = sqlx::query_as_unchecked!(
|
if let Ok(c) = sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, channel, name, nudge, blacklisted, webhook_id, webhook_token, paused,
|
SELECT id, channel, nudge, webhook_id, webhook_token, paused,
|
||||||
paused_until
|
paused_until
|
||||||
FROM channels
|
FROM channels
|
||||||
WHERE channel = ?
|
WHERE channel = ?
|
||||||
@@ -45,9 +43,8 @@ impl ChannelData {
|
|||||||
if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
|
if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))",
|
"INSERT IGNORE INTO channels (channel, guild_id) VALUES (?, (SELECT id FROM guilds WHERE guild = ?))",
|
||||||
channel_id,
|
channel_id,
|
||||||
channel_name,
|
|
||||||
guild_id
|
guild_id
|
||||||
)
|
)
|
||||||
.execute(&pool.clone())
|
.execute(&pool.clone())
|
||||||
@@ -56,7 +53,7 @@ impl ChannelData {
|
|||||||
Ok(sqlx::query_as_unchecked!(
|
Ok(sqlx::query_as_unchecked!(
|
||||||
Self,
|
Self,
|
||||||
"
|
"
|
||||||
SELECT id, channel, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until
|
SELECT id, channel, nudge, webhook_id, webhook_token, paused, paused_until
|
||||||
FROM channels
|
FROM channels
|
||||||
WHERE channel = ?
|
WHERE channel = ?
|
||||||
",
|
",
|
||||||
@@ -72,18 +69,14 @@ impl ChannelData {
|
|||||||
"
|
"
|
||||||
UPDATE channels
|
UPDATE channels
|
||||||
SET
|
SET
|
||||||
name = ?,
|
|
||||||
nudge = ?,
|
nudge = ?,
|
||||||
blacklisted = ?,
|
|
||||||
webhook_id = ?,
|
webhook_id = ?,
|
||||||
webhook_token = ?,
|
webhook_token = ?,
|
||||||
paused = ?,
|
paused = ?,
|
||||||
paused_until = ?
|
paused_until = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
",
|
",
|
||||||
self.name,
|
|
||||||
self.nudge,
|
self.nudge,
|
||||||
self.blacklisted,
|
|
||||||
self.webhook_id,
|
self.webhook_id,
|
||||||
self.webhook_token,
|
self.webhook_token,
|
||||||
self.paused,
|
self.paused,
|
||||||
|
|||||||
@@ -215,17 +215,17 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
for scope in self.scopes {
|
for scope in self.scopes {
|
||||||
let db_channel_id = match scope {
|
let db_channel_id = match scope {
|
||||||
ReminderScope::User(user_id) => {
|
ReminderScope::User(user_id) => {
|
||||||
if let Ok(user) = UserId::new(user_id).to_user(&self.ctx).await {
|
let user_id = UserId::new(user_id);
|
||||||
let user_data = UserData::from_user(
|
match UserData::from_user(
|
||||||
&user,
|
&user_id,
|
||||||
&self.ctx.serenity_context(),
|
&self.ctx.serenity_context(),
|
||||||
&self.ctx.data().database,
|
&self.ctx.data().database,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
{
|
||||||
|
Ok(user_data) => {
|
||||||
if let Some(guild_id) = self.guild_id {
|
if let Some(guild_id) = self.guild_id {
|
||||||
if guild_id.member(&self.ctx, user).await.is_err() {
|
if guild_id.member(&self.ctx, user_id).await.is_err() {
|
||||||
Err(ReminderError::InvalidTag)
|
Err(ReminderError::InvalidTag)
|
||||||
} else if self.set_by.map_or(true, |i| i != user_data.id)
|
} else if self.set_by.map_or(true, |i| i != user_data.id)
|
||||||
&& !user_data.allowed_dm
|
&& !user_data.allowed_dm
|
||||||
@@ -237,8 +237,8 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
} else {
|
} else {
|
||||||
Ok((user_data.dm_channel, None))
|
Ok((user_data.dm_channel, None))
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
Err(ReminderError::InvalidTag)
|
Err(_) => Err(ReminderError::InvalidTag),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReminderScope::Channel(channel_with_thread) => {
|
ReminderScope::Channel(channel_with_thread) => {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ impl IpBlocking {
|
|||||||
fn contains<I: Into<u32>>(&self, ip: I) -> bool {
|
fn contains<I: Into<u32>>(&self, ip: I) -> bool {
|
||||||
let ip: u32 = ip.into();
|
let ip: u32 = ip.into();
|
||||||
|
|
||||||
let mut prev_index = self.upper_ips.len() - 1;
|
let _prev_index = self.upper_ips.len() - 1;
|
||||||
let mut index = self.upper_ips.len() / 2;
|
let mut index = self.upper_ips.len() / 2;
|
||||||
loop {
|
loop {
|
||||||
if self.upper_ips[index] <= ip && self.lower_ips[index] >= ip {
|
if self.upper_ips[index] <= ip && self.lower_ips[index] >= ip {
|
||||||
|
|||||||
+3
-6
@@ -63,7 +63,6 @@ use log::{error, info, warn};
|
|||||||
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
||||||
use poise::serenity_prelude::{
|
use poise::serenity_prelude::{
|
||||||
client::Context,
|
client::Context,
|
||||||
http::CacheHttp,
|
|
||||||
model::id::{GuildId, UserId},
|
model::id::{GuildId, UserId},
|
||||||
};
|
};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
@@ -76,13 +75,10 @@ use rocket::{
|
|||||||
};
|
};
|
||||||
use rocket_dyn_templates::Template;
|
use rocket_dyn_templates::Template;
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
use std::net::Ipv4Addr;
|
|
||||||
use std::{env, path::Path};
|
use std::{env, path::Path};
|
||||||
|
|
||||||
use crate::web::{
|
use crate::web::consts::{DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN};
|
||||||
consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES},
|
use crate::web::fairings::metrics::MetricProducer;
|
||||||
fairings::metrics::MetricProducer,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Database = MySql;
|
type Database = MySql;
|
||||||
|
|
||||||
@@ -126,6 +122,7 @@ pub async fn initialize(
|
|||||||
|
|
||||||
let static_path =
|
let static_path =
|
||||||
if Path::new("static").exists() { "static" } else { "/lib/reminder-rs/static" };
|
if Path::new("static").exists() { "static" } else { "/lib/reminder-rs/static" };
|
||||||
|
info!("Using static path: {}", static_path);
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.attach(MetricProducer)
|
.attach(MetricProducer)
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ mod roles;
|
|||||||
mod templates;
|
mod templates;
|
||||||
pub mod todos;
|
pub mod todos;
|
||||||
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use crate::utils::check_subscription;
|
use crate::utils::check_subscription;
|
||||||
use crate::web::guards::transaction::Transaction;
|
use crate::web::guards::transaction::Transaction;
|
||||||
use crate::web::{check_authorization, routes::JsonResult};
|
use crate::web::{check_authorization, routes::JsonResult};
|
||||||
@@ -16,10 +14,7 @@ pub use reminders::*;
|
|||||||
use rocket::{get, http::CookieJar, serde::json::json, State};
|
use rocket::{get, http::CookieJar, serde::json::json, State};
|
||||||
pub use roles::get_guild_roles;
|
pub use roles::get_guild_roles;
|
||||||
use serenity::all::UserId;
|
use serenity::all::UserId;
|
||||||
use serenity::{
|
use serenity::{client::Context, model::id::GuildId};
|
||||||
client::Context,
|
|
||||||
model::id::{GuildId, RoleId},
|
|
||||||
};
|
|
||||||
pub use templates::*;
|
pub use templates::*;
|
||||||
|
|
||||||
#[get("/api/guild/<id>")]
|
#[get("/api/guild/<id>")]
|
||||||
|
|||||||
@@ -267,9 +267,8 @@ pub async fn edit_reminder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reminder.channel > 0 {
|
if reminder.channel > 0 {
|
||||||
let channel_guild = ChannelId::new(reminder.channel)
|
let channel_guild =
|
||||||
.to_channel_cached(&ctx.cache)
|
ctx.cache.channel(ChannelId::new(reminder.channel)).map(|channel| channel.guild_id);
|
||||||
.map(|channel| channel.guild_id);
|
|
||||||
match channel_guild {
|
match channel_guild {
|
||||||
Some(channel_guild) => {
|
Some(channel_guild) => {
|
||||||
let channel_matches_guild = channel_guild.get() == id;
|
let channel_matches_guild = channel_guild.get() == id;
|
||||||
|
|||||||
@@ -3,13 +3,10 @@ use crate::web::{
|
|||||||
check_authorization,
|
check_authorization,
|
||||||
guards::transaction::Transaction,
|
guards::transaction::Transaction,
|
||||||
routes::{
|
routes::{
|
||||||
dashboard::{
|
dashboard::{ImportBody, ReminderTemplateCsv},
|
||||||
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
|
||||||
},
|
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::Database;
|
|
||||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
use csv::{QuoteStyle, WriterBuilder};
|
use csv::{QuoteStyle, WriterBuilder};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
@@ -20,10 +17,7 @@ use rocket::{
|
|||||||
serde::json::{json, Json},
|
serde::json::{json, Json},
|
||||||
State,
|
State,
|
||||||
};
|
};
|
||||||
use serenity::{
|
use serenity::{client::Context, model::id::GuildId};
|
||||||
client::Context,
|
|
||||||
model::id::{ChannelId, GuildId, UserId},
|
|
||||||
};
|
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
#[get("/api/guild/<id>/export/reminder_templates")]
|
#[get("/api/guild/<id>/export/reminder_templates")]
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ use crate::web::{
|
|||||||
check_authorization,
|
check_authorization,
|
||||||
guards::transaction::Transaction,
|
guards::transaction::Transaction,
|
||||||
routes::{
|
routes::{
|
||||||
dashboard::{
|
dashboard::{create_reminder, CreateReminder, ImportBody, ReminderCsv},
|
||||||
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
|
||||||
},
|
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -21,7 +19,7 @@ use rocket::{
|
|||||||
};
|
};
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
model::id::{ChannelId, GuildId, UserId},
|
model::id::{GuildId, UserId},
|
||||||
};
|
};
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
use crate::web::{
|
use crate::web::{
|
||||||
check_authorization,
|
check_authorization,
|
||||||
guards::transaction::Transaction,
|
|
||||||
routes::{
|
routes::{
|
||||||
dashboard::{
|
dashboard::{ImportBody, TodoCsv},
|
||||||
create_reminder, CreateReminder, ImportBody, ReminderCsv, ReminderTemplateCsv, TodoCsv,
|
|
||||||
},
|
|
||||||
JsonResult,
|
JsonResult,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::Database;
|
|
||||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
use csv::{QuoteStyle, WriterBuilder};
|
use csv::{QuoteStyle, WriterBuilder};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
@@ -21,7 +17,7 @@ use rocket::{
|
|||||||
};
|
};
|
||||||
use serenity::{
|
use serenity::{
|
||||||
client::Context,
|
client::Context,
|
||||||
model::id::{ChannelId, GuildId, UserId},
|
model::id::{ChannelId, GuildId},
|
||||||
};
|
};
|
||||||
use sqlx::{MySql, Pool};
|
use sqlx::{MySql, Pool};
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/home/jude/reminder-bot/reminder-dashboard/dist/static/assets
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/home/jude/reminder-bot/reminder-dashboard/dist/index.html
|
|
||||||
Reference in New Issue
Block a user