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

Keep track of when helpers are last active and prune ones who have not had activity in the HR Center for 2 months #851

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading