Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
439575acd0 | ||
|
|
2665c61d33 |
No files matched your search
+84
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n SELECT id, channel, nudge, webhook_id, webhook_token, paused,\n paused_until\n FROM channels\n WHERE channel = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "channel",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "nudge",
|
||||
"type_info": {
|
||||
"type": "Short",
|
||||
"flags": "NOT_NULL",
|
||||
"max_size": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "webhook_id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNIQUE_KEY | UNSIGNED",
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "webhook_token",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB",
|
||||
"max_size": 262140
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "paused",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "paused_until",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "BINARY",
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "017b3f73101926a3fe029ed75c412ec902aa74fee334c23fd75fe0cde1e324ca"
|
||||
}
|
||||
+70
-30
@@ -1,19 +1,10 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n SELECT reminders.attachment,\n reminders.attachment_name,\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.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?\n ",
|
||||
"query": "\n SELECT\n reminders.attachment_name,\n reminders.avatar,\n channels.channel,\n reminders.message_json,\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.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?",
|
||||
"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",
|
||||
@@ -21,8 +12,35 @@
|
||||
"max_size": 1040
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "avatar",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "",
|
||||
"max_size": 2048
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "channel",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -31,7 +49,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 5,
|
||||
"name": "embed_author",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -40,7 +58,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 6,
|
||||
"name": "embed_author_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -49,7 +67,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 7,
|
||||
"name": "embed_color",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -58,7 +76,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 8,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -67,7 +85,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 9,
|
||||
"name": "embed_footer",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -76,7 +94,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 10,
|
||||
"name": "embed_footer_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -85,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 11,
|
||||
"name": "embed_image_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 12,
|
||||
"name": "embed_thumbnail_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +121,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 13,
|
||||
"name": "embed_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -112,7 +130,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 14,
|
||||
"name": "embed_fields",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
@@ -121,7 +139,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 15,
|
||||
"name": "enabled",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -130,7 +148,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 16,
|
||||
"name": "expires",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -139,7 +157,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 17,
|
||||
"name": "interval_seconds",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -148,7 +166,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 18,
|
||||
"name": "interval_days",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -157,7 +175,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 19,
|
||||
"name": "interval_months",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -166,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 20,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -175,7 +193,16 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 21,
|
||||
"name": "restartable",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "tts",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -184,7 +211,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 23,
|
||||
"name": "uid",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -193,7 +220,16 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 24,
|
||||
"name": "username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "",
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "utc_time",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -206,6 +242,8 @@
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -227,8 +265,10 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ecbea4acb61da864a4f83004b1163391068456a366fee95daff5f46413bcf494"
|
||||
"hash": "168e34b942db6025a5674dca06ac154309cc8f86341272e7c771abfad1b61003"
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n SELECT id, channel, nudge, webhook_id, webhook_token, paused, paused_until\n FROM channels\n WHERE channel = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "channel",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "nudge",
|
||||
"type_info": {
|
||||
"type": "Short",
|
||||
"flags": "NOT_NULL",
|
||||
"max_size": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "webhook_id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNIQUE_KEY | UNSIGNED",
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "webhook_token",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB",
|
||||
"max_size": 262140
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "paused",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "paused_until",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "BINARY",
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "21b660ecbeffcac31ef283f59e420360a28b344759407024a11dc4a04da9341d"
|
||||
}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n INSERT INTO reminders (\n `uid`,\n `channel_id`,\n `thread_id`,\n `utc_time`,\n `timezone`,\n `interval_seconds`,\n `interval_days`,\n `interval_months`,\n `expires`,\n `content`,\n `tts`,\n `attachment_name`,\n `attachment`,\n `set_by`\n ) VALUES (\n ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?\n )\n ",
|
||||
"query": "\n INSERT INTO reminders (\n `uid`,\n `channel_id`,\n `thread_id`,\n `utc_time`,\n `timezone`,\n `interval_seconds`,\n `interval_days`,\n `interval_months`,\n `expires`,\n `message_json`,\n `attachment_name`,\n `attachment`,\n `set_by`\n ) VALUES (\n ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 14
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f9d2d9f6809b6c852297e5c60b6c356af5176c6b81b86cd701c1b8af8212f194"
|
||||
"hash": "22ea7fbff927d2696e5c3dc78f714d1cb5dadcc560854e22f15def122c64fc0c"
|
||||
}
|
||||
+14
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 reminders.id = ?\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.message_json,\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 reminders.id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -86,6 +86,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "set_by",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
@@ -125,10 +134,11 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "67b970d7b85f7bf3e71a58af10bedf7281cf0d8601b149383037d604a22a64c7"
|
||||
"hash": "2753eb51d5f4b51771b696555752cc59d8d0c78737fc35210c5f6108d32f3540"
|
||||
}
|
||||
+14
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 reminders.uid = ?\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.message_json,\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 reminders.uid = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -86,6 +86,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "set_by",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
@@ -125,10 +134,11 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d7f469dd1c7f37f1aca643e9018083b48da1cf988030b9efeae451e77e54a23d"
|
||||
"hash": "40ceeb037b5266e743a77a4a657d222b769e871b521d3a1745caed0292e9e6cc"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO reminders (\n uid,\n attachment,\n attachment_name,\n channel_id,\n avatar,\n content,\n embed_author,\n embed_author_url,\n embed_color,\n embed_description,\n embed_footer,\n embed_footer_url,\n embed_image_url,\n embed_thumbnail_url,\n embed_title,\n embed_fields,\n enabled,\n expires,\n interval_seconds,\n interval_days,\n interval_months,\n name,\n restartable,\n tts,\n username,\n `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 26
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4cc3ff2bbdf8502f6e739a002c7aa16df8c4f24de0966b26b268efab97e205e3"
|
||||
}
|
||||
+14
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 channels.id = reminders.channel_id\n WHERE\n `status` = 'pending' AND\n channels.id = (SELECT dm_channel FROM users WHERE id = ?)\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.message_json,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n channels.id = reminders.channel_id\n WHERE\n `status` = 'pending' AND\n channels.id = (SELECT dm_channel FROM users WHERE id = ?)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -86,6 +86,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "set_by",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
@@ -125,10 +134,11 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "45e555a2a0664662f55cc738f91abf8c51648fb687f9568c4dd5f48e9273253a"
|
||||
"hash": "5575971e2a460476adcc70147810caa91fb07f52d788af0f7ec319a15ee5020c"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT IGNORE INTO channels (channel, guild_id) VALUES (?, (SELECT id FROM guilds WHERE guild = ?))",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5d7a787e3a6f487c3fe00bd39d308c9350c82e58b0ad1aae0b92824375786f02"
|
||||
}
|
||||
+14
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)\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.message_json,\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 channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -86,6 +86,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "set_by",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
@@ -125,10 +134,11 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4dc78a4ebdad0d9fb721389013537093dd814c313bc8ecc80fc9767420afb798"
|
||||
"hash": "5efa565633215c62ae826b670140c2f36ccc019d56db7a0182ec00f1a2713562"
|
||||
}
|
||||
+22
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n SELECT\n reminders.`id` AS id,\n\n channels.`channel` AS channel_id,\n reminders.`thread_id` AS thread_id,\n channels.`webhook_id` AS webhook_id,\n channels.`webhook_token` AS webhook_token,\n\n channels.`paused` AS 'channel_paused',\n channels.`paused_until` AS 'channel_paused_until',\n reminders.`enabled` AS 'enabled',\n\n reminders.`tts` AS tts,\n reminders.`pin` AS pin,\n reminders.`content` AS content,\n reminders.`attachment` AS attachment,\n reminders.`attachment_name` AS attachment_name,\n\n reminders.`utc_time` AS 'utc_time',\n reminders.`timezone` AS timezone,\n reminders.`restartable` AS restartable,\n reminders.`expires` AS 'expires',\n reminders.`interval_seconds` AS 'interval_seconds',\n reminders.`interval_days` AS 'interval_days',\n reminders.`interval_months` AS 'interval_months',\n\n reminders.`avatar` AS avatar,\n reminders.`username` AS username\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.`status` = 'pending' AND\n reminders.`id` IN (\n SELECT\n MIN(id)\n FROM\n reminders\n WHERE\n reminders.`utc_time` <= NOW() AND\n `status` = 'pending' AND\n (\n reminders.`interval_seconds` IS NOT NULL\n OR reminders.`interval_months` IS NOT NULL\n OR reminders.`interval_days` IS NOT NULL\n OR reminders.enabled\n )\n GROUP BY channel_id\n )\n ",
|
||||
"query": "\n SELECT\n reminders.`id` AS id,\n\n channels.`channel` AS channel_id,\n reminders.`thread_id` AS thread_id,\n channels.`webhook_id` AS webhook_id,\n channels.`webhook_token` AS webhook_token,\n\n channels.`paused` AS 'channel_paused',\n channels.`paused_until` AS 'channel_paused_until',\n reminders.`enabled` AS 'enabled',\n\n reminders.`tts` AS tts,\n reminders.`pin` AS pin,\n reminders.`content` AS content,\n reminders.`message_json` AS message_json,\n reminders.`attachment` AS attachment,\n reminders.`attachment_name` AS attachment_name,\n\n reminders.`utc_time` AS 'utc_time',\n reminders.`timezone` AS timezone,\n reminders.`restartable` AS restartable,\n reminders.`expires` AS 'expires',\n reminders.`interval_seconds` AS 'interval_seconds',\n reminders.`interval_days` AS 'interval_days',\n reminders.`interval_months` AS 'interval_months',\n\n reminders.`avatar` AS avatar,\n reminders.`username` AS username\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.`status` = 'pending' AND\n reminders.`id` IN (\n SELECT\n MIN(id)\n FROM\n reminders\n WHERE\n reminders.`utc_time` <= NOW() AND\n `status` = 'pending' AND\n (\n reminders.`interval_seconds` IS NOT NULL\n OR reminders.`interval_months` IS NOT NULL\n OR reminders.`interval_days` IS NOT NULL\n OR reminders.enabled\n )\n GROUP BY channel_id\n )\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -104,6 +104,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "attachment",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
@@ -112,7 +121,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "attachment_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -121,7 +130,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "utc_time",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -130,7 +139,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "timezone",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
@@ -139,7 +148,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "restartable",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -148,7 +157,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "expires",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -157,7 +166,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "interval_seconds",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -166,7 +175,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "interval_days",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -175,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "interval_months",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -184,7 +193,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "avatar",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -193,7 +202,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -219,6 +228,7 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
@@ -230,5 +240,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "a33ef7167746183766b6a9b4e31784dd3712fa2b4f72482d4b681e0a9fdbaf1d"
|
||||
"hash": "6cc6fafaa4dab75639c6e5348d2a836bc6be510e76534c36df709883a4694859"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO reminders (\n uid,\n attachment,\n attachment_name,\n channel_id,\n content,\n embed_author,\n embed_author_url,\n embed_color,\n embed_description,\n embed_footer,\n embed_footer_url,\n embed_image_url,\n embed_thumbnail_url,\n embed_title,\n embed_fields,\n enabled,\n expires,\n interval_seconds,\n interval_days,\n interval_months,\n name,\n tts,\n `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 23
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7f44451c441507ccd7f8d41633fb156f95a28389fcc9c489fdb228446f88342e"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO reminders (\n uid, attachment, attachment_name, channel_id, message_json,\n enabled, expires, interval_seconds, interval_days, interval_months,\n name, restartable, `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8051034e8334a31d5c8ffca9893f85b689955c1e3d144e1162d4c991a47d9852"
|
||||
}
|
||||
+17
-37
@@ -1,10 +1,19 @@
|
||||
{
|
||||
"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 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.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?",
|
||||
"query": "\n SELECT reminders.attachment,\n reminders.attachment_name,\n reminders.message_json,\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.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?\n ",
|
||||
"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",
|
||||
@@ -12,22 +21,13 @@
|
||||
"max_size": 1040
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "avatar",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "",
|
||||
"max_size": 2048
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "channel",
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
"max_size": 20
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -185,15 +185,6 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "restartable",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "tts",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -202,7 +193,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 21,
|
||||
"name": "uid",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -211,16 +202,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "",
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 22,
|
||||
"name": "utc_time",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -255,10 +237,8 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "fa58f079f00bec4c3f9cbb2ee0a607f63c5b586654c541bff6b99eff83ae0a44"
|
||||
"hash": "8e5e68f6cc7549dc3c1e777def9f6fd8a6b61c6790ee9594ca20c5504ca834fa"
|
||||
}
|
||||
+33
-23
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 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.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?",
|
||||
"query": "\n SELECT\n reminders.attachment_name,\n reminders.avatar,\n channels.channel,\n reminders.message_json,\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.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -40,7 +49,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 5,
|
||||
"name": "embed_author",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -49,7 +58,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 6,
|
||||
"name": "embed_author_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -58,7 +67,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "embed_color",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -67,7 +76,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -76,7 +85,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "embed_footer",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -85,7 +94,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "embed_footer_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_image_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "embed_thumbnail_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -112,7 +121,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "embed_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -121,7 +130,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "embed_fields",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
@@ -130,7 +139,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "enabled",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -139,7 +148,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "expires",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -148,7 +157,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "interval_seconds",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -157,7 +166,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "interval_days",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -166,7 +175,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "interval_months",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -175,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -184,7 +193,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "restartable",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -193,7 +202,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "tts",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -202,7 +211,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "uid",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -211,7 +220,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -220,7 +229,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "utc_time",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -236,6 +245,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -260,5 +270,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "53db321bc70b45800f696115b65da8550de208104017f8e0d0a163781488a534"
|
||||
"hash": "8e8d6ebf8b5e69d246a73ef3b746ed3dc5dc6f8204f69aeb55dc5e2ae17319aa"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO reminders (\n uid, attachment, attachment_name, channel_id, message_json,\n enabled, expires, interval_seconds, interval_days, interval_months,\n name, `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "992666a8cec803ab31a5a86266a07daa4fdf26eeb0969abd4d7987f9672dfae8"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n UPDATE channels\n SET\n nudge = ?,\n webhook_id = ?,\n webhook_token = ?,\n paused = ?,\n paused_until = ?\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "af08f3524c316f438feb62a24c67b96197e3655a87efe128914ed386be20d900"
|
||||
}
|
||||
+31
-21
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "SELECT\n reminders.attachment,\n reminders.attachment_name,\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.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n WHERE uid = ?",
|
||||
"query": "SELECT\n reminders.attachment,\n reminders.attachment_name,\n reminders.message_json,\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.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n WHERE uid = ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -23,6 +23,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -31,7 +40,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 4,
|
||||
"name": "embed_author",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -40,7 +49,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 5,
|
||||
"name": "embed_author_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -49,7 +58,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 6,
|
||||
"name": "embed_color",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -58,7 +67,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -67,7 +76,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "embed_footer",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -76,7 +85,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "embed_footer_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -85,7 +94,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "embed_image_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_thumbnail_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "embed_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -112,7 +121,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "embed_fields",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
@@ -121,7 +130,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "enabled",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -130,7 +139,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "expires",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -139,7 +148,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "interval_seconds",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -148,7 +157,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "interval_days",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -157,7 +166,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "interval_months",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -166,7 +175,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -175,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "tts",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -184,7 +193,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "uid",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -193,7 +202,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "utc_time",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -206,6 +215,7 @@
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -230,5 +240,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2613618c00941649726b31f7c50b5ef50d85323a73def5c87bab2a58c80bf6cd"
|
||||
"hash": "b708e057ab42c09c36fdc71bda77970fe04e8c066fd9c2701ac25afb059c32b6"
|
||||
}
|
||||
+31
-21
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n SELECT\n reminders.attachment,\n reminders.attachment_name,\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.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n INNER JOIN channels ON channels.id = reminders.channel_id\n WHERE `status` = 'pending' AND channels.channel = ?\n ",
|
||||
"query": "\n SELECT\n reminders.attachment,\n reminders.attachment_name,\n reminders.message_json,\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.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n INNER JOIN channels ON channels.id = reminders.channel_id\n WHERE `status` = 'pending' AND channels.channel = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -23,6 +23,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -31,7 +40,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 4,
|
||||
"name": "embed_author",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -40,7 +49,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 5,
|
||||
"name": "embed_author_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -49,7 +58,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 6,
|
||||
"name": "embed_color",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -58,7 +67,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -67,7 +76,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "embed_footer",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -76,7 +85,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "embed_footer_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -85,7 +94,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "embed_image_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_thumbnail_url",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "embed_title",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -112,7 +121,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "embed_fields",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -121,7 +130,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "enabled",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -130,7 +139,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "expires",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -139,7 +148,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "interval_seconds",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -148,7 +157,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "interval_days",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -157,7 +166,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "interval_months",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -166,7 +175,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -175,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "tts",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -184,7 +193,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "uid",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -193,7 +202,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "utc_time",
|
||||
"type_info": {
|
||||
"type": "Datetime",
|
||||
@@ -206,6 +215,7 @@
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
@@ -230,5 +240,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "48a9268e9106a3a05e86ab36551944ba855a07b0241e912c6b1adee779c6f0ea"
|
||||
"hash": "d9e0f517df37a6c03868b6e1470169eaf06d2e6f1a9df62165656d3e97dbe929"
|
||||
}
|
||||
+14
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 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.message_json,\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": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -86,6 +86,15 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "message_json",
|
||||
"type_info": {
|
||||
"type": "Json",
|
||||
"flags": "BLOB | BINARY",
|
||||
"max_size": 4294967295
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "content",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -94,7 +103,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "embed_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -103,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "set_by",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
@@ -125,10 +134,11 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d2921961627fef0e12892dbbcd4b891e58c0e52c20897aab3d95365774c01bda"
|
||||
"hash": "ec12254245aa433f8d31711c2942254d91c61f6b47a5b427c43a8cac468379a3"
|
||||
}
|
||||
Generated
+247
-74
@@ -35,7 +35,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -389,6 +389,23 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.1",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
@@ -468,7 +485,7 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"hkdf",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"time",
|
||||
@@ -500,6 +517,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
@@ -583,7 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
@@ -1066,15 +1092,6 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
@@ -1111,6 +1128,20 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"rand_core 0.10.1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
@@ -1427,6 +1458,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tower-service",
|
||||
"webpki-roots 0.26.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1725,10 +1757,12 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.72"
|
||||
version = "0.3.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
||||
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1844,6 +1878,12 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@@ -2045,7 +2085,7 @@ dependencies = [
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -2104,15 +2144,15 @@ checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"chrono",
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"http 0.2.12",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.11.27",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -2276,7 +2316,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
@@ -2340,7 +2380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2427,7 +2467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.15",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
@@ -2449,9 +2489,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.89"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2481,7 +2521,7 @@ dependencies = [
|
||||
"memchr",
|
||||
"parking_lot",
|
||||
"protobuf",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2501,6 +2541,62 @@ dependencies = [
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.16",
|
||||
"socket2",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.4.3",
|
||||
"lru-slab",
|
||||
"rand 0.10.2",
|
||||
"rand_pcg",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.20",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
@@ -2510,6 +2606,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@@ -2518,7 +2620,18 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.3",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2528,7 +2641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2537,7 +2650,22 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
|
||||
dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2641,7 +2769,7 @@ dependencies = [
|
||||
"oauth2",
|
||||
"poise",
|
||||
"prometheus",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"recordable_derive",
|
||||
"regex",
|
||||
"reqwest 0.12.9",
|
||||
@@ -2678,7 +2806,6 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
@@ -2691,12 +2818,10 @@ dependencies = [
|
||||
"system-configuration 0.5.1",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.4",
|
||||
"winreg",
|
||||
@@ -2725,11 +2850,15 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@@ -2737,11 +2866,15 @@ dependencies = [
|
||||
"system-configuration 0.6.1",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots 0.26.6",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
@@ -2753,7 +2886,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
@@ -2803,7 +2936,7 @@ dependencies = [
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"ref-cast",
|
||||
"rocket_codegen",
|
||||
"rocket_http",
|
||||
@@ -2893,7 +3026,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
@@ -2906,6 +3039,12 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.40"
|
||||
@@ -2982,6 +3121,9 @@ name = "rustls-pki-types"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
@@ -3189,9 +3331,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serenity"
|
||||
version = "0.12.4"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76"
|
||||
checksum = "9bde37f42765dfdc34e2a039e0c84afbf79a3101c1941763b0beb816c2f17541"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"async-trait",
|
||||
@@ -3202,11 +3344,11 @@ dependencies = [
|
||||
"dashmap",
|
||||
"flate2",
|
||||
"futures",
|
||||
"fxhash",
|
||||
"mime_guess",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"reqwest 0.11.27",
|
||||
"reqwest 0.12.9",
|
||||
"rustc-hash",
|
||||
"secrecy",
|
||||
"serde",
|
||||
"serde_cow",
|
||||
@@ -3227,7 +3369,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.15",
|
||||
"digest",
|
||||
]
|
||||
|
||||
@@ -3238,7 +3380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.15",
|
||||
"digest",
|
||||
]
|
||||
|
||||
@@ -3273,7 +3415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3413,7 +3555,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
@@ -3491,7 +3633,7 @@ dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
"serde",
|
||||
"sha1",
|
||||
@@ -3499,7 +3641,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
@@ -3533,14 +3675,14 @@ dependencies = [
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"whoami",
|
||||
]
|
||||
@@ -3638,6 +3780,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
@@ -3739,7 +3892,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -3753,7 +3906,16 @@ version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3767,6 +3929,17 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
@@ -4070,11 +4243,11 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.22.4",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
@@ -4343,27 +4516,14 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.95"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
||||
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -4381,9 +4541,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.95"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
||||
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -4391,22 +4551,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.95"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"wasm-bindgen-backend",
|
||||
"syn 3.0.6",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.95"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
@@ -4431,6 +4594,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.4"
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ futures = "0.3.30"
|
||||
prometheus = "0.13.3"
|
||||
rocket = { version = "0.5.0", features = ["tls", "secrets", "json"] }
|
||||
rocket_dyn_templates = { version = "0.2.0", features = ["tera"] }
|
||||
serenity = { version = "0.12", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
|
||||
serenity = { version = "0.12.5", default-features = false, features = ["builder", "cache", "client", "gateway", "http", "model", "utils", "rustls_backend"] }
|
||||
oauth2 = "4"
|
||||
csv = "1.2"
|
||||
sd-notify = "0.4.1"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE reminders ADD COLUMN message_json JSON DEFAULT NULL;
|
||||
@@ -5,6 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode development",
|
||||
"build": "vite build",
|
||||
"test": "node --test tests/*.test.mjs",
|
||||
"preview": "vite preview",
|
||||
"prettier": "prettier -w src/"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { prepareReminder } from "./components/Reminder/message";
|
||||
import type { MessagePayload } from "./components/Reminder/message";
|
||||
import axios from "axios";
|
||||
|
||||
enum ColorScheme {
|
||||
@@ -38,6 +40,7 @@ type EmbedField = {
|
||||
};
|
||||
|
||||
export type Reminder = {
|
||||
message_json?: MessagePayload | null;
|
||||
attachment: string | null;
|
||||
attachment_name: string | null;
|
||||
avatar: string | null;
|
||||
@@ -186,12 +189,14 @@ export const fetchGuildReminders = (guild: string) => ({
|
||||
|
||||
export const patchGuildReminder = (guild: string) => ({
|
||||
mutationFn: (reminder: Reminder) =>
|
||||
axios.patch(`/dashboard/api/guild/${guild}/reminders`, reminder),
|
||||
axios.patch(`/dashboard/api/guild/${guild}/reminders`, prepareReminder(reminder)),
|
||||
});
|
||||
|
||||
export const postGuildReminder = (guild: string) => ({
|
||||
mutationFn: (reminder: Reminder) =>
|
||||
axios.post(`/dashboard/api/guild/${guild}/reminders`, reminder).then((resp) => resp.data),
|
||||
axios
|
||||
.post(`/dashboard/api/guild/${guild}/reminders`, prepareReminder(reminder))
|
||||
.then((resp) => resp.data),
|
||||
});
|
||||
|
||||
export const deleteReminder = () => ({
|
||||
@@ -257,9 +262,12 @@ export const fetchUserReminders = () => ({
|
||||
|
||||
export const postUserReminder = () => ({
|
||||
mutationFn: (reminder: Reminder) =>
|
||||
axios.post(`/dashboard/api/user/reminders`, reminder).then((resp) => resp.data),
|
||||
axios
|
||||
.post(`/dashboard/api/user/reminders`, prepareReminder(reminder))
|
||||
.then((resp) => resp.data),
|
||||
});
|
||||
|
||||
export const patchUserReminder = () => ({
|
||||
mutationFn: (reminder: Reminder) => axios.patch(`/dashboard/api/user/reminders`, reminder),
|
||||
mutationFn: (reminder: Reminder) =>
|
||||
axios.patch(`/dashboard/api/user/reminders`, prepareReminder(reminder)),
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Embed } from "./index";
|
||||
import { ReminderContext, useReminder } from "../ReminderContext";
|
||||
import { Reminder } from "../../../api";
|
||||
import {
|
||||
addEmbed,
|
||||
embedFields,
|
||||
embedTextLength,
|
||||
MAX_EMBEDS,
|
||||
MAX_EMBED_TEXT,
|
||||
reminderEmbeds,
|
||||
removeEmbed,
|
||||
updateEmbed,
|
||||
} from "../message";
|
||||
|
||||
const EmbedEditor = ({ index }: { index: number }) => {
|
||||
const [reminder, setReminder] = useReminder();
|
||||
const embed = reminderEmbeds(reminder)[index];
|
||||
const update = (change: (r: Reminder) => Reminder) => {
|
||||
setReminder((current) => updateEmbed(current, index, change));
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="reminder-embed-editor" role="group" aria-label={`Embed ${index + 1}`}>
|
||||
<ReminderContext.Provider value={[{ ...reminder, ...embedFields(embed) }, update]}>
|
||||
<Embed />
|
||||
</ReminderContext.Provider>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-small is-text remove-embed"
|
||||
aria-label={`Remove embed ${index + 1}`}
|
||||
onClick={() => setReminder((current) => removeEmbed(current, index))}
|
||||
>
|
||||
<i class="fas fa-trash" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Embeds = () => {
|
||||
const [reminder, setReminder] = useReminder();
|
||||
const embeds = reminderEmbeds(reminder);
|
||||
const textLength = embedTextLength(embeds);
|
||||
|
||||
return (
|
||||
<div class="reminder-embeds">
|
||||
{embeds.map((_, index) => (
|
||||
<EmbedEditor key={index} index={index} />
|
||||
))}
|
||||
<div class="add-embed-row">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-small is-rounded add-embed"
|
||||
aria-label="Add embed"
|
||||
title={embeds.length >= MAX_EMBEDS ? "Maximum of 10 embeds" : "Add embed"}
|
||||
disabled={embeds.length >= MAX_EMBEDS}
|
||||
onClick={() => setReminder(addEmbed)}
|
||||
>
|
||||
<i class="fas fa-plus" aria-hidden="true" />
|
||||
</button>
|
||||
<span
|
||||
class={`embed-limit${textLength > MAX_EMBED_TEXT ? " has-text-danger" : ""}`}
|
||||
aria-live="polite"
|
||||
>
|
||||
{embeds.length}/{MAX_EMBEDS} embeds · {textLength}/{MAX_EMBED_TEXT} characters
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,10 @@ export const Fields = () => {
|
||||
|
||||
return (
|
||||
<div class={"embed-multifield-box"}>
|
||||
{[...embed_fields, { value: "", title: "", inline: true }].map((field, index) => (
|
||||
{[
|
||||
...(embed_fields || []),
|
||||
...(embed_fields?.length >= 25 ? [] : [{ value: "", title: "", inline: true }]),
|
||||
].map((field, index) => (
|
||||
<Field
|
||||
{...field}
|
||||
index={index}
|
||||
|
||||
@@ -6,13 +6,14 @@ import { Color } from "./Color";
|
||||
import { Fields } from "./Fields";
|
||||
import { Reminder } from "../../../api";
|
||||
import { ImagePicker } from "../ImagePicker";
|
||||
import { DEFAULT_EMBED_COLOR } from "../message";
|
||||
import { useReminder } from "../ReminderContext";
|
||||
|
||||
function intToColor(num: number) {
|
||||
return `#${num.toString(16).padStart(6, "0")}`;
|
||||
}
|
||||
|
||||
export const DEFAULT_COLOR = 9418359;
|
||||
export const DEFAULT_COLOR = DEFAULT_EMBED_COLOR;
|
||||
|
||||
export const Embed = () => {
|
||||
const [reminder, setReminder] = useReminder();
|
||||
@@ -21,12 +22,12 @@ export const Embed = () => {
|
||||
<div
|
||||
class="discord-embed"
|
||||
style={{
|
||||
borderLeftColor: intToColor(reminder.embed_color || DEFAULT_COLOR),
|
||||
borderLeftColor: intToColor(reminder.embed_color ?? DEFAULT_COLOR),
|
||||
}}
|
||||
>
|
||||
<div class="embed-body">
|
||||
<Color
|
||||
color={intToColor(reminder.embed_color || DEFAULT_COLOR)}
|
||||
color={intToColor(reminder.embed_color ?? DEFAULT_COLOR)}
|
||||
setReminder={setReminder}
|
||||
></Color>
|
||||
<div class="a">
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useReminder } from "./ReminderContext";
|
||||
import {
|
||||
addLinkButton,
|
||||
canAddLinkButton,
|
||||
linkButtons,
|
||||
MAX_LINK_BUTTONS,
|
||||
updateLinkButton,
|
||||
} from "./message";
|
||||
|
||||
export const LinkButtons = () => {
|
||||
const [reminder, setReminder] = useReminder();
|
||||
const buttons = linkButtons(reminder);
|
||||
|
||||
return (
|
||||
<div class="reminder-link-buttons" role="group" aria-label="Link buttons">
|
||||
{buttons.map(({ button, rowIndex, buttonIndex }, index) => (
|
||||
<div class="link-button-editor" key={`${rowIndex}-${buttonIndex}`}>
|
||||
<label class="link-button-label">
|
||||
Button label
|
||||
<input
|
||||
class="input is-small"
|
||||
aria-label={`Link button ${index + 1} label`}
|
||||
maxLength={80}
|
||||
value={button.label || ""}
|
||||
onInput={(event) => {
|
||||
const label = event.currentTarget.value;
|
||||
setReminder((current) =>
|
||||
updateLinkButton(current, rowIndex, buttonIndex, { label }),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label class="link-button-url">
|
||||
URL
|
||||
<input
|
||||
class="input is-small"
|
||||
type="url"
|
||||
aria-label={`Link button ${index + 1} URL`}
|
||||
placeholder="https://example.com"
|
||||
maxLength={512}
|
||||
value={button.url || ""}
|
||||
onInput={(event) => {
|
||||
const url = event.currentTarget.value;
|
||||
setReminder((current) =>
|
||||
updateLinkButton(current, rowIndex, buttonIndex, { url }),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-small is-text"
|
||||
aria-label={`Remove link button ${index + 1}`}
|
||||
onClick={() =>
|
||||
setReminder((current) =>
|
||||
updateLinkButton(current, rowIndex, buttonIndex, null),
|
||||
)
|
||||
}
|
||||
>
|
||||
<i class="fas fa-trash" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div class="add-link-button-row">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-small is-rounded"
|
||||
disabled={!canAddLinkButton(reminder)}
|
||||
onClick={() => setReminder(addLinkButton)}
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-plus" aria-hidden="true" />
|
||||
</span>
|
||||
<span>Add link button</span>
|
||||
</button>
|
||||
<span class="link-button-limit" aria-live="polite">
|
||||
{buttons.length}/{MAX_LINK_BUTTONS} link buttons
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -82,6 +82,7 @@ const LoadTemplateModal = ({ setModalOpen }) => {
|
||||
setReminder((reminder) => ({
|
||||
...reminder,
|
||||
...template,
|
||||
message_json: null,
|
||||
// drop the template's ID
|
||||
id: undefined,
|
||||
}));
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Username } from "./Username";
|
||||
import { Content } from "./Content";
|
||||
import { Embed } from "./Embed";
|
||||
import { Embeds } from "./Embed/Embeds";
|
||||
import { Avatar } from "./Avatar";
|
||||
import { LinkButtons } from "./LinkButtons";
|
||||
|
||||
export const Message = () => (
|
||||
<div class="column discord-frame">
|
||||
@@ -15,7 +16,8 @@ export const Message = () => (
|
||||
<div class="content">
|
||||
<Username />
|
||||
<Content />
|
||||
<Embed />
|
||||
<Embeds />
|
||||
<LinkButtons />
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
import type { Reminder } from "../../api";
|
||||
|
||||
export const MAX_EMBEDS = 10;
|
||||
export const MAX_EMBED_TEXT = 6000;
|
||||
export const DEFAULT_EMBED_COLOR = 9418359;
|
||||
export const MAX_BUTTON_ROWS = 5;
|
||||
export const MAX_BUTTONS_PER_ROW = 5;
|
||||
export const MAX_LINK_BUTTONS = MAX_BUTTON_ROWS * MAX_BUTTONS_PER_ROW;
|
||||
|
||||
export type MessageComponent = {
|
||||
type: number;
|
||||
style?: number;
|
||||
label?: string;
|
||||
url?: string;
|
||||
components?: MessageComponent[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type DiscordEmbed = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
color?: number;
|
||||
author?: { name?: string; icon_url?: string; [key: string]: unknown };
|
||||
footer?: { text?: string; icon_url?: string; [key: string]: unknown };
|
||||
image?: { url?: string; [key: string]: unknown };
|
||||
thumbnail?: { url?: string; [key: string]: unknown };
|
||||
fields?: { name: string; value: string; inline?: boolean }[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type MessagePayload = {
|
||||
content?: string;
|
||||
tts?: boolean;
|
||||
username?: string;
|
||||
avatar_url?: string;
|
||||
embeds?: DiscordEmbed[];
|
||||
attachments?: { id: number; filename: string }[];
|
||||
components?: MessageComponent[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export function embedFields(embed: DiscordEmbed) {
|
||||
return {
|
||||
embed_title: embed.title || "",
|
||||
embed_description: embed.description || "",
|
||||
embed_color: embed.color ?? DEFAULT_EMBED_COLOR,
|
||||
embed_author: embed.author?.name || "",
|
||||
embed_author_url: embed.author?.icon_url || null,
|
||||
embed_footer: embed.footer?.text || "",
|
||||
embed_footer_url: embed.footer?.icon_url || null,
|
||||
embed_image_url: embed.image?.url || null,
|
||||
embed_thumbnail_url: embed.thumbnail?.url || null,
|
||||
embed_fields: (embed.fields || []).map(({ name, value, inline }) => ({
|
||||
title: name,
|
||||
value,
|
||||
inline: inline ?? false,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function toEmbed(reminder: Reminder, original: DiscordEmbed = {}): DiscordEmbed {
|
||||
const embed: DiscordEmbed = { ...original, color: reminder.embed_color };
|
||||
for (const [key, value] of [
|
||||
["title", reminder.embed_title],
|
||||
["description", reminder.embed_description],
|
||||
]) {
|
||||
if (value) embed[key] = value;
|
||||
else delete embed[key];
|
||||
}
|
||||
for (const [key, textKey, text, icon] of [
|
||||
["author", "name", reminder.embed_author, reminder.embed_author_url],
|
||||
["footer", "text", reminder.embed_footer, reminder.embed_footer_url],
|
||||
]) {
|
||||
const part = { ...(original[key] as object) };
|
||||
if (text) part[textKey] = text;
|
||||
else delete part[textKey];
|
||||
if (icon) part["icon_url"] = icon;
|
||||
else delete part["icon_url"];
|
||||
if (Object.keys(part).length) embed[key] = part;
|
||||
else delete embed[key];
|
||||
}
|
||||
for (const [key, url] of [
|
||||
["image", reminder.embed_image_url],
|
||||
["thumbnail", reminder.embed_thumbnail_url],
|
||||
]) {
|
||||
if (url) embed[key] = { ...(original[key] as object), url };
|
||||
else delete embed[key];
|
||||
}
|
||||
const fields = (reminder.embed_fields || []).map(({ title, value, inline }) => ({
|
||||
name: title,
|
||||
value,
|
||||
inline,
|
||||
}));
|
||||
if (fields.length) embed.fields = fields;
|
||||
else delete embed.fields;
|
||||
return embed;
|
||||
}
|
||||
|
||||
export function hasEmbedContent(embed: DiscordEmbed): boolean {
|
||||
return (
|
||||
[
|
||||
embed.title,
|
||||
embed.description,
|
||||
embed.author?.name,
|
||||
embed.footer?.text,
|
||||
embed.footer?.icon_url,
|
||||
embed.image?.url,
|
||||
embed.thumbnail?.url,
|
||||
].some((value) => !!value?.trim()) ||
|
||||
!!embed.fields?.length ||
|
||||
Object.keys(embed).some(
|
||||
(key) =>
|
||||
![
|
||||
"title",
|
||||
"description",
|
||||
"author",
|
||||
"footer",
|
||||
"image",
|
||||
"thumbnail",
|
||||
"color",
|
||||
"fields",
|
||||
].includes(key),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function reminderEmbeds(reminder: Reminder): DiscordEmbed[] {
|
||||
const embeds = reminder.message_json?.embeds;
|
||||
if (embeds) return embeds.length ? embeds : [{ color: DEFAULT_EMBED_COLOR }];
|
||||
return [toEmbed(reminder)];
|
||||
}
|
||||
|
||||
export function updateEmbed(
|
||||
reminder: Reminder,
|
||||
index: number,
|
||||
update: (r: Reminder) => Reminder,
|
||||
): Reminder {
|
||||
const embeds = [...reminderEmbeds(reminder)];
|
||||
const original = embeds[index];
|
||||
if (!original) return reminder;
|
||||
const changed = update({ ...reminder, ...embedFields(original) });
|
||||
embeds[index] = toEmbed(changed, original);
|
||||
return {
|
||||
...reminder,
|
||||
...embedFields(embeds[0]),
|
||||
message_json: { ...reminder.message_json, embeds },
|
||||
};
|
||||
}
|
||||
|
||||
export function addEmbed(reminder: Reminder): Reminder {
|
||||
const embeds = reminderEmbeds(reminder);
|
||||
if (embeds.length >= MAX_EMBEDS) return reminder;
|
||||
return {
|
||||
...reminder,
|
||||
message_json: {
|
||||
...reminder.message_json,
|
||||
embeds: [...embeds, { color: DEFAULT_EMBED_COLOR }],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function removeEmbed(reminder: Reminder, index: number): Reminder {
|
||||
const embeds = reminderEmbeds(reminder).filter((_, i) => i !== index);
|
||||
return {
|
||||
...reminder,
|
||||
...embedFields(embeds[0] || {}),
|
||||
message_json: { ...reminder.message_json, embeds },
|
||||
};
|
||||
}
|
||||
|
||||
export function embedTextLength(embeds: DiscordEmbed[]): number {
|
||||
return embeds.reduce(
|
||||
(sum, embed) =>
|
||||
sum +
|
||||
[
|
||||
embed.title,
|
||||
embed.description,
|
||||
embed.author?.name,
|
||||
embed.footer?.text,
|
||||
...(embed.fields || []).flatMap((field) => [field.name, field.value]),
|
||||
].reduce((sum, value) => sum + Array.from((value || "").trim()).length, 0),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
export function linkButtons(reminder: Reminder) {
|
||||
return (reminder.message_json?.components || []).flatMap((row, rowIndex) =>
|
||||
row.type === 1
|
||||
? (row.components || []).flatMap((button, buttonIndex) =>
|
||||
button.type === 2 && button.style === 5
|
||||
? [{ button, rowIndex, buttonIndex }]
|
||||
: [],
|
||||
)
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
function availableButtonRow(components: MessageComponent[]): number {
|
||||
return components.findIndex(
|
||||
(row) =>
|
||||
row.type === 1 &&
|
||||
!!row.components &&
|
||||
row.components.length < MAX_BUTTONS_PER_ROW &&
|
||||
row.components.every((button) => button.type === 2),
|
||||
);
|
||||
}
|
||||
|
||||
export function canAddLinkButton(reminder: Reminder): boolean {
|
||||
const components = reminder.message_json?.components || [];
|
||||
return availableButtonRow(components) >= 0 || components.length < MAX_BUTTON_ROWS;
|
||||
}
|
||||
|
||||
export function addLinkButton(reminder: Reminder): Reminder {
|
||||
if (!canAddLinkButton(reminder)) return reminder;
|
||||
const components = [...(reminder.message_json?.components || [])];
|
||||
const rowIndex = availableButtonRow(components);
|
||||
const button: MessageComponent = { type: 2, style: 5, label: "Open link", url: "" };
|
||||
if (rowIndex >= 0) {
|
||||
const row = components[rowIndex];
|
||||
components[rowIndex] = { ...row, components: [...row.components, button] };
|
||||
} else components.push({ type: 1, components: [button] });
|
||||
return { ...reminder, message_json: { ...reminder.message_json, components } };
|
||||
}
|
||||
|
||||
export function updateLinkButton(
|
||||
reminder: Reminder,
|
||||
rowIndex: number,
|
||||
buttonIndex: number,
|
||||
change: { label?: string; url?: string } | null,
|
||||
): Reminder {
|
||||
const components = [...(reminder.message_json?.components || [])];
|
||||
const row = components[rowIndex];
|
||||
const button = row?.components?.[buttonIndex];
|
||||
if (row?.type !== 1 || button?.type !== 2 || button.style !== 5) return reminder;
|
||||
const buttons = [...row.components];
|
||||
if (change) buttons[buttonIndex] = { ...button, ...change };
|
||||
else buttons.splice(buttonIndex, 1);
|
||||
if (buttons.length) components[rowIndex] = { ...row, components: buttons };
|
||||
else components.splice(rowIndex, 1);
|
||||
return { ...reminder, message_json: { ...reminder.message_json, components } };
|
||||
}
|
||||
|
||||
function validateComponents(components: MessageComponent[] = []) {
|
||||
if (components.length > MAX_BUTTON_ROWS)
|
||||
throw new Error(`A reminder can have at most ${MAX_BUTTON_ROWS} button rows.`);
|
||||
for (const row of components) {
|
||||
if (row.type !== 1) continue;
|
||||
if (
|
||||
!Array.isArray(row.components) ||
|
||||
!row.components.length ||
|
||||
row.components.length > MAX_BUTTONS_PER_ROW
|
||||
)
|
||||
throw new Error(
|
||||
`A button row must have between 1 and ${MAX_BUTTONS_PER_ROW} components.`,
|
||||
);
|
||||
for (const button of row.components) {
|
||||
if (button.type !== 2 || button.style !== 5) continue;
|
||||
if (
|
||||
(button.label !== undefined && typeof button.label !== "string") ||
|
||||
Array.from(button.label || "").length > 80 ||
|
||||
(!button.label && (!button.emoji || typeof button.emoji !== "object"))
|
||||
)
|
||||
throw new Error(
|
||||
"Link buttons require a label of at most 80 characters or an emoji.",
|
||||
);
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(button.url);
|
||||
} catch {
|
||||
throw new Error("Link buttons require a valid HTTP or HTTPS URL.");
|
||||
}
|
||||
if (!["http:", "https:"].includes(url.protocol) || Array.from(button.url).length > 512)
|
||||
throw new Error(
|
||||
"Link buttons require an HTTP or HTTPS URL of at most 512 characters.",
|
||||
);
|
||||
if (button.custom_id !== undefined || button.sku_id !== undefined)
|
||||
throw new Error("Link buttons cannot have a custom_id or sku_id.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function prepareReminder(reminder: Reminder): Reminder {
|
||||
validateComponents(reminder.message_json?.components);
|
||||
const embeds = reminderEmbeds(reminder).filter(hasEmbedContent);
|
||||
if (embeds.length > MAX_EMBEDS)
|
||||
throw new Error(`A reminder can have at most ${MAX_EMBEDS} embeds.`);
|
||||
if (embedTextLength(embeds) > MAX_EMBED_TEXT)
|
||||
throw new Error(`Embed text exceeds ${MAX_EMBED_TEXT} characters across all embeds.`);
|
||||
const message: MessagePayload = {
|
||||
...reminder.message_json,
|
||||
content: reminder.content,
|
||||
tts: reminder.tts,
|
||||
embeds,
|
||||
};
|
||||
if (reminder.username) message.username = reminder.username;
|
||||
else delete message.username;
|
||||
if (reminder.avatar) message.avatar_url = reminder.avatar;
|
||||
else delete message.avatar_url;
|
||||
if (reminder.attachment_name)
|
||||
message.attachments = [{ id: 0, filename: reminder.attachment_name }];
|
||||
else delete message.attachments;
|
||||
return { ...reminder, ...embedFields(embeds[0] || {}), message_json: message };
|
||||
}
|
||||
@@ -34,3 +34,55 @@ div.reminderContent {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.reminder-embed-editor {
|
||||
position: relative;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.discord-embed {
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
|
||||
.remove-embed {
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-embed-row,
|
||||
.add-link-button-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
|
||||
.embed-limit,
|
||||
.link-button-limit {
|
||||
font-size: 0.8rem;
|
||||
color: #737373;
|
||||
}
|
||||
}
|
||||
|
||||
.reminder-link-buttons {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.link-button-editor {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
label {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.link-button-label {
|
||||
flex: 1 1 140px;
|
||||
}
|
||||
.link-button-url {
|
||||
flex: 2 1 200px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import ts from "typescript";
|
||||
|
||||
// Run the pure editor helpers without a browser or additional test dependencies.
|
||||
const source = await readFile(
|
||||
new URL("../src/components/Reminder/message.ts", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const { outputText } = ts.transpileModule(source, {
|
||||
compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2020 },
|
||||
});
|
||||
const {
|
||||
addEmbed,
|
||||
embedFields,
|
||||
prepareReminder,
|
||||
reminderEmbeds,
|
||||
removeEmbed,
|
||||
updateEmbed,
|
||||
addLinkButton,
|
||||
canAddLinkButton,
|
||||
linkButtons,
|
||||
updateLinkButton,
|
||||
} = await import(`data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`);
|
||||
|
||||
function reminder(embeds) {
|
||||
return {
|
||||
content: "hello",
|
||||
tts: false,
|
||||
username: "",
|
||||
avatar: null,
|
||||
attachment_name: null,
|
||||
uid: "unchanged",
|
||||
utc_time: "2026-10-12T12:00:00",
|
||||
...embedFields(embeds?.[0] || {}),
|
||||
message_json: embeds ? { embeds, allowed_mentions: { parse: [] } } : null,
|
||||
};
|
||||
}
|
||||
|
||||
test("editing one embed preserves the others and unedited payload properties", () => {
|
||||
const original = reminder([
|
||||
{ title: "first" },
|
||||
{ description: "second", author: { name: "author", url: "https://example.com" } },
|
||||
]);
|
||||
const edited = updateEmbed(original, 1, (r) => ({ ...r, embed_description: "updated" }));
|
||||
const payload = prepareReminder(edited).message_json;
|
||||
assert.equal(payload.embeds[0].title, "first");
|
||||
assert.equal(payload.embeds[1].description, "updated");
|
||||
assert.equal(payload.embeds[1].author.url, "https://example.com");
|
||||
assert.deepEqual(payload.allowed_mentions, { parse: [] });
|
||||
assert.equal(edited.uid, original.uid);
|
||||
assert.equal(original.message_json.embeds[1].description, "second");
|
||||
});
|
||||
|
||||
test("adding stops at ten embeds and removal permits another", () => {
|
||||
let value = reminder([{ title: "first" }]);
|
||||
for (let i = 0; i < 12; i++) value = addEmbed(value);
|
||||
assert.equal(reminderEmbeds(value).length, 10);
|
||||
assert.equal(addEmbed(value), value);
|
||||
value = removeEmbed(value, 0);
|
||||
assert.equal(reminderEmbeds(value).length, 9);
|
||||
value = updateEmbed(value, 0, (r) => ({ ...r, embed_title: "new first" }));
|
||||
assert.equal(prepareReminder(value).message_json.embeds[0].title, "new first");
|
||||
assert.equal(reminderEmbeds(addEmbed(value)).length, 10);
|
||||
});
|
||||
|
||||
test("saving and reloading multiple embeds preserves order and drops blank editors", () => {
|
||||
let value = addEmbed(reminder([{ description: "first" }]));
|
||||
value = updateEmbed(value, 1, (r) => ({ ...r, embed_description: "second" }));
|
||||
value = addEmbed(value);
|
||||
const saved = prepareReminder(value);
|
||||
assert.deepEqual(
|
||||
saved.message_json.embeds.map((embed) => embed.description),
|
||||
["first", "second"],
|
||||
);
|
||||
assert.deepEqual(reminderEmbeds(JSON.parse(JSON.stringify(saved))), saved.message_json.embeds);
|
||||
assert.deepEqual(prepareReminder(reminder()).message_json.embeds, []);
|
||||
});
|
||||
|
||||
test("legacy embeds are retained when a second embed is added", () => {
|
||||
const legacy = {
|
||||
...reminder(),
|
||||
embed_title: "legacy",
|
||||
embed_fields: [{ title: "name", value: "value", inline: true }],
|
||||
};
|
||||
const saved = prepareReminder(addEmbed(legacy));
|
||||
assert.equal(saved.message_json.embeds[0].title, "legacy");
|
||||
assert.deepEqual(saved.message_json.embeds[0].fields, [
|
||||
{ name: "name", value: "value", inline: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test("combined embed text accepts 6000 characters and rejects 6001", () => {
|
||||
const embeds = [{ description: "x".repeat(3000) }, { description: "y".repeat(3000) }];
|
||||
assert.equal(prepareReminder(reminder(embeds)).message_json.embeds.length, 2);
|
||||
assert.throws(
|
||||
() => prepareReminder(reminder([embeds[0], { description: "y".repeat(3001) }])),
|
||||
/6000/,
|
||||
);
|
||||
assert.throws(
|
||||
() => prepareReminder(reminder(Array.from({ length: 11 }, () => ({ title: "embed" })))),
|
||||
/10 embeds/,
|
||||
);
|
||||
});
|
||||
|
||||
test("link buttons can be added, edited, saved, reloaded and removed without losing embeds", () => {
|
||||
const original = reminder([{ title: "first" }, { title: "second" }]);
|
||||
let value = addLinkButton(original);
|
||||
value = updateLinkButton(value, 0, 0, { label: "Visit", url: "https://example.com" });
|
||||
const saved = prepareReminder(value);
|
||||
assert.deepEqual(saved.message_json.components, [
|
||||
{
|
||||
type: 1,
|
||||
components: [{ type: 2, style: 5, label: "Visit", url: "https://example.com" }],
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(saved.message_json.embeds, original.message_json.embeds);
|
||||
assert.deepEqual(saved.message_json.allowed_mentions, { parse: [] });
|
||||
assert.equal(
|
||||
linkButtons(JSON.parse(JSON.stringify(saved)))[0].button.url,
|
||||
"https://example.com",
|
||||
);
|
||||
assert.deepEqual(
|
||||
prepareReminder(updateLinkButton(saved, 0, 0, null)).message_json.components,
|
||||
[],
|
||||
);
|
||||
assert.equal(original.message_json.components, undefined);
|
||||
const legacy = updateLinkButton(addLinkButton({ ...reminder(), embed_title: "legacy" }), 0, 0, {
|
||||
url: "https://example.com",
|
||||
});
|
||||
assert.equal(prepareReminder(legacy).message_json.embeds[0].title, "legacy");
|
||||
});
|
||||
|
||||
test("link buttons fill five rows of five and preserve other components", () => {
|
||||
let value = reminder();
|
||||
for (let i = 0; i < 26; i++) value = addLinkButton(value);
|
||||
assert.equal(linkButtons(value).length, 25);
|
||||
assert.deepEqual(
|
||||
value.message_json.components.map((row) => row.components.length),
|
||||
[5, 5, 5, 5, 5],
|
||||
);
|
||||
assert.equal(canAddLinkButton(value), false);
|
||||
assert.equal(addLinkButton(value), value);
|
||||
value = updateLinkButton(value, 2, 2, null);
|
||||
assert.equal(canAddLinkButton(value), true);
|
||||
assert.equal(linkButtons(addLinkButton(value)).length, 25);
|
||||
const other = { type: 1, id: 7, components: [{ type: 3, custom_id: "select", options: [] }] };
|
||||
value = { ...reminder(), message_json: { components: [other] } };
|
||||
value = addLinkButton(value);
|
||||
assert.deepEqual(value.message_json.components[0], other);
|
||||
assert.equal(linkButtons(value)[0].rowIndex, 1);
|
||||
});
|
||||
|
||||
test("invalid link button URLs, labels and row limits are rejected on save", () => {
|
||||
let value = addLinkButton(reminder());
|
||||
assert.throws(() => prepareReminder(value), /URL/);
|
||||
value = updateLinkButton(value, 0, 0, {
|
||||
url: "https://example.com/" + "x".repeat(492),
|
||||
label: "x".repeat(80),
|
||||
});
|
||||
assert.equal(prepareReminder(value).message_json.components.length, 1);
|
||||
for (const change of [
|
||||
{ url: "invalid" },
|
||||
{ url: "javascript:alert(1)" },
|
||||
{ url: "https://example.com/" + "x".repeat(493) },
|
||||
{ label: "" },
|
||||
{ label: "x".repeat(81) },
|
||||
])
|
||||
assert.throws(() => prepareReminder(updateLinkButton(value, 0, 0, change)));
|
||||
const row = value.message_json.components[0];
|
||||
assert.throws(
|
||||
() => prepareReminder({ ...value, message_json: { components: Array(6).fill(row) } }),
|
||||
/5 button rows/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
prepareReminder({
|
||||
...value,
|
||||
message_json: {
|
||||
components: [{ ...row, components: Array(6).fill(row.components[0]) }],
|
||||
},
|
||||
}),
|
||||
/5 components/,
|
||||
);
|
||||
});
|
||||
@@ -10,6 +10,7 @@ mod event_handlers;
|
||||
#[cfg(not(test))]
|
||||
mod hooks;
|
||||
mod interval_parser;
|
||||
mod message;
|
||||
mod metrics;
|
||||
#[cfg(not(test))]
|
||||
mod models;
|
||||
|
||||
+604
@@ -0,0 +1,604 @@
|
||||
//! Conversion between the dashboard's message fields and Discord webhook payloads.
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use sqlx::{types::Json, FromRow, MySqlConnection};
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, FromRow)]
|
||||
#[serde(default)]
|
||||
struct MessageFields {
|
||||
content: String,
|
||||
tts: bool,
|
||||
username: Option<String>,
|
||||
avatar: Option<String>,
|
||||
embed_title: String,
|
||||
embed_description: String,
|
||||
embed_author: String,
|
||||
embed_author_url: Option<String>,
|
||||
embed_footer: String,
|
||||
embed_footer_url: Option<String>,
|
||||
embed_image_url: Option<String>,
|
||||
embed_thumbnail_url: Option<String>,
|
||||
embed_color: u32,
|
||||
embed_fields: Option<Json<Value>>,
|
||||
message_json: Option<Json<Value>>,
|
||||
attachment_name: Option<String>,
|
||||
has_attachment: bool,
|
||||
}
|
||||
|
||||
const EMBED_PATHS: &[(&str, &[&str])] = &[
|
||||
("embed_title", &["title"]),
|
||||
("embed_description", &["description"]),
|
||||
("embed_author", &["author", "name"]),
|
||||
("embed_author_url", &["author", "icon_url"]),
|
||||
("embed_footer", &["footer", "text"]),
|
||||
("embed_footer_url", &["footer", "icon_url"]),
|
||||
("embed_image_url", &["image", "url"]),
|
||||
("embed_thumbnail_url", &["thumbnail", "url"]),
|
||||
("embed_color", &["color"]),
|
||||
("embed_fields", &["fields"]),
|
||||
];
|
||||
|
||||
fn set_path(target: &mut Value, path: &[&str], value: Value) {
|
||||
if !target.is_object() {
|
||||
*target = json!({});
|
||||
}
|
||||
let object = target.as_object_mut().unwrap();
|
||||
if path.len() == 1 {
|
||||
if value.is_null() {
|
||||
object.remove(path[0]);
|
||||
} else {
|
||||
object.insert(path[0].to_string(), value);
|
||||
}
|
||||
} else {
|
||||
set_path(object.entry(path[0]).or_insert_with(|| json!({})), &path[1..], value);
|
||||
}
|
||||
}
|
||||
|
||||
fn embed_has_content(embed: &Value) -> bool {
|
||||
["/title", "/description", "/author/name", "/footer/text", "/footer/icon_url",
|
||||
"/image/url", "/thumbnail/url"]
|
||||
.iter()
|
||||
.any(|path| embed.pointer(path).and_then(Value::as_str).is_some_and(|s| !s.trim().is_empty()))
|
||||
|| embed["fields"].as_array().is_some_and(|fields| !fields.is_empty())
|
||||
// Preserve payload features the dashboard does not edit.
|
||||
|| embed.as_object().is_some_and(|object| object.keys().any(|key|
|
||||
!["title", "description", "author", "footer", "image", "thumbnail", "color", "fields"].contains(&key.as_str())))
|
||||
}
|
||||
|
||||
fn discord_fields(value: &Value) -> Value {
|
||||
match value.as_array() {
|
||||
Some(fields) => Value::Array(
|
||||
fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
json!({
|
||||
"name": field["title"], "value": field["value"], "inline": field["inline"]
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
None => json!([]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Use an explicit payload verbatim, or build one from existing dashboard fields.
|
||||
pub fn payload(reminder: &impl Serialize) -> Value {
|
||||
let fields = serde_json::to_value(reminder).expect("Reminder fields are serializable");
|
||||
if !fields["message_json"].is_null() {
|
||||
return fields["message_json"].clone();
|
||||
}
|
||||
let mut payload = json!({ "content": fields["content"], "tts": fields["tts"] });
|
||||
if fields["username"].as_str().is_some_and(|s| !s.is_empty()) {
|
||||
payload["username"] = fields["username"].clone();
|
||||
}
|
||||
if !fields["avatar"].is_null() {
|
||||
payload["avatar_url"] = fields["avatar"].clone();
|
||||
}
|
||||
let mut embed = json!({});
|
||||
for (field, path) in EMBED_PATHS {
|
||||
let value = if *field == "embed_fields" {
|
||||
discord_fields(&fields[*field])
|
||||
} else {
|
||||
fields[*field].clone()
|
||||
};
|
||||
set_path(&mut embed, path, value);
|
||||
}
|
||||
if embed_has_content(&embed) {
|
||||
payload["embeds"] = json!([embed]);
|
||||
}
|
||||
if (!fields["attachment"].is_null() || fields["has_attachment"].as_bool() == Some(true))
|
||||
&& fields["attachment_name"].is_string()
|
||||
{
|
||||
payload["attachments"] = json!([{ "id": 0, "filename": fields["attachment_name"] }]);
|
||||
}
|
||||
payload
|
||||
}
|
||||
|
||||
/// Validate Discord's link buttons, embed count and text limits.
|
||||
pub fn validate_payload(message: &Value) -> Result<(), String> {
|
||||
if !message.is_object() {
|
||||
return Err("message_json must be an object".into());
|
||||
}
|
||||
if let Some(content) = message.get("content") {
|
||||
if content.as_str().is_none_or(|text| text.chars().count() > 2000) {
|
||||
return Err("Message content exceeds 2000 characters or is not text".into());
|
||||
}
|
||||
}
|
||||
validate_components(message)?;
|
||||
let Some(embeds) = message.get("embeds") else {
|
||||
return Ok(());
|
||||
};
|
||||
let embeds = embeds.as_array().ok_or("embeds must be an array")?;
|
||||
if embeds.len() > 10 {
|
||||
return Err("A reminder can have at most 10 embeds".into());
|
||||
}
|
||||
let mut total = 0;
|
||||
for (index, embed) in embeds.iter().enumerate() {
|
||||
if !embed.is_object() {
|
||||
return Err(format!("Embed {} must be an object", index + 1));
|
||||
}
|
||||
let mut check_text = |value: Option<&Value>, limit: usize| -> Result<(), String> {
|
||||
if let Some(value) = value {
|
||||
let text = value
|
||||
.as_str()
|
||||
.ok_or_else(|| format!("Embed {} has a non-text field", index + 1))?;
|
||||
let length = text.trim().chars().count();
|
||||
if length > limit {
|
||||
return Err(format!(
|
||||
"Embed {} exceeds a {} character field limit",
|
||||
index + 1,
|
||||
limit
|
||||
));
|
||||
}
|
||||
total += length;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
for (path, limit) in
|
||||
[("/title", 256), ("/description", 4096), ("/author/name", 256), ("/footer/text", 2048)]
|
||||
{
|
||||
check_text(embed.pointer(path), limit)?;
|
||||
}
|
||||
if let Some(fields) = embed.get("fields") {
|
||||
let fields = fields.as_array().ok_or("Embed fields must be an array")?;
|
||||
if fields.len() > 25 {
|
||||
return Err("An embed can have at most 25 fields".into());
|
||||
}
|
||||
for field in fields {
|
||||
if !field.is_object() {
|
||||
return Err("Embed fields must be objects".into());
|
||||
}
|
||||
for (name, limit) in [("name", 256), ("value", 1024)] {
|
||||
let text = field.get(name).ok_or("Embed fields require a name and value")?;
|
||||
if text.as_str().is_none_or(|text| text.trim().is_empty()) {
|
||||
return Err("Embed fields require a non-empty name and value".into());
|
||||
}
|
||||
check_text(Some(text), limit)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if total > 6000 {
|
||||
return Err("Embed text exceeds 6000 characters across all embeds".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_components(message: &Value) -> Result<(), String> {
|
||||
let Some(components) = message.get("components") else {
|
||||
return Ok(());
|
||||
};
|
||||
let rows = components.as_array().ok_or("components must be an array")?;
|
||||
// Components V2 has a different layout and count limit. Leave its validation to Discord.
|
||||
if message["flags"].as_u64().is_some_and(|flags| flags & (1 << 15) != 0) {
|
||||
return Ok(());
|
||||
}
|
||||
if rows.len() > 5 {
|
||||
return Err("A reminder can have at most 5 button rows (25 buttons)".into());
|
||||
}
|
||||
for row in rows {
|
||||
if row["type"].as_u64() != Some(1) {
|
||||
continue;
|
||||
}
|
||||
let buttons = row["components"].as_array().ok_or("Button rows require components")?;
|
||||
if buttons.is_empty() || buttons.len() > 5 {
|
||||
return Err("A button row must have between 1 and 5 components".into());
|
||||
}
|
||||
for button in buttons {
|
||||
if button["type"].as_u64() != Some(2) || button["style"].as_u64() != Some(5) {
|
||||
continue;
|
||||
}
|
||||
let label = button.get("label").and_then(Value::as_str);
|
||||
if label.is_some_and(|label| label.chars().count() > 80)
|
||||
|| (label.is_none_or(str::is_empty) && !button["emoji"].is_object())
|
||||
|| button.get("label").is_some_and(|label| !label.is_string())
|
||||
{
|
||||
return Err(
|
||||
"Link buttons require a label of at most 80 characters or an emoji".into()
|
||||
);
|
||||
}
|
||||
let url = button["url"].as_str().ok_or("Link buttons require a URL")?;
|
||||
if url.chars().count() > 512
|
||||
|| !reqwest::Url::parse(url)
|
||||
.is_ok_and(|url| ["http", "https"].contains(&url.scheme()))
|
||||
{
|
||||
return Err(
|
||||
"Link buttons require an HTTP or HTTPS URL of at most 512 characters".into()
|
||||
);
|
||||
}
|
||||
if button.get("custom_id").is_some() || button.get("sku_id").is_some() {
|
||||
return Err("Link buttons cannot have a custom_id or sku_id".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Present the first embed through the existing dashboard controls.
|
||||
pub fn response(reminder: impl Serialize) -> Value {
|
||||
let mut reminder = serde_json::to_value(reminder).expect("Reminder fields are serializable");
|
||||
if let Some(reminders) = reminder.as_array_mut() {
|
||||
for reminder in reminders {
|
||||
*reminder = response(&*reminder);
|
||||
}
|
||||
return reminder;
|
||||
}
|
||||
let payload = reminder["message_json"].clone();
|
||||
if payload.is_null() {
|
||||
return reminder;
|
||||
}
|
||||
reminder["content"] = payload["content"].as_str().unwrap_or_default().into();
|
||||
reminder["tts"] = payload["tts"].as_bool().unwrap_or(false).into();
|
||||
reminder["username"] = payload["username"].clone();
|
||||
reminder["avatar"] = payload["avatar_url"].clone();
|
||||
for (field, path) in EMBED_PATHS {
|
||||
let value = payload["embeds"][0]
|
||||
.pointer(&format!("/{}", path.join("/")))
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
reminder[*field] = match *field {
|
||||
"embed_fields" => Value::Array(value.as_array().map(|fields| fields.iter().map(|field| json!({
|
||||
"title": field["name"], "value": field["value"], "inline": field["inline"].as_bool().unwrap_or(false)
|
||||
})).collect()).unwrap_or_default()),
|
||||
"embed_color" => value.as_u64().unwrap_or(0).into(),
|
||||
"embed_title" | "embed_description" | "embed_author" | "embed_footer" => value.as_str().unwrap_or_default().into(),
|
||||
_ => value,
|
||||
};
|
||||
}
|
||||
reminder
|
||||
}
|
||||
|
||||
pub fn is_message_field(field: &str) -> bool {
|
||||
["content", "tts", "username", "avatar"].contains(&field)
|
||||
|| EMBED_PATHS.iter().any(|(name, _)| *name == field)
|
||||
}
|
||||
|
||||
fn patch(payload: &mut Value, field: &str, mut value: Value) {
|
||||
if let Some((_, path)) = EMBED_PATHS.iter().find(|(name, _)| *name == field) {
|
||||
if !payload["embeds"].is_array() {
|
||||
payload["embeds"] = json!([]);
|
||||
}
|
||||
let embeds = payload["embeds"].as_array_mut().unwrap();
|
||||
if embeds.is_empty() {
|
||||
embeds.push(json!({}));
|
||||
}
|
||||
if field == "embed_fields" {
|
||||
value = discord_fields(&value);
|
||||
}
|
||||
set_path(&mut embeds[0], path, value);
|
||||
if !embed_has_content(&embeds[0]) {
|
||||
embeds.remove(0);
|
||||
}
|
||||
} else {
|
||||
if field == "username" && value.as_str() == Some("") {
|
||||
value = Value::Null;
|
||||
}
|
||||
set_path(payload, &[if field == "avatar" { "avatar_url" } else { field }], value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert legacy rows on first edit and preserve unedited payload keys.
|
||||
pub async fn update_field(
|
||||
connection: &mut MySqlConnection,
|
||||
uid: &str,
|
||||
field: &str,
|
||||
value: &impl Serialize,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
let fields = sqlx::query_as::<_, MessageFields>(
|
||||
"SELECT content, tts, username, avatar, embed_title, embed_description,
|
||||
embed_author, embed_author_url, embed_footer, embed_footer_url, embed_image_url,
|
||||
embed_thumbnail_url, embed_color, embed_fields, message_json, attachment_name,
|
||||
attachment IS NOT NULL AS has_attachment FROM reminders WHERE uid = ? FOR UPDATE",
|
||||
)
|
||||
.bind(uid)
|
||||
.fetch_one(&mut *connection)
|
||||
.await?;
|
||||
let mut message = payload(&fields);
|
||||
let value = if field == "attachments" {
|
||||
if fields.has_attachment {
|
||||
fields
|
||||
.attachment_name
|
||||
.as_ref()
|
||||
.map(|name| json!([{ "id": 0, "filename": name }]))
|
||||
.unwrap_or_else(|| json!([]))
|
||||
} else {
|
||||
json!([])
|
||||
}
|
||||
} else {
|
||||
serde_json::to_value(value).map_err(|e| sqlx::Error::Encode(e.into()))?
|
||||
};
|
||||
patch(&mut message, field, value);
|
||||
validate_payload(&message).map_err(sqlx::Error::Protocol)?;
|
||||
sqlx::query("UPDATE reminders SET message_json = ? WHERE uid = ?")
|
||||
.bind(Json(message))
|
||||
.bind(uid)
|
||||
.execute(connection)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn builds_discord_payload_and_omits_empty_embed() {
|
||||
assert_eq!(
|
||||
payload(&json!({"content": "hello", "tts": true})),
|
||||
json!({"content": "hello", "tts": true})
|
||||
);
|
||||
let message = payload(&json!({"content": "", "tts": false, "embed_description": "body",
|
||||
"embed_fields": [{"title": "heading", "value": "value", "inline": true}],
|
||||
"attachment": "bytes", "attachment_name": "file.txt"}));
|
||||
assert_eq!(message["embeds"][0]["fields"][0]["name"], "heading");
|
||||
assert_eq!(message["attachments"], json!([{ "id": 0, "filename": "file.txt" }]));
|
||||
assert_eq!(response(json!({"message_json": message}))["embed_description"], "body");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_payload_is_exact() {
|
||||
let exact = json!({"content": "<<timenow:UTC:%Y>>", "flags": 32768, "components": [], "custom": true});
|
||||
assert_eq!(
|
||||
payload(&json!({"message_json": exact, "content": "ignored", "tts": true})),
|
||||
exact
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_payload_falls_back_to_legacy_fields() {
|
||||
let legacy = json!({
|
||||
"message_json": null, "content": "legacy", "tts": false,
|
||||
"username": "Reminder", "avatar": "https://example.com/avatar.png"
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
payload(&legacy),
|
||||
json!({
|
||||
"content": "legacy", "tts": false, "username": "Reminder",
|
||||
"avatar_url": "https://example.com/avatar.png"
|
||||
})
|
||||
);
|
||||
let expected_response = legacy.clone();
|
||||
|
||||
assert_eq!(response(legacy), expected_response);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashboard_response_uses_payload_and_preserves_schedule() {
|
||||
let reminder = response(json!({
|
||||
"uid": "reminder-id", "utc_time": "2026-10-12T12:00:00", "enabled": true,
|
||||
"content": "stale", "embed_description": "stale", "tts": false,
|
||||
"message_json": {
|
||||
"content": "current", "tts": true,
|
||||
"embeds": [{"description": "body", "fields": [{"name": "heading", "value": "value"}]}]
|
||||
}
|
||||
}));
|
||||
|
||||
assert_eq!(reminder["content"], "current");
|
||||
assert_eq!(reminder["tts"], true);
|
||||
assert_eq!(reminder["embed_description"], "body");
|
||||
assert_eq!(
|
||||
reminder["embed_fields"],
|
||||
json!([{"title": "heading", "value": "value", "inline": false}])
|
||||
);
|
||||
assert_eq!(reminder["uid"], "reminder-id");
|
||||
assert_eq!(reminder["utc_time"], "2026-10-12T12:00:00");
|
||||
assert_eq!(reminder["enabled"], true);
|
||||
assert_eq!(reminder["message_json"]["embeds"].as_array().unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clearing_webhook_identity_preserves_other_payload_keys() {
|
||||
let mut message = json!({
|
||||
"content": "hello", "username": "Reminder",
|
||||
"avatar_url": "https://example.com/avatar.png", "flags": 4
|
||||
});
|
||||
|
||||
patch(&mut message, "username", json!(""));
|
||||
patch(&mut message, "avatar", Value::Null);
|
||||
|
||||
assert_eq!(message, json!({"content": "hello", "flags": 4}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edits_preserve_extra_payload_features() {
|
||||
let mut message = json!({"flags": 4, "embeds": [{"description": "before", "url": "https://example.com"}, {"title": "second"}]});
|
||||
patch(&mut message, "embed_description", json!("after"));
|
||||
patch(&mut message, "embed_author_url", json!("https://example.com/icon.png"));
|
||||
assert_eq!(message["flags"], 4);
|
||||
assert_eq!(message["embeds"][0]["description"], "after");
|
||||
assert_eq!(message["embeds"][0]["url"], "https://example.com");
|
||||
assert_eq!(message["embeds"][1]["title"], "second");
|
||||
patch(&mut message, "embed_author_url", Value::Null);
|
||||
assert!(message["embeds"][0]["author"]["icon_url"].is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clearing_last_embed_content_removes_embed() {
|
||||
let mut message = json!({"embeds": [{"description": "body", "color": 123}]});
|
||||
patch(&mut message, "embed_description", json!(""));
|
||||
assert_eq!(message["embeds"], json!([]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_embed_count_and_combined_text_boundaries() {
|
||||
assert!(validate_payload(&json!({"embeds": vec![json!({"title": "embed"}); 10]})).is_ok());
|
||||
assert!(validate_payload(&json!({"embeds": vec![json!({"title": "embed"}); 11]})).is_err());
|
||||
let mut message = json!({"embeds": [{"description": "x".repeat(3000)}, {"description": "y".repeat(3000)}]});
|
||||
assert!(validate_payload(&message).is_ok());
|
||||
message["embeds"][1]["title"] = json!("x");
|
||||
assert!(validate_payload(&message).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_each_embed_and_field() {
|
||||
assert!(validate_payload(&json!({"embeds": [{"description": "x".repeat(4097)}]})).is_err());
|
||||
assert!(validate_payload(
|
||||
&json!({"embeds": [{"fields": vec![json!({"name": "x", "value": "y"}); 26]}]})
|
||||
)
|
||||
.is_err());
|
||||
for message in [
|
||||
json!([]),
|
||||
json!({"embeds": {}}),
|
||||
json!({"embeds": [null]}),
|
||||
json!({"embeds": [{"fields": [{"name": "x"}]}]}),
|
||||
json!({"embeds": [{"description": 1}]}),
|
||||
] {
|
||||
assert!(validate_payload(&message).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashboard_response_retains_all_embeds_for_editing() {
|
||||
let message = json!({"embeds": [{"title": "first"}, {"title": "second"}], "allowed_mentions": {"parse": []}});
|
||||
let displayed =
|
||||
response(json!({"message_json": message, "content": "", "uid": "unchanged"}));
|
||||
assert_eq!(displayed["message_json"], message);
|
||||
assert_eq!(displayed["embed_title"], "first");
|
||||
assert_eq!(displayed["uid"], "unchanged");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_buttons_survive_payload_response_and_legacy_edits() {
|
||||
let components = json!([{"type": 1, "components": [{
|
||||
"type": 2, "style": 5, "label": "Open link", "url": "https://example.com"
|
||||
}]}]);
|
||||
let mut message = json!({"content": "hello", "components": components});
|
||||
assert!(validate_payload(&message).is_ok());
|
||||
assert_eq!(payload(&json!({"message_json": message})), message);
|
||||
assert_eq!(response(json!({"message_json": message}))["message_json"], message);
|
||||
patch(&mut message, "content", json!("updated"));
|
||||
patch(&mut message, "embed_description", json!("embed"));
|
||||
assert_eq!(message["components"], components);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_link_button_count_and_text_boundaries() {
|
||||
let button = json!({"type": 2, "style": 5, "label": "x".repeat(80),
|
||||
"url": format!("https://example.com/{}", "x".repeat(492))});
|
||||
let row = json!({"type": 1, "components": vec![button.clone(); 5]});
|
||||
assert!(validate_payload(&json!({"components": vec![row.clone(); 5]})).is_ok());
|
||||
assert!(validate_payload(&json!({"components": vec![row; 6]})).is_err());
|
||||
assert!(validate_payload(
|
||||
&json!({"components": [{"type": 1, "components": vec![button.clone(); 6]}]})
|
||||
)
|
||||
.is_err());
|
||||
for (field, value) in [
|
||||
("label", json!("x".repeat(81))),
|
||||
("url", json!(format!("https://example.com/{}", "x".repeat(493)))),
|
||||
] {
|
||||
let mut invalid = button.clone();
|
||||
invalid[field] = value;
|
||||
assert!(validate_payload(
|
||||
&json!({"components": [{"type": 1, "components": [invalid]}]})
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_link_buttons_without_embeds() {
|
||||
for change in [
|
||||
json!({"url": ""}),
|
||||
json!({"url": "javascript:alert(1)"}),
|
||||
json!({"url": "not a URL"}),
|
||||
json!({"label": ""}),
|
||||
json!({"label": 1}),
|
||||
json!({"custom_id": "interactive"}),
|
||||
] {
|
||||
let mut button =
|
||||
json!({"type": 2, "style": 5, "label": "Open", "url": "https://example.com"});
|
||||
button.as_object_mut().unwrap().extend(change.as_object().unwrap().clone());
|
||||
assert!(validate_payload(
|
||||
&json!({"components": [{"type": 1, "components": [button]}]})
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
assert!(validate_payload(&json!({"components": {}})).is_err());
|
||||
assert!(validate_payload(&json!({"components": [{"type": 1, "components": []}]})).is_err());
|
||||
assert!(validate_payload(&json!({"components": [{"type": 1, "components": [{"type": 2, "style": 5, "emoji": {"name": "🔗"}, "url": "https://example.com"}]}]})).is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires MESSAGE_JSON_TEST_DATABASE_URL pointing to an isolated MySQL database"]
|
||||
async fn database_edits_convert_legacy_messages_and_preserve_payload() {
|
||||
let pool =
|
||||
sqlx::MySqlPool::connect(&std::env::var("MESSAGE_JSON_TEST_DATABASE_URL").unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let mut transaction = pool.begin().await.unwrap();
|
||||
let channel = sqlx::query("INSERT INTO channels (channel) VALUES (?)")
|
||||
.bind(rand::random::<u64>())
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.unwrap()
|
||||
.last_insert_id();
|
||||
let uid = format!("message-json-test-{}", rand::random::<u64>());
|
||||
sqlx::query("INSERT INTO reminders (uid, channel_id, `utc_time`, content, embed_description, attachment, attachment_name) VALUES (?, ?, NOW(), 'legacy', 'embed', 'bytes', 'file.txt')")
|
||||
.bind(&uid).bind(channel).execute(&mut *transaction).await.unwrap();
|
||||
update_field(&mut transaction, &uid, "content", &"edited").await.unwrap();
|
||||
let stored: (Json<Value>, String) =
|
||||
sqlx::query_as("SELECT message_json, content FROM reminders WHERE uid = ?")
|
||||
.bind(&uid)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(stored.0["content"], "edited");
|
||||
assert_eq!(stored.0["embeds"][0]["description"], "embed");
|
||||
assert_eq!(stored.0["attachments"][0]["filename"], "file.txt");
|
||||
assert_eq!(stored.1, "legacy");
|
||||
let exact = json!({"content": "exact", "custom": [1, 2], "embeds": [{"description": "first"}, {"description": "second"}]});
|
||||
sqlx::query("UPDATE reminders SET message_json = ? WHERE uid = ?")
|
||||
.bind(Json(exact))
|
||||
.bind(&uid)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
update_field(&mut transaction, &uid, "tts", &true).await.unwrap();
|
||||
update_field(&mut transaction, &uid, "embed_description", &"updated").await.unwrap();
|
||||
let stored: (Json<Value>,) =
|
||||
sqlx::query_as("SELECT message_json FROM reminders WHERE uid = ?")
|
||||
.bind(&uid)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(stored.0["tts"], true);
|
||||
assert_eq!(stored.0["custom"], json!([1, 2]));
|
||||
assert_eq!(stored.0["embeds"][0]["description"], "updated");
|
||||
assert_eq!(stored.0["embeds"][1]["description"], "second");
|
||||
sqlx::query("UPDATE reminders SET attachment = NULL WHERE uid = ?")
|
||||
.bind(&uid)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
update_field(&mut transaction, &uid, "attachments", &()).await.unwrap();
|
||||
let stored: (Json<Value>,) =
|
||||
sqlx::query_as("SELECT message_json FROM reminders WHERE uid = ?")
|
||||
.bind(&uid)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(stored.0["attachments"], json!([]));
|
||||
transaction.rollback().await.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use poise::serenity_prelude::{
|
||||
model::id::{ChannelId, GuildId, UserId},
|
||||
ChannelType,
|
||||
};
|
||||
use sqlx::MySqlPool;
|
||||
use sqlx::{types::Json, MySqlPool};
|
||||
|
||||
use crate::{
|
||||
consts::{DAY, MAX_TIME, MIN_INTERVAL},
|
||||
@@ -76,6 +76,12 @@ impl ReminderBuilder {
|
||||
if utc_time < (Utc::now() - TimeDelta::try_minutes(1).unwrap()).naive_local() {
|
||||
Err(ReminderError::PastTime)
|
||||
} else {
|
||||
let mut message_json =
|
||||
serde_json::json!({"content": self.content, "tts": self.tts});
|
||||
if let (Some(_), Some(name)) = (&self.attachment, &self.attachment_name) {
|
||||
message_json["attachments"] =
|
||||
serde_json::json!([{ "id": 0, "filename": name }]);
|
||||
}
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO reminders (
|
||||
@@ -88,13 +94,12 @@ impl ReminderBuilder {
|
||||
`interval_days`,
|
||||
`interval_months`,
|
||||
`expires`,
|
||||
`content`,
|
||||
`tts`,
|
||||
`message_json`,
|
||||
`attachment_name`,
|
||||
`attachment`,
|
||||
`set_by`
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
",
|
||||
self.uid,
|
||||
@@ -106,8 +111,7 @@ impl ReminderBuilder {
|
||||
self.interval_days,
|
||||
self.interval_months,
|
||||
self.expires,
|
||||
self.content,
|
||||
self.tts,
|
||||
Json(message_json),
|
||||
self.attachment_name,
|
||||
self.attachment,
|
||||
self.set_by
|
||||
|
||||
@@ -49,6 +49,7 @@ pub struct Reminder {
|
||||
pub interval_months: Option<u32>,
|
||||
pub expires: Option<NaiveDateTime>,
|
||||
pub enabled: bool,
|
||||
pub message_json: Option<sqlx::types::Json<serde_json::Value>>,
|
||||
pub content: String,
|
||||
pub embed_description: String,
|
||||
pub set_by: Option<u64>,
|
||||
@@ -83,6 +84,7 @@ impl Reminder {
|
||||
reminders.interval_months,
|
||||
reminders.expires,
|
||||
reminders.enabled,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_description,
|
||||
reminders.set_by
|
||||
@@ -116,6 +118,7 @@ impl Reminder {
|
||||
reminders.interval_months,
|
||||
reminders.expires,
|
||||
reminders.enabled,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_description,
|
||||
reminders.set_by
|
||||
@@ -156,6 +159,7 @@ impl Reminder {
|
||||
reminders.interval_months,
|
||||
reminders.expires,
|
||||
reminders.enabled,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_description,
|
||||
reminders.set_by
|
||||
@@ -215,6 +219,7 @@ impl Reminder {
|
||||
reminders.interval_months,
|
||||
reminders.expires,
|
||||
reminders.enabled,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_description,
|
||||
reminders.set_by
|
||||
@@ -251,6 +256,7 @@ impl Reminder {
|
||||
reminders.interval_months,
|
||||
reminders.expires,
|
||||
reminders.enabled,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_description,
|
||||
reminders.set_by
|
||||
@@ -284,6 +290,7 @@ impl Reminder {
|
||||
reminders.interval_months,
|
||||
reminders.expires,
|
||||
reminders.enabled,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_description,
|
||||
reminders.set_by
|
||||
@@ -321,6 +328,13 @@ impl Reminder {
|
||||
}
|
||||
|
||||
pub fn display_content(&self) -> &str {
|
||||
if let Some(message) = &self.message_json {
|
||||
let content = message["content"].as_str().unwrap_or_default();
|
||||
if !content.is_empty() {
|
||||
return content;
|
||||
}
|
||||
return message["embeds"][0]["description"].as_str().unwrap_or_default();
|
||||
}
|
||||
if self.content.is_empty() {
|
||||
&self.embed_description
|
||||
} else {
|
||||
|
||||
+54
-1
@@ -17,6 +17,7 @@ use poise::serenity_prelude::{
|
||||
Error, Result,
|
||||
};
|
||||
use regex::{Captures, Regex};
|
||||
use secrecy::ExposeSecret;
|
||||
use serde::Deserialize;
|
||||
use sqlx::{
|
||||
types::{
|
||||
@@ -341,6 +342,7 @@ pub struct Reminder {
|
||||
tts: bool,
|
||||
pin: bool,
|
||||
content: String,
|
||||
message_json: Option<Json<serde_json::Value>>,
|
||||
attachment: Option<Vec<u8>>,
|
||||
attachment_name: Option<String>,
|
||||
|
||||
@@ -376,6 +378,7 @@ impl Reminder {
|
||||
reminders.`tts` AS tts,
|
||||
reminders.`pin` AS pin,
|
||||
reminders.`content` AS content,
|
||||
reminders.`message_json` AS message_json,
|
||||
reminders.`attachment` AS attachment,
|
||||
reminders.`attachment_name` AS attachment_name,
|
||||
|
||||
@@ -585,6 +588,15 @@ impl Reminder {
|
||||
let _ = http.as_ref().pin_message(self.channel_id.into(), message_id.into(), None).await;
|
||||
}
|
||||
|
||||
fn files(&self) -> Vec<CreateAttachment> {
|
||||
match (&self.attachment, &self.attachment_name) {
|
||||
(Some(attachment), Some(name)) => {
|
||||
vec![CreateAttachment::bytes(attachment.clone(), name)]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(
|
||||
&self,
|
||||
pool: impl Executor<'_, Database = Database> + Copy,
|
||||
@@ -595,6 +607,22 @@ impl Reminder {
|
||||
reminder: &Reminder,
|
||||
embed: Option<CreateEmbed>,
|
||||
) -> Result<()> {
|
||||
if let Some(payload) = &reminder.message_json {
|
||||
let files = reminder.files();
|
||||
let message = cache_http
|
||||
.http()
|
||||
.send_message(
|
||||
ChannelId::new(reminder.thread_id.unwrap_or(reminder.channel_id)),
|
||||
files,
|
||||
&payload.0,
|
||||
)
|
||||
.await?;
|
||||
if reminder.pin {
|
||||
reminder.pin_message(message.id, cache_http.http()).await;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let channel = if let Some(thread_id) = reminder.thread_id {
|
||||
ChannelId::new(thread_id).to_channel(&cache_http).await
|
||||
} else {
|
||||
@@ -650,6 +678,27 @@ impl Reminder {
|
||||
webhook: Webhook,
|
||||
embed: Option<CreateEmbed>,
|
||||
) -> Result<()> {
|
||||
if let Some(payload) = &reminder.message_json {
|
||||
let token = webhook.token.as_ref().ok_or(Error::Other("Webhook token missing"))?;
|
||||
let message = cache_http
|
||||
.http()
|
||||
.execute_webhook_with_components(
|
||||
webhook.id,
|
||||
reminder.thread_id.map(ChannelId::new),
|
||||
token.expose_secret(),
|
||||
reminder.pin || reminder.restartable,
|
||||
reminder.files(),
|
||||
&payload.0,
|
||||
)
|
||||
.await?;
|
||||
if reminder.pin {
|
||||
if let Some(message) = message {
|
||||
reminder.pin_message(message.id, cache_http.http()).await;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut builder = if let Some(thread_id) = reminder.thread_id {
|
||||
ExecuteWebhook::new()
|
||||
.content(&reminder.content)
|
||||
@@ -712,7 +761,11 @@ impl Reminder {
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
let embed = Embed::from_id(pool, self.id).await.map(|e| e.into());
|
||||
let embed = if self.message_json.is_none() {
|
||||
Embed::from_id(pool, self.id).await.map(|e| e.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result = if let (Some(webhook_id), Some(webhook_token)) =
|
||||
(self.webhook_id, &self.webhook_token)
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::Database;
|
||||
pub struct Transaction<'a>(sqlx::Transaction<'a, Database>);
|
||||
|
||||
impl Transaction<'_> {
|
||||
pub fn executor(&mut self) -> impl sqlx::Executor<'_, Database = Database> {
|
||||
pub fn executor(&mut self) -> &mut sqlx::MySqlConnection {
|
||||
&mut *(self.0)
|
||||
}
|
||||
|
||||
|
||||
+27
-9
@@ -57,16 +57,34 @@ macro_rules! check_url_opt {
|
||||
macro_rules! update_field {
|
||||
($pool:expr, $error:ident, $reminder:ident.[$field:ident]) => {
|
||||
if let Some(value) = &$reminder.$field {
|
||||
match sqlx::query(concat!(
|
||||
"UPDATE reminders SET `",
|
||||
stringify!($field),
|
||||
"` = ? WHERE uid = ?"
|
||||
))
|
||||
.bind(value)
|
||||
.bind(&$reminder.uid)
|
||||
.execute($pool)
|
||||
.await
|
||||
// The complete payload takes precedence over legacy message controls.
|
||||
let result = if crate::message::is_message_field(stringify!($field))
|
||||
&& $reminder.message_json.is_some()
|
||||
{
|
||||
Ok(())
|
||||
} else if crate::message::is_message_field(stringify!($field)) {
|
||||
crate::message::update_field($pool, &$reminder.uid, stringify!($field), value).await
|
||||
} else {
|
||||
let result = sqlx::query(concat!(
|
||||
"UPDATE reminders SET `",
|
||||
stringify!($field),
|
||||
"` = ? WHERE uid = ?"
|
||||
))
|
||||
.bind(value)
|
||||
.bind(&$reminder.uid)
|
||||
.execute($pool)
|
||||
.await
|
||||
.map(|_| ());
|
||||
if result.is_ok()
|
||||
&& $reminder.message_json.is_none()
|
||||
&& ["attachment", "attachment_name"].contains(&stringify!($field))
|
||||
{
|
||||
crate::message::update_field($pool, &$reminder.uid, "attachments", &()).await
|
||||
} else {
|
||||
result
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
|
||||
@@ -83,6 +83,7 @@ pub async fn get_reminders(
|
||||
reminders.attachment_name,
|
||||
reminders.avatar,
|
||||
channels.channel,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
@@ -121,7 +122,7 @@ pub async fn get_reminders(
|
||||
query
|
||||
.fetch_all(pool.inner())
|
||||
.await
|
||||
.map(|reminders| Ok(json!(reminders)))
|
||||
.map(|reminders| Ok(crate::message::response(reminders)))
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("Failed to complete SQL query: {:?}", e);
|
||||
|
||||
@@ -152,6 +153,11 @@ pub async fn edit_reminder(
|
||||
let user_id =
|
||||
cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten().unwrap();
|
||||
|
||||
if let Some(message) = &reminder.message_json {
|
||||
crate::message::validate_payload(message).map_err(|error| json!({"error": error}))?;
|
||||
update_field!(transaction.executor(), error, reminder.[message_json]);
|
||||
}
|
||||
|
||||
if reminder.message_ok() {
|
||||
update_field!(transaction.executor(), error, reminder.[
|
||||
content,
|
||||
@@ -349,6 +355,7 @@ pub async fn edit_reminder(
|
||||
reminders.attachment_name,
|
||||
reminders.avatar,
|
||||
channels.channel,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
@@ -379,7 +386,9 @@ pub async fn edit_reminder(
|
||||
.fetch_one(pool.inner())
|
||||
.await
|
||||
{
|
||||
Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})),
|
||||
Ok(reminder) => {
|
||||
Ok(json!({"reminder": crate::message::response(reminder), "errors": error}))
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
warn!("Error exiting `edit_reminder': {:?}", e);
|
||||
|
||||
@@ -24,17 +24,24 @@ use crate::web::{
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Reminder {
|
||||
pub message_json: Option<Json<serde_json::Value>>,
|
||||
pub attachment: Option<Attachment>,
|
||||
pub attachment_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub content: String,
|
||||
#[serde(default)]
|
||||
pub embed_author: String,
|
||||
pub embed_author_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub embed_color: u32,
|
||||
#[serde(default)]
|
||||
pub embed_description: String,
|
||||
#[serde(default)]
|
||||
pub embed_footer: String,
|
||||
pub embed_footer_url: Option<String>,
|
||||
pub embed_image_url: Option<String>,
|
||||
pub embed_thumbnail_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub embed_title: String,
|
||||
pub embed_fields: Option<Json<Vec<EmbedField>>>,
|
||||
pub enabled: bool,
|
||||
@@ -44,6 +51,7 @@ pub struct Reminder {
|
||||
pub interval_months: Option<u32>,
|
||||
#[serde(default = "name_default")]
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub tts: bool,
|
||||
#[serde(default)]
|
||||
pub uid: String,
|
||||
@@ -79,35 +87,42 @@ pub async fn create_reminder(
|
||||
|
||||
let channel = channel.unwrap();
|
||||
|
||||
let message_json = Json(crate::message::payload(&reminder));
|
||||
if let Err(error) = crate::message::validate_payload(&message_json) {
|
||||
return Err(json!({"error": error}));
|
||||
}
|
||||
|
||||
// validate lengths
|
||||
check_length!(MAX_NAME_LENGTH, reminder.name);
|
||||
check_length!(MAX_CONTENT_LENGTH, reminder.content);
|
||||
check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder.embed_description);
|
||||
check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
|
||||
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
|
||||
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
|
||||
check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
|
||||
if let Some(fields) = &reminder.embed_fields {
|
||||
for field in &fields.0 {
|
||||
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
|
||||
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
|
||||
if reminder.message_json.is_none() {
|
||||
check_length!(MAX_CONTENT_LENGTH, reminder.content);
|
||||
check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder.embed_description);
|
||||
check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
|
||||
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
|
||||
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
|
||||
check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
|
||||
if let Some(fields) = &reminder.embed_fields {
|
||||
for field in &fields.0 {
|
||||
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
|
||||
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
check_length_opt!(
|
||||
MAX_URL_LENGTH,
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url
|
||||
);
|
||||
check_length_opt!(
|
||||
MAX_URL_LENGTH,
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url
|
||||
);
|
||||
|
||||
// validate urls
|
||||
check_url_opt!(
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url
|
||||
);
|
||||
// validate urls
|
||||
check_url_opt!(
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url
|
||||
);
|
||||
}
|
||||
|
||||
// validate time and interval
|
||||
if reminder.utc_time < Utc::now().naive_utc() {
|
||||
@@ -142,52 +157,21 @@ pub async fn create_reminder(
|
||||
// write to db
|
||||
match sqlx::query!(
|
||||
"INSERT INTO reminders (
|
||||
uid,
|
||||
attachment,
|
||||
attachment_name,
|
||||
channel_id,
|
||||
content,
|
||||
embed_author,
|
||||
embed_author_url,
|
||||
embed_color,
|
||||
embed_description,
|
||||
embed_footer,
|
||||
embed_footer_url,
|
||||
embed_image_url,
|
||||
embed_thumbnail_url,
|
||||
embed_title,
|
||||
embed_fields,
|
||||
enabled,
|
||||
expires,
|
||||
interval_seconds,
|
||||
interval_days,
|
||||
interval_months,
|
||||
name,
|
||||
tts,
|
||||
`utc_time`
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
uid, attachment, attachment_name, channel_id, message_json,
|
||||
enabled, expires, interval_seconds, interval_days, interval_months,
|
||||
name, `utc_time`
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
new_uid,
|
||||
reminder.attachment,
|
||||
reminder.attachment_name,
|
||||
channel,
|
||||
reminder.content,
|
||||
reminder.embed_author,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_color,
|
||||
reminder.embed_description,
|
||||
reminder.embed_footer,
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_image_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_title,
|
||||
reminder.embed_fields,
|
||||
message_json,
|
||||
reminder.enabled,
|
||||
reminder.expires,
|
||||
reminder.interval_seconds,
|
||||
reminder.interval_days,
|
||||
reminder.interval_months,
|
||||
name,
|
||||
reminder.tts,
|
||||
reminder.utc_time,
|
||||
)
|
||||
.execute(transaction.executor())
|
||||
@@ -198,6 +182,7 @@ pub async fn create_reminder(
|
||||
"SELECT
|
||||
reminders.attachment,
|
||||
reminders.attachment_name,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
@@ -224,7 +209,7 @@ pub async fn create_reminder(
|
||||
)
|
||||
.fetch_one(transaction.executor())
|
||||
.await
|
||||
.map(|r| Ok(json!(r)))
|
||||
.map(|r| Ok(crate::message::response(r)))
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("Failed to complete SQL query: {:?}", e);
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ pub async fn get_reminders(
|
||||
SELECT
|
||||
reminders.attachment,
|
||||
reminders.attachment_name,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
@@ -97,7 +98,7 @@ pub async fn get_reminders(
|
||||
)
|
||||
.fetch_all(pool.inner())
|
||||
.await
|
||||
.map(|r| Ok(json!(r)))
|
||||
.map(|r| Ok(crate::message::response(r)))
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("Failed to complete SQL query: {:?}", e);
|
||||
|
||||
@@ -130,6 +131,11 @@ pub async fn edit_reminder(
|
||||
let mut error = vec![];
|
||||
let user_id = user_id_cookie.unwrap();
|
||||
|
||||
if let Some(message) = &reminder.message_json {
|
||||
crate::message::validate_payload(message).map_err(|error| json!({"error": error}))?;
|
||||
update_field!(transaction.executor(), error, reminder.[message_json]);
|
||||
}
|
||||
|
||||
if reminder.message_ok() {
|
||||
update_field!(transaction.executor(), error, reminder.[
|
||||
content,
|
||||
@@ -246,6 +252,7 @@ pub async fn edit_reminder(
|
||||
"
|
||||
SELECT reminders.attachment,
|
||||
reminders.attachment_name,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
@@ -275,7 +282,9 @@ pub async fn edit_reminder(
|
||||
.fetch_one(pool.inner())
|
||||
.await
|
||||
{
|
||||
Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})),
|
||||
Ok(reminder) => {
|
||||
Ok(json!({"reminder": crate::message::response(reminder), "errors": error}))
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
warn!("Error exiting `edit_reminder': {:?}", e);
|
||||
|
||||
@@ -48,6 +48,7 @@ pub async fn export(
|
||||
reminders.attachment_name,
|
||||
reminders.avatar,
|
||||
CONCAT('#', channels.channel) AS channel,
|
||||
CAST(reminders.message_json AS CHAR) AS message_json,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
@@ -150,6 +151,11 @@ pub async fn import(
|
||||
match channel_id.parse::<u64>() {
|
||||
Ok(channel_id) => {
|
||||
let reminder = CreateReminder {
|
||||
message_json: record
|
||||
.message_json
|
||||
.map(|s| serde_json::from_str(&s).map(sqlx::types::Json))
|
||||
.transpose()
|
||||
.map_err(|_| json!({"error": "Invalid message_json"}))?,
|
||||
attachment: record.attachment,
|
||||
attachment_name: record.attachment_name,
|
||||
avatar: record.avatar,
|
||||
|
||||
+106
-76
@@ -148,22 +148,29 @@ pub struct EmbedField {
|
||||
inline: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateReminder {
|
||||
message_json: Option<Json<serde_json::Value>>,
|
||||
attachment: Option<Attachment>,
|
||||
attachment_name: Option<String>,
|
||||
avatar: Option<String>,
|
||||
#[serde(with = "string")]
|
||||
channel: u64,
|
||||
#[serde(default)]
|
||||
content: String,
|
||||
#[serde(default)]
|
||||
embed_author: String,
|
||||
embed_author_url: Option<String>,
|
||||
#[serde(default)]
|
||||
embed_color: u32,
|
||||
#[serde(default)]
|
||||
embed_description: String,
|
||||
#[serde(default)]
|
||||
embed_footer: String,
|
||||
embed_footer_url: Option<String>,
|
||||
embed_image_url: Option<String>,
|
||||
embed_thumbnail_url: Option<String>,
|
||||
#[serde(default)]
|
||||
embed_title: String,
|
||||
embed_fields: Option<Json<Vec<EmbedField>>>,
|
||||
enabled: bool,
|
||||
@@ -174,6 +181,7 @@ pub struct CreateReminder {
|
||||
#[serde(default = "name_default")]
|
||||
name: String,
|
||||
restartable: bool,
|
||||
#[serde(default)]
|
||||
tts: bool,
|
||||
username: Option<String>,
|
||||
utc_time: NaiveDateTime,
|
||||
@@ -181,6 +189,7 @@ pub struct CreateReminder {
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
pub struct GetReminder {
|
||||
message_json: Option<Json<serde_json::Value>>,
|
||||
attachment_name: Option<String>,
|
||||
avatar: Option<String>,
|
||||
#[serde(with = "string")]
|
||||
@@ -213,6 +222,8 @@ pub struct GetReminder {
|
||||
|
||||
#[derive(Serialize, Deserialize, FromRow)]
|
||||
pub struct ReminderCsv {
|
||||
#[serde(default)]
|
||||
message_json: Option<String>,
|
||||
attachment: Option<Attachment>,
|
||||
attachment_name: Option<String>,
|
||||
avatar: Option<String>,
|
||||
@@ -243,6 +254,8 @@ pub struct ReminderCsv {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PatchReminder {
|
||||
#[serde(default)]
|
||||
message_json: Option<Json<serde_json::Value>>,
|
||||
uid: String,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_optional_field")]
|
||||
@@ -311,6 +324,9 @@ pub struct PatchReminder {
|
||||
|
||||
impl PatchReminder {
|
||||
fn message_ok(&self) -> bool {
|
||||
if let Some(message) = &self.message_json {
|
||||
return crate::message::validate_payload(message).is_ok();
|
||||
}
|
||||
self.content.as_ref().map_or(true, |c| c.len() <= MAX_CONTENT_LENGTH)
|
||||
&& self.embed_author.as_ref().map_or(true, |c| c.len() <= MAX_EMBED_AUTHOR_LENGTH)
|
||||
&& self
|
||||
@@ -422,38 +438,45 @@ pub(crate) async fn create_reminder(
|
||||
|
||||
let channel = channel.unwrap();
|
||||
|
||||
let message_json = Json(crate::message::payload(&reminder));
|
||||
if let Err(error) = crate::message::validate_payload(&message_json) {
|
||||
return Err(json!({"error": error}));
|
||||
}
|
||||
|
||||
// validate lengths
|
||||
check_length!(MAX_NAME_LENGTH, reminder.name);
|
||||
check_length!(MAX_CONTENT_LENGTH, reminder.content);
|
||||
check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder.embed_description);
|
||||
check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
|
||||
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
|
||||
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
|
||||
check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
|
||||
if let Some(fields) = &reminder.embed_fields {
|
||||
for field in &fields.0 {
|
||||
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
|
||||
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
|
||||
if reminder.message_json.is_none() {
|
||||
check_length!(MAX_CONTENT_LENGTH, reminder.content);
|
||||
check_length!(MAX_EMBED_DESCRIPTION_LENGTH, reminder.embed_description);
|
||||
check_length!(MAX_EMBED_TITLE_LENGTH, reminder.embed_title);
|
||||
check_length!(MAX_EMBED_AUTHOR_LENGTH, reminder.embed_author);
|
||||
check_length!(MAX_EMBED_FOOTER_LENGTH, reminder.embed_footer);
|
||||
check_length_opt!(MAX_EMBED_FIELDS, reminder.embed_fields);
|
||||
if let Some(fields) = &reminder.embed_fields {
|
||||
for field in &fields.0 {
|
||||
check_length!(MAX_EMBED_FIELD_VALUE_LENGTH, field.value);
|
||||
check_length!(MAX_EMBED_FIELD_TITLE_LENGTH, field.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
check_length_opt!(MAX_USERNAME_LENGTH, reminder.username);
|
||||
check_length_opt!(
|
||||
MAX_URL_LENGTH,
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url,
|
||||
reminder.avatar
|
||||
);
|
||||
check_length_opt!(MAX_USERNAME_LENGTH, reminder.username);
|
||||
check_length_opt!(
|
||||
MAX_URL_LENGTH,
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url,
|
||||
reminder.avatar
|
||||
);
|
||||
|
||||
// validate urls
|
||||
check_url_opt!(
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url,
|
||||
reminder.avatar
|
||||
);
|
||||
// validate urls
|
||||
check_url_opt!(
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_image_url,
|
||||
reminder.avatar
|
||||
);
|
||||
}
|
||||
|
||||
// validate time and interval
|
||||
if reminder.utc_time < Utc::now().naive_utc() {
|
||||
@@ -483,60 +506,21 @@ pub(crate) async fn create_reminder(
|
||||
}
|
||||
|
||||
let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() };
|
||||
let username = if reminder.username.as_ref().map(|s| s.is_empty()).unwrap_or(true) {
|
||||
None
|
||||
} else {
|
||||
reminder.username
|
||||
};
|
||||
|
||||
let new_uid = generate_uid();
|
||||
|
||||
// write to db
|
||||
match sqlx::query!(
|
||||
"INSERT INTO reminders (
|
||||
uid,
|
||||
attachment,
|
||||
attachment_name,
|
||||
channel_id,
|
||||
avatar,
|
||||
content,
|
||||
embed_author,
|
||||
embed_author_url,
|
||||
embed_color,
|
||||
embed_description,
|
||||
embed_footer,
|
||||
embed_footer_url,
|
||||
embed_image_url,
|
||||
embed_thumbnail_url,
|
||||
embed_title,
|
||||
embed_fields,
|
||||
enabled,
|
||||
expires,
|
||||
interval_seconds,
|
||||
interval_days,
|
||||
interval_months,
|
||||
name,
|
||||
restartable,
|
||||
tts,
|
||||
username,
|
||||
`utc_time`
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
uid, attachment, attachment_name, channel_id, message_json,
|
||||
enabled, expires, interval_seconds, interval_days, interval_months,
|
||||
name, restartable, `utc_time`
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
new_uid,
|
||||
reminder.attachment,
|
||||
reminder.attachment_name,
|
||||
channel,
|
||||
reminder.avatar,
|
||||
reminder.content,
|
||||
reminder.embed_author,
|
||||
reminder.embed_author_url,
|
||||
reminder.embed_color,
|
||||
reminder.embed_description,
|
||||
reminder.embed_footer,
|
||||
reminder.embed_footer_url,
|
||||
reminder.embed_image_url,
|
||||
reminder.embed_thumbnail_url,
|
||||
reminder.embed_title,
|
||||
reminder.embed_fields,
|
||||
message_json,
|
||||
reminder.enabled,
|
||||
reminder.expires,
|
||||
reminder.interval_seconds,
|
||||
@@ -544,8 +528,6 @@ pub(crate) async fn create_reminder(
|
||||
reminder.interval_months,
|
||||
name,
|
||||
reminder.restartable,
|
||||
reminder.tts,
|
||||
username,
|
||||
reminder.utc_time,
|
||||
)
|
||||
.execute(transaction.executor())
|
||||
@@ -558,6 +540,7 @@ pub(crate) async fn create_reminder(
|
||||
reminders.attachment_name,
|
||||
reminders.avatar,
|
||||
channels.channel,
|
||||
reminders.message_json,
|
||||
reminders.content,
|
||||
reminders.embed_author,
|
||||
reminders.embed_author_url,
|
||||
@@ -587,7 +570,7 @@ pub(crate) async fn create_reminder(
|
||||
)
|
||||
.fetch_one(transaction.executor())
|
||||
.await
|
||||
.map(|r| Ok(json!(r)))
|
||||
.map(|r| Ok(crate::message::response(r)))
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("Failed to complete SQL query: {:?}", e);
|
||||
|
||||
@@ -869,3 +852,50 @@ async fn render_dashboard(cookies: &CookieJar<'_>) -> DashboardPage {
|
||||
DashboardPage::Unauthorised(Redirect::to("/login/discord"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod message_json_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires MESSAGE_JSON_TEST_DATABASE_URL pointing to an isolated MySQL database"]
|
||||
async fn patch_keeps_embeds_and_buttons_and_ignores_legacy_message_fields() {
|
||||
let pool =
|
||||
sqlx::MySqlPool::connect(&std::env::var("MESSAGE_JSON_TEST_DATABASE_URL").unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let mut transaction = pool.begin().await.unwrap();
|
||||
let channel = sqlx::query("INSERT INTO channels (channel) VALUES (?)")
|
||||
.bind(rand::random::<u64>())
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.unwrap()
|
||||
.last_insert_id();
|
||||
let uid = format!("multi-embed-test-{}", rand::random::<u64>());
|
||||
sqlx::query("INSERT INTO reminders (uid, channel_id, `utc_time`) VALUES (?, ?, NOW())")
|
||||
.bind(&uid)
|
||||
.bind(channel)
|
||||
.execute(&mut *transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
let payload = json!({"content": "current", "embeds": [{"title": "first"}, {"title": "second"}],
|
||||
"components": [{"type": 1, "components": [{"type": 2, "style": 5,
|
||||
"label": "Open link", "url": "https://example.com"}]}]});
|
||||
let reminder: PatchReminder = serde_json::from_value(json!({
|
||||
"uid": uid, "message_json": payload, "content": "stale", "embed_title": "stale"
|
||||
}))
|
||||
.unwrap();
|
||||
let mut errors = vec![];
|
||||
assert!(reminder.message_ok());
|
||||
update_field!(&mut *transaction, errors, reminder.[message_json, content, embed_title]);
|
||||
assert!(errors.is_empty());
|
||||
let stored: (Json<serde_json::Value>,) =
|
||||
sqlx::query_as("SELECT message_json FROM reminders WHERE uid = ?")
|
||||
.bind(&uid)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(stored.0 .0, payload);
|
||||
transaction.rollback().await.unwrap();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user