Skip to content

Commit

Permalink
automate year and alumni roles (#331)
Browse files Browse the repository at this point in the history
* automate year and alumni roles

* lint

* addressed changes, added decade roles, refactor

* addressed comments, refactor

* small change
  • Loading branch information
victorzheng02 authored Aug 21, 2022
1 parent c779c79 commit 1c83a65
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 26 deletions.
7 changes: 3 additions & 4 deletions src/commands/profile/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Message, MessageEmbed } from 'discord.js';
import { getCoinBalanceByUserId } from '../../components/coin';
import {
configMaps,
editUserProfileById,
editUserProfile,
getUserProfileById,
UserProfile,
validCustomizations,
Expand Down Expand Up @@ -69,7 +69,6 @@ export class ProfileCommand extends SubCommandPluginCommand {
}

public async set(message: Message, args: Args): Promise<Message> {
const { author } = message;
const customization = <keyof typeof configMaps>await args.pick('string').catch(() => false);
// if no customization is supplied, or its not one of the customizations we provide, return
if (typeof customization === 'boolean' || !validCustomizations.includes(customization)) {
Expand All @@ -82,8 +81,8 @@ export class ProfileCommand extends SubCommandPluginCommand {
return message.reply('Please enter a description.');
}
const { reason, parsedDescription } = validUserCustomization(customization, description);
if (reason === 'valid') {
editUserProfileById(author.id, {
if (reason === 'valid' && message.member) {
editUserProfile(message.member, {
[configMaps[customization]]: parsedDescription,
} as UserProfile);
return message.reply(`${customization} has been set!`);
Expand Down
144 changes: 122 additions & 22 deletions src/components/profile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { openDB } from './db';
import { ColorResolvable, GuildMember, RoleManager } from 'discord.js';

export interface UserProfile {
about_me?: string;
Expand Down Expand Up @@ -29,7 +30,14 @@ const validTerms: string[] = [
'Professor',
];
const yearStart = 1900;
const yearEnd = new Date().getFullYear() + 7;
const currentYear = new Date().getFullYear();
// the last valid year of graduation in the future. while most people graduate in <5 years, +2 years just to be safe
const yearEnd = currentYear + 7;
// range of years that can be assigned as a role.
enum validRoleYears {
validRoleYearStart = currentYear - 2,
validRoleYearEnd = yearEnd,
}

export enum configMaps {
aboutme = 'about_me',
Expand Down Expand Up @@ -157,41 +165,133 @@ export const getUserProfileById = async (userId: string): Promise<UserProfile |

// NOTE: data will always be just one customization, but we do not know which one, meaning many of
// the loops are just to extract an unknown key-val pair
export const editUserProfileById = async (userId: string, data: UserProfile): Promise<void> => {
export const editUserProfile = async (member: GuildMember, data: UserProfile): Promise<void> => {
const db = await openDB();
// check if a user exists in the user_profile_table already
const res = await db.get(
`SELECT COUNT(*) as found FROM user_profile_table where user_id = ?`,
userId,
member.id,
);
const user = res;
let query;

// grab the only customization and its corresponding description
const [customization, description] = Object.entries(data)[0];

if (customization === 'year') {
// description here would be the new year role
await updateMemberGradRoles(member, description);
}

if (user.found === 1) {
let onlyDescription;
let onlyCustomization;
for (const [customization, description] of Object.entries(data)) {
// grab the only customization and its corresponding description
onlyCustomization = customization;
onlyDescription = description;
}
// if customization is year, change year roles as well
// escape any instances of ' character by placing another ' in front of it by sqlite specifications
onlyDescription.replace(/'/g, "''");
query = `UPDATE user_profile_table SET last_updated=CURRENT_DATE, ${onlyCustomization}=? WHERE user_id=?`;
await db.run(query, onlyDescription, userId);
description.replace(/'/g, "''");
query = `UPDATE user_profile_table SET last_updated=CURRENT_DATE, ${customization}=? WHERE user_id=?`;
await db.run(query, description, member.id);
} else {
query = `
INSERT INTO user_profile_table
(user_id, last_updated,
(user_id, last_updated, ${customization})
VALUES (?, CURRENT_DATE, ?)
`;
// add on the one data value that will be set
for (const customization of Object.keys(data)) {
query = query.concat(`${customization})`);
}
query = query.concat(`VALUES (?, CURRENT_DATE, ?)`);
// there is only one element in object.values (which is the description)
const description = Object.values(data)[0];
// escape any instances of ' character by placing another ' in front of it by sqlite specifications
description.replace(/'/g, "''");
await db.run(query, userId, description);
await db.run(query, member.id, description);
}
};

const addOrRemove = {
add: true,
remove: false,
};

const updateMemberRole = async (
member: GuildMember,
roleName: string,
add: boolean,
): Promise<void> => {
const userId = member.id;
const role = member.guild?.roles.cache.find((role) => role?.name === roleName);
if (!role) {
throw new Error(`Could not find the role ${roleName}`);
}
try {
if (add) {
await member?.roles.add(role);
} else {
await member?.roles.remove(role);
}
} catch (err) {
throw new Error(`Failed to ${add ? 'add to' : 'remove from'} user ${userId}`);
}
};

const yearToDecade = (year: number) => {
return year - (year % 10);
};

const createNewRole = async (roleName: string, roles: RoleManager): Promise<void> => {
try {
// create role object
const newRole = {
name: roleName,
color: 'GREY' as ColorResolvable,
reason: `AUTOMATED: Creating new role for ${roleName}`,
};
await roles.create(newRole);
} catch (err) {
throw new Error(`Failed to create new role for ${roleName}: ${err}`);
}
};

const updateMemberGradRoles = async (member: GuildMember, gradYear: number): Promise<void> => {
// no roles created if gradYear is < 1900 (impossible)
if (gradYear < 1900) {
return;
}
if (gradYear < currentYear) {
// assign alumni role to the user
updateMemberRole(member, 'Alumni', addOrRemove.add);
} else {
// remove alumni role to the user if they for some reason have the alumni role and are not graduated
updateMemberRole(member, 'Alumni', addOrRemove.remove);
}
let newYearRoleName: string;
if (gradYear >= validRoleYears.validRoleYearStart) {
newYearRoleName = gradYear.toString();
// check if role for that year exists, if not add it
const findRole = member.guild?.roles.cache.find((role) => role?.name === newYearRoleName);

if (!findRole && member.guild?.roles) {
await createNewRole(newYearRoleName, member.guild.roles);
}
// assign that role to the user
updateMemberRole(member, newYearRoleName, addOrRemove.add);
} else {
// otherwise, decade role. e.g: 1980s, 1990s, etc.
newYearRoleName = yearToDecade(gradYear) + 's';
const findRole = member.guild?.roles.cache.find((role) => role?.name === newYearRoleName);

if (!findRole && member.guild?.roles) {
await createNewRole(newYearRoleName, member.guild.roles);
}
// assign that role to the user
updateMemberRole(member, newYearRoleName, addOrRemove.add);
}

// final step: remove their existing year role
// grab oldYear from database - as this call happens before db update
const oldProfileDetails: UserProfile | undefined = await getUserProfileById(member.id);
const oldYear = oldProfileDetails?.year;
if (oldYear) {
let roleToRemove = oldYear.toString();
if (oldYear < validRoleYears.validRoleYearStart) {
// while profile year may be something like 2008, the role itself is called 2000s so have to convert
roleToRemove = yearToDecade(oldYear).toString() + 's';
}
if (newYearRoleName !== roleToRemove) {
updateMemberRole(member, roleToRemove, addOrRemove.remove);
}
}
};

0 comments on commit 1c83a65

Please sign in to comment.