diff --git a/.eslintrc.json b/.eslintrc.json index 5052df3..9f08ddf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,6 +11,17 @@ "rules": { "require-await": "off" } + }, + { + "files": "src/contexts/productBoard.ts", + "rules": { + "camelcase": [ + "error", + { + "allow": ["display_url"] + } + ] + } } ] } diff --git a/.github/workflows/code-ql.yml b/.github/workflows/code-ql.yml index 9ddecea..c67e29c 100644 --- a/.github/workflows/code-ql.yml +++ b/.github/workflows/code-ql.yml @@ -30,8 +30,8 @@ jobs: - name: Build files run: pnpm run build - name: Setup CodeQL - uses: github/codeql-action/init@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2 + uses: github/codeql-action/init@1a077f8f6c71a45340c26ca0b877e00459e5f443 # v2 with: languages: ${{ matrix.language }} - name: Perform Analysis - uses: github/codeql-action/analyze@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2 + uses: github/codeql-action/analyze@1a077f8f6c71a45340c26ca0b877e00459e5f443 # v2 diff --git a/.github/workflows/fly-cd.yml b/.github/workflows/fly-cd.yml index 44dd439..aef0b2b 100644 --- a/.github/workflows/fly-cd.yml +++ b/.github/workflows/fly-cd.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - name: Install Fly - uses: superfly/flyctl-actions/setup-flyctl@dfdfedc86b296f5e5384f755a18bf400409a15d0 # v1.4 + uses: superfly/flyctl-actions/setup-flyctl@fc53c09e1bc3be6f54706524e3b82c4f462f77be # 1.5 - name: Deploy to Fly.io run: flyctl deploy --remote-only diff --git a/.nvmrc b/.nvmrc index 85aee5a..9a2a0e2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20 \ No newline at end of file +v20 diff --git a/package.json b/package.json index 48cb2ff..a8b9ac5 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "dependencies": { "@octokit/graphql": "7.0.2", "discord.js": "14.14.1", - "dotenv": "16.4.1", + "dotenv": "16.4.4", "node-fetch": "2", "node-schedule": "2.1.1", "winston": "3.11.0" @@ -46,12 +46,12 @@ "@nhcarrigan/typescript-config": "1.0.1", "@types/chai": "4.3.11", "@types/mocha": "10.0.6", - "@types/node": "18.19.12", + "@types/node": "18.19.15", "@types/node-fetch": "2", - "@types/node-schedule": "2.1.5", + "@types/node-schedule": "2.1.6", "chai": "4.4.1", "eslint": "8.56.0", - "mocha": "10.2.0", + "mocha": "10.3.0", "prettier": "2.8.8", "ts-mocha": "10.0.0", "typescript": "5.3.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d0afa9..42c2132 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ dependencies: specifier: 14.14.1 version: 14.14.1 dotenv: - specifier: 16.4.1 - version: 16.4.1 + specifier: 16.4.4 + version: 16.4.4 node-fetch: specifier: '2' version: 2.7.0 @@ -44,14 +44,14 @@ devDependencies: specifier: 10.0.6 version: 10.0.6 '@types/node': - specifier: 18.19.12 - version: 18.19.12 + specifier: 18.19.15 + version: 18.19.15 '@types/node-fetch': specifier: '2' version: 2.6.11 '@types/node-schedule': - specifier: 2.1.5 - version: 2.1.5 + specifier: 2.1.6 + version: 2.1.6 chai: specifier: 4.4.1 version: 4.4.1 @@ -59,14 +59,14 @@ devDependencies: specifier: 8.56.0 version: 8.56.0 mocha: - specifier: 10.2.0 - version: 10.2.0 + specifier: 10.3.0 + version: 10.3.0 prettier: specifier: 2.8.8 version: 2.8.8 ts-mocha: specifier: 10.0.0 - version: 10.0.0(mocha@10.2.0) + version: 10.0.0(mocha@10.3.0) typescript: specifier: 5.3.3 version: 5.3.3 @@ -216,7 +216,7 @@ packages: hasBin: true dependencies: chalk: 5.3.0 - diff: 5.1.0 + diff: 5.2.0 ejs: 3.1.9 shell-quote: 1.8.1 yargs: 17.7.2 @@ -297,7 +297,7 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.0 + fastq: 1.17.1 dev: true /@octokit/endpoint@9.0.4: @@ -312,7 +312,7 @@ packages: resolution: {integrity: sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==} engines: {node: '>= 18'} dependencies: - '@octokit/request': 8.1.6 + '@octokit/request': 8.2.0 '@octokit/types': 12.4.0 universal-user-agent: 6.0.1 dev: false @@ -330,8 +330,8 @@ packages: once: 1.4.0 dev: false - /@octokit/request@8.1.6: - resolution: {integrity: sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==} + /@octokit/request@8.2.0: + resolution: {integrity: sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==} engines: {node: '>= 18'} dependencies: '@octokit/endpoint': 9.0.4 @@ -384,23 +384,23 @@ packages: /@types/node-fetch@2.6.11: resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} dependencies: - '@types/node': 18.19.12 + '@types/node': 18.19.15 form-data: 4.0.0 dev: true - /@types/node-schedule@2.1.5: - resolution: {integrity: sha512-bN0MiplDBUxNMmlEi4iykjLYD7+Ze3DEevzliCn8WYuDwYSPj/5XFh8wZw+YXPLpLxiNWlIONYiQ67g/vowSMA==} + /@types/node-schedule@2.1.6: + resolution: {integrity: sha512-6AlZSUiNTdaVmH5jXYxX9YgmF1zfOlbjUqw0EllTBmZCnN1R5RR/m/u3No1OiWR05bnQ4jM4/+w4FcGvkAtnKQ==} dependencies: - '@types/node': 18.19.12 + '@types/node': 18.19.15 dev: true - /@types/node@18.19.12: - resolution: {integrity: sha512-uLcpWEAvatBEubmgCMzWforZbAu1dT9syweWnU3/DNwbeUBq2miP5nG8Y4JL9MDMKWt+7Yv1CSvA8xELdEl54w==} + /@types/node@18.19.15: + resolution: {integrity: sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==} dependencies: undici-types: 5.26.5 - /@types/semver@7.5.6: - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + /@types/semver@7.5.7: + resolution: {integrity: sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==} dev: true /@types/triple-beam@1.3.5: @@ -410,7 +410,7 @@ packages: /@types/ws@8.5.9: resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} dependencies: - '@types/node': 18.19.12 + '@types/node': 18.19.15 dev: false /@typescript-eslint/eslint-plugin@5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.56.0)(typescript@5.3.3): @@ -434,7 +434,7 @@ packages: grapheme-splitter: 1.0.4 ignore: 5.3.1 natural-compare-lite: 1.4.0 - semver: 7.5.4 + semver: 7.6.0 tsutils: 3.21.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -508,7 +508,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.4 + semver: 7.6.0 tsutils: 3.21.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -523,13 +523,13 @@ packages: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@types/json-schema': 7.0.15 - '@types/semver': 7.5.6 + '@types/semver': 7.5.7 '@typescript-eslint/scope-manager': 5.59.5 '@typescript-eslint/types': 5.59.5 '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.3.3) eslint: 8.56.0 eslint-scope: 5.1.1 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -609,21 +609,22 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + /array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - is-array-buffer: 3.0.2 + call-bind: 1.0.7 + is-array-buffer: 3.0.4 dev: true /array-includes@3.1.7: resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 + es-abstract: 1.22.4 + get-intrinsic: 1.2.4 is-string: 1.0.7 dev: true @@ -636,9 +637,9 @@ packages: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 es-shim-unscopables: 1.0.2 dev: true @@ -646,22 +647,23 @@ packages: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 es-shim-unscopables: 1.0.2 dev: true - /arraybuffer.prototype.slice@1.0.2: - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + /arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.5 + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - is-array-buffer: 3.0.2 + es-abstract: 1.22.4 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.2 dev: true @@ -723,12 +725,15 @@ packages: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true - /call-bind@1.0.5: - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.2 - set-function-length: 1.2.0 + get-intrinsic: 1.2.4 + set-function-length: 1.2.1 dev: true /callsites@3.1.0: @@ -918,21 +923,21 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /define-data-property@1.1.1: - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 + es-define-property: 1.0.0 + es-errors: 1.3.0 gopd: 1.0.1 - has-property-descriptors: 1.0.1 dev: true /define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 - has-property-descriptors: 1.0.1 + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 object-keys: 1.1.1 dev: true @@ -955,8 +960,8 @@ packages: engines: {node: '>=0.3.1'} dev: true - /diff@5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + /diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} dev: true @@ -1008,8 +1013,8 @@ packages: esutils: 2.0.3 dev: true - /dotenv@16.4.1: - resolution: {integrity: sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==} + /dotenv@16.4.4: + resolution: {integrity: sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==} engines: {node: '>=12'} dev: false @@ -1029,64 +1034,78 @@ packages: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} dev: false - /es-abstract@1.22.3: - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} + /es-abstract@1.22.4: + resolution: {integrity: sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==} engines: {node: '>= 0.4'} dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.2 + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 available-typed-arrays: 1.0.6 - call-bind: 1.0.5 + call-bind: 1.0.7 + es-define-property: 1.0.0 + es-errors: 1.3.0 es-set-tostringtag: 2.0.2 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.2 - get-symbol-description: 1.0.0 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 globalthis: 1.0.3 gopd: 1.0.1 - has-property-descriptors: 1.0.1 + has-property-descriptors: 1.0.2 has-proto: 1.0.1 has-symbols: 1.0.3 - hasown: 2.0.0 - internal-slot: 1.0.6 - is-array-buffer: 3.0.2 + hasown: 2.0.1 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 is-callable: 1.2.7 is-negative-zero: 2.0.2 is-regex: 1.1.4 is-shared-array-buffer: 1.0.2 is-string: 1.0.7 - is-typed-array: 1.1.12 + is-typed-array: 1.1.13 is-weakref: 1.0.2 object-inspect: 1.13.1 object-keys: 1.1.1 object.assign: 4.1.5 - regexp.prototype.flags: 1.5.1 + regexp.prototype.flags: 1.5.2 safe-array-concat: 1.1.0 - safe-regex-test: 1.0.2 + safe-regex-test: 1.0.3 string.prototype.trim: 1.2.8 string.prototype.trimend: 1.0.7 string.prototype.trimstart: 1.0.7 - typed-array-buffer: 1.0.0 + typed-array-buffer: 1.0.1 typed-array-byte-length: 1.0.0 typed-array-byte-offset: 1.0.0 typed-array-length: 1.0.4 unbox-primitive: 1.0.2 - which-typed-array: 1.1.13 + which-typed-array: 1.1.14 + dev: true + + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + dev: true + + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} dev: true /es-set-tostringtag@2.0.2: resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 - has-tostringtag: 1.0.0 - hasown: 2.0.0 + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.1 dev: true /es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 dev: true /es-to-primitive@1.2.1: @@ -1098,8 +1117,8 @@ packages: is-symbol: 1.0.4 dev: true - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} dev: true @@ -1202,7 +1221,7 @@ packages: escape-string-regexp: 4.0.0 eslint: 8.56.0 esquery: 1.5.0 - semver: 7.5.4 + semver: 7.6.0 spdx-expression-parse: 3.0.1 transitivePeerDependencies: - supports-color @@ -1362,8 +1381,8 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fastq@1.17.0: - resolution: {integrity: sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==} + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 dev: true @@ -1457,9 +1476,9 @@ packages: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 functions-have-names: 1.2.3 dev: true @@ -1476,21 +1495,24 @@ packages: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true - /get-intrinsic@1.2.2: - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} dependencies: + es-errors: 1.3.0 function-bind: 1.1.2 has-proto: 1.0.1 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.1 dev: true - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + /get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 dev: true /glob-parent@5.1.2: @@ -1507,8 +1529,8 @@ packages: is-glob: 4.0.3 dev: true - /glob@7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -1518,15 +1540,15 @@ packages: path-is-absolute: 1.0.1 dev: true - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 5.0.1 once: 1.4.0 - path-is-absolute: 1.0.1 dev: true /globals@13.24.0: @@ -1558,7 +1580,7 @@ packages: /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: - get-intrinsic: 1.2.2 + get-intrinsic: 1.2.4 dev: true /grapheme-splitter@1.0.4: @@ -1578,10 +1600,10 @@ packages: engines: {node: '>=8'} dev: true - /has-property-descriptors@1.0.1: - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: - get-intrinsic: 1.2.2 + es-define-property: 1.0.0 dev: true /has-proto@1.0.1: @@ -1594,8 +1616,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 @@ -1606,8 +1628,8 @@ packages: engines: {node: '>= 0.4.0'} dev: true - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + /hasown@2.0.1: + resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 @@ -1646,21 +1668,21 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /internal-slot@1.0.6: - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + /internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 - hasown: 2.0.0 - side-channel: 1.0.4 + es-errors: 1.3.0 + hasown: 2.0.1 + side-channel: 1.0.5 dev: true - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + /is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 dev: true /is-arrayish@0.3.2: @@ -1684,8 +1706,8 @@ packages: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true /is-callable@1.2.7: @@ -1696,14 +1718,14 @@ packages: /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 dev: true /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-extglob@2.1.1: @@ -1732,7 +1754,7 @@ packages: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-number@7.0.0: @@ -1754,14 +1776,14 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 dev: true /is-stream@2.0.1: @@ -1773,7 +1795,7 @@ packages: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-symbol@1.0.4: @@ -1783,11 +1805,11 @@ packages: has-symbols: 1.0.3 dev: true - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + /is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.13 + which-typed-array: 1.1.14 dev: true /is-unicode-supported@0.1.0: @@ -1798,7 +1820,7 @@ packages: /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 dev: true /isarray@2.0.5: @@ -1995,8 +2017,8 @@ packages: minimist: 1.2.8 dev: true - /mocha@10.2.0: - resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} + /mocha@10.3.0: + resolution: {integrity: sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==} engines: {node: '>= 14.0.0'} hasBin: true dependencies: @@ -2007,13 +2029,12 @@ packages: diff: 5.0.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 - glob: 7.2.0 + glob: 8.1.0 he: 1.2.0 js-yaml: 4.1.0 log-symbols: 4.1.0 minimatch: 5.0.1 ms: 2.1.3 - nanoid: 3.3.3 serialize-javascript: 6.0.0 strip-json-comments: 3.1.1 supports-color: 8.1.1 @@ -2030,12 +2051,6 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /nanoid@3.3.3: - resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -2083,7 +2098,7 @@ packages: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 @@ -2093,9 +2108,9 @@ packages: resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /once@1.4.0: @@ -2224,12 +2239,13 @@ packages: picomatch: 2.3.1 dev: true - /regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + /regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 + es-errors: 1.3.0 set-function-name: 2.0.1 dev: true @@ -2274,8 +2290,8 @@ packages: resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} engines: {node: '>=0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 dev: true @@ -2283,12 +2299,12 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - /safe-regex-test@1.0.2: - resolution: {integrity: sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==} + /safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 is-regex: 1.1.4 dev: true @@ -2302,8 +2318,8 @@ packages: hasBin: true dev: true - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} engines: {node: '>=10'} hasBin: true dependencies: @@ -2316,24 +2332,25 @@ packages: randombytes: 2.1.0 dev: true - /set-function-length@1.2.0: - resolution: {integrity: sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==} + /set-function-length@1.2.1: + resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 + define-data-property: 1.1.4 + es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.2 + get-intrinsic: 1.2.4 gopd: 1.0.1 - has-property-descriptors: 1.0.1 + has-property-descriptors: 1.0.2 dev: true /set-function-name@2.0.1: resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 + define-data-property: 1.1.4 functions-have-names: 1.2.3 - has-property-descriptors: 1.0.1 + has-property-descriptors: 1.0.2 dev: true /shebang-command@2.0.0: @@ -2352,11 +2369,13 @@ packages: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} dev: true - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + /side-channel@1.0.5: + resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 object-inspect: 1.13.1 dev: true @@ -2387,19 +2406,19 @@ packages: engines: {node: '>=0.10.0'} dev: true - /spdx-exceptions@2.4.0: - resolution: {integrity: sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==} + /spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} dev: true /spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: - spdx-exceptions: 2.4.0 - spdx-license-ids: 3.0.16 + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 dev: true - /spdx-license-ids@3.0.16: - resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} + /spdx-license-ids@3.0.17: + resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} dev: true /stack-trace@0.0.10: @@ -2419,25 +2438,25 @@ packages: resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /string.prototype.trimend@1.0.7: resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /string.prototype.trimstart@1.0.7: resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /string_decoder@1.3.0: @@ -2511,14 +2530,14 @@ packages: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-mocha@10.0.0(mocha@10.2.0): + /ts-mocha@10.0.0(mocha@10.3.0): resolution: {integrity: sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==} engines: {node: '>= 6.X.X'} hasBin: true peerDependencies: mocha: ^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X dependencies: - mocha: 10.2.0 + mocha: 10.3.0 ts-node: 7.0.1 optionalDependencies: tsconfig-paths: 3.15.0 @@ -2583,23 +2602,23 @@ packages: engines: {node: '>=10'} dev: true - /typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + /typed-array-buffer@1.0.1: + resolution: {integrity: sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 dev: true /typed-array-byte-length@1.0.0: resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 has-proto: 1.0.1 - is-typed-array: 1.1.12 + is-typed-array: 1.1.13 dev: true /typed-array-byte-offset@1.0.0: @@ -2607,18 +2626,18 @@ packages: engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.6 - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 has-proto: 1.0.1 - is-typed-array: 1.1.12 + is-typed-array: 1.1.13 dev: true /typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 - is-typed-array: 1.1.12 + is-typed-array: 1.1.13 dev: true /typescript@5.3.3: @@ -2630,7 +2649,7 @@ packages: /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 @@ -2681,15 +2700,15 @@ packages: is-symbol: 1.0.4 dev: true - /which-typed-array@1.1.13: - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + /which-typed-array@1.1.14: + resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==} engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.6 - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /which@2.0.2: @@ -2700,8 +2719,8 @@ packages: isexe: 2.0.0 dev: true - /winston-transport@4.6.0: - resolution: {integrity: sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==} + /winston-transport@4.7.0: + resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} engines: {node: '>= 12.0.0'} dependencies: logform: 2.6.0 @@ -2723,7 +2742,7 @@ packages: safe-stable-stringify: 2.4.3 stack-trace: 0.0.10 triple-beam: 1.4.1 - winston-transport: 4.6.0 + winston-transport: 4.7.0 dev: false /workerpool@6.2.1: @@ -2789,7 +2808,7 @@ packages: engines: {node: '>=10'} dependencies: cliui: 7.0.4 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -2802,7 +2821,7 @@ packages: engines: {node: '>=12'} dependencies: cliui: 8.0.1 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 diff --git a/sample.env b/sample.env index 70de9d0..134a69b 100644 --- a/sample.env +++ b/sample.env @@ -14,3 +14,5 @@ AI_URL="" GITHUB_TOKEN="" GITHUB_OWNER="" GITHUB_REPO="" +# This is for the ProductBoard integration. +PRODUCT_BOARD_API_KEY="" diff --git a/src/commands/stats.ts b/src/commands/stats.ts new file mode 100644 index 0000000..4d3cd8a --- /dev/null +++ b/src/commands/stats.ts @@ -0,0 +1,82 @@ +import { + GuildMember, + PermissionFlagsBits, + SlashCommandBuilder, +} from "discord.js"; + +import { ResponseText } from "../config/ResponseText"; +import { Command } from "../interfaces/Command"; +import { errorHandler } from "../utils/errorHandler"; + +export const stats: Command = { + data: new SlashCommandBuilder() + .setName("stats") + .setDescription("Generate a report on help threads.") + .setDMPermission(false), + run: async (bot, interaction) => { + try { + await interaction.deferReply({ ephemeral: true }); + + const { member } = interaction; + if ( + !member || + !(member instanceof GuildMember) || + (!bot.env.helperRoles.some((r) => member.roles.cache.has(r)) && + !member.permissions.has(PermissionFlagsBits.Administrator)) + ) { + await interaction.editReply({ + content: ResponseText.MustBeHelper, + }); + return; + } + + const rawArchived = await bot.cache.helpChannel.threads.fetchArchived({ + fetchAll: true, + }); + const archived = rawArchived.threads; + const active = (await bot.cache.helpChannel.threads.fetchActive()) + .threads; + const threads = [...archived.map((e) => e), ...active.map((e) => e)]; + const sorted = threads + .map((e) => e) + .sort((a, b) => (b.createdTimestamp ?? 0) - (a.createdTimestamp ?? 0)); + + const total = sorted.length; + const answeredArray = sorted.filter((thread) => + thread.appliedTags.includes(bot.cache.answerTag) + ); + const answered = answeredArray.length; + const unanswered = sorted.filter( + (thread) => !thread.appliedTags.includes(bot.cache.answerTag) + ).length; + const inactive = sorted.filter((thread) => + thread.appliedTags.includes(bot.cache.inactiveTag) + ).length; + const open = sorted.filter((thread) => !thread.archived).length; + const closed = sorted.filter((thread) => thread.archived).length; + const moved = sorted.filter( + (thread) => thread.ownerId === bot.user?.id + ).length; + + /* TODO: Original plan of checking based on updatedAt doesn't work because that doesn't exist for threads. */ + // const averageTime = answeredArray.reduce((thread, sum) => { + // sum += (thread.) + // }) + + await interaction.editReply({ + content: `# Help Channel Stats: +- Answered Threads: ${answered} +- Unanswered Threads: ${unanswered} +- Inactive Threads: ${inactive} +- Open Threads: ${open} +- Closed Threads: ${closed} +- Moved Threads: ${moved} +───────────────────────── +- Average time to answer: Coming soon? +- Total Threads: ${total}`, + }); + } catch (err) { + await errorHandler(bot, "stats command", err); + } + }, +}; diff --git a/src/contexts/productBoard.ts b/src/contexts/productBoard.ts new file mode 100644 index 0000000..624c59b --- /dev/null +++ b/src/contexts/productBoard.ts @@ -0,0 +1,61 @@ +import { ResponseText } from "../config/ResponseText"; +import { Context } from "../interfaces/Context"; +import { errorHandler } from "../utils/errorHandler"; + +export const productBoard: Context = { + data: { + name: "Send to Product Board", + type: 3, + }, + run: async (bot, interaction) => { + try { + if (!process.env.PRODUCT_BOARD_API_KEY) { + await interaction.editReply({ + content: "Product Board API is not configured.", + }); + return; + } + const { member } = interaction; + + if (!bot.env.helperRoles.some((r) => member.roles.cache.has(r))) { + await interaction.editReply({ + content: ResponseText.MustBeHelper, + }); + return; + } + + const message = interaction.options.getMessage("message"); + if (!message) { + await interaction.editReply({ + content: ResponseText.NoMessage, + }); + return; + } + const { content, author } = message; + + const req = await fetch("https://api.productboard.com/notes", { + headers: { + authorization: `Bearer ${process.env.PRODUCT_BOARD_API_KEY}`, + "X-Version": "1", + accept: "application/json; charset=utf-8", + "content-type": "application/json", + }, + method: "POST", + body: JSON.stringify({ + title: `${author.username}'s Feedback`, + content, + display_url: message.url, + }), + }); + const res = (await req.json()) as { links: { html: string } }; + await interaction.editReply({ + content: `[Note created](<${res.links.html}>)`, + }); + } catch (err) { + await errorHandler(bot, "product board context", err); + await interaction.editReply({ + content: ResponseText.InteractionError, + }); + } + }, +}; diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 213ccec..b2cb9c5 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -18,6 +18,18 @@ export const interactionCreate = async ( interaction: Interaction ) => { try { + if (interaction.isChatInputCommand()) { + const target = bot.commands.find( + (c) => c.data.name === interaction.commandName + ); + if (!target) { + await interaction.editReply({ + content: ResponseText.NoCommand, + }); + return; + } + await target.run(bot, interaction); + } if (interaction.isContextMenuCommand()) { await interaction.deferReply({ ephemeral: true }); if (!isGuildContextCommand(interaction)) { diff --git a/src/index.ts b/src/index.ts index b3316e3..3b7e0ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,9 +9,11 @@ import { sendStickyMessage } from "./modules/sendStickyMessage"; import { aggregateDailyUnansweredThreads } from "./modules/threads/aggregateDailyUnansweredThreads"; import { aggregateUnansweredThreads } from "./modules/threads/aggregateUnansweredThreads"; import { aggregateWeeklyThreads } from "./modules/threads/aggregateWeeklyThreads"; +import { autorespondToThreads } from "./modules/threads/autorespondToThreads"; import { errorHandler } from "./utils/errorHandler"; import { healthCheck } from "./utils/healthCheck"; import { loadChannels } from "./utils/loadChannels"; +import { loadCommands } from "./utils/loadCommands"; import { loadContexts } from "./utils/loadContexts"; import { logHandler } from "./utils/logHandler"; import { registerCommands } from "./utils/registerCommands"; @@ -21,6 +23,7 @@ import { validateEnv } from "./utils/validateEnv"; try { const bot = new Client({ intents: IntentOptions }) as ExtendedClient; bot.env = validateEnv(); + await loadCommands(bot); await loadContexts(bot); bot.on(Events.InteractionCreate, async (interaction) => { @@ -42,6 +45,10 @@ import { validateEnv } from "./utils/validateEnv"; bot.env.stickyFrequency * 1000 * 60 ); + scheduleJob("0 8 * * *", async () => { + await autorespondToThreads(bot); + }); + scheduleJob("0 9 * * *", async () => { await aggregateDailyUnansweredThreads(bot); }); diff --git a/src/interfaces/Command.ts b/src/interfaces/Command.ts new file mode 100644 index 0000000..ace8b95 --- /dev/null +++ b/src/interfaces/Command.ts @@ -0,0 +1,11 @@ +import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; + +import { ExtendedClient } from "./ExtendedClient"; + +export interface Command { + data: SlashCommandBuilder; + run: ( + bot: ExtendedClient, + interaction: ChatInputCommandInteraction + ) => Promise; +} diff --git a/src/interfaces/ExtendedClient.ts b/src/interfaces/ExtendedClient.ts index a4832b8..1444799 100644 --- a/src/interfaces/ExtendedClient.ts +++ b/src/interfaces/ExtendedClient.ts @@ -6,6 +6,7 @@ import { WebhookClient, } from "discord.js"; +import { Command } from "./Command"; import { Context } from "./Context"; export interface ExtendedClient extends Client { @@ -26,7 +27,9 @@ export interface ExtendedClient extends Client { modChannel: GuildTextBasedChannel; questionTag: string; answerTag: string; + inactiveTag: string; lastSticky: string; }; contexts: Context[]; + commands: Command[]; } diff --git a/src/modules/postGithubDiscussion.ts b/src/modules/postGithubDiscussion.ts index 3838149..64d72ba 100644 --- a/src/modules/postGithubDiscussion.ts +++ b/src/modules/postGithubDiscussion.ts @@ -97,6 +97,17 @@ export const postGithubDiscussion = async ( const discussionId = discussionQuery.createDiscussion.discussion.id; + // await github(` + // mutation { + // addLabelsToLabelable(input:{ + // labelableId: "${discussionId}", + // labelIds: ["${label}"] + // }) { + // clientMutationId + // } + // } + // `); + const commentQuery: { addDiscussionComment: { comment: { id: string } } } = await github(` mutation { diff --git a/src/modules/threads/aggregateDailyUnansweredThreads.ts b/src/modules/threads/aggregateDailyUnansweredThreads.ts index 94dfb50..19f4847 100644 --- a/src/modules/threads/aggregateDailyUnansweredThreads.ts +++ b/src/modules/threads/aggregateDailyUnansweredThreads.ts @@ -1,5 +1,6 @@ import { ExtendedClient } from "../../interfaces/ExtendedClient"; import { errorHandler } from "../../utils/errorHandler"; +import { stripLinks } from "../../utils/stripLinks"; /** * Fetches the threads from the help channel, finds the three most recent threads without @@ -9,6 +10,15 @@ import { errorHandler } from "../../utils/errorHandler"; */ export const aggregateDailyUnansweredThreads = async (bot: ExtendedClient) => { try { + const lastMessage = ( + await bot.cache.generalChannel.messages.fetch({ + limit: 1, + }) + ).first(); + + if (lastMessage?.author.id === bot.user?.id) { + return; + } const threads = (await bot.cache.helpChannel.threads.fetchActive()).threads; const unanswered = threads.filter( (thread) => !thread.appliedTags.includes(bot.cache.answerTag) @@ -20,7 +30,7 @@ export const aggregateDailyUnansweredThreads = async (bot: ExtendedClient) => { await bot.cache.generalChannel.send({ content: `Heya! It looks like some of your fellow community members might need some assistance.\n${firstThree - .map((t) => `- [${t.name}](<${t.url}>)`) + .map((t) => `- [${stripLinks(t.name)}](<${t.url}>)`) .join("\n")}`, }); } catch (err) { diff --git a/src/modules/threads/aggregateUnansweredThreads.ts b/src/modules/threads/aggregateUnansweredThreads.ts index 3e75298..d0772bd 100644 --- a/src/modules/threads/aggregateUnansweredThreads.ts +++ b/src/modules/threads/aggregateUnansweredThreads.ts @@ -1,5 +1,6 @@ import { ExtendedClient } from "../../interfaces/ExtendedClient"; import { errorHandler } from "../../utils/errorHandler"; +import { stripLinks } from "../../utils/stripLinks"; /** * Fetches the threads from the help channel, finds unanswered threads, posts a list @@ -9,23 +10,32 @@ import { errorHandler } from "../../utils/errorHandler"; */ export const aggregateUnansweredThreads = async (bot: ExtendedClient) => { try { - const archived = (await bot.cache.helpChannel.threads.fetchArchived()) - .threads; + const archived = ( + await bot.cache.helpChannel.threads.fetchArchived({ fetchAll: true }) + ).threads; const active = (await bot.cache.helpChannel.threads.fetchActive()).threads; const threads = [...archived.map((e) => e), ...active.map((e) => e)]; const unanswered = threads.filter( - (thread) => !thread.appliedTags.includes(bot.cache.answerTag) + (thread) => + !thread.appliedTags.includes(bot.cache.answerTag) && + !thread.appliedTags.includes(bot.cache.inactiveTag) ); const mapped = unanswered .map((e) => e) - .sort((a, b) => (a.createdTimestamp ?? 0) - (b.createdTimestamp ?? 0)); + .sort((a, b) => (a.createdTimestamp ?? 0) - (b.createdTimestamp ?? 0)) + .map((t) => `- [${stripLinks(t.name)}](<${t.url}>)`); await bot.cache.modChannel.send({ content: `Please take a look at these threads which are waiting for an answer.\n${mapped - .map((t) => `- [${t.name}](<${t.url}>)`) + .splice(0, 5) .join("\n")}`, }); + while (mapped.length > 0) { + await bot.cache.modChannel.send({ + content: mapped.splice(0, 5).join("\n"), + }); + } } catch (err) { - await errorHandler(bot, "aggregate daily unanswered threads", err); + await errorHandler(bot, "aggregate all unanswered threads", err); } }; diff --git a/src/modules/threads/aggregateWeeklyThreads.ts b/src/modules/threads/aggregateWeeklyThreads.ts index 997019d..b473855 100644 --- a/src/modules/threads/aggregateWeeklyThreads.ts +++ b/src/modules/threads/aggregateWeeklyThreads.ts @@ -1,5 +1,6 @@ import { ExtendedClient } from "../../interfaces/ExtendedClient"; import { errorHandler } from "../../utils/errorHandler"; +import { stripLinks } from "../../utils/stripLinks"; /** * Fetches the threads from the help channel, finds answered threads within the last week, @@ -9,44 +10,34 @@ import { errorHandler } from "../../utils/errorHandler"; */ export const aggregateWeeklyThreads = async (bot: ExtendedClient) => { try { - const archived = (await bot.cache.helpChannel.threads.fetchArchived()) - .threads; + const archived = ( + await bot.cache.helpChannel.threads.fetchArchived({ fetchAll: true }) + ).threads; const active = (await bot.cache.helpChannel.threads.fetchActive()).threads; const threads = [...archived.map((e) => e), ...active.map((e) => e)]; const answered = threads .filter((thread) => thread.appliedTags.includes(bot.cache.answerTag)) .map((e) => e) .sort((a, b) => (b.createdTimestamp ?? 0) - (a.createdTimestamp ?? 0)); - let oldest = answered.slice(-1)[0]; - while ( - oldest.createdTimestamp && - oldest.createdTimestamp > Date.now() - 1000 * 60 * 60 * 24 * 7 - ) { - const archived = ( - await bot.cache.helpChannel.threads.fetchArchived({ before: oldest.id }) - ).threads; - answered.push( - ...archived - .map((e) => e) - .filter((thread) => thread.appliedTags.includes(bot.cache.answerTag)) - ); - answered.sort( - (a, b) => (b.createdTimestamp ?? 0) - (a.createdTimestamp ?? 0) - ); - oldest = answered.slice(-1)[0]; - } + const filtered = answered + .filter( + (t) => (t.createdTimestamp ?? 0) >= Date.now() - 1000 * 60 * 60 * 24 * 7 + ) + .map((t) => `- [${stripLinks(t.name)}](<${t.url}>)`); await bot.cache.modChannel.send({ - content: `Here's a recap of the threads that have been answered this week.\n${answered - .filter( - (t) => - (t.createdTimestamp ?? 0) >= Date.now() - 1000 * 60 * 60 * 24 * 7 - ) - .map((t) => `- [${t.name}](<${t.url}>)`) + content: `Here's a recap of the threads that have been answered this week.\n${filtered + .splice(0, 5) .join("\n")}`, }); + + while (filtered.length > 0) { + await bot.cache.modChannel.send({ + content: filtered.splice(0, 5).join("\n"), + }); + } } catch (err) { - await errorHandler(bot, "aggregate daily unanswered threads", err); + await errorHandler(bot, "aggregate weekly unanswered threads", err); } }; diff --git a/src/modules/threads/autorespondToThreads.ts b/src/modules/threads/autorespondToThreads.ts new file mode 100644 index 0000000..ecb6443 --- /dev/null +++ b/src/modules/threads/autorespondToThreads.ts @@ -0,0 +1,52 @@ +import { ExtendedClient } from "../../interfaces/ExtendedClient"; +import { errorHandler } from "../../utils/errorHandler"; + +/** + * Fetches the threads from the help channel, finds unanswered threads, posts a response to + * threads that have not received a message in 7 days, or in 14 days. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const autorespondToThreads = async (bot: ExtendedClient) => { + try { + const archived = ( + await bot.cache.helpChannel.threads.fetchArchived({ fetchAll: true }) + ).threads; + const active = (await bot.cache.helpChannel.threads.fetchActive()).threads; + const threads = [...archived.map((e) => e), ...active.map((e) => e)]; + const unanswered = threads + .filter((thread) => !thread.appliedTags.includes(bot.cache.answerTag)) + .map((e) => e) + .sort((a, b) => (b.createdTimestamp ?? 0) - (a.createdTimestamp ?? 0)); + + for (const thread of unanswered) { + const lastMessage = (await thread.messages.fetch({ limit: 1 })).first(); + const owner = await thread.fetchOwner(); + if (!lastMessage || !owner) { + continue; + } + if (lastMessage.author.id === owner.id) { + continue; + } + if ( + lastMessage.createdTimestamp > Date.now() - 1000 * 60 * 60 * 24 * 8 && + lastMessage.createdTimestamp < Date.now() - 1000 * 60 * 60 * 24 * 7 + ) { + await thread.send({ + content: `Hey there <@!${owner.id}>~! Did you forget about this thread? Let us know if you still need help!`, + }); + } + if ( + lastMessage.createdTimestamp > Date.now() - 1000 * 60 * 60 * 24 * 15 && + lastMessage.createdTimestamp < Date.now() - 1000 * 60 * 60 * 24 * 14 + ) { + await thread.setAppliedTags([ + ...thread.appliedTags, + bot.cache.inactiveTag, + ]); + } + } + } catch (err) { + await errorHandler(bot, "autorespond to threads", err); + } +}; diff --git a/src/utils/loadChannels.ts b/src/utils/loadChannels.ts index be0dc3b..5d56bfe 100644 --- a/src/utils/loadChannels.ts +++ b/src/utils/loadChannels.ts @@ -3,6 +3,7 @@ import { ChannelType } from "discord.js"; import { ExtendedClient } from "../interfaces/ExtendedClient"; import { errorHandler } from "./errorHandler"; +import { logHandler } from "./logHandler"; /** * Loads the guild and channel IDs from the environment, fetches @@ -41,12 +42,18 @@ export const loadChannels = async (bot: ExtendedClient) => { const answerTag = helpChannel.availableTags.find( (t) => t.name === "Answered" ); + const inactiveTag = helpChannel.availableTags.find( + (t) => t.name === "Inactive" + ); if (!questionTag) { throw new Error("Could not find `Question` tag."); } if (!answerTag) { throw new Error("Could not find `Answer` tag."); } + if (!inactiveTag) { + throw new Error("Could not find `Inactive` tag."); + } const generalChannel = homeGuild.channels.cache.get(bot.env.generalChannel) || @@ -75,9 +82,24 @@ export const loadChannels = async (bot: ExtendedClient) => { modChannel, questionTag: questionTag.id, answerTag: answerTag.id, + inactiveTag: inactiveTag.id, lastSticky: "", }; } + + logHandler.debug("Loading help threads to cache."); + + const rawArchived = await bot.cache.helpChannel.threads.fetchArchived({ + fetchAll: true, + }); + const archived = rawArchived.threads; + const active = (await bot.cache.helpChannel.threads.fetchActive()).threads; + const threads = [...archived.map((e) => e), ...active.map((e) => e)]; + const sorted = threads + .map((e) => e) + .sort((a, b) => (b.createdTimestamp ?? 0) - (a.createdTimestamp ?? 0)); + + logHandler.debug(`Loaded ${sorted.length} threads.`); } catch (err) { await errorHandler(bot, "load channels utility", err); // shut down because the cache is essential. diff --git a/src/utils/loadCommands.ts b/src/utils/loadCommands.ts new file mode 100644 index 0000000..45de0f1 --- /dev/null +++ b/src/utils/loadCommands.ts @@ -0,0 +1,34 @@ +import { readdir } from "fs/promises"; +import { join } from "path"; + +import { Command } from "../interfaces/Command"; +import { ExtendedClient } from "../interfaces/ExtendedClient"; + +import { errorHandler } from "./errorHandler"; + +/** + * Dynamically reads the `commands` directory and imports the files. Mounts + * the commands to the bot to be used elsewhere. + * + * @param {ExtendedClient} bot The bot's Discord instance. + */ +export const loadCommands = async (bot: ExtendedClient) => { + try { + const result: Command[] = []; + const files = await readdir( + join(process.cwd(), "prod", "commands"), + "utf-8" + ); + for (const file of files) { + const name = file.split(".")[0]; + if (!name) { + continue; + } + const mod = await import(join(process.cwd(), "prod", "commands", file)); + result.push(mod[name] as Command); + } + bot.commands = result; + } catch (err) { + await errorHandler(bot, "load commands utility", err); + } +}; diff --git a/src/utils/registerCommands.ts b/src/utils/registerCommands.ts index 1c17715..67ec8c6 100644 --- a/src/utils/registerCommands.ts +++ b/src/utils/registerCommands.ts @@ -15,11 +15,12 @@ export const registerCommands = async (bot: ExtendedClient) => { throw new Error("Bot is not logged in. Cannot register commands yet."); } const rest = new REST({ version: "10" }).setToken(bot.env.token); + const commands = bot.commands.map((command) => command.data.toJSON()); const contexts = bot.contexts.map((context) => context.data); await rest.put( Routes.applicationGuildCommands(bot.user.id, bot.env.homeGuild), - { body: contexts } + { body: [...commands, ...contexts] } ); } catch (err) { await errorHandler(bot, "register commands utility", err); diff --git a/src/utils/stripLinks.ts b/src/utils/stripLinks.ts new file mode 100644 index 0000000..9fa3205 --- /dev/null +++ b/src/utils/stripLinks.ts @@ -0,0 +1,8 @@ +/** + * Utility to strip markdown links out of a string. + * + * @param {string} text The string to strip. + * @returns {string} The string, without links. + */ +export const stripLinks = (text: string): string => + text.replace(/\[(.*?)\]\(.*?\)/g, "$1"); diff --git a/test/utils/stripLinks.spec.ts b/test/utils/stripLinks.spec.ts new file mode 100644 index 0000000..8a8fddc --- /dev/null +++ b/test/utils/stripLinks.spec.ts @@ -0,0 +1,27 @@ +import { assert } from "chai"; + +import { stripLinks } from "../../src/utils/stripLinks"; + +suite("stripLinks utility", () => { + test("does not mutate message with no links", () => { + const str = "Hi, I'm Naomi!"; + assert.equal(stripLinks(str), "Hi, I'm Naomi!"); + }); + test("removes link from message", () => { + const str = "Hi, I'm Naomi! [Check out my profile!](https://naomi.lgbt)"; + assert.equal(stripLinks(str), "Hi, I'm Naomi! Check out my profile!"); + }); + test("removes multiple links from message", () => { + const str = + "Hi, I'm [Naomi](https://naomi.lgbt)! Join my [server?](https://chat.naomi.lgbt)"; + assert.equal(stripLinks(str), "Hi, I'm Naomi! Join my server?"); + }); + test("handles message with ONLY link", () => { + const str = "[Check out my profile!]()"; + assert.equal(stripLinks(str), "Check out my profile!"); + }); + test("handles embed-suppressed links", () => { + const str = "Hi, I'm Naomi! [Check out my profile!]()"; + assert.equal(stripLinks(str), "Hi, I'm Naomi! Check out my profile!"); + }); +});