generated from nhcarrigan/template
feat: log entitlement purchases (#3)
### Explanation _No response_ ### Issue _No response_ ### Attestations - [x] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [x] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [x] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [x] I have pinned the dependencies to a specific patch version. ### Style - [x] I have run the linter and resolved any errors. - [x] My pull request uses an appropriate title, matching the conventional commit standards. - [x] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Reviewed-on: #3 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
@ -22,6 +22,7 @@
|
||||
"typescript": "5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord-verify": "1.2.0",
|
||||
"fastify": "5.2.1",
|
||||
"nodemailer": "6.10.0"
|
||||
}
|
||||
|
83
pnpm-lock.yaml
generated
83
pnpm-lock.yaml
generated
@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
discord-verify:
|
||||
specifier: 1.2.0
|
||||
version: 1.2.0
|
||||
fastify:
|
||||
specifier: 5.2.1
|
||||
version: 5.2.1
|
||||
@ -432,15 +435,33 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: '>=8.40.0'
|
||||
|
||||
'@types/body-parser@1.19.6':
|
||||
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||
|
||||
'@types/estree@1.0.6':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
'@types/express-serve-static-core@4.19.6':
|
||||
resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==}
|
||||
|
||||
'@types/express@4.17.23':
|
||||
resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==}
|
||||
|
||||
'@types/http-errors@2.0.5':
|
||||
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
||||
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/json5@0.0.29':
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
|
||||
'@types/mime@1.3.5':
|
||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||
|
||||
'@types/node@22.13.1':
|
||||
resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==}
|
||||
|
||||
@ -450,6 +471,18 @@ packages:
|
||||
'@types/normalize-package-data@2.4.4':
|
||||
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
|
||||
|
||||
'@types/qs@6.14.0':
|
||||
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
|
||||
|
||||
'@types/range-parser@1.2.7':
|
||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||
|
||||
'@types/send@0.17.5':
|
||||
resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==}
|
||||
|
||||
'@types/serve-static@1.15.8':
|
||||
resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.19.0':
|
||||
resolution: {integrity: sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@ -831,6 +864,10 @@ packages:
|
||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
discord-verify@1.2.0:
|
||||
resolution: {integrity: sha512-8qlrMROW8DhpzWWzgNq9kpeLDxKanWa4EDVoj/ASVv2nr+dSr4JPmu2tFSydf3hAGI/OIJTnZyD0JulMYIxx4w==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
doctrine@2.1.0:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -2358,12 +2395,39 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@types/body-parser@1.19.6':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 22.13.1
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 22.13.1
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/express-serve-static-core@4.19.6':
|
||||
dependencies:
|
||||
'@types/node': 22.13.1
|
||||
'@types/qs': 6.14.0
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.5
|
||||
|
||||
'@types/express@4.17.23':
|
||||
dependencies:
|
||||
'@types/body-parser': 1.19.6
|
||||
'@types/express-serve-static-core': 4.19.6
|
||||
'@types/qs': 6.14.0
|
||||
'@types/serve-static': 1.15.8
|
||||
|
||||
'@types/http-errors@2.0.5': {}
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/json5@0.0.29': {}
|
||||
|
||||
'@types/mime@1.3.5': {}
|
||||
|
||||
'@types/node@22.13.1':
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
@ -2374,6 +2438,21 @@ snapshots:
|
||||
|
||||
'@types/normalize-package-data@2.4.4': {}
|
||||
|
||||
'@types/qs@6.14.0': {}
|
||||
|
||||
'@types/range-parser@1.2.7': {}
|
||||
|
||||
'@types/send@0.17.5':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 22.13.1
|
||||
|
||||
'@types/serve-static@1.15.8':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 22.13.1
|
||||
'@types/send': 0.17.5
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.20.0)(typescript@5.7.3))(eslint@9.20.0)(typescript@5.7.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@ -2833,6 +2912,10 @@ snapshots:
|
||||
dependencies:
|
||||
path-type: 4.0.0
|
||||
|
||||
discord-verify@1.2.0:
|
||||
dependencies:
|
||||
'@types/express': 4.17.23
|
||||
|
||||
doctrine@2.1.0:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
|
96
src/config/applicationData.ts
Normal file
96
src/config/applicationData.ts
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- We are using the numerical ids as the keys.*/
|
||||
export const applicationData: Record<string, { name: string; key: string }> = {
|
||||
"1235128719836712970": {
|
||||
key: "e255cd3a348ce604e18e2f2c0f5a59a9273a420ca732630d569ef5f839f2143e",
|
||||
name: "Celestine",
|
||||
},
|
||||
"1338596130207957035": {
|
||||
key: "ef9d2a3c8e9a4d165a12ca798be93d719dd51cc7ff901ade8136cb992cd7fe3e",
|
||||
name: "Aria Iuvo",
|
||||
},
|
||||
"1338664192714211459": {
|
||||
key: "a28c9c2683acb209341e04d5b43bd2a24bc81f5b263095a1bf487e94ca6a3ff9",
|
||||
name: "Cordelia Taryne",
|
||||
},
|
||||
"1338753576583041074": {
|
||||
key: "62468252371a68791c650b5aaa599ef2b7b148a7169bb5fa9f56075705ac405e",
|
||||
name: "Melody Iuvo",
|
||||
},
|
||||
"1343341112437248041": {
|
||||
key: "3ccf23b9c10dda9a7af434e0c5fc116d05aba66caf2ebd8c738c60ba9875436f",
|
||||
name: "Becca Lyria",
|
||||
},
|
||||
"1343370633916059668": {
|
||||
key: "a9146e39ab5f69129e3c50db5d5848c56bc4a2cb90bba10e492e08355acd3d12",
|
||||
name: "Maylin Taryne",
|
||||
},
|
||||
"1343413943447584819": {
|
||||
key: "6d95c7132ee9f28a4ea5fffbc8dd33ecaffe0f50add0c631d5ef51cc071bd2df",
|
||||
name: "Gwen Abalise",
|
||||
},
|
||||
"1347642447643017289": {
|
||||
key: "091be058344e543e87baafeb9fe20b004bd170ca5ee57b17afff595271080a27",
|
||||
name: "Mommy",
|
||||
},
|
||||
"1386862413936328796": {
|
||||
key: "831e3981135a4c9d30e8c02e6f8cba73436d30c36b1a14f1fb7bc7147cd4cb75",
|
||||
name: "Maribelle",
|
||||
},
|
||||
"1391117878182281316": {
|
||||
key: "b182039c954ba57216bf53bdc4d5ba3d89efc2b41c850cbf7f3430fbb6cc0c56",
|
||||
name: "Hikari",
|
||||
},
|
||||
"1391488058913718374": {
|
||||
key: "fc1a838c3102351602221f2db63d997f9e4495510044d5ec436844038b9a5bd5",
|
||||
name: "Rosalia Nightsong",
|
||||
},
|
||||
"1391489982887362761": {
|
||||
key: "ef173a181d6fcf506b72795812fca8ecc3a81c2ff382bfd39564bab63fd90eee",
|
||||
name: "Sorielle",
|
||||
},
|
||||
"1391491102657482863": {
|
||||
key: "a412583e5d5510aebb6d54eb49f36797e62f9567efd79108863adbb93cf00236",
|
||||
name: "Verena",
|
||||
},
|
||||
"1391492296222179459": {
|
||||
key: "aab61af4411fc1dc710d5b5aba5d16fba9effaad9415b878a3f1595428dbebcd",
|
||||
name: "Liora",
|
||||
},
|
||||
"1391493722176356434": {
|
||||
key: "3dc2abd018271a5a1f65c09597738a711b2fc619d34da59189279d6c2a1922f4",
|
||||
name: "Thessalia",
|
||||
},
|
||||
"1391494389477412906": {
|
||||
key: "8d11517cf954779e4645b8c10432697a0572dcfcecc0bb14d0fe2397f5a03453",
|
||||
name: "Callista",
|
||||
},
|
||||
"1391495288421879849": {
|
||||
key: "fb0894e2dcd3e736addd70a675b40d5681d39c133289cdfe4f7378006163e817",
|
||||
name: "Eirene",
|
||||
},
|
||||
"1391497269177483405": {
|
||||
key: "0887f7e12053d559f8944a06ed323ec237d34f8e15860b672fb7abe6dc32d192",
|
||||
name: "Sybil",
|
||||
},
|
||||
"1391503229287928000": {
|
||||
key: "8a4d4f18dae4e2d0ec610e3da47254a812c39cfac6ee986cfaa20eb848a2db92",
|
||||
name: "Clarion",
|
||||
},
|
||||
"1391503834073006181": {
|
||||
key: "60ffe0a3503d5166c1e4b7370753793dff60d75a28ebf4ace8b6623ad5207821",
|
||||
name: "Evangeline",
|
||||
},
|
||||
"1391504577811185744": {
|
||||
key: "33bb085873b46199facfbfae00b7d71ed04d55377deeaa2a0c8afa303c68a75d",
|
||||
name: "Theodora",
|
||||
},
|
||||
"1391505285465509978": {
|
||||
key: "edce882890078d52c00c653b9e305565a909a0807abf86b36f1d7679434b80fa",
|
||||
name: "Veluna",
|
||||
},
|
||||
};
|
31
src/interfaces/entitlement.ts
Normal file
31
src/interfaces/entitlement.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention -- Needs to match Discord's structure. */
|
||||
export interface Entitlement {
|
||||
application_id: string;
|
||||
event: {
|
||||
data: {
|
||||
application_id: string;
|
||||
consumed: boolean;
|
||||
deleted: boolean;
|
||||
ends_at: string;
|
||||
gift_code_flags: number;
|
||||
guild_id: string;
|
||||
id: string;
|
||||
promotion_id: string | null;
|
||||
sku_id: string;
|
||||
starts_at: string;
|
||||
subscription_id: string;
|
||||
type: number;
|
||||
user_id: string;
|
||||
};
|
||||
timestamp: string;
|
||||
type: string;
|
||||
};
|
||||
type: number;
|
||||
version: number;
|
||||
}
|
@ -17,6 +17,7 @@ export const sendDiscord = async(
|
||||
body: JSON.stringify({
|
||||
components: [
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Needs to match Discord's structure.
|
||||
accent_color: 15_418_782,
|
||||
components: [
|
||||
{
|
||||
@ -40,6 +41,7 @@ export const sendDiscord = async(
|
||||
flags: 32_768,
|
||||
}),
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Needs to match Discord's structure.
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
|
50
src/modules/validateWebhook.ts
Normal file
50
src/modules/validateWebhook.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @copyright nhcarrigan
|
||||
* @license Naomi's Public License
|
||||
* @author Naomi Carrigan
|
||||
*/
|
||||
import { webcrypto } from "node:crypto";
|
||||
import { verify } from "discord-verify/node";
|
||||
import { applicationData } from "../config/applicationData.js";
|
||||
import { sendDiscord } from "./discord.js";
|
||||
import type { Entitlement } from "../interfaces/entitlement.js";
|
||||
import type { FastifyRequest } from "fastify";
|
||||
|
||||
/**
|
||||
* Validates that the webhook request is from Discord by checking the signature.
|
||||
* @param request - The Fastify request object containing the webhook data.
|
||||
* @returns A boolean indicating whether the webhook signature is valid.
|
||||
*/
|
||||
export const validateWebhook = async(
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Body must be capitalised for Fastify.
|
||||
request: FastifyRequest<{ Body: Entitlement; Headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Header must be formatted for Fastify.
|
||||
"x-signature-ed25519": string;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Header must be formatted for Fastify.
|
||||
"x-signature-timestamp": string;
|
||||
}; }>,
|
||||
): Promise<boolean> => {
|
||||
const { application_id: applicationId } = request.body;
|
||||
const appData = applicationData[applicationId];
|
||||
if (appData === undefined) {
|
||||
void sendDiscord(`[NOTIFICATION]: Invalid Application ID`, `Received an entitlement event for an invalid application ID: ${applicationId}`);
|
||||
return false;
|
||||
}
|
||||
const signature = request.headers["x-signature-ed25519"];
|
||||
const timestamp = request.headers["x-signature-timestamp"];
|
||||
const rawBody = JSON.stringify(request.body);
|
||||
const isValid = await verify(
|
||||
rawBody,
|
||||
signature,
|
||||
timestamp,
|
||||
appData.key,
|
||||
webcrypto.subtle,
|
||||
);
|
||||
if (!isValid) {
|
||||
void sendDiscord(
|
||||
`[NOTIFICATION]: Invalid Webhook Signature`,
|
||||
`Received an entitlement event with an invalid signature.\nApplication ID: ${applicationId}\nSignature: ${signature}\nTimestamp: ${timestamp}\nRaw Body: ${rawBody}`,
|
||||
);
|
||||
}
|
||||
return isValid;
|
||||
};
|
@ -5,12 +5,15 @@
|
||||
*/
|
||||
|
||||
import fastify from "fastify";
|
||||
import { applicationData } from "../config/applicationData.js";
|
||||
import { auth } from "../modules/auth.js";
|
||||
import { sendDiscord } from "../modules/discord.js";
|
||||
import { sendMail } from "../modules/sendMail.js";
|
||||
import { validateWebhook } from "../modules/validateWebhook.js";
|
||||
import { errorSchema } from "../schemas/errorSchema.js";
|
||||
import { logSchema } from "../schemas/logSchema.js";
|
||||
import { uptimeSchema } from "../schemas/uptimeSchema.js";
|
||||
import type { Entitlement } from "../interfaces/entitlement.js";
|
||||
import type { Error } from "../interfaces/error.js";
|
||||
import type { Log } from "../interfaces/log.js";
|
||||
import type { Uptime } from "../interfaces/uptime.js";
|
||||
@ -90,7 +93,10 @@ export const instantiateServer = (): void => {
|
||||
return;
|
||||
}
|
||||
const { application, context, stack, message } = request.body;
|
||||
await sendMail(`[ERROR]: ${context} - ${application}`, `${message}\n\n${stack}`);
|
||||
await sendMail(
|
||||
`[ERROR]: ${context} - ${application}`,
|
||||
`${message}\n\n${stack}`,
|
||||
);
|
||||
await sendDiscord(
|
||||
`[ERROR]: ${context} - ${application}`,
|
||||
`${message}\n\n\`\`\`\n${stack}\n\`\`\``,
|
||||
@ -115,13 +121,72 @@ export const instantiateServer = (): void => {
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Body must be capitalised for Fastify.
|
||||
server.post<{ Body: Entitlement; Headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Header must be formatted for Fastify.
|
||||
"x-signature-ed25519": string;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Header must be formatted for Fastify.
|
||||
"x-signature-timestamp": string;
|
||||
}; }>(
|
||||
"/entitlement",
|
||||
// eslint-disable-next-line complexity -- Fuck off.
|
||||
async(request, response) => {
|
||||
const { type, application_id: applicationId, event } = request.body;
|
||||
const appInfo = applicationData[applicationId];
|
||||
const isValid = await validateWebhook(request);
|
||||
if (!isValid) {
|
||||
await response.status(401).send({ success: false });
|
||||
void sendDiscord(
|
||||
`[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`,
|
||||
"An invalid webhook signature was received.",
|
||||
);
|
||||
void sendMail(
|
||||
`[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`,
|
||||
"An invalid webhook signature was received.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
await response.status(204).send();
|
||||
if (type === 0) {
|
||||
void sendDiscord(
|
||||
`[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`,
|
||||
"Received a ping from Discord.",
|
||||
);
|
||||
void sendMail(
|
||||
`[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`,
|
||||
"Received a ping from Discord.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const {
|
||||
user_id: userId,
|
||||
guild_id: guildId,
|
||||
ends_at: endsAt,
|
||||
} = event.data;
|
||||
await sendDiscord(
|
||||
`[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`,
|
||||
`Entitlement purchased!\n- **User ID**: ${userId}\n- **Guild ID**: ${guildId}\n- **Ends At**: ${endsAt}`,
|
||||
);
|
||||
await sendMail(
|
||||
`[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`,
|
||||
`Entitlement purchased!\n- **User ID**: ${userId}\n- **Guild ID**: ${guildId}\n- **Ends At**: ${endsAt}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
server.listen({ port: 5003 }, (error) => {
|
||||
const application = "Alert Server";
|
||||
if (error) {
|
||||
const { message, stack } = error;
|
||||
const context = "Server Startup";
|
||||
void sendMail(`[ERROR]: ${context} - ${application}`, `${message}\n\n${String(stack)}`);
|
||||
void sendDiscord(`[ERROR]: ${context} - ${application}`, `${message}\n\n\`\`\`\n${String(stack)}\n\`\`\``);
|
||||
void sendMail(
|
||||
`[ERROR]: ${context} - ${application}`,
|
||||
`${message}\n\n${String(stack)}`,
|
||||
);
|
||||
void sendDiscord(
|
||||
`[ERROR]: ${context} - ${application}`,
|
||||
`${message}\n\n\`\`\`\n${String(stack)}\n\`\`\``,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const level = "debug";
|
||||
@ -134,7 +199,13 @@ export const instantiateServer = (): void => {
|
||||
const context = "Server Startup";
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Totally being lazy.
|
||||
const { message, stack } = error as Error;
|
||||
void sendMail(`[ERROR]: ${context} - ${application}`, `${message}\n\n${stack}`);
|
||||
void sendDiscord(`[ERROR]: ${context} - ${application}`, `${message}\n\n\`\`\`\n${stack}\n\`\`\``);
|
||||
void sendMail(
|
||||
`[ERROR]: ${context} - ${application}`,
|
||||
`${message}\n\n${stack}`,
|
||||
);
|
||||
void sendDiscord(
|
||||
`[ERROR]: ${context} - ${application}`,
|
||||
`${message}\n\n\`\`\`\n${stack}\n\`\`\``,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user