Skip to content

Commit

Permalink
Keep track of when helpers are last active and prune ones who have no…
Browse files Browse the repository at this point in the history
…t had activity in the HR Center for 2 months (#851)

* new users column & migration, helper activity tracking, timer for autopruning inactive helpers

* fix: channel reference, kinder notification msg, compliant loops

* Add default value for new column last_helper_activity
  • Loading branch information
theimperious1 authored Oct 31, 2024
1 parent 29d4056 commit fc5531f
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/discord/events/messageCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { imagesOnly } from '../utils/imagesOnly';
import { countMessage } from '../commands/guild/d.counting';
import { bridgeMessage } from '../utils/bridge';
import { discordAiModerate } from '../commands/global/d.ai';
import { helperActivityUpdate } from '../utils/helperActivityUpdate';
// import { awayMessage } from '../utils/awayMessage';
// import log from '../../global/utils/log';
// import {parse} from 'path';
Expand Down Expand Up @@ -89,6 +90,7 @@ export const messageCreate: MessageCreateEvent = {
karma(message);
imagesOnly(message);
discordAiModerate(message);
helperActivityUpdate(message);

// Disabled for testing
// thoughtPolice(message);
Expand Down
50 changes: 50 additions & 0 deletions src/discord/utils/helperActivityUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
Message, TextChannel,
} from 'discord.js';

export default helperActivityUpdate;

// const F = f(__filename);

/**
* helperActivityUpdate
* @param {Message} message The message that was sent
* @return {Promise<void>}
* */
export async function helperActivityUpdate(message: Message): Promise<void> {
if (!message.guild) return; // If not in a guild then ignore all messages
if (message.guild.id !== env.DISCORD_GUILD_ID) return; // If not in tripsit ignore all messages

// Determine if the message was sent in a TextChannel
if (!(message.channel instanceof TextChannel)) return;

if (message.channel.parentId !== env.CATEGORY_HARMREDUCTIONCENTRE) return;

const guildData = await db.discord_guilds.upsert({
where: {
id: message.guild?.id,
},
create: {
id: message.guild?.id,
},
update: {},
});

if (!guildData.role_helper) return;

const role = await message.guild?.roles.fetch(guildData.role_helper);

if (!message.member || !role || !message.member.roles.cache.has(role.id)) return;

await db.users.upsert({
where: {
discord_id: message.author.id,
},
create: {
discord_id: message.author.id,
},
update: {
last_helper_activity: new Date(),
},
});
}
93 changes: 93 additions & 0 deletions src/global/utils/timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,98 @@ async function checkMoodle() { // eslint-disable-line
// }
}

async function pruneInactiveHelpers() {
const inactiveThreshold = new Date();
// 2 months for production, 1 minute for dev
if (env.NODE_ENV === 'production') {
inactiveThreshold.setMonth(inactiveThreshold.getMonth() - 2);
} else {
inactiveThreshold.setMinutes(inactiveThreshold.getMinutes() - 1);
}

const inactiveHelpers = await db.users.findMany({
where: {
last_helper_activity: {
lt: inactiveThreshold,
},
},
});

const guild = discordClient.guilds.cache.get(env.DISCORD_GUILD_ID);
if (!guild) {
log.error(F, `Guild with ID ${env.DISCORD_GUILD_ID} not found.`);
return;
}

const guildData = await db.discord_guilds.upsert({
where: {
id: guild.id,
},
create: {
id: guild.id,
},
update: {},
});

if (!guildData.role_helper) {
log.error(F, `Unable to fetch helper role from db in pruneInactiveHelpers for guild ID ${env.DISCORD_GUILD_ID}`);
return;
}

const role = guild.roles.cache.get(guildData.role_helper);
if (!role) {
log.error(F, `Unable to fetch helper role from Discord in pruneInactiveHelpers for guild ID ${env.DISCORD_GUILD_ID}`);
return;
}

// Loop through the inactive helpers and check their guild membership
const promises = inactiveHelpers.map(async user => {
if (!user.discord_id) {
log.info(F, `Failed to fetch discord ID for db entry ${user.id}`);
return; // No need to continue for this user
}

const member = await guild.members.fetch(user.discord_id).catch(() => null);
if (member) {
await member.roles.remove(role);

try {
const channelHowToVolunteer = await guild.channels.fetch(env.CHANNEL_HOW_TO_VOLUNTEER);
await member.send(stripIndents`
Your helper role has been automatically removed on TripSit due to inactivity,
but no worries—you can easily reapply for it at any time through ${channelHowToVolunteer}
if you’d like to start helping out again.
Thank you for all your past contributions, and we’d love to have you back whenever you're ready!
`);
} catch (error) {
log.error(F, `Failed to send DM to ${member.user.username}: ${error}`);
}
} else { // If we run into a Helper who is no longer a member, then notify the Tripsitters.
const target = await guild.client.users.fetch(user.discord_id);
log.info(F, `Helper ${target.username}(${user.discord_id}) is no longer a member of the guild :(`);
const channelTripsitters = await guild.channels.fetch(env.CHANNEL_TRIPSITTERS) as TextChannel;
await channelTripsitters.send(stripIndents`Helper ${target.username}(${user.discord_id}) is no longer a member of the guild :(`);
}

// Reset activity to prevent looping the same user again
await db.users.upsert({
where: {
discord_id: user.discord_id,
},
create: {
discord_id: user.discord_id,
},
update: {
last_helper_activity: null,
},
});
});

await Promise.all(promises); // Wait for all promises to resolve
log.info(F, `${inactiveHelpers.length} inactive helpers have been pruned and notified.`);
}

async function checkEvery(
callback: () => Promise<void>,
interval: number,
Expand Down Expand Up @@ -1158,6 +1250,7 @@ async function runTimer() {
{ callback: checkMoodle, interval: env.NODE_ENV === 'production' ? seconds60 : seconds5 },
// { callback: checkLpm, interval: env.NODE_ENV === 'production' ? seconds10 : seconds5 },
{ callback: updateDb, interval: env.NODE_ENV === 'production' ? hours24 : hours48 },
{ callback: pruneInactiveHelpers, interval: env.NODE_ENV === 'production' ? hours48 : seconds60 },
];

timers.forEach(timer => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "users"
ADD COLUMN "last_helper_activity" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP;
1 change: 1 addition & 0 deletions src/prisma/tripbot/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ model users {
wordle_scores wordle_scores[]
connections_scores connections_scores[]
mini_scores mini_scores[]
last_helper_activity DateTime?
}

model wordle_scores {
Expand Down

0 comments on commit fc5531f

Please sign in to comment.