diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index f762d3e3..b323e413 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -2,6 +2,7 @@
Update _ December 2022
+- feat: stickies for forum posts which get posted on forum post create (19/12/2022)
- refactor: outdated information + links in commands (08/12/2022)
- fix: fix latlong image typo and prime meridian alignment (04/12/2022)
- refactor: update wb command for change in Stable (03/12/2022)
diff --git a/.github/command-docs.md b/.github/command-docs.md
index b48cb529..ad0fd752 100644
--- a/.github/command-docs.md
+++ b/.github/command-docs.md
@@ -173,6 +173,7 @@
| .faq | Sends the FAQ | --- |
| .roleassignment | Sends the role assignment messages | --- |
| .rules | Sends the rules | --- |
+| .sticky | Manage sticky messages which are posted in Forum Posts when a new post is created | --- |
| .temporarycommandedit | Manage temporary commands, which are simple output commands to highlight temporary situations to users. | .tempcommandedit
.tcedit
.tcmod |
| .timeout | --- | --- |
| .unban | --- | --- |
diff --git a/package-lock.json b/package-lock.json
index 0e836029..11119a55 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -191,12 +191,13 @@
}
},
"node_modules/@discordjs/builders": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.1.0.tgz",
- "integrity": "sha512-EO8TSltiIc9Z1wE854wAFvv5AccqEtvjFmao9PPoxQhRaJ0hEb7FwWRTCA1jGg4ZWI3hcp4m+RET5ufZQz3rOg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.3.0.tgz",
+ "integrity": "sha512-Pvca6Nw8Hp+n3N+Wp17xjygXmMvggbh5ywUsOYE2Et4xkwwVRwgzxDJiMUuYapPtnYt4w/8aKlf5khc8ipLvhg==",
"dependencies": {
- "@sapphire/shapeshift": "^3.5.1",
- "discord-api-types": "^0.36.3",
+ "@discordjs/util": "^0.1.0",
+ "@sapphire/shapeshift": "^3.7.0",
+ "discord-api-types": "^0.37.12",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.1",
"tslib": "^2.4.0"
@@ -206,30 +207,39 @@
}
},
"node_modules/@discordjs/collection": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.0.1.tgz",
- "integrity": "sha512-5V/wswzR3r2RVYXLxxg4TvrAnBhVCNgHTXhC+OUtLoriJ072rPMHo+Iw1SS1vrCckp8Es40XM411+WkNRPaXFw==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.2.0.tgz",
+ "integrity": "sha512-VvrrtGb7vbfPHzbhGq9qZB5o8FOB+kfazrxdt0OtxzSkoBuw9dURMkCwWizZ00+rDpiK2HmLHBZX+y6JsG9khw==",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/@discordjs/rest": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.0.1.tgz",
- "integrity": "sha512-w08CTKVzzYYvKxEjXKOs9AdS7KQ1J502TrPfF8eCZ2lF6AfKuMP/32YgDakiwIyYTDjEQS/v0nKLSFcncHRMtg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.3.0.tgz",
+ "integrity": "sha512-U6X5J+r/MxYpPTlHFuPxXEf92aKsBaD2teBC7sWkKILIr30O8c9+XshfL7KFBCavnAqS/qE+PF9fgRilO3N44g==",
"dependencies": {
- "@discordjs/collection": "^1.0.1",
- "@sapphire/async-queue": "^1.3.2",
+ "@discordjs/collection": "^1.2.0",
+ "@discordjs/util": "^0.1.0",
+ "@sapphire/async-queue": "^1.5.0",
"@sapphire/snowflake": "^3.2.2",
- "discord-api-types": "^0.36.3",
- "file-type": "^17.1.4",
+ "discord-api-types": "^0.37.12",
+ "file-type": "^18.0.0",
"tslib": "^2.4.0",
- "undici": "^5.8.0"
+ "undici": "^5.11.0"
},
"engines": {
"node": ">=16.9.0"
}
},
+ "node_modules/@discordjs/util": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz",
+ "integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
"node_modules/@elastic/ecs-helpers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@elastic/ecs-helpers/-/ecs-helpers-1.1.0.tgz",
@@ -396,18 +406,18 @@
}
},
"node_modules/@sapphire/async-queue": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.2.tgz",
- "integrity": "sha512-rUpMLATsoAMnlN3gecAcr9Ecnw1vG7zi5Xr+IX22YzRzi1k9PF9vKzoT8RuEJbiIszjcimu3rveqUnvwDopz8g==",
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz",
+ "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==",
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/@sapphire/shapeshift": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.5.1.tgz",
- "integrity": "sha512-7JFsW5IglyOIUQI1eE0g6h06D/Far6HqpcowRScgCiLSqTf3hhkPWCWotVTtVycnDCMYIwPeaw6IEPBomKC8pA==",
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.7.0.tgz",
+ "integrity": "sha512-A6vI1zJoxhjWo4grsxpBRBgk96SqSdjLX5WlzKp9H+bJbkM07mvwcbtbVAmUZHbi/OG3HLfiZ1rlw4BhH6tsBQ==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"lodash.uniqwith": "^4.5.0"
@@ -1126,6 +1136,17 @@
"ieee754": "^1.1.13"
}
},
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -1531,26 +1552,27 @@
}
},
"node_modules/discord-api-types": {
- "version": "0.36.3",
- "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.3.tgz",
- "integrity": "sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg=="
+ "version": "0.37.20",
+ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.20.tgz",
+ "integrity": "sha512-uAO+55E11rMkYR36/paE1vKN8c2bZa1mgrIaiQIBgIZRKZTDIGOZB+8I5eMRPFJcGxrg16riUu+0aTu2JQEPew=="
},
"node_modules/discord.js": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.1.1.tgz",
- "integrity": "sha512-6Oa2f+Y0+s6B5HTTqcAP7Z6tUmguNTKpzbuTmE1JIeT/aUTr9dVe397D/bvcBSRpJERQzMyEWyEiVQnMRHcx4A==",
- "dependencies": {
- "@discordjs/builders": "^1.1.0",
- "@discordjs/collection": "^1.0.1",
- "@discordjs/rest": "^1.0.1",
+ "version": "14.6.0",
+ "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.6.0.tgz",
+ "integrity": "sha512-On1K7xpJZRe0KsziIaDih2ksYPhgxym/ZqV45i1f3yig4vUotikqs7qp5oXiTzQ/UTiNRCixUWFTh7vA1YBCqw==",
+ "dependencies": {
+ "@discordjs/builders": "^1.3.0",
+ "@discordjs/collection": "^1.2.0",
+ "@discordjs/rest": "^1.3.0",
+ "@discordjs/util": "^0.1.0",
"@sapphire/snowflake": "^3.2.2",
"@types/ws": "^8.5.3",
- "discord-api-types": "^0.36.3",
+ "discord-api-types": "^0.37.12",
"fast-deep-equal": "^3.1.3",
"lodash.snakecase": "^4.1.1",
"tslib": "^2.4.0",
- "undici": "^5.8.0",
- "ws": "^8.8.1"
+ "undici": "^5.11.0",
+ "ws": "^8.9.0"
},
"engines": {
"node": ">=16.9.0"
@@ -2441,16 +2463,16 @@
}
},
"node_modules/file-type": {
- "version": "17.1.4",
- "resolved": "https://registry.npmjs.org/file-type/-/file-type-17.1.4.tgz",
- "integrity": "sha512-3w/rJUUPBj6CYhVER3D5JCKwYJJiC36uj5dP+LnyubHI6H6FJo1TeWVCEA09YLVoZqV3/mLP26j9+Pz1GjAyjQ==",
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.0.0.tgz",
+ "integrity": "sha512-jjMwFpnW8PKofLE/4ohlhqwDk5k0NC6iy0UHAJFKoY1fQeGMN0GDdLgHQrvCbSpMwbqzoCZhRI5dETCZna5qVA==",
"dependencies": {
"readable-web-to-node-stream": "^3.0.2",
- "strtok3": "^7.0.0-alpha.9",
- "token-types": "^5.0.0-alpha.2"
+ "strtok3": "^7.0.0",
+ "token-types": "^5.0.1"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=14.16"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
@@ -4018,11 +4040,11 @@
}
},
"node_modules/peek-readable": {
- "version": "5.0.0-alpha.5",
- "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0-alpha.5.tgz",
- "integrity": "sha512-pJohF/tDwV3ntnT5+EkUo4E700q/j/OCDuPxtM+5/kFGjyOai/sK4/We4Cy1MB2OiTQliWU5DxPvYIKQAdPqAA==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz",
+ "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==",
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=14.16"
},
"funding": {
"type": "github",
@@ -4729,6 +4751,14 @@
"readable-stream": "^3.0.6"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -4878,15 +4908,15 @@
}
},
"node_modules/strtok3": {
- "version": "7.0.0-alpha.9",
- "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0-alpha.9.tgz",
- "integrity": "sha512-G8WxjBFjTZ77toVElv1i7k3jCXNkBB14FVaZ/6LIOka/WGo4La5XHLrU7neFVLdKbXESZf4BejVKZu5maOmocA==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz",
+ "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
- "peek-readable": "^5.0.0-alpha.5"
+ "peek-readable": "^5.0.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=14.16"
},
"funding": {
"type": "github",
@@ -5023,15 +5053,15 @@
}
},
"node_modules/token-types": {
- "version": "5.0.0-alpha.2",
- "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.0-alpha.2.tgz",
- "integrity": "sha512-EsG9UxAW4M6VATrEEjhPFTKEUi1OiJqTUMIZOGBN49fGxYjZB36k0p7to3HZSmWRoHm1QfZgrg3e02fpqAt5fQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz",
+ "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=14.16"
},
"funding": {
"type": "github",
@@ -5072,9 +5102,9 @@
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"node_modules/ts-mixer": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz",
- "integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg=="
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.2.tgz",
+ "integrity": "sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A=="
},
"node_modules/ts-node": {
"version": "10.9.1",
@@ -5144,9 +5174,9 @@
}
},
"node_modules/tslib": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
- "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
+ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -5239,9 +5269,12 @@
"dev": true
},
"node_modules/undici": {
- "version": "5.8.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.8.0.tgz",
- "integrity": "sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q==",
+ "version": "5.13.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.13.0.tgz",
+ "integrity": "sha512-UDZKtwb2k7KRsK4SdXWG7ErXiL7yTGgLWvk2AXO1JMjgjh404nFo6tWSCM2xMpJwMPx3J8i/vfqEh1zOqvj82Q==",
+ "dependencies": {
+ "busboy": "^1.6.0"
+ },
"engines": {
"node": ">=12.18"
}
@@ -5451,9 +5484,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
- "version": "8.8.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
- "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+ "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
@@ -5742,36 +5775,43 @@
}
},
"@discordjs/builders": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.1.0.tgz",
- "integrity": "sha512-EO8TSltiIc9Z1wE854wAFvv5AccqEtvjFmao9PPoxQhRaJ0hEb7FwWRTCA1jGg4ZWI3hcp4m+RET5ufZQz3rOg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.3.0.tgz",
+ "integrity": "sha512-Pvca6Nw8Hp+n3N+Wp17xjygXmMvggbh5ywUsOYE2Et4xkwwVRwgzxDJiMUuYapPtnYt4w/8aKlf5khc8ipLvhg==",
"requires": {
- "@sapphire/shapeshift": "^3.5.1",
- "discord-api-types": "^0.36.3",
+ "@discordjs/util": "^0.1.0",
+ "@sapphire/shapeshift": "^3.7.0",
+ "discord-api-types": "^0.37.12",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.1",
"tslib": "^2.4.0"
}
},
"@discordjs/collection": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.0.1.tgz",
- "integrity": "sha512-5V/wswzR3r2RVYXLxxg4TvrAnBhVCNgHTXhC+OUtLoriJ072rPMHo+Iw1SS1vrCckp8Es40XM411+WkNRPaXFw=="
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.2.0.tgz",
+ "integrity": "sha512-VvrrtGb7vbfPHzbhGq9qZB5o8FOB+kfazrxdt0OtxzSkoBuw9dURMkCwWizZ00+rDpiK2HmLHBZX+y6JsG9khw=="
},
"@discordjs/rest": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.0.1.tgz",
- "integrity": "sha512-w08CTKVzzYYvKxEjXKOs9AdS7KQ1J502TrPfF8eCZ2lF6AfKuMP/32YgDakiwIyYTDjEQS/v0nKLSFcncHRMtg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.3.0.tgz",
+ "integrity": "sha512-U6X5J+r/MxYpPTlHFuPxXEf92aKsBaD2teBC7sWkKILIr30O8c9+XshfL7KFBCavnAqS/qE+PF9fgRilO3N44g==",
"requires": {
- "@discordjs/collection": "^1.0.1",
- "@sapphire/async-queue": "^1.3.2",
+ "@discordjs/collection": "^1.2.0",
+ "@discordjs/util": "^0.1.0",
+ "@sapphire/async-queue": "^1.5.0",
"@sapphire/snowflake": "^3.2.2",
- "discord-api-types": "^0.36.3",
- "file-type": "^17.1.4",
+ "discord-api-types": "^0.37.12",
+ "file-type": "^18.0.0",
"tslib": "^2.4.0",
- "undici": "^5.8.0"
+ "undici": "^5.11.0"
}
},
+ "@discordjs/util": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.1.0.tgz",
+ "integrity": "sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ=="
+ },
"@elastic/ecs-helpers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@elastic/ecs-helpers/-/ecs-helpers-1.1.0.tgz",
@@ -5904,14 +5944,14 @@
"integrity": "sha512-hf+3bwuBwtXsugA2ULBc95qxrOqP2pOekLz34BJhcAKawt94vfeNyUKpYc0lZQ/3sCP6LqRa7UAdHA7i5UODzQ=="
},
"@sapphire/async-queue": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.2.tgz",
- "integrity": "sha512-rUpMLATsoAMnlN3gecAcr9Ecnw1vG7zi5Xr+IX22YzRzi1k9PF9vKzoT8RuEJbiIszjcimu3rveqUnvwDopz8g=="
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz",
+ "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA=="
},
"@sapphire/shapeshift": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.5.1.tgz",
- "integrity": "sha512-7JFsW5IglyOIUQI1eE0g6h06D/Far6HqpcowRScgCiLSqTf3hhkPWCWotVTtVycnDCMYIwPeaw6IEPBomKC8pA==",
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.7.0.tgz",
+ "integrity": "sha512-A6vI1zJoxhjWo4grsxpBRBgk96SqSdjLX5WlzKp9H+bJbkM07mvwcbtbVAmUZHbi/OG3HLfiZ1rlw4BhH6tsBQ==",
"requires": {
"fast-deep-equal": "^3.1.3",
"lodash.uniqwith": "^4.5.0"
@@ -6432,6 +6472,14 @@
"ieee754": "^1.1.13"
}
},
+ "busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "requires": {
+ "streamsearch": "^1.1.0"
+ }
+ },
"bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -6731,26 +6779,27 @@
}
},
"discord-api-types": {
- "version": "0.36.3",
- "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.3.tgz",
- "integrity": "sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg=="
+ "version": "0.37.20",
+ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.20.tgz",
+ "integrity": "sha512-uAO+55E11rMkYR36/paE1vKN8c2bZa1mgrIaiQIBgIZRKZTDIGOZB+8I5eMRPFJcGxrg16riUu+0aTu2JQEPew=="
},
"discord.js": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.1.1.tgz",
- "integrity": "sha512-6Oa2f+Y0+s6B5HTTqcAP7Z6tUmguNTKpzbuTmE1JIeT/aUTr9dVe397D/bvcBSRpJERQzMyEWyEiVQnMRHcx4A==",
- "requires": {
- "@discordjs/builders": "^1.1.0",
- "@discordjs/collection": "^1.0.1",
- "@discordjs/rest": "^1.0.1",
+ "version": "14.6.0",
+ "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.6.0.tgz",
+ "integrity": "sha512-On1K7xpJZRe0KsziIaDih2ksYPhgxym/ZqV45i1f3yig4vUotikqs7qp5oXiTzQ/UTiNRCixUWFTh7vA1YBCqw==",
+ "requires": {
+ "@discordjs/builders": "^1.3.0",
+ "@discordjs/collection": "^1.2.0",
+ "@discordjs/rest": "^1.3.0",
+ "@discordjs/util": "^0.1.0",
"@sapphire/snowflake": "^3.2.2",
"@types/ws": "^8.5.3",
- "discord-api-types": "^0.36.3",
+ "discord-api-types": "^0.37.12",
"fast-deep-equal": "^3.1.3",
"lodash.snakecase": "^4.1.1",
"tslib": "^2.4.0",
- "undici": "^5.8.0",
- "ws": "^8.8.1"
+ "undici": "^5.11.0",
+ "ws": "^8.9.0"
}
},
"doctrine": {
@@ -7457,13 +7506,13 @@
}
},
"file-type": {
- "version": "17.1.4",
- "resolved": "https://registry.npmjs.org/file-type/-/file-type-17.1.4.tgz",
- "integrity": "sha512-3w/rJUUPBj6CYhVER3D5JCKwYJJiC36uj5dP+LnyubHI6H6FJo1TeWVCEA09YLVoZqV3/mLP26j9+Pz1GjAyjQ==",
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.0.0.tgz",
+ "integrity": "sha512-jjMwFpnW8PKofLE/4ohlhqwDk5k0NC6iy0UHAJFKoY1fQeGMN0GDdLgHQrvCbSpMwbqzoCZhRI5dETCZna5qVA==",
"requires": {
"readable-web-to-node-stream": "^3.0.2",
- "strtok3": "^7.0.0-alpha.9",
- "token-types": "^5.0.0-alpha.2"
+ "strtok3": "^7.0.0",
+ "token-types": "^5.0.1"
}
},
"fill-range": {
@@ -8629,9 +8678,9 @@
"dev": true
},
"peek-readable": {
- "version": "5.0.0-alpha.5",
- "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0-alpha.5.tgz",
- "integrity": "sha512-pJohF/tDwV3ntnT5+EkUo4E700q/j/OCDuPxtM+5/kFGjyOai/sK4/We4Cy1MB2OiTQliWU5DxPvYIKQAdPqAA=="
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz",
+ "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A=="
},
"picomatch": {
"version": "2.3.1",
@@ -9167,6 +9216,11 @@
"readable-stream": "^3.0.6"
}
},
+ "streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
+ },
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -9273,12 +9327,12 @@
"dev": true
},
"strtok3": {
- "version": "7.0.0-alpha.9",
- "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0-alpha.9.tgz",
- "integrity": "sha512-G8WxjBFjTZ77toVElv1i7k3jCXNkBB14FVaZ/6LIOka/WGo4La5XHLrU7neFVLdKbXESZf4BejVKZu5maOmocA==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz",
+ "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==",
"requires": {
"@tokenizer/token": "^0.3.0",
- "peek-readable": "^5.0.0-alpha.5"
+ "peek-readable": "^5.0.0"
}
},
"supports-color": {
@@ -9385,9 +9439,9 @@
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
},
"token-types": {
- "version": "5.0.0-alpha.2",
- "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.0-alpha.2.tgz",
- "integrity": "sha512-EsG9UxAW4M6VATrEEjhPFTKEUi1OiJqTUMIZOGBN49fGxYjZB36k0p7to3HZSmWRoHm1QfZgrg3e02fpqAt5fQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz",
+ "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==",
"requires": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
@@ -9421,9 +9475,9 @@
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"ts-mixer": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz",
- "integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg=="
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.2.tgz",
+ "integrity": "sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A=="
},
"ts-node": {
"version": "10.9.1",
@@ -9467,9 +9521,9 @@
}
},
"tslib": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
- "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
+ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"tsutils": {
"version": "3.21.0",
@@ -9536,9 +9590,12 @@
"dev": true
},
"undici": {
- "version": "5.8.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.8.0.tgz",
- "integrity": "sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q=="
+ "version": "5.13.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.13.0.tgz",
+ "integrity": "sha512-UDZKtwb2k7KRsK4SdXWG7ErXiL7yTGgLWvk2AXO1JMjgjh404nFo6tWSCM2xMpJwMPx3J8i/vfqEh1zOqvj82Q==",
+ "requires": {
+ "busboy": "^1.6.0"
+ }
},
"unicode-byte-truncate": {
"version": "1.0.0",
@@ -9705,9 +9762,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
- "version": "8.8.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
- "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==",
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+ "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"requires": {}
},
"y18n": {
diff --git a/src/commands/index.ts b/src/commands/index.ts
index 2f4edf19..d845c44e 100644
--- a/src/commands/index.ts
+++ b/src/commands/index.ts
@@ -154,6 +154,7 @@ import { flyPadAbout } from './support/flyPadAbout';
import { navdata } from './support/navdata';
import { winss } from './support/winss';
import { simridgeLog } from './support/simbridgeLog';
+import { sticky } from './moderation/sticky';
const commands: BaseCommandDefinition[] = [
typeCommand,
@@ -310,6 +311,7 @@ const commands: BaseCommandDefinition[] = [
navdata,
winss,
simridgeLog,
+ sticky,
];
const commandsObject: { [k: string]: BaseCommandDefinition } = {};
diff --git a/src/commands/moderation/sticky.ts b/src/commands/moderation/sticky.ts
new file mode 100644
index 00000000..8d140c34
--- /dev/null
+++ b/src/commands/moderation/sticky.ts
@@ -0,0 +1,460 @@
+import { Colors, EmbedField, BaseChannel, TextChannel, TextBasedChannel } from 'discord.js';
+import moment from 'moment';
+import { CommandDefinition } from '../../lib/command';
+import { Roles, Channels, CommandCategory } from '../../constants';
+import { makeEmbed } from '../../lib/embed';
+import { getConn } from '../../lib/db';
+import StickyMessage from '../../lib/schemas/stickyMessageSchema';
+import { stickyMessageEmbed, STICKY_MOD_TITLE } from '../../lib/stickyMessageEmbed';
+import Logger from '../../lib/logger';
+
+const permittedRoles = [
+ Roles.ADMIN_TEAM,
+ Roles.MODERATION_TEAM,
+];
+const DEFAULT_TIME_INTERVAL = 15;
+const DEFAULT_MESSAGE_COUNT = 5;
+const MAX_TIME_INTERVAL = 86400;
+const MAX_MESSAGE_COUNT = 50;
+const MIN_TIME_INTERVAL = 15;
+const MIN_MESSAGE_COUNT = 5;
+
+const helpEmbed = (evokedCommand: String) => makeEmbed({
+ title: 'Manage Sticky Messages - Help',
+ description: 'A command to set and manage sticky messages for channels.',
+ fields: [
+ {
+ name: 'Channel ID parameter',
+ value: 'If you do not provide a Channel ID, the command applies to the channel in which it is executed.',
+ inline: false,
+ },
+ {
+ name: `Setting a sticky message: \`${evokedCommand} set [channel id] \``,
+ value: 'Sets the provided message as a sticky message for the channel in which the command is executed. A message can contain new lines and simple Markdown elements.',
+ inline: false,
+ },
+ {
+ name: `Setting the image URL: \`${evokedCommand} image [channel id] \``,
+ value: 'An image URL can be configured which gets posted as part of the sticky.',
+ inline: false,
+ },
+ {
+ name: `Setting the minimum message count: \`${evokedCommand} count [channel id] \``,
+ value: 'If neither the minimum message count have been posted in the channel or the minimum time interval has passed, the sticky will not be posted. This prevents spamming a channel.',
+ inline: false,
+ },
+ {
+ name: `Setting the minimum time interval: \`${evokedCommand} time [channel id] \``,
+ value: 'If neither the minimum message count have been posted in the channel or the minimum time interval has passed, the sticky will not be posted. This prevents spamming a channel.',
+ inline: false,
+ },
+ {
+ name: `Removing a sticky message: \`${evokedCommand} unset [channel id]\``,
+ value: 'Removes the configured sticky message from the channel and stop posting it.',
+ inline: false,
+ },
+ {
+ name: `Showing info about a sticky message: \`${evokedCommand} info [channel id]\``,
+ value: 'Shows all the information available for a configured sticky message for a channel.',
+ inline: false,
+ },
+ ],
+});
+
+const infoEmbed = (fields: EmbedField[]) => makeEmbed({
+ title: 'Sticky Message - Info',
+ fields,
+});
+
+const failedEmbed = (action: string, channel: string) => makeEmbed({
+ title: `Sticky Message - ${action} failed`,
+ description: `Failed to ${action} the sticky message for channel <#${channel}>, change not saved to mongoDB.`,
+ color: Colors.Red,
+});
+
+const modLogEmbed = (action: string, fields: any, color: number) => makeEmbed({
+ title: `${STICKY_MOD_TITLE} - ${action}`,
+ fields,
+ color,
+});
+
+const missingInfoEmbed = (action: string, information: string) => makeEmbed({
+ title: `Sticky Message - ${action} - missing information`,
+ description: `${information}`,
+ color: Colors.Red,
+});
+
+const missingChannelEmbed = (channel: string) => makeEmbed({
+ title: 'Sticky Message - Channel does not exist',
+ description: `The Channel ID ${channel} does not refer to an existing Text or Forum channel, or a Thread. Please provide a valid channel.`,
+ color: Colors.Red,
+});
+
+const noChannelEmbed = (action:string, channelName: string) => makeEmbed({
+ title: `Sticky Message - ${action} - No ${channelName} channel`,
+ description: `The command was successful, but no message to ${channelName} was sent. Please check the channel still exists.`,
+ color: Colors.Yellow,
+});
+
+const noConnEmbed = makeEmbed({
+ title: 'Sticky Message - No Connection',
+ description: 'Could not connect to the database.',
+ color: Colors.Red,
+});
+
+const noPermEmbed = makeEmbed({
+ title: 'Sticky Message - Permission missing',
+ description: 'You do not have permission to use this command.',
+ color: Colors.Red,
+});
+
+const notFoundEmbed = (action: string, channel: string) => makeEmbed({
+ title: `Sticky Message - ${action} - Not found`,
+ description: `No Sticky Message for <#${channel}> was found.`,
+ color: Colors.Red,
+});
+
+const updatedTimestampText = (updatedTimestamp: Date) => moment(updatedTimestamp).utcOffset(0).format('YYYY-MM-DD HH:mm:ss');
+
+const stickyMessageEmbedField = (updatedTimestampText: string, moderator: string, message: string, imageUrl: string, timeInterval: string, messageCount: string, channel: string): EmbedField[] => [
+ {
+ inline: true,
+ name: 'Channel',
+ value: `<#${channel}>`,
+ },
+ {
+ inline: true,
+ name: 'Moderator',
+ value: moderator,
+ },
+ {
+ inline: true,
+ name: 'Last updated timestamp',
+ value: updatedTimestampText,
+ },
+ {
+ inline: true,
+ name: 'Minimum time interval',
+ value: timeInterval,
+ },
+ {
+ inline: true,
+ name: 'Minimum message count',
+ value: messageCount,
+ },
+ {
+ inline: true,
+ name: 'Image URL',
+ value: imageUrl || 'Empty',
+ },
+ {
+ inline: false,
+ name: 'Message',
+ value: message,
+ },
+];
+
+export const sticky: CommandDefinition = {
+ name: ['sticky'],
+ description: 'Manages sticky messages.',
+ category: CommandCategory.MODERATION,
+ executor: async (msg) => {
+ const subCommands = ['set', 'image', 'count', 'time', 'unset', 'info'];
+ const conn = getConn();
+ if (!conn) {
+ return msg.channel.send({ embeds: [noConnEmbed] });
+ }
+
+ const hasPermittedRole = msg.member.roles.cache.some((role) => permittedRoles.map((r) => r.toString()).includes(role.id));
+ if (!hasPermittedRole) {
+ return msg.channel.send({ embeds: [noPermEmbed] });
+ }
+
+ const modLogsChannel = msg.guild.channels.resolve(Channels.MOD_LOGS) as TextChannel | null;
+ const { author } = msg;
+ const [evokedCommand] = msg.content.trim().split(/\s+/);
+ const args = msg.content.replace(evokedCommand, '').trim();
+ if (!args || args === 'help') {
+ return msg.channel.send({ embeds: [helpEmbed(evokedCommand)] });
+ }
+
+ let [subCommand] = args.split(/\s+/);
+ let subArgs = args.replace(subCommand, '').trim();
+ if (!subCommands.includes(subCommand)) {
+ subCommand = 'info';
+ subArgs = args;
+ }
+
+ let { channelId } = msg;
+ const regexCheck = /^(?:(?[\d]{6,}))\s*.*$/s;
+ const regexMatches = subArgs.match(regexCheck);
+ if (regexMatches && regexMatches.groups.channelId) {
+ ({ channelId } = regexMatches.groups);
+ subArgs = subArgs.replace(channelId, '').trim();
+ }
+ const stickiedChannel = msg.guild.channels.resolve(channelId) as BaseChannel | null;
+ if (!stickiedChannel || ['TextChannel', 'ForumChannel', 'ThreadChannel'].indexOf(stickiedChannel.constructor.name) === -1) {
+ return msg.channel.send({ embeds: [missingChannelEmbed(channelId)] });
+ }
+
+ const stickyMessageSearchResult = await StickyMessage.find({ channelId });
+ let [stickyMessage] = stickyMessageSearchResult.length === 1 ? stickyMessageSearchResult : [];
+
+ if (subCommand === 'set') {
+ const regexCheck = /^(?[\S].+[\S])\s*$/s;
+ const regexMatches = subArgs.match(regexCheck);
+ if (regexMatches === null || !regexMatches.groups.message) {
+ return msg.channel.send({ embeds: [missingInfoEmbed('Set', `You need to provide the expected format to create a sticky message. Check \`${evokedCommand} help\` for more details.`)] });
+ }
+
+ const { message } = regexMatches.groups;
+ if (!stickyMessage) {
+ stickyMessage = new StickyMessage({
+ channelId,
+ timeInterval: DEFAULT_TIME_INTERVAL,
+ messageCount: DEFAULT_MESSAGE_COUNT,
+ });
+ }
+ stickyMessage.message = message;
+ stickyMessage.moderator = author.toString();
+ stickyMessage.updatedTimestamp = new Date();
+ if (['TextChannel', 'ThreadChannel'].indexOf(stickiedChannel.constructor.name) >= 0) {
+ if (stickyMessage.lastPostedId) {
+ try {
+ const previousSticky = await (stickiedChannel as TextBasedChannel).messages.fetch(stickyMessage.lastPostedId);
+ previousSticky.delete();
+ } catch {
+ Logger.debug('Last posted sticky can not be found or deleted, ignoring and continuing.');
+ }
+ }
+ const currentSticky = await (stickiedChannel as TextBasedChannel).send({ embeds: [stickyMessageEmbed(stickyMessage.message, stickyMessage.imageUrl)] });
+ stickyMessage.lastPostedId = currentSticky.id;
+ }
+
+ try {
+ stickyMessage.save();
+ } catch {
+ return msg.channel.send({ embeds: [failedEmbed('Set', channelId)] });
+ }
+
+ try {
+ await modLogsChannel.send({
+ embeds: [modLogEmbed('Set',
+ stickyMessageEmbedField(
+ updatedTimestampText(stickyMessage.updatedTimestamp),
+ stickyMessage.moderator,
+ stickyMessage.message,
+ stickyMessage.imageUrl,
+ `${stickyMessage.timeInterval}`,
+ `${stickyMessage.messageCount}`,
+ stickyMessage.channelId,
+ ),
+ Colors.Green)],
+ });
+ } catch {
+ msg.channel.send({ embeds: [noChannelEmbed('Set', 'Mod Log')] });
+ }
+
+ return msg.react('✅');
+ }
+
+ if (subCommand === 'image') {
+ if (!stickyMessage) {
+ return msg.channel.send({ embeds: [notFoundEmbed('Configure image URL', channelId)] });
+ }
+
+ const regexCheck = /^(?https?:\/\/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))\s*$/;
+ const regexMatches = subArgs.match(regexCheck);
+ if (regexMatches === null || !regexMatches.groups.imageUrl) {
+ return msg.channel.send({ embeds: [missingInfoEmbed('Configure image URL', `You need to provide a URL for the image to include in the sticky. Check \`${evokedCommand} help\` for more details.`)] });
+ }
+
+ const { imageUrl } = regexMatches.groups;
+ stickyMessage.imageUrl = imageUrl;
+ stickyMessage.moderator = author.toString();
+ stickyMessage.updatedTimestamp = new Date();
+
+ try {
+ stickyMessage.save();
+ } catch {
+ return msg.channel.send({ embeds: [failedEmbed('Configure image URL', channelId)] });
+ }
+
+ try {
+ await modLogsChannel.send({
+ embeds: [modLogEmbed('Configure image URL',
+ stickyMessageEmbedField(
+ updatedTimestampText(stickyMessage.updatedTimestamp),
+ stickyMessage.moderator,
+ stickyMessage.message,
+ stickyMessage.imageUrl,
+ `${stickyMessage.timeInterval}`,
+ `${stickyMessage.messageCount}`,
+ stickyMessage.channelId,
+ ),
+ Colors.Green)],
+ });
+ } catch {
+ msg.channel.send({ embeds: [noChannelEmbed('Configure image URL', 'Mod Log')] });
+ }
+
+ return msg.react('✅');
+ }
+
+ if (subCommand === 'count') {
+ if (!stickyMessage) {
+ return msg.channel.send({ embeds: [notFoundEmbed('Configure minimum message count', channelId)] });
+ }
+
+ const regexCheck = /^(?[\d]+)\s*$/;
+ const regexMatches = subArgs.match(regexCheck);
+ if (regexMatches === null || !regexMatches.groups.messageCount) {
+ return msg.channel.send({ embeds: [missingInfoEmbed('Configure minimum message count', `You need to provide a number to set the minimum message count for a sticky message. Check \`${evokedCommand} help\` for more details.`)] });
+ }
+
+ const { messageCount } = regexMatches.groups;
+ if (parseInt(messageCount) < MIN_MESSAGE_COUNT || parseInt(messageCount) > MAX_MESSAGE_COUNT) {
+ return msg.channel.send({ embeds: [missingInfoEmbed('Configure minimum message count', `You need to provide a number between ${MIN_MESSAGE_COUNT} and ${MAX_MESSAGE_COUNT} to set the minimum message count for a sticky message. Check \`${evokedCommand} help\` for more details.`)] });
+ }
+ stickyMessage.messageCount = parseInt(messageCount);
+ stickyMessage.moderator = author.toString();
+ stickyMessage.updatedTimestamp = new Date();
+
+ try {
+ stickyMessage.save();
+ } catch {
+ return msg.channel.send({ embeds: [failedEmbed('Configure minimum message count', channelId)] });
+ }
+
+ try {
+ await modLogsChannel.send({
+ embeds: [modLogEmbed('Configure minimum message count',
+ stickyMessageEmbedField(
+ updatedTimestampText(stickyMessage.updatedTimestamp),
+ stickyMessage.moderator,
+ stickyMessage.message,
+ stickyMessage.imageUrl,
+ `${stickyMessage.timeInterval}`,
+ `${stickyMessage.messageCount}`,
+ stickyMessage.channelId,
+ ),
+ Colors.Green)],
+ });
+ } catch {
+ msg.channel.send({ embeds: [noChannelEmbed('Configure minimum message count', 'Mod Log')] });
+ }
+
+ return msg.react('✅');
+ }
+
+ if (subCommand === 'time') {
+ if (!stickyMessage) {
+ return msg.channel.send({ embeds: [notFoundEmbed('Configure minimum time interval', channelId)] });
+ }
+
+ const regexCheck = /^(?[\d]+)\s*$/;
+ const regexMatches = subArgs.match(regexCheck);
+ if (regexMatches === null || !regexMatches.groups.timeInterval) {
+ return msg.channel.send({ embeds: [missingInfoEmbed('Configure minimum time interval', `You need to provide a number to set the minimum time interval for a sticky message. Check \`${evokedCommand} help\` for more details.`)] });
+ }
+
+ const { timeInterval } = regexMatches.groups;
+ if (parseInt(timeInterval) < MIN_TIME_INTERVAL || parseInt(timeInterval) > MAX_TIME_INTERVAL) {
+ return msg.channel.send({ embeds: [missingInfoEmbed('Configure minimum time interval', `You need to provide a number between ${MIN_TIME_INTERVAL} and ${MAX_TIME_INTERVAL} to set the minimum time interval for a sticky message. Check \`${evokedCommand} help\` for more details.`)] });
+ }
+ stickyMessage.timeInterval = parseInt(timeInterval);
+ stickyMessage.moderator = author.toString();
+ stickyMessage.updatedTimestamp = new Date();
+
+ try {
+ stickyMessage.save();
+ } catch {
+ return msg.channel.send({ embeds: [failedEmbed('Configure minimum message count', channelId)] });
+ }
+
+ try {
+ await modLogsChannel.send({
+ embeds: [modLogEmbed('Configure minimum message count',
+ stickyMessageEmbedField(
+ updatedTimestampText(stickyMessage.updatedTimestamp),
+ stickyMessage.moderator,
+ stickyMessage.message,
+ stickyMessage.imageUrl,
+ `${stickyMessage.timeInterval}`,
+ `${stickyMessage.messageCount}`,
+ stickyMessage.channelId,
+ ),
+ Colors.Green)],
+ });
+ } catch {
+ msg.channel.send({ embeds: [noChannelEmbed('Configure minimum message count', 'Mod Log')] });
+ }
+
+ return msg.react('✅');
+ }
+
+ if (subCommand === 'unset') {
+ if (!stickyMessage) {
+ return msg.channel.send({ embeds: [notFoundEmbed('Delete', channelId)] });
+ }
+
+ try {
+ if (stickyMessage.lastPostedId && ['TextChannel', 'ThreadChannel'].indexOf(stickiedChannel.constructor.name) >= 0) {
+ try {
+ const previousSticky = await (stickiedChannel as TextBasedChannel).messages.fetch(stickyMessage.lastPostedId);
+ previousSticky.delete();
+ } catch {
+ Logger.debug('Last posted sticky can not be found or deleted, ignoring and continuing.');
+ }
+ }
+ stickyMessage.delete();
+ } catch {
+ return msg.channel.send({ embeds: [failedEmbed('Delete', channelId)] });
+ }
+
+ try {
+ await modLogsChannel.send({
+ embeds: [modLogEmbed('Delete',
+ stickyMessageEmbedField(
+ updatedTimestampText(stickyMessage.updatedTimestamp),
+ stickyMessage.moderator,
+ stickyMessage.message,
+ stickyMessage.imageUrl,
+ `${stickyMessage.timeInterval}`,
+ `${stickyMessage.messageCount}`,
+ stickyMessage.channelId,
+ ),
+ Colors.Red)],
+ });
+ } catch {
+ msg.channel.send({ embeds: [noChannelEmbed('Delete', 'Mod Log')] });
+ }
+
+ return msg.react('✅');
+ }
+
+ if (subCommand === 'info') {
+ const searchResult = await StickyMessage.find({ channelId });
+ if (searchResult.length === 0) {
+ return msg.channel.send({ embeds: [notFoundEmbed('Info', channelId)] });
+ }
+
+ const [stickyMessage] = searchResult;
+ return msg.channel.send({
+ embeds: [infoEmbed(
+ stickyMessageEmbedField(
+ updatedTimestampText(stickyMessage.updatedTimestamp),
+ stickyMessage.moderator,
+ stickyMessage.message,
+ stickyMessage.imageUrl,
+ `${stickyMessage.timeInterval}`,
+ `${stickyMessage.messageCount}`,
+ stickyMessage.channelId,
+ ),
+ )],
+ });
+ }
+
+ return msg.channel.send({ embeds: [helpEmbed(evokedCommand)] });
+ },
+};
diff --git a/src/handlers/forumPostCreate.ts b/src/handlers/forumPostCreate.ts
new file mode 100644
index 00000000..a79b1e6d
--- /dev/null
+++ b/src/handlers/forumPostCreate.ts
@@ -0,0 +1,35 @@
+import { getConn } from '../lib/db';
+import Logger from '../lib/logger';
+import StickyMessage from '../lib/schemas/stickyMessageSchema';
+import { stickyMessageEmbed } from '../lib/stickyMessageEmbed';
+
+module.exports = {
+ event: 'threadCreate',
+ executor: async (thread) => {
+ const { parentId, parent, name } = thread;
+ Logger.debug(`Thread Create - Handling thread create for ${name}`);
+ const { name: parentName, type } = parent;
+ Logger.debug(`Thread Create - Parent ID: ${parentId} - Parent Type: ${type} - Parent Name: ${parentName}`);
+ if (type !== 15) {
+ Logger.debug('Thread Create - Thread not created in a Forum Channel, skipping.');
+ return;
+ }
+
+ const conn = getConn();
+ if (!conn) {
+ Logger.debug('Thread Create - Unable to connect to database, skipping.');
+ return;
+ }
+
+ const stickyMessageSearchResult = await StickyMessage.find({ channelId: parentId });
+ const [stickyMessage] = stickyMessageSearchResult.length === 1 ? stickyMessageSearchResult : [];
+ if (!stickyMessage) {
+ Logger.debug('Forum Post Create - No sticky for the forum, skipping.');
+ return;
+ }
+
+ const { message, imageUrl } = stickyMessage;
+ Logger.debug('Forum Post Create - Posting new sticky.');
+ await thread.send({ embeds: [stickyMessageEmbed(message, imageUrl)] });
+ },
+};
diff --git a/src/lib/schemas/stickyMessageSchema.ts b/src/lib/schemas/stickyMessageSchema.ts
new file mode 100644
index 00000000..b33726e9
--- /dev/null
+++ b/src/lib/schemas/stickyMessageSchema.ts
@@ -0,0 +1,19 @@
+import mongoose, { Schema } from 'mongoose';
+
+const stickyMessageSchema = new Schema({
+ channelId: {
+ type: String,
+ unique: true,
+ },
+ message: String,
+ imageUrl: String,
+ timeInterval: Number,
+ messageCount: Number,
+ moderator: String,
+ updatedTimestamp: Date,
+ lastPostedId: String,
+});
+
+const stickyMessage = mongoose.model('StickyMessage', stickyMessageSchema);
+
+export default stickyMessage;
diff --git a/src/lib/stickyMessageEmbed.ts b/src/lib/stickyMessageEmbed.ts
new file mode 100644
index 00000000..b654e4d4
--- /dev/null
+++ b/src/lib/stickyMessageEmbed.ts
@@ -0,0 +1,12 @@
+import { Colors } from '../constants';
+import { makeEmbed } from './embed';
+
+export const STICKY_MESSAGE_TITLE = 'Stickied Message';
+export const STICKY_MOD_TITLE = 'Sticky Message - Log';
+
+export const stickyMessageEmbed = (description: string, imageUrl: string) => makeEmbed({
+ title: STICKY_MESSAGE_TITLE,
+ description,
+ color: Colors.FBW_CYAN,
+ image: imageUrl ? { url: imageUrl } : null,
+});