Compare commits

..

8 Commits

16 changed files with 497 additions and 675 deletions

2
.gitignore vendored
View File

@ -1,7 +1,7 @@
### Node template
# Configurations
config.json
.vscode
# Logs
logs
*.log

View File

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

View File

@ -1,7 +1,9 @@
# Community Relations v2 Gamma Edition System - CRRA/G
# Community Relations Alpha Edition System - CRRA
[![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
@ -86,4 +88,4 @@ __Library of Code Department of Engineering__ @:
- [Email](mailto:engineering@libraryofcode.org)
---
Thank you for checking out CRv2!
Thank you for checking out CRRA!

View File

@ -1,4 +1,4 @@
import { prop, getModelForClass, Ref } from "@typegoose/typegoose";
import { prop, getModelForClass } from "@typegoose/typegoose";
import Member from "./Member";
/* TODO
@ -14,7 +14,7 @@ export type PartnerTitle =
| "Deputy Director of Engineering"
| "Deputy Director of Operations"
| "Services Manager"
| "Community Manager"
| "Project Manager"
| "Engineering Core Partner"
| "Operations Core Partner"
| "Community Moderator"
@ -51,9 +51,6 @@ export default class Partner implements SharedMemberAttributes {
@prop({ required: true })
public roleType: PartnerRoleType | undefined;
@prop()
public isKeyHolder: boolean | undefined;
@prop({ required: true })
public commissionType: PartnerCommissionType | undefined;
@ -63,9 +60,9 @@ export default class Partner implements SharedMemberAttributes {
@prop({ required: true })
public title: PartnerTitle | "Partner" | undefined;
@prop({ ref: () => Partner })
@prop()
//
public directReport?: Ref<Partner> | string | undefined;
public directReport: Partner | string | undefined;
@prop()
// this field dictates if the partner is able to perform developer commands, such as "eval"

View File

@ -2,11 +2,10 @@ import DiscordInteractionCommand from "../../util/DiscordInteractionCommand";
import { ChatInputCommandInteraction } from "discord.js";
import { inspect } from "util";
import { discordBotToken } from "../../config.json";
import { PartnerModel } from "../../database/Partner";
export default class Eval extends DiscordInteractionCommand {
// This is a list of IDs that are allowed to use this command.
private listOfAllowedIDs: string[] = [];
private listOfAllowedIDs: string[];
constructor() {
super("eval", "Executes arbitrary JS code and returns the output.");
@ -26,13 +25,9 @@ export default class Eval extends DiscordInteractionCommand {
option.setName("depth").setDescription("The depth of the inspection.").setRequired(false)
);
// this checks against the database and adds all of the partners that are "allowed to perform dev commands"
// 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);
}
});
this.listOfAllowedIDs = [
"278620217221971968", // Matthew
];
}
public async execute(interaction: ChatInputCommandInteraction) {

View File

@ -1,13 +1,392 @@
import DiscordInteractionCommand from "../../util/DiscordInteractionCommand";
import { ChatInputCommandInteraction } from "discord.js";
import { MemberModel } from "../../database/Member";
import Partner, {
PartnerModel,
PartnerCommissionType,
PartnerRoleType,
PartnerDepartment,
PartnerTitle,
} from "../../database/Partner";
export default class Ping extends DiscordInteractionCommand {
import DiscordInteractionCommand from "../../util/DiscordInteractionCommand";
import {
ChatInputCommandInteraction,
InteractionContextType,
PermissionFlagsBits,
} from "discord.js";
//TODO: ad email validation
//TODO: cover all partner model properties in cooresponding sub commands
const partnerTitles: PartnerTitle[] = [
"Director of Engineering",
"Director of Operations",
"Deputy Director of Engineering",
"Deputy Director of Operations",
"Services Manager",
"Project Manager",
"Engineering Core Partner",
"Operations Core Partner",
"Community Moderator",
"Technician",
];
export default class PartnerCommand extends DiscordInteractionCommand {
constructor() {
super("partner", "Manipulates partner information.");
this.builder
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.setContexts(InteractionContextType.Guild);
this.builder.addSubcommand((c) =>
c
.setName("add")
.setDescription("test")
.addUserOption((option) =>
option.setName("partner").setDescription("the partner you want to add.").setRequired(true)
)
.addStringOption((option) =>
option.setName("email").setDescription("their email address.").setRequired(true)
)
.addStringOption((option) =>
option
.setName("role-type")
.setDescription("their roleType.")
.setChoices(this.formatOptionsForDiscordFromEnum(PartnerRoleType))
.setRequired(true)
)
.addStringOption((option) =>
option
.setName("commission-type")
.setDescription("their commissionType.")
.setChoices(this.formatOptionsForDiscordFromEnum(PartnerCommissionType))
.setRequired(true)
)
.addStringOption((option) =>
option
.setName("title")
.setDescription("their title.")
.setChoices(this.formatPartnerTitlesArrayForDiscord())
.setRequired(true)
)
.addStringOption((option) =>
option
.setName("department")
.setDescription("their department.")
.setChoices(this.formatOptionsForDiscordFromEnum(PartnerDepartment))
.setRequired(true)
)
.addUserOption((option) =>
option.setName("direct-report").setDescription("their direct report.")
)
);
this.builder.addSubcommand((c) =>
c
.setName("get")
.setDescription("get")
.addUserOption((option) =>
option
.setName("partner")
.setDescription("the partner you want to get info abouts.")
.setRequired(true)
)
);
this.builder.addSubcommand((c) =>
c
.setName("delete")
.setDescription("delete")
.addUserOption((option) =>
option
.setName("partner")
.setDescription("the partner you want to delete.")
.setRequired(true)
)
);
this.builder.addSubcommand((c) =>
c
.setName("update")
.setDescription("update")
.addUserOption((option) =>
option
.setName("partner")
.setDescription("the partner you want to update.")
.setRequired(true)
)
.addUserOption((option) =>
option.setName("direct-report").setDescription("their direct report.")
)
.addStringOption((option) => option.setName("email").setDescription("their email address."))
.addStringOption((option) =>
option
.setName("role-type")
.setDescription("their roleType.")
.setChoices(this.formatOptionsForDiscordFromEnum(PartnerRoleType))
)
.addStringOption((option) =>
option
.setName("commission-type")
.setDescription("their commissionType.")
.setChoices(this.formatOptionsForDiscordFromEnum(PartnerCommissionType))
)
.addStringOption((option) =>
option
.setName("title")
.setDescription("their title.")
.setChoices(this.formatPartnerTitlesArrayForDiscord())
)
.addStringOption((option) =>
option
.setName("department")
.setDescription("their department.")
.setChoices(this.formatOptionsForDiscordFromEnum(PartnerDepartment))
)
);
}
public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
if (interaction.options?.getSubcommand(true) === "add") {
const subcommandName = interaction.options.getSubcommand(true);
switch (subcommandName) {
case "get":
await this.handleGetSubcommand(interaction);
break;
case "add":
await this.handleAddSubcommand(interaction);
break;
case "delete":
await this.handleDeleteSubcommand(interaction);
break;
case "update":
await this.handleUpdateSubcommand(interaction);
break;
default:
//discord does not allow parent/main commands to be excutable, this range is limited to options entered via commandbuiler
break;
}
}
async handleAddSubcommand(interaction: ChatInputCommandInteraction) {
const partnerOption = interaction.options.getUser("partner", true);
const directReport = interaction.options.getUser("direct-report", false);
const partnerOptionEmailAddress = interaction.options.getString("email", true);
const partnerOptionRoleType = interaction.options.getString("role-type", true);
const partnerOptionCommisioComissionType = interaction.options.getString(
"commission-type",
true
);
const partnerOptionDepartment = interaction.options.getString("department", true);
const partnerOptionTitle = interaction.options.getString("title", true);
const partner = await PartnerModel.findOne({ discordID: partnerOption.id }).exec();
if (partner)
return interaction.reply({
content: "The specified user already has a partner entry.",
ephemeral: false,
});
/*
const member = await MemberModel.findOne({ discordID: partnerOption.id }).exec();
if (!member)
return interaction.reply({
content: "The specified partner does not have a base member entry.",
ephemeral: false,
});
*/
let directReportPartnerDocumentFromDb;
if (directReport) {
directReportPartnerDocumentFromDb = await PartnerModel.findOne({
discordID: directReport.id,
}).exec();
if (!directReportPartnerDocumentFromDb)
return interaction.reply({
content: `the specified directReport ${directReport.username} does not have an entry in partner database, please add them first them before assigning subordinates`,
ephemeral: false,
});
}
let newPartner = new PartnerModel({
discordID: partnerOption.id,
emailAddress: partnerOptionEmailAddress,
roleType: partnerOptionRoleType,
commissionType: partnerOptionCommisioComissionType,
department: partnerOptionDepartment,
title: partnerOptionTitle,
directReport:
directReport && directReportPartnerDocumentFromDb
? directReportPartnerDocumentFromDb._id
: null,
});
await newPartner.save();
return interaction.reply({
content: `\`\`\`\n${JSON.stringify(newPartner, null, 2)}\n\`\`\``,
ephemeral: false,
});
}
async handleGetSubcommand(interaction: ChatInputCommandInteraction) {
const partnerOption = interaction.options.getUser("partner", true);
let partner = await PartnerModel.findOne({ discordID: partnerOption.id }).exec();
if (!partner)
return interaction.reply({
content: "The specified partner does not an entry in the database.",
ephemeral: false,
});
if (partner.directReport) await partner.populate("directReport");
if (partner.directReport && partner.directReport instanceof Partner) {
console.log(partner.directReport);
return interaction.reply({
content: `Raw entry \`\`\`\n${JSON.stringify(partner, null, 2)}\n\`\`\`\n\nDirect report: \`\`\`\n${JSON.stringify(partner.directReport, null, 2)}\n\`\`\``,
ephemeral: false,
});
}
return interaction.reply({
content: `Raw entry \`\`\`\n${JSON.stringify(partner, null, 2)}\n\`\`\``,
ephemeral: false,
});
}
async handleDeleteSubcommand(interaction: ChatInputCommandInteraction) {
const partnerOption = interaction.options.getUser("partner", true);
const partner = await PartnerModel.findOne({ discordID: partnerOption.id })
.populate("directReport")
.exec();
if (!partner)
return interaction.reply({
content: "The specified user does not have an entry.",
ephemeral: false,
});
if (
partner.directReport &&
partner.directReport instanceof Partner &&
interaction.user.id !== partner.directReport.discordID
)
return interaction.reply({
content:
"You're not authorized to delete this partner's information, only their direct report can.",
ephemeral: false,
});
await PartnerModel.findByIdAndDelete(partner.id);
return interaction.reply({
content: `removed partner entry from the database.`,
ephemeral: false,
});
}
async handleUpdateSubcommand(interaction: ChatInputCommandInteraction) {
const partnerOption = interaction.options.getUser("partner", true);
const directReport = interaction.options.getUser("direct-report");
const partnerOptionEmailAddress = interaction.options.getString("email");
const partnerOptionRoleType = interaction.options.getString("role-type");
const partnerOptionCommisioComissionType = interaction.options.getString("commission-type");
const partnerOptionDepartment = interaction.options.getString("department");
const partnerOptionTitle = interaction.options.getString("title");
if (
!directReport &&
!partnerOptionEmailAddress &&
!partnerOptionEmailAddress &&
!partnerOptionRoleType &&
!partnerOptionCommisioComissionType &&
!partnerOptionDepartment &&
!partnerOptionTitle
) {
return interaction.reply({
content: "You need to select atleast one option to update",
ephemeral: false,
});
}
let partner = await PartnerModel.findOne({ discordID: partnerOption.id }).exec();
if (!partner)
return interaction.reply({
content: "The specified partner does not have an entry.",
ephemeral: false,
});
if (partner.directReport) partner = await partner.populate("directReport");
console.log(partner.directReport);
if (
partner.directReport instanceof PartnerModel &&
interaction.user.id !== partner.directReport.discordID
)
return interaction.reply({
content:
"You're not authorized to update this partner's information, only their direct report can.",
ephemeral: false,
});
let directReportPartnerDocumentFromDb;
if (directReport) {
directReportPartnerDocumentFromDb = await PartnerModel.findOne({
discordID: directReport.id,
}).exec();
if (!directReportPartnerDocumentFromDb)
return interaction.reply({
content: `the specified directReport ${directReport.username} does not have an entry in partner database, please add them first them before assigning subordinates`,
ephemeral: false,
});
}
let updateObj = {
discordID: partnerOption.id,
emailAddress: partnerOptionEmailAddress,
roleType: partnerOptionRoleType,
commissionType: partnerOptionCommisioComissionType,
department: partnerOptionDepartment,
title: partnerOptionTitle,
directReport:
directReport && directReportPartnerDocumentFromDb
? directReportPartnerDocumentFromDb.id
: null,
};
try {
let updatedPartner = await this.updatePartnerInfo(partner.id, updateObj);
return interaction.reply({
content: `updated partner!\n\n\`\`\`\n${JSON.stringify(updatedPartner, null, 2)}\n\`\`\``,
ephemeral: false,
});
} catch (error) {
return interaction.reply({
content: `an error occured !\n\n\`\`\`\n${JSON.stringify(error, null, 2)}\n\`\`\``,
ephemeral: false,
});
}
}
private formatPartnerTitlesArrayForDiscord(): { name: PartnerTitle; value: string }[] {
return partnerTitles.map((title) => ({
name: title,
value: title,
}));
}
private formatOptionsForDiscordFromEnum(args: any): { name: string; value: string }[] {
return Object.entries(args)
.filter(([key, value]) => typeof value === "number") // Filter out reverse mappings
.map(([key, value]) => ({
name: key,
value: (value as number).toString(),
}));
}
private async updatePartnerInfo<T>(id: string, updateObj: Partial<T>) {
// Remove keys with falsy values (undefined, null, etc.)
const filteredUpdate = Object.fromEntries(
Object.entries(updateObj).filter(([key, value]) => key !== "discordID" && value)
);
if (Object.keys(filteredUpdate).length === 0) {
throw new Error(
"Error in Partner update command, no options specified on update, you can safely ignore this error."
);
}
// Find and update the document by ID with only the valid fields
return await PartnerModel.findByIdAndUpdate(id, filteredUpdate, { new: true });
}
}

View File

@ -1,34 +0,0 @@
import DiscordInteractionCommand, {
DiscordInteractionCommandSkeleton,
} from "../../util/DiscordInteractionCommand";
import { guildID } from "../../config.json";
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
import { MemberModel } from "../../database/Member";
import { PartnerModel } from "../../database/Partner";
export default class PartnerAdd implements DiscordInteractionCommandSkeleton {
public GUILD_ID: string;
public name: string;
public description: string;
public builder: SlashCommandBuilder;
constructor() {
this.name = "partner";
this.description = "Creates a new partner entry.";
this.builder = new SlashCommandBuilder();
this.GUILD_ID = guildID;
}
public async execute(interaction: ChatInputCommandInteraction) {
const member = MemberModel.findOne({ discordID: interaction.user.id });
if (!member)
return interaction.reply({
content: "The specified partner does not have a base member entry.",
ephemeral: true,
});
if (!(await PartnerModel.findOne({ discordID: interaction.user.id })))
return interaction.reply({
content: "The specified partner already has a partner entry.",
ephemeral: true,
});
}
}

View File

@ -1,293 +0,0 @@
import DiscordInteractionCommand from "../../util/DiscordInteractionCommand";
import { ChatInputCommandInteraction, EmbedBuilder } from "discord.js";
import axios from "axios";
interface CertificateDetails {
bitLength: number;
connection: {
cipherSuite: string;
tlsVersion: string;
};
emailAddresses: [];
extendedKeyUsage: number[];
extendedKeyUsageAsText: string[];
fingerprint: string;
issuer: {
commonName: string;
country: string[];
locality: never; // TODO: needs clarification
province: string[];
organization: string[];
organizationalUnit: never; // TODO: needs clarification
};
keyUsageAsText: string[];
notAfter: Date;
notBefore: Date;
publicKeyAlgorithm: string;
san: string[];
serialNumber: string;
signatureAlgorithm: string;
status: boolean;
subject: {
commonName: string;
country: string[];
locality: never; // TODO: needs clarification
province: string[];
organization: string[];
organizationalUnit: never; // TODO: needs clarification
};
validationType: "DV" | "OV" | "EV";
}
// Define an enum for security levels
enum SecurityLevel {
MostSecure,
Secure,
LessSecure,
NotSecure,
}
interface CipherSuite {
cipher: string;
securityLevel: SecurityLevel;
}
const CipherSuites: CipherSuite[] = [
// Most Secure (TLS 1.3 AEAD Ciphers)
{ cipher: "TLS_AES_256_GCM_SHA384", securityLevel: SecurityLevel.MostSecure },
{ cipher: "TLS_CHACHA20_POLY1305_SHA256", securityLevel: SecurityLevel.MostSecure },
{ cipher: "TLS_AES_128_GCM_SHA256", securityLevel: SecurityLevel.MostSecure },
// Secure (TLS 1.2 AEAD Ciphers with Forward Secrecy)
{ cipher: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", securityLevel: SecurityLevel.Secure },
{ cipher: "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", securityLevel: SecurityLevel.Secure },
// Less Secure (CBC with TLS 1.2 and SHA-256/SHA-384)
{ cipher: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", securityLevel: SecurityLevel.LessSecure },
{ cipher: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", securityLevel: SecurityLevel.LessSecure },
{ cipher: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", securityLevel: SecurityLevel.LessSecure },
{ cipher: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", securityLevel: SecurityLevel.LessSecure },
{ cipher: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", securityLevel: SecurityLevel.LessSecure },
{ cipher: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", securityLevel: SecurityLevel.LessSecure },
{ cipher: "TLS_RSA_WITH_AES_256_CBC_SHA256", securityLevel: SecurityLevel.LessSecure },
{ cipher: "TLS_RSA_WITH_AES_128_CBC_SHA256", securityLevel: SecurityLevel.LessSecure },
// Not Secure (CBC with TLS 1.0/1.1 or SHA-1)
{ cipher: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_RSA_WITH_AES_256_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_RSA_WITH_AES_128_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_RSA_WITH_3DES_EDE_CBC_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_RSA_WITH_RC4_128_SHA", securityLevel: SecurityLevel.NotSecure },
{ cipher: "TLS_RSA_WITH_RC4_128_MD5", securityLevel: SecurityLevel.NotSecure },
];
export default class TLS extends DiscordInteractionCommand {
constructor() {
super("tls", "Receives TLS information about an HTTP server with a FQDN.");
this.builder.addStringOption((option) => {
return option
.setName("fqdn")
.setDescription(
"The Fully Qualified Domain Name (FQDN) for the server you want to perform the TLS/SSL lookup for."
)
.setRequired(true)
.setMinLength(3);
});
}
public getCipherSecurityLevel(cipher: string): SecurityLevel | null {
const result = CipherSuites.find((entry) => entry.cipher === cipher);
return result ? result.securityLevel : null;
}
public async execute(interaction: ChatInputCommandInteraction) {
await interaction.deferReply({ ephemeral: false });
try {
const certAPIReq = await axios.get(`https://certapi.libraryofcode.org/`, {
params: { q: interaction.options.getString("fqdn", true) },
});
if (certAPIReq.status !== 200) {
return interaction.editReply({
content:
"Could not fetch information for this FQDN's HTTP server. Please check the FQDN, its server, and try again.",
});
}
const certData: CertificateDetails = certAPIReq.data;
if (!certData.status) {
return interaction.editReply({
content: "Issue when fetching this FQDN's TLS certificate. Please try again later.",
});
}
const embed = new EmbedBuilder();
embed.setAuthor({
name: interaction.options.getString("fqdn", true),
iconURL: `https://${interaction.options.getString("fqdn", true)}/favicon.ico`,
});
let desc = "";
if (certData.validationType === "EV") {
desc += `**Certificate issued to:** __${certData.subject.organization[0]} [${certData.subject.country[0]}]__\n**Verified by:** __${certData.issuer.organization[0]}__\n\n`;
} else if (certData.issuer.organization) {
desc += `**Verified by:** __${certData.issuer.organization[0]}__\n\n`;
}
if (certData.subject) {
desc += "## Subject\n";
if (certData.subject.organization && certData.issuer.commonName) {
desc += `__**${certData.subject.organization[0]} (${certData.subject.commonName})**__\n`;
}
if (certData.subject.commonName) {
desc += `**Common Name:** ${certData.subject.commonName}\n`;
}
if (certData.subject.organization) {
desc += `**Organization:** ${certData.subject.organization[0]}\n`;
}
if (certData.subject.organizationalUnit) {
desc += `**Organizational Unit:** ${certData.subject.organizationalUnit[0]}\n`;
}
if (certData.subject.locality) {
desc += `**Locality:** ${certData.subject.locality[0]}\n`;
}
if (certData.subject.province) {
desc += `**State/Province:** ${certData.subject.province[0]}\n`;
}
if (certData.subject.country) {
desc += `**Country:** ${certData.subject.country[0]}\n`;
}
}
if (certData.issuer) {
desc += "## Issuer\n";
if (certData.issuer.organization && certData.issuer.commonName) {
desc += `__**${certData.issuer.organization[0]} (${certData.issuer.commonName})**__\n`;
}
if (certData.issuer.commonName) {
desc += `**Common Name:** ${certData.issuer.commonName}\n`;
}
if (certData.issuer.organization) {
desc += `**Organization:** ${certData.issuer.organization[0]}\n`;
}
if (certData.issuer.organizationalUnit) {
desc += `**Organizational Unit:** ${certData.issuer.organizationalUnit[0]}\n`;
}
if (certData.subject.locality) {
desc += `**Locality:** ${certData.subject.locality[0]}\n`;
}
if (certData.issuer.province) {
desc += `**State/Province:** ${certData.issuer.province[0]}\n`;
}
if (certData.issuer.country) {
desc += `**Country:** ${certData.issuer.country[0]}\n`;
}
}
embed.setDescription(desc);
let validationType:
| "Domain Validation (DV)"
| "Organization Validation (OV)"
| ":lock: Extended Validation (EV)"
| string;
switch (certData.validationType) {
case "DV":
validationType = "Domain Validation (DV)";
break;
case "OV":
validationType = "Organization Validation (OV)";
embed.setColor("#4287f5");
break;
case "EV":
embed.setColor("#42f554");
validationType = ":lock: Extended Validation (EV)";
break;
default:
validationType = "N/A';";
break;
}
let cipherSuiteText: string = "";
switch (this.getCipherSecurityLevel(certData.connection.cipherSuite)) {
case SecurityLevel.MostSecure:
cipherSuiteText = `:green_circle: ${certData.connection.cipherSuite} (${certData.connection.tlsVersion})`;
break;
case SecurityLevel.Secure:
cipherSuiteText = `:yellow_circle: ${certData.connection.cipherSuite} (${certData.connection.tlsVersion})`;
break;
case SecurityLevel.LessSecure:
cipherSuiteText = `:orange_circle: ${certData.connection.cipherSuite} (${certData.connection.tlsVersion})`;
break;
case SecurityLevel.NotSecure:
cipherSuiteText = `:red_circle: ${certData.connection.cipherSuite} (${certData.connection.tlsVersion})`;
break;
default:
cipherSuiteText = `:grey_question: ${certData.connection.cipherSuite} (${certData.connection.tlsVersion})`;
break;
}
embed.addFields(
{
name: "Validation Type",
value: validationType,
inline: true,
},
{
name: "Cipher Suite",
value: cipherSuiteText,
inline: false,
},
{
name: "Public Key Algorithm",
value: `${certData.publicKeyAlgorithm} ${certData.bitLength}`,
inline: true,
},
{
name: "Signature Algorithm",
value: certData.signatureAlgorithm,
inline: true,
},
{ name: "Not Before", value: new Date(certData.notBefore).toUTCString(), inline: true },
{ name: "Not After", value: new Date(certData.notAfter).toUTCString(), inline: true },
{
name: "Serial Number",
value: certData.serialNumber,
inline: true,
}
);
console.log(certData); // TODO: Remove after testing.
if (certData.keyUsageAsText?.length)
embed.addFields({
name: "Key Usages",
value: certData.keyUsageAsText.join(", "),
inline: true,
});
if (certData.extendedKeyUsageAsText?.length)
embed.addFields({
name: "Extended Key Usages",
value: certData.extendedKeyUsageAsText.join(", "),
inline: true,
});
// embed.addField('Common Name', x509.data.subject.commonName, true);
// embed.addField('Issuer', x509.data.issuer.commonName, true);
// embed.addBlankField();
// embed.addField('Public Key Algorithm', x509.data.publicKeyAlgorithm, true);
// embed.addField('Not Before', new Date(x509.data.notBefore).toUTCString(), true);
// embed.addField('Not After', new Date(x509.data.notAfter).toUTCString(), true);
// if (x509.data.keyUsageAsText.length) embed.addField('Key Usages', x509.data.keyUsageAsText.join(', '), true);
// if (x509.data.extendedKeyUsageAsText.length) embed.addField('Extended Key Usages', x509.data.extendedKeyUsageAsText.join(', '), true);
return await interaction.editReply({ embeds: [embed] });
} catch (err) {
return interaction.editReply({ content: `Error processing retrieval from FQDN: ${err}` });
}
}
}

View File

@ -1,15 +1,14 @@
import DiscordInteractionCommand from "../../util/DiscordInteractionCommand";
import { MemberAdditionalAcknowledgement, MemberModel } from "../../database/Member";
import { MemberModel } from "../../database/Member";
import Partner, {
PartnerCommissionType,
PartnerDepartment,
PartnerModel,
PartnerRoleType,
} from "../../database/Partner";
import { ChatInputCommandInteraction, EmbedBuilder, GuildMember, Snowflake } from "discord.js";
import { ChatInputCommandInteraction, EmbedBuilder, GuildMember } from "discord.js";
import MemberUtil from "../../util/MemberUtil";
import EmojiConfig from "../../util/EmojiConfig";
import Formatters from "../../util/Formatters";
export default class Whois extends DiscordInteractionCommand {
constructor() {
@ -37,7 +36,7 @@ export default class Whois extends DiscordInteractionCommand {
const embed = new EmbedBuilder();
// 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
const formattedName = Formatters.formatName(guildMember, partner);
const formattedName = MemberUtil.formatName(guildMember, partner);
embed.setAuthor({ name: formattedName.text, iconURL: formattedName.iconURL });
// set the thumbnail to the user's avatar
embed.setThumbnail(guildMember.user.displayAvatarURL());
@ -80,23 +79,16 @@ export default class Whois extends DiscordInteractionCommand {
break;
}
if (partner.directReport) {
// fetch direct report object ref
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`;
if (partner.directReport instanceof Partner) {
embedDescription += `**Direct Report**: ${partner.directReport.title}\n`;
}
}
}
embed.setColor(guildMember.displayColor);
if (embedDescription?.length > 0)
embed.setDescription(`${embedDescription}\n\n<@${guildMember.id}>`);
if (embedDescription?.length > 0) embed.setDescription(embedDescription);
// add status to embed
if (guildMember.presence?.status) {
// TODO: this currently doesn't work for some reason
switch (guildMember.presence.status) {
case "online":
embed.addFields({ name: "Status", value: "Online", inline: true });
@ -107,7 +99,7 @@ export default class Whois extends DiscordInteractionCommand {
case "dnd":
embed.addFields({ name: "Status", value: "Do Not Disturb", inline: true });
break;
case "offline":
case "offline" || "invisible":
embed.addFields({ name: "Status", value: "Online", inline: true });
break;
default:
@ -115,87 +107,9 @@ export default class Whois extends DiscordInteractionCommand {
embed.addFields({ name: "Status", value: "", inline: true });
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({
text: `Discord ID: ${guildMember.id}${databaseMember ? `| Internal ID: ${databaseMember?._id}` : ""}${partner ? ` | Partner ID: ${partner?.id}` : ""}`,
text: `Discord ID: ${guildMember.id}${databaseMember ? `Internal ID: ${databaseMember?._id}` : ""}`,
});
return await interaction.editReply({ embeds: [embed] });

View File

@ -1,13 +1,14 @@
import { Client, GatewayIntentBits, Partials, REST, Routes } from "discord.js";
import { discordBotToken, discordClientID, mongoDBConnectionURI } from "./config.json";
import Collection from "./util/Collection";
import DiscordInteractionCommand from "./util/DiscordInteractionCommand";
import DiscordEvent from "./util/DiscordEvent";
import * as DiscordInteractionCommandsIndex from "./discord/commands";
import * as DiscordEventsIndex from "./discord/events";
import mongoose from "mongoose";
import { Client, GatewayIntentBits, Partials, REST, Routes } from 'discord.js';
import { discordBotToken, discordClientID, MongoDbUrl } from './config.json';
import Collection from './util/Collection';
import DiscordInteractionCommand from './util/DiscordInteractionCommand';
import DiscordEvent from './util/DiscordEvent';
import * as DiscordInteractionCommandsIndex from './discord/commands';
import * as DiscordEventsIndex from './discord/events';
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();
// Instantiates a new Discord client
@ -21,7 +22,12 @@ const discordClient = new Client({
GatewayIntentBits.GuildInvites,
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 stripeClient = new Stripe(stripeToken, { typescript: true });
@ -29,11 +35,12 @@ const discordREST = new REST().setToken(discordBotToken);
export async function main() {
// Connect to the databases
try {
mongoose.connection.once("open", () => {
console.info("[Info - Database] Connected to MongoDB");
//@ts-ignore
mongoose.connection.once('open', () => {
console.info('[Info - Database] Connected to MongoDB');
});
// TODO: Fetch the MongoDB URI from the config file
await mongoose.connect(mongoDBConnectionURI, {});
await mongoose.connect(MongoDbUrl, {});
} catch (error) {
console.error(`[Error - Database] Failed to connect to MongoDB: ${error}`);
process.exit(1);
@ -42,7 +49,9 @@ export async function main() {
for (const Command of Object.values(DiscordInteractionCommandsIndex)) {
const instance = new Command();
DiscordInteractionCommands.add(instance.name, instance);
console.info(`[Info - Discord] Loaded interaction command: ${instance.name}`);
console.info(
`[Info - Discord] Loaded interaction command: ${instance.name}`
);
}
// Load Discord events
for (const Event of Object.values(DiscordEventsIndex)) {
@ -54,19 +63,22 @@ export async function main() {
await discordClient.login(discordBotToken);
try {
console.log(`Started refreshing ${DiscordInteractionCommands.size} application (/) commands.`);
console.log(
`Started refreshing ${DiscordInteractionCommands.size} application (/) commands.`
);
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.`);
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);

133
package-lock.json generated
View File

@ -1,15 +1,12 @@
{
"name": "crv2",
"name": "crra",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "crv2",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@typegoose/typegoose": "^12.2.0",
"auth0": "^4.12.0",
"axios": "^1.7.8",
"discord.js": "^14.14.1",
"mongoose": "^8.2.2",
"stripe": "^14.21.0",
@ -329,9 +326,9 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
"integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz",
"integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==",
"dev": true,
"dependencies": {
"levn": "^0.4.1"
@ -937,39 +934,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/auth0": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/auth0/-/auth0-4.12.0.tgz",
"integrity": "sha512-5WDAHb8EvWSmRyA9D+FTBrHdEL1RM48PTPHVPxSmzbiAXrhR4pSgwSJyoGjia2+rvMR2NMXhtMfuRRqosEp7PA==",
"dependencies": {
"jose": "^4.13.2",
"undici-types": "^6.15.0",
"uuid": "^9.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/auth0/node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
},
"node_modules/axios": {
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -1337,17 +1301,6 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@ -1370,9 +1323,9 @@
"devOptional": true
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@ -1425,14 +1378,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@ -1927,25 +1872,6 @@
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
@ -1961,19 +1887,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -2322,14 +2235,6 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jose": {
"version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@ -2714,25 +2619,6 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
@ -3229,11 +3115,6 @@
"node": ">=6.0.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

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

View File

@ -11,11 +11,11 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* 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. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
"experimentalDecorators": true /* Enable experimental support for legacy experimental decorators. */,
"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'. */
// "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*'. */
@ -25,7 +25,7 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* 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. */
// "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. */
@ -39,7 +39,7 @@
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package 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. */
"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. */
// "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. */
// "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. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "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. */
@ -77,12 +77,12 @@
// "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. */
// "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. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* 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. */
// "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. */
@ -104,6 +104,6 @@
/* Completeness */
// "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.execute = this.execute.bind(this);
}
public abstract execute(...args: never[]): Error | Promise<void>;
public abstract execute(...args: any[]): Error | Promise<void>;
}

View File

@ -1,64 +0,0 @@
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 partner is designated as a KeyHolder, add a [k] to the end of the name
// if the partner exists, set the iconURL to the organizational logo
if (partner?.isKeyHolder) {
return {
text: `${target.displayName} [k]`,
iconURL: target.displayAvatarURL(),
};
} else if (partner?.roleType === PartnerRoleType.MANAGERIAL) {
// if a partner is of RoleType MANAGERIAL, add [m] to their name
return {
text: `${target.displayName} [m]`,
iconURL:
target instanceof GuildMember
? target.user.displayAvatarURL()
: 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,14 +8,13 @@ import Partner, {
import Member, { MemberAdditionalAcknowledgement, MemberModel } from "../database/Member";
import { Client, GuildMember, User } from "discord.js";
import { guildID } from "../config.json";
import { Ref } from "@typegoose/typegoose";
export interface PartnerOptions {
roleType: PartnerRoleType;
commissionType: PartnerCommissionType;
department: PartnerDepartment;
title: PartnerTitle;
directReport: Ref<Partner>;
directReport: Partner | string;
}
export interface FormatNameOptions {
@ -26,7 +25,7 @@ export interface FormatNameOptions {
// TODO: Add the rest of the remaining role configurations
export const PartnerDiscordRoleMap = {
// Director of Engineering, Management, Staff, Technician, Core Team, Play Caller
Engineering: [
"Director of Engineering": [
"1077646568091570236",
"1077646956890951690",
"446104438969466890",
@ -35,7 +34,7 @@ export const PartnerDiscordRoleMap = {
"1014978134573064293",
],
// Director of Operations, Management, Staff, Moderator, Core Team, Play Caller
Operations: [
"Director of Operations": [
"1077647072163020840",
"1077646956890951690",
"446104438969466890",
@ -80,4 +79,40 @@ export default class MemberUtil {
{ $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(),
};
}
}
}