Compare commits

..

14 Commits

11 changed files with 256 additions and 134 deletions

View File

@ -1,4 +1,4 @@
# Contributing to LOC's Community Relations Alpha Edition System # Contributing to LOC's Community Relations Gamma Edition System
Thank you for considering contributing to this project! Your contributions are highly valued, and were excited to collaborate with you. Thank you for considering contributing to this project! Your contributions are highly valued, and were excited to collaborate with you.

View File

@ -1,9 +1,7 @@
# Community Relations Alpha Edition System - CRRA # Community Relations v2 Gamma Edition System - CRRA/G
[![License](https://img.shields.io/badge/license-AGPLv3-blue.svg)](LICENSE) [![License](https://img.shields.io/badge/license-AGPLv3-blue.svg)](LICENSE)
A brief description of what your project does, what problem it solves, and why its useful.
--- ---
## Table of Contents ## Table of Contents
@ -88,4 +86,4 @@ __Library of Code Department of Engineering__ @:
- [Email](mailto:engineering@libraryofcode.org) - [Email](mailto:engineering@libraryofcode.org)
--- ---
Thank you for checking out CRRA! Thank you for checking out CRv2!

View File

@ -1,4 +1,4 @@
import { prop, getModelForClass } from "@typegoose/typegoose"; import { prop, getModelForClass, Ref } from "@typegoose/typegoose";
import Member from "./Member"; import Member from "./Member";
/* TODO /* TODO
@ -60,9 +60,9 @@ export default class Partner implements SharedMemberAttributes {
@prop({ required: true }) @prop({ required: true })
public title: PartnerTitle | "Partner" | undefined; public title: PartnerTitle | "Partner" | undefined;
@prop() @prop({ ref: () => Partner })
// //
public directReport: Partner | string | undefined; public directReport?: Ref<Partner> | string | undefined;
@prop() @prop()
// this field dictates if the partner is able to perform developer commands, such as "eval" // this field dictates if the partner is able to perform developer commands, such as "eval"

View File

@ -2,10 +2,11 @@ import DiscordInteractionCommand from "../../util/DiscordInteractionCommand";
import { ChatInputCommandInteraction } from "discord.js"; import { ChatInputCommandInteraction } from "discord.js";
import { inspect } from "util"; import { inspect } from "util";
import { discordBotToken } from "../../config.json"; import { discordBotToken } from "../../config.json";
import { PartnerModel } from "../../database/Partner";
export default class Eval extends DiscordInteractionCommand { export default class Eval extends DiscordInteractionCommand {
// This is a list of IDs that are allowed to use this command. // This is a list of IDs that are allowed to use this command.
private listOfAllowedIDs: string[]; private listOfAllowedIDs: string[] = [];
constructor() { constructor() {
super("eval", "Executes arbitrary JS code and returns the output."); super("eval", "Executes arbitrary JS code and returns the output.");
@ -25,9 +26,13 @@ export default class Eval extends DiscordInteractionCommand {
option.setName("depth").setDescription("The depth of the inspection.").setRequired(false) option.setName("depth").setDescription("The depth of the inspection.").setRequired(false)
); );
this.listOfAllowedIDs = [ // this checks against the database and adds all of the partners that are "allowed to perform dev commands"
"278620217221971968", // Matthew // doing the database check in the initialization prevents us from having to check the database every time this command is ran
]; PartnerModel.find({ canPerformDevCommands: true }).then((partners) => {
for (const partner of partners) {
this.listOfAllowedIDs.push(partner.discordID as string);
}
});
} }
public async execute(interaction: ChatInputCommandInteraction) { public async execute(interaction: ChatInputCommandInteraction) {

View File

@ -1,14 +1,15 @@
import DiscordInteractionCommand from "../../util/DiscordInteractionCommand"; import DiscordInteractionCommand from "../../util/DiscordInteractionCommand";
import { MemberModel } from "../../database/Member"; import { MemberAdditionalAcknowledgement, MemberModel } from "../../database/Member";
import Partner, { import Partner, {
PartnerCommissionType, PartnerCommissionType,
PartnerDepartment, PartnerDepartment,
PartnerModel, PartnerModel,
PartnerRoleType, PartnerRoleType,
} from "../../database/Partner"; } from "../../database/Partner";
import { ChatInputCommandInteraction, EmbedBuilder, GuildMember } from "discord.js"; import { ChatInputCommandInteraction, EmbedBuilder, GuildMember, Snowflake } from "discord.js";
import MemberUtil from "../../util/MemberUtil"; import MemberUtil from "../../util/MemberUtil";
import EmojiConfig from "../../util/EmojiConfig"; import EmojiConfig from "../../util/EmojiConfig";
import Formatters from "../../util/Formatters";
export default class Whois extends DiscordInteractionCommand { export default class Whois extends DiscordInteractionCommand {
constructor() { constructor() {
@ -36,7 +37,7 @@ export default class Whois extends DiscordInteractionCommand {
const embed = new EmbedBuilder(); const embed = new EmbedBuilder();
// if the role type is managerial, add a [k] to the end of the name // if the role type is managerial, add a [k] to the end of the name
// if the partner exists, set the iconURL to the organizational logo // if the partner exists, set the iconURL to the organizational logo
const formattedName = MemberUtil.formatName(guildMember, partner); const formattedName = Formatters.formatName(guildMember, partner);
embed.setAuthor({ name: formattedName.text, iconURL: formattedName.iconURL }); embed.setAuthor({ name: formattedName.text, iconURL: formattedName.iconURL });
// set the thumbnail to the user's avatar // set the thumbnail to the user's avatar
embed.setThumbnail(guildMember.user.displayAvatarURL()); embed.setThumbnail(guildMember.user.displayAvatarURL());
@ -79,16 +80,23 @@ export default class Whois extends DiscordInteractionCommand {
break; break;
} }
if (partner.directReport) { if (partner.directReport) {
if (partner.directReport instanceof Partner) { // fetch direct report object ref
embedDescription += `**Direct Report**: ${partner.directReport.title}\n`; await partner.populate("directReport");
// ensures that the population propagated correctly before adding to embed
if (partner.directReport instanceof PartnerModel) {
const directReportGuildMember = await guild?.members.fetch(
partner.directReport.discordID as Snowflake
);
// fetches GuildMember for the direct report
embedDescription += `**Direct Report**: ${directReportGuildMember ? Formatters.formatName(directReportGuildMember, partner.directReport).text + ", " : ""}${partner.directReport.title}\n`;
} }
} }
} }
embed.setColor(guildMember.displayColor); embed.setColor(guildMember.displayColor);
if (embedDescription?.length > 0) embed.setDescription(embedDescription); if (embedDescription?.length > 0)
embed.setDescription(`${embedDescription}\n\n<@${guildMember.id}>`);
// add status to embed // add status to embed
if (guildMember.presence?.status) { if (guildMember.presence?.status) {
// TODO: this currently doesn't work for some reason
switch (guildMember.presence.status) { switch (guildMember.presence.status) {
case "online": case "online":
embed.addFields({ name: "Status", value: "Online", inline: true }); embed.addFields({ name: "Status", value: "Online", inline: true });
@ -99,7 +107,7 @@ export default class Whois extends DiscordInteractionCommand {
case "dnd": case "dnd":
embed.addFields({ name: "Status", value: "Do Not Disturb", inline: true }); embed.addFields({ name: "Status", value: "Do Not Disturb", inline: true });
break; break;
case "offline" || "invisible": case "offline":
embed.addFields({ name: "Status", value: "Online", inline: true }); embed.addFields({ name: "Status", value: "Online", inline: true });
break; break;
default: default:
@ -107,9 +115,87 @@ export default class Whois extends DiscordInteractionCommand {
embed.addFields({ name: "Status", value: "", inline: true }); embed.addFields({ name: "Status", value: "", inline: true });
break; break;
} }
} else {
embed.addFields({ name: "Status", value: "Offline", inline: true });
}
// calculations for joined / created at
embed.addFields(
{
name: "Joined At",
value: guildMember.joinedTimestamp
? `<t:${Math.floor(guildMember.joinedTimestamp / 1000)}>`
: "Invalid Date",
inline: true,
},
{
name: "Created At",
value: guildMember.user.createdTimestamp
? `<t:${Math.floor(guildMember.user.createdTimestamp / 1000)}>`
: "Invalid Date",
inline: true,
}
);
// TODO: calculations for commscore
embed.addFields({ name: "CommScore™", value: "[PLACEHOLDER]", inline: false });
// role calculation (sorting roles by their position)
let roleString = "";
for (const role of guildMember.roles.valueOf().sort((a, b) => b.position - a.position)) {
roleString += `<@&${role[1].id}> `;
}
if (roleString) {
embed.addFields({ name: "Roles", value: roleString });
} else {
embed.addFields({ name: "Roles", value: "None" });
}
// listing permissions
const serializedPermissions = guildMember.permissions.serialize();
const permissionsArray: string[] = [];
// adding serialized string representation of permissions to array to use in embed field
if (serializedPermissions.Administrator) permissionsArray.push("Administrator");
if (serializedPermissions.ManageGuild) permissionsArray.push("Manage Guild");
if (serializedPermissions.ManageChannels) permissionsArray.push("Manage Channels");
if (serializedPermissions.ManageRoles) permissionsArray.push("Manage Roles");
if (serializedPermissions.ManageMessages) permissionsArray.push("Manage Messages");
if (serializedPermissions.ManageEvents) permissionsArray.push("Manage Events");
if (serializedPermissions.ManageNicknames) permissionsArray.push("Manage Nicknames");
if (serializedPermissions.ManageEmojisAndStickers) permissionsArray.push("Manage Emojis");
if (serializedPermissions.ManageWebhooks) permissionsArray.push("Manage Webhooks");
if (serializedPermissions.ModerateMembers) permissionsArray.push("Moderate Members");
if (serializedPermissions.BanMembers) permissionsArray.push("Ban Members");
if (serializedPermissions.KickMembers) permissionsArray.push("Kick Members");
if (serializedPermissions.DeafenMembers) permissionsArray.push("Deafen Members");
// setting key permissions embed field
if (permissionsArray?.length > 0) {
embed.addFields({ name: "Key Permissions", value: permissionsArray.join(", ") });
}
// determining acknowledgements: MemberAdditionalAcknowledgement || "Guild Owner", "Guild Admin", "Guild Manager", "Guild Moderator"
const acknowledgementsArray: MemberAdditionalAcknowledgement[] = [];
if (guildMember.id === guildMember.guild.ownerId) {
acknowledgementsArray.push("Guild Owner");
} else if (serializedPermissions.Administrator) {
acknowledgementsArray.push("Guild Admin");
} else if (serializedPermissions.ManageGuild) {
acknowledgementsArray.push("Guild Manager");
} else if (serializedPermissions.ModerateMembers || serializedPermissions.ManageMessages) {
acknowledgementsArray.push("Guild Moderator");
}
if (partner?.canPerformDevCommands) {
acknowledgementsArray.push("System Developer");
}
// adding acknowledgements to embed
if (acknowledgementsArray.length > 0) {
embed.addFields({ name: "Acknowledgements", value: acknowledgementsArray.join(", ") });
} }
embed.setFooter({ embed.setFooter({
text: `Discord ID: ${guildMember.id}${databaseMember ? `Internal ID: ${databaseMember?._id}` : ""}`, text: `Discord ID: ${guildMember.id}${databaseMember ? `| Internal ID: ${databaseMember?._id}` : ""}${partner ? ` | Partner ID: ${partner?.id}` : ""}`,
}); });
return await interaction.editReply({ embeds: [embed] }); return await interaction.editReply({ embeds: [embed] });

135
index.ts
View File

@ -1,77 +1,88 @@
import { Client, GatewayIntentBits, Partials, REST, Routes } from "discord.js"; import { Client, GatewayIntentBits, Partials, REST, Routes } from 'discord.js';
import { discordBotToken, discordClientID } from "./config.json" import { discordBotToken, discordClientID, MongoDbUrl } from './config.json';
import Collection from "./util/Collection"; import Collection from './util/Collection';
import DiscordInteractionCommand from "./util/DiscordInteractionCommand"; import DiscordInteractionCommand from './util/DiscordInteractionCommand';
import DiscordEvent from "./util/DiscordEvent"; import DiscordEvent from './util/DiscordEvent';
import * as DiscordInteractionCommandsIndex from "./discord/commands"; import * as DiscordInteractionCommandsIndex from './discord/commands';
import * as DiscordEventsIndex from "./discord/events"; import * as DiscordEventsIndex from './discord/events';
import mongoose from "mongoose"; import mongoose from 'mongoose';
export const DiscordInteractionCommands: Collection<DiscordInteractionCommand> = new Collection(); export const DiscordInteractionCommands: Collection<DiscordInteractionCommand> =
new Collection();
export const DiscordEvents: Collection<DiscordEvent> = new Collection(); export const DiscordEvents: Collection<DiscordEvent> = new Collection();
// Instantiates a new Discord client // Instantiates a new Discord client
const discordClient = new Client({ const discordClient = new Client({
intents: [ intents: [
GatewayIntentBits.DirectMessages, GatewayIntentBits.DirectMessages,
GatewayIntentBits.GuildIntegrations, GatewayIntentBits.GuildIntegrations,
GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildPresences,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildInvites, GatewayIntentBits.GuildInvites,
GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildModeration,
], ],
partials: [ Partials.GuildMember, Partials.Message, Partials.User, Partials.Channel, ], partials: [
Partials.GuildMember,
Partials.Message,
Partials.User,
Partials.Channel,
],
}); });
const discordREST = new REST().setToken(discordBotToken); const discordREST = new REST().setToken(discordBotToken);
// const stripeClient = new Stripe(stripeToken, { typescript: true }); // const stripeClient = new Stripe(stripeToken, { typescript: true });
export async function main() { export async function main() {
// Connect to the databases // Connect to the databases
try { try {
mongoose.connection.once("open", () => { //@ts-ignore
console.info("[Info - Database] Connected to MongoDB"); mongoose.connection.once('open', () => {
}) console.info('[Info - Database] Connected to MongoDB');
// TODO: Fetch the MongoDB URI from the config file });
await mongoose.connect("mongodb://localhost:27017/crra-main", {}); // TODO: Fetch the MongoDB URI from the config file
} catch (error) { await mongoose.connect(MongoDbUrl, {});
console.error(`[Error - Database] Failed to connect to MongoDB: ${error}`); } catch (error) {
process.exit(1); console.error(`[Error - Database] Failed to connect to MongoDB: ${error}`);
} process.exit(1);
// Load Discord interaction commands }
for (const Command of Object.values(DiscordInteractionCommandsIndex)) { // Load Discord interaction commands
const instance = new Command(); for (const Command of Object.values(DiscordInteractionCommandsIndex)) {
DiscordInteractionCommands.add(instance.name, instance); const instance = new Command();
console.info(`[Info - Discord] Loaded interaction command: ${instance.name}`); DiscordInteractionCommands.add(instance.name, instance);
} console.info(
// Load Discord events `[Info - Discord] Loaded interaction command: ${instance.name}`
for (const Event of Object.values(DiscordEventsIndex)) { );
const instance = new Event(discordClient); }
DiscordEvents.add(instance.name, instance); // Load Discord events
discordClient.on(instance.name, instance.execute); for (const Event of Object.values(DiscordEventsIndex)) {
console.info(`[Info - Discord] Loaded event: ${instance.name}`); const instance = new Event(discordClient);
} DiscordEvents.add(instance.name, instance);
await discordClient.login(discordBotToken); discordClient.on(instance.name, instance.execute);
console.info(`[Info - Discord] Loaded event: ${instance.name}`);
}
await discordClient.login(discordBotToken);
try { try {
console.log(`Started refreshing ${DiscordInteractionCommands.size} application (/) commands.`); console.log(
const interactionCommandsData = []; `Started refreshing ${DiscordInteractionCommands.size} application (/) commands.`
for (const command of DiscordInteractionCommands.values()) { );
interactionCommandsData.push(command.builder.toJSON()); const interactionCommandsData = [];
} for (const command of DiscordInteractionCommands.values()) {
interactionCommandsData.push(command.builder.toJSON());
// The put method is used to fully refresh all commands in the guild with the current set
const data = await discordREST.put(
Routes.applicationCommands(discordClientID),
{ body: interactionCommandsData },
);
// @ts-ignore
console.log(`Successfully reloaded ${data?.length} application (/) commands.`);
} catch (error) {
// And of course, make sure you catch and log any errors!
console.error(error);
} }
// The put method is used to fully refresh all commands in the guild with the current set
const data = await discordREST.put(
Routes.applicationCommands(discordClientID),
{ body: interactionCommandsData }
);
console.log(
`Successfully reloaded ${interactionCommandsData?.length} application (/) commands.`
);
} catch (error) {
// And of course, make sure you catch and log any errors!
console.error(error);
}
} }
main(); main();

View File

@ -1,4 +1,5 @@
{ {
"name": "crv2",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.13.0", "@eslint/js": "^9.13.0",
@ -18,6 +19,7 @@
}, },
"dependencies": { "dependencies": {
"@typegoose/typegoose": "^12.2.0", "@typegoose/typegoose": "^12.2.0",
"auth0": "^4.12.0",
"discord.js": "^14.14.1", "discord.js": "^14.14.1",
"mongoose": "^8.2.2", "mongoose": "^8.2.2",
"stripe": "^14.21.0", "stripe": "^14.21.0",

View File

@ -11,11 +11,11 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */ /* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */ // "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ "experimentalDecorators": true /* Enable experimental support for legacy experimental decorators. */,
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
@ -25,7 +25,7 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */ /* Modules */
"module": "commonjs", /* Specify what module code is generated. */ "module": "commonjs" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */ // "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
@ -39,7 +39,7 @@
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
"resolveJsonModule": true, /* Enable importing .json files. */ "resolveJsonModule": true /* Enable importing .json files. */,
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
@ -55,7 +55,7 @@
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */ "outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */ // "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */ // "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
@ -77,12 +77,12 @@
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */ /* Type Checking */
"strict": true, /* Enable all strict type-checking options. */ "strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
@ -104,6 +104,6 @@
/* Completeness */ /* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */
} }
} }

View File

@ -8,5 +8,5 @@ export default abstract class DiscordEvent {
this.client = client; this.client = client;
this.execute = this.execute.bind(this); this.execute = this.execute.bind(this);
} }
public abstract execute(...args: any[]): Error | Promise<void>; public abstract execute(...args: never[]): Error | Promise<void>;
} }

55
util/Formatters.ts Normal file
View File

@ -0,0 +1,55 @@
import { GuildMember, User } from "discord.js";
import Partner, { PartnerCommissionType, PartnerRoleType } from "../database/Partner";
import { FormatNameOptions } from "./MemberUtil";
export default class Formatters {
public static formatStandardDate(date: Date | string | number): string {
const resolvedDate = new Date(date);
if (!resolvedDate) return "";
const year = resolvedDate.getFullYear();
const month = String(resolvedDate.getMonth() + 1).padStart(2, "0");
const day = String(resolvedDate.getDate()).padStart(2, "0");
const hours = String(resolvedDate.getHours()).padStart(2, "0");
const minutes = String(resolvedDate.getMinutes()).padStart(2, "0");
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return `${year}-${month}-${day} @ ${hours}:${minutes} (${timeZone})`;
}
// TODO: comments and extended formatting
public static formatName(
target: GuildMember | User,
partner?: Partner | null
): FormatNameOptions {
console.debug(
`[MemberUtil] Formatting name for ${target.displayName} at url ${target instanceof GuildMember ? target.user.displayAvatarURL() : target.displayAvatarURL()}`
);
// if the role type is managerial, add a [k] to the end of the name
// if the partner exists, set the iconURL to the organizational logo
if (partner?.roleType == PartnerRoleType.MANAGERIAL) {
return {
text: `${target.displayName} [k]`,
iconURL: target.displayAvatarURL(),
};
} else if (partner?.commissionType == PartnerCommissionType.CONTRACTUAL) {
// if the commission type is contractual, add a [c] to the end of the name
return {
text: `${target.displayName} [c]`,
iconURL:
target instanceof GuildMember
? target.user.displayAvatarURL()
: target.displayAvatarURL(),
};
} else {
// otherwise, just set the author to the member's display name
return {
text: target.displayName,
iconURL:
target instanceof GuildMember
? target.user.displayAvatarURL()
: target.displayAvatarURL(),
};
}
}
}

View File

@ -8,13 +8,14 @@ import Partner, {
import Member, { MemberAdditionalAcknowledgement, MemberModel } from "../database/Member"; import Member, { MemberAdditionalAcknowledgement, MemberModel } from "../database/Member";
import { Client, GuildMember, User } from "discord.js"; import { Client, GuildMember, User } from "discord.js";
import { guildID } from "../config.json"; import { guildID } from "../config.json";
import { Ref } from "@typegoose/typegoose";
export interface PartnerOptions { export interface PartnerOptions {
roleType: PartnerRoleType; roleType: PartnerRoleType;
commissionType: PartnerCommissionType; commissionType: PartnerCommissionType;
department: PartnerDepartment; department: PartnerDepartment;
title: PartnerTitle; title: PartnerTitle;
directReport: Partner | string; directReport: Ref<Partner>;
} }
export interface FormatNameOptions { export interface FormatNameOptions {
@ -25,7 +26,7 @@ export interface FormatNameOptions {
// TODO: Add the rest of the remaining role configurations // TODO: Add the rest of the remaining role configurations
export const PartnerDiscordRoleMap = { export const PartnerDiscordRoleMap = {
// Director of Engineering, Management, Staff, Technician, Core Team, Play Caller // Director of Engineering, Management, Staff, Technician, Core Team, Play Caller
"Director of Engineering": [ Engineering: [
"1077646568091570236", "1077646568091570236",
"1077646956890951690", "1077646956890951690",
"446104438969466890", "446104438969466890",
@ -34,7 +35,7 @@ export const PartnerDiscordRoleMap = {
"1014978134573064293", "1014978134573064293",
], ],
// Director of Operations, Management, Staff, Moderator, Core Team, Play Caller // Director of Operations, Management, Staff, Moderator, Core Team, Play Caller
"Director of Operations": [ Operations: [
"1077647072163020840", "1077647072163020840",
"1077646956890951690", "1077646956890951690",
"446104438969466890", "446104438969466890",
@ -79,40 +80,4 @@ export default class MemberUtil {
{ $push: { additionalAcknowledgement: acknowledgement } } { $push: { additionalAcknowledgement: acknowledgement } }
); );
} }
// TODO: comments and extended formatting
public static formatName(
target: GuildMember | User,
partner?: Partner | null
): FormatNameOptions {
console.log(
`[MemberUtil] Formatting name for ${target.displayName} at url ${target instanceof GuildMember ? target.user.displayAvatarURL() : target.displayAvatarURL()}`
);
// if the role type is managerial, add a [k] to the end of the name
// if the partner exists, set the iconURL to the organizational logo
if (partner?.roleType == PartnerRoleType.MANAGERIAL) {
return {
text: `${target.displayName} [k]`,
iconURL: target.displayAvatarURL(),
};
} else if (partner?.commissionType == PartnerCommissionType.CONTRACTUAL) {
// if the commission type is contractual, add a [c] to the end of the name
return {
text: `${target.displayName} [c]`,
iconURL:
target instanceof GuildMember
? target.user.displayAvatarURL()
: target.displayAvatarURL(),
};
} else {
// otherwise, just set the author to the member's display name
return {
text: target.displayName,
iconURL:
target instanceof GuildMember
? target.user.displayAvatarURL()
: target.displayAvatarURL(),
};
}
}
} }