Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

automate year and alumni roles #331

Merged
merged 5 commits into from
Aug 21, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
victorzheng02 marked this conversation as resolved.
Show resolved Hide resolved
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') {
// onlyDescription here would be the new year role
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace onlyDescription with description?

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this else should be like... === 0 right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess we never have more than 1 tho

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 {
Picowchew marked this conversation as resolved.
Show resolved Hide resolved
// 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);
}
}
};