feat: add birthday system

This commit is contained in:
Naomi Carrigan 2024-07-07 12:47:29 -07:00
parent 1d2ae0e5a1
commit 1db7d33e22
Signed by: naomi
SSH Key Fingerprint: SHA256:rca1iUI2OhAM6n4FIUaFcZcicmri0jgocqKiTTAfrt8
9 changed files with 342 additions and 13 deletions

View File

@ -29,6 +29,7 @@
"@nhcarrigan/prettier-config": "3.2.0", "@nhcarrigan/prettier-config": "3.2.0",
"@nhcarrigan/typescript-config": "3.0.0", "@nhcarrigan/typescript-config": "3.0.0",
"@types/express": "4.17.21", "@types/express": "4.17.21",
"@types/node-schedule": "2.1.7",
"eslint": "8.57.0", "eslint": "8.57.0",
"knip": "5.15.0", "knip": "5.15.0",
"prettier": "3.2.5", "prettier": "3.2.5",
@ -42,6 +43,7 @@
"dotenv": "16.4.5", "dotenv": "16.4.5",
"express": "4.19.2", "express": "4.19.2",
"node-html-to-image": "4.0.0", "node-html-to-image": "4.0.0",
"node-schedule": "2.1.1",
"winston": "3.13.0" "winston": "3.13.0"
} }
} }

78
pnpm-lock.yaml generated
View File

@ -26,6 +26,9 @@ importers:
node-html-to-image: node-html-to-image:
specifier: 4.0.0 specifier: 4.0.0
version: 4.0.0 version: 4.0.0
node-schedule:
specifier: 2.1.1
version: 2.1.1
winston: winston:
specifier: 3.13.0 specifier: 3.13.0
version: 3.13.0 version: 3.13.0
@ -42,6 +45,9 @@ importers:
'@types/express': '@types/express':
specifier: 4.17.21 specifier: 4.17.21
version: 4.17.21 version: 4.17.21
'@types/node-schedule':
specifier: 2.1.7
version: 2.1.7
eslint: eslint:
specifier: 8.57.0 specifier: 8.57.0
version: 8.57.0 version: 8.57.0
@ -323,6 +329,9 @@ packages:
'@types/mime@3.0.1': '@types/mime@3.0.1':
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
'@types/node-schedule@2.1.7':
resolution: {integrity: sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==}
'@types/node@20.3.1': '@types/node@20.3.1':
resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==}
@ -650,6 +659,10 @@ packages:
resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==}
engines: {node: '>=14'} engines: {node: '>=14'}
cron-parser@4.9.0:
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'}
cross-fetch@4.0.0: cross-fetch@4.0.0:
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
@ -1439,6 +1452,9 @@ packages:
logform@2.5.1: logform@2.5.1:
resolution: {integrity: sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==} resolution: {integrity: sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==}
long-timeout@0.1.1:
resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==}
lru-cache@6.0.0: lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1447,6 +1463,10 @@ packages:
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
engines: {node: '>=12'} engines: {node: '>=12'}
luxon@3.4.4:
resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==}
engines: {node: '>=12'}
magic-bytes.js@1.10.0: magic-bytes.js@1.10.0:
resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==}
@ -1539,6 +1559,10 @@ packages:
node-html-to-image@4.0.0: node-html-to-image@4.0.0:
resolution: {integrity: sha512-lB8fkRleAKG4afJ2Wr7qJzIA5+//ue9OEoz+BMxQsowriGKR8sf4j4lK/pIXKakYwf/3aZHoDUNgOXuJ4HOzYA==} resolution: {integrity: sha512-lB8fkRleAKG4afJ2Wr7qJzIA5+//ue9OEoz+BMxQsowriGKR8sf4j4lK/pIXKakYwf/3aZHoDUNgOXuJ4HOzYA==}
node-schedule@2.1.1:
resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==}
engines: {node: '>=6'}
object-inspect@1.12.3: object-inspect@1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
@ -1866,6 +1890,9 @@ packages:
resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
sorted-array-functions@1.3.0:
resolution: {integrity: sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==}
source-map@0.6.1: source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2338,14 +2365,14 @@ snapshots:
'@nhcarrigan/eslint-config@3.2.0(eslint@8.57.0)(prettier@3.2.5)(typescript@5.4.5)': '@nhcarrigan/eslint-config@3.2.0(eslint@8.57.0)(prettier@3.2.5)(typescript@5.4.5)':
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0 eslint: 8.57.0
eslint-config-prettier: 9.0.0(eslint@8.57.0) eslint-config-prettier: 9.0.0(eslint@8.57.0)
eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint@8.57.0) eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)
eslint-plugin-jsdoc: 41.1.2(eslint@8.57.0) eslint-plugin-jsdoc: 41.1.2(eslint@8.57.0)
eslint-plugin-no-only-tests: 3.1.0 eslint-plugin-no-only-tests: 3.1.0
eslint-plugin-prettier: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.57.0)(prettier@3.2.5) eslint-plugin-prettier: 5.0.0(eslint-config-prettier@9.0.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/eslint' - '@types/eslint'
- eslint-import-resolver-typescript - eslint-import-resolver-typescript
@ -2452,7 +2479,7 @@ snapshots:
'@pkgr/core@0.1.1': {} '@pkgr/core@0.1.1': {}
'@prisma/client@5.13.0(prisma@5.13.0)': '@prisma/client@5.13.0(prisma@5.13.0)':
dependencies: optionalDependencies:
prisma: 5.13.0 prisma: 5.13.0
'@prisma/debug@5.13.0': {} '@prisma/debug@5.13.0': {}
@ -2536,6 +2563,10 @@ snapshots:
'@types/mime@3.0.1': {} '@types/mime@3.0.1': {}
'@types/node-schedule@2.1.7':
dependencies:
'@types/node': 20.3.1
'@types/node@20.3.1': {} '@types/node@20.3.1': {}
'@types/qs@6.9.7': {} '@types/qs@6.9.7': {}
@ -2565,7 +2596,7 @@ snapshots:
'@types/node': 20.3.1 '@types/node': 20.3.1
optional: true optional: true
'@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.4.5)': '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.5.1 '@eslint-community/regexpp': 4.5.1
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
@ -2579,6 +2610,7 @@ snapshots:
natural-compare-lite: 1.4.0 natural-compare-lite: 1.4.0
semver: 7.5.2 semver: 7.5.2
tsutils: 3.21.0(typescript@5.4.5) tsutils: 3.21.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.4.5 typescript: 5.4.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2590,6 +2622,7 @@ snapshots:
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5) '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5)
debug: 4.3.4 debug: 4.3.4
eslint: 8.57.0 eslint: 8.57.0
optionalDependencies:
typescript: 5.4.5 typescript: 5.4.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2606,6 +2639,7 @@ snapshots:
debug: 4.3.4 debug: 4.3.4
eslint: 8.57.0 eslint: 8.57.0
tsutils: 3.21.0(typescript@5.4.5) tsutils: 3.21.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.4.5 typescript: 5.4.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2621,6 +2655,7 @@ snapshots:
is-glob: 4.0.3 is-glob: 4.0.3
semver: 7.5.2 semver: 7.5.2
tsutils: 3.21.0(typescript@5.4.5) tsutils: 3.21.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.4.5 typescript: 5.4.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2912,6 +2947,10 @@ snapshots:
parse-json: 5.2.0 parse-json: 5.2.0
path-type: 4.0.0 path-type: 4.0.0
cron-parser@4.9.0:
dependencies:
luxon: 3.4.4
cross-fetch@4.0.0: cross-fetch@4.0.0:
dependencies: dependencies:
node-fetch: 2.7.0 node-fetch: 2.7.0
@ -3202,18 +3241,18 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint@8.57.0): eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.7)(eslint@8.57.0):
dependencies: dependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
debug: 3.2.7 debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0 eslint: 8.57.0
eslint-import-resolver-node: 0.3.7 eslint-import-resolver-node: 0.3.7
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.62.0)(eslint@8.57.0): eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0):
dependencies: dependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
array-includes: 3.1.6 array-includes: 3.1.6
array.prototype.findlastindex: 1.2.5 array.prototype.findlastindex: 1.2.5
array.prototype.flat: 1.3.1 array.prototype.flat: 1.3.1
@ -3222,7 +3261,7 @@ snapshots:
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.57.0 eslint: 8.57.0
eslint-import-resolver-node: 0.3.7 eslint-import-resolver-node: 0.3.7
eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint@8.57.0) eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.7)(eslint@8.57.0)
has: 1.0.3 has: 1.0.3
is-core-module: 2.13.1 is-core-module: 2.13.1
is-glob: 4.0.3 is-glob: 4.0.3
@ -3232,6 +3271,8 @@ snapshots:
object.values: 1.1.6 object.values: 1.1.6
semver: 6.3.1 semver: 6.3.1
tsconfig-paths: 3.14.2 tsconfig-paths: 3.14.2
optionalDependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.4.5)
transitivePeerDependencies: transitivePeerDependencies:
- eslint-import-resolver-typescript - eslint-import-resolver-typescript
- eslint-import-resolver-webpack - eslint-import-resolver-webpack
@ -3253,13 +3294,14 @@ snapshots:
eslint-plugin-no-only-tests@3.1.0: {} eslint-plugin-no-only-tests@3.1.0: {}
eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.57.0)(prettier@3.2.5): eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5):
dependencies: dependencies:
eslint: 8.57.0 eslint: 8.57.0
eslint-config-prettier: 9.0.0(eslint@8.57.0)
prettier: 3.2.5 prettier: 3.2.5
prettier-linter-helpers: 1.0.0 prettier-linter-helpers: 1.0.0
synckit: 0.8.8 synckit: 0.8.8
optionalDependencies:
eslint-config-prettier: 9.0.0(eslint@8.57.0)
eslint-scope@5.1.1: eslint-scope@5.1.1:
dependencies: dependencies:
@ -3891,12 +3933,16 @@ snapshots:
safe-stable-stringify: 2.4.3 safe-stable-stringify: 2.4.3
triple-beam: 1.3.0 triple-beam: 1.3.0
long-timeout@0.1.1: {}
lru-cache@6.0.0: lru-cache@6.0.0:
dependencies: dependencies:
yallist: 4.0.0 yallist: 4.0.0
lru-cache@7.18.3: {} lru-cache@7.18.3: {}
luxon@3.4.4: {}
magic-bytes.js@1.10.0: {} magic-bytes.js@1.10.0: {}
magic-string@0.16.0: magic-string@0.16.0:
@ -3967,6 +4013,12 @@ snapshots:
- supports-color - supports-color
- utf-8-validate - utf-8-validate
node-schedule@2.1.1:
dependencies:
cron-parser: 4.9.0
long-timeout: 0.1.1
sorted-array-functions: 1.3.0
object-inspect@1.12.3: {} object-inspect@1.12.3: {}
object-inspect@1.13.1: {} object-inspect@1.13.1: {}
@ -4346,6 +4398,8 @@ snapshots:
ip-address: 9.0.5 ip-address: 9.0.5
smart-buffer: 4.2.0 smart-buffer: 4.2.0
sorted-array-functions@1.3.0: {}
source-map@0.6.1: {} source-map@0.6.1: {}
spdx-exceptions@2.3.0: {} spdx-exceptions@2.3.0: {}

View File

@ -60,6 +60,7 @@ model configs {
eventLogChannel String @default("") eventLogChannel String @default("")
messageReportChannel String @default("") messageReportChannel String @default("")
joinRole String @default("") joinRole String @default("")
birthdayChannel String @default("")
@@unique([serverId], map: "serverId") @@unique([serverId], map: "serverId")
} }
@ -80,3 +81,12 @@ model roles {
@@unique([serverId, roleId], map: "serverId_roleId") @@unique([serverId, roleId], map: "serverId_roleId")
} }
model birthdays {
id String @id @default(auto()) @map("_id") @db.ObjectId
serverId String
userId String
birthday DateTime
@@unique([serverId, userId], map: "serverId_userId")
}

143
src/commands/birthday.ts Normal file
View File

@ -0,0 +1,143 @@
import { SlashCommandBuilder } from "discord.js";
import { Command } from "../interfaces/Command";
import { errorHandler } from "../utils/errorHandler";
/**
* Validates that the day provided is a valid day of the month.
*
* @param {string} month The month to validate.
* @param {number} day The day to validate.
* @returns {boolean} True if the day is within the month's range.
*/
const validateDate = (month: string, day: number): boolean => {
switch (month) {
case "Jan":
case "Mar":
case "May":
case "Jul":
case "Aug":
case "Oct":
case "Dec":
return day >= 1 && day <= 31;
case "Feb":
return day >= 1 && day <= 29;
case "Apr":
case "Jun":
case "Sep":
case "Nov":
return day >= 1 && day <= 30;
default:
return false;
}
};
export const bbset: Command = {
data: new SlashCommandBuilder()
.setName("bbset")
.setDescription("Set your birthday!")
.addStringOption((option) =>
option
.setName("month")
.setDescription("Your Birth Month")
.setRequired(true)
.setChoices(
{
name: "January",
value: "Jan"
},
{
name: "February",
value: "Feb"
},
{
name: "March",
value: "Mar"
},
{
name: "April",
value: "Apr"
},
{
name: "May",
value: "May"
},
{
name: "June",
value: "Jun"
},
{
name: "July",
value: "Jul"
},
{
name: "August",
value: "Aug"
},
{
name: "September",
value: "Sep"
},
{
name: "October",
value: "Oct"
},
{
name: "November",
value: "Nov"
},
{
name: "December",
value: "Dec"
}
)
)
.addIntegerOption((option) =>
option
.setName("day")
.setDescription("Your Birth Day (1-31)")
.setRequired(true)
.setMinValue(1)
.setMaxValue(31)
),
run: async (bot, interaction) => {
try {
await interaction.deferReply();
const month = interaction.options.getString("month", true);
const day = interaction.options.getInteger("day", true);
if (!validateDate(month, day)) {
await interaction.editReply({
content: `${month} ${day} is not a valid date!`
});
return;
}
await bot.db.birthdays.upsert({
where: {
serverId_userId: {
serverId: interaction.guild.id,
userId: interaction.user.id
}
},
update: {
birthday: new Date(`${month}-${day}-2000`)
},
create: {
serverId: interaction.guild.id,
userId: interaction.user.id,
birthday: new Date(`${month}-${day}-2000`)
}
});
await interaction.editReply(
`Your birthday has been set to ${month}-${day}!`
);
} catch (err) {
const id = await errorHandler(bot, "birthday command", err);
await interaction.editReply({
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
});
}
}
};

View File

@ -11,6 +11,7 @@ import { Command } from "../interfaces/Command";
import { CommandHandler } from "../interfaces/CommandHandler"; import { CommandHandler } from "../interfaces/CommandHandler";
import { getConfig } from "../modules/data/getConfig"; import { getConfig } from "../modules/data/getConfig";
import { handleAppealLink } from "../modules/subcommands/config/handleAppealLink"; import { handleAppealLink } from "../modules/subcommands/config/handleAppealLink";
import { handleBirthdayChannel } from "../modules/subcommands/config/handleBirthdayChannel";
import { handleInviteLink } from "../modules/subcommands/config/handleInviteLink"; import { handleInviteLink } from "../modules/subcommands/config/handleInviteLink";
import { handleJoinRole } from "../modules/subcommands/config/handleJoinRole"; import { handleJoinRole } from "../modules/subcommands/config/handleJoinRole";
import { handleList } from "../modules/subcommands/config/handleList"; import { handleList } from "../modules/subcommands/config/handleList";
@ -24,7 +25,8 @@ const handlers: { [key: string]: CommandHandler } = {
"appeal-link": handleAppealLink, "appeal-link": handleAppealLink,
logging: handleLogging, logging: handleLogging,
role: handleRole, role: handleRole,
"join-role": handleJoinRole "join-role": handleJoinRole,
"birthday-channel": handleBirthdayChannel
}; };
export const config: Command = { export const config: Command = {
@ -101,6 +103,19 @@ export const config: Command = {
.setDescription("The role to assign.") .setDescription("The role to assign.")
.setRequired(true) .setRequired(true)
) )
)
.addSubcommand(
new SlashCommandSubcommandBuilder()
.setName("birthday-channel")
.setDescription(
"Configure a channel where members can be wished a happy birthday."
)
.addChannelOption((o) =>
o
.setName("channel")
.setDescription("The channel to send birthday messages in.")
.setRequired(true)
)
), ),
run: async (bot, interaction) => { run: async (bot, interaction) => {
try { try {

View File

@ -1,4 +1,7 @@
import { scheduleJob } from "node-schedule";
import { ExtendedClient } from "../../interfaces/ExtendedClient"; import { ExtendedClient } from "../../interfaces/ExtendedClient";
import { postBirthdays } from "../../modules/postBirthdays";
import { registerCommands } from "../../utils/registerCommands"; import { registerCommands } from "../../utils/registerCommands";
import { sendDebugMessage } from "../../utils/sendDebugMessage"; import { sendDebugMessage } from "../../utils/sendDebugMessage";
@ -10,4 +13,7 @@ import { sendDebugMessage } from "../../utils/sendDebugMessage";
export const onReady = async (bot: ExtendedClient) => { export const onReady = async (bot: ExtendedClient) => {
await sendDebugMessage(bot, `Logged in as ${bot.user?.tag}`); await sendDebugMessage(bot, `Logged in as ${bot.user?.tag}`);
await registerCommands(bot); await registerCommands(bot);
// Daily at 9am PST
scheduleJob("birthdays", "0 9 * * *", async () => await postBirthdays(bot));
}; };

View File

@ -20,6 +20,12 @@ export const onMemberRemove = async (
const config = await getConfig(bot, guild.id); const config = await getConfig(bot, guild.id);
await bot.db.birthdays.delete({
where: {
serverId_userId: { serverId: guild.id, userId: user.id }
}
});
if (!config.eventLogChannel) { if (!config.eventLogChannel) {
return; return;
} }

View File

@ -0,0 +1,41 @@
import { ExtendedClient } from "../interfaces/ExtendedClient";
import { errorHandler } from "../utils/errorHandler";
/**
* Fetches the configs from the database, then for each config that
* has a birthday channel set, fetch birthdays. If any are from today,
* post!
*
* @param {ExtendedClient} bot The bot's Discord instance.
*/
export const postBirthdays = async (bot: ExtendedClient) => {
try {
const configs = await bot.db.configs.findMany();
const withChannel = configs.filter((c) => c.birthdayChannel);
for (const record of withChannel) {
const guild = bot.guilds.cache.get(record.serverId);
if (!guild) {
continue;
}
const channel = guild.channels.cache.get(record.birthdayChannel);
if (!channel || !("send" in channel)) {
continue;
}
const hasBirthdaySet = await bot.db.birthdays.findMany({
where: { serverId: guild.id }
});
const today = new Date();
const todayIn2000 = new Date(
`2000-${today.getMonth() + 1}-${today.getDate()}`
);
const isBirthdayToday = hasBirthdaySet.filter(
(r) => r.birthday === todayIn2000
);
const names = isBirthdayToday.map((r) => `<@${r.userId}>`).join(", ");
await channel.send(`Happy birthday to these lovely people~!\n${names}`);
}
} catch (err) {
await errorHandler(bot, "post birthdays", err);
}
};

View File

@ -0,0 +1,52 @@
import { PermissionFlagsBits } from "discord.js";
import { CommandHandler } from "../../../interfaces/CommandHandler";
import { errorHandler } from "../../../utils/errorHandler";
import { setConfig } from "../../data/setConfig";
/**
* Sets the birthday channel for the server.
*/
export const handleBirthdayChannel: CommandHandler = async (
bot,
interaction
) => {
try {
const channel = interaction.options.getChannel("channel", true);
if (!("send" in channel)) {
await interaction.editReply({
content: "You must specify a text channel!"
});
return;
}
const me = await interaction.guild.members.fetchMe();
if (!me.permissionsIn(channel).has(PermissionFlagsBits.SendMessages)) {
await interaction.editReply({
content: "I can't send messages there. :c"
});
return;
}
const success = await setConfig(
bot,
interaction.guild.id,
"birthdayChannel",
channel.id
);
if (success) {
await interaction.editReply({
content: `Birthdays will be posted in ${channel.toString()}. Members can set their birthdays with the \`/birthday\` command.`
});
return;
}
await interaction.editReply({
content: "Failed to set the settings."
});
} catch (err) {
const id = await errorHandler(bot, "automod logging subcommand", err);
await interaction.editReply({
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
});
}
};