diff --git a/config/staging/vars.json b/config/staging/vars.json index d94ee691..bafc2b5c 100644 --- a/config/staging/vars.json +++ b/config/staging/vars.json @@ -2,7 +2,9 @@ "TARGET_GUILD_ID": "701335585272627200", "COFFEE_ROLE_ID": "861744590775779358", "OFFICE_PING_ROLE_ID": "1031378676077572188", - "CODEY_COIN_ROLE_ID": "1071958737100742756", + "CODEY_COIN_T5_ROLE_ID": "1244832373946847253", + "CODEY_COIN_T10_ROLE_ID": "1244832151904456775", + "CODEY_COIN_T20_ROLE_ID": "1244832331618062336", "NOTIF_CHANNEL_ID": "866861080301797386", "ANNOUNCEMENTS_CHANNEL_ID": "1014739389206777867", "OFFICE_STATUS_CHANNEL_ID": "866861080301797386", diff --git a/config/vars.template.json b/config/vars.template.json index 7f1356b5..d21050bc 100644 --- a/config/vars.template.json +++ b/config/vars.template.json @@ -2,7 +2,9 @@ "TARGET_GUILD_ID": "GUILD_ID", "COFFEE_ROLE_ID": "ROLE_ID", "OFFICE_PING_ROLE_ID": "ROLE_ID", - "CODEY_COIN_ROLE_ID": "ROLE_ID", + "CODEY_COIN_T5_ROLE_ID": "ROLE_ID", + "CODEY_COIN_T10_ROLE_ID": "ROLE_ID", + "CODEY_COIN_T20_ROLE_ID": "ROLE_ID", "NOTIF_CHANNEL_ID": "CHANNEL_ID", "ANNOUNCEMENTS_CHANNEL_ID": "CHANNEL_ID", "OFFICE_STATUS_CHANNEL_ID": "CHANNEL_ID", diff --git a/src/components/cron.ts b/src/components/cron.ts index 4b8e5ca5..7c64c4bc 100644 --- a/src/components/cron.ts +++ b/src/components/cron.ts @@ -8,7 +8,13 @@ import { alertUsers } from './officeOpenDM'; import { vars } from '../config'; import { DEFAULT_EMBED_COLOUR } from '../utils/embeds'; import { getMatch, writeHistoricMatches } from '../components/coffeeChat'; -import { adjustCoinBalanceByUserId, BonusType, coinBonusMap, getCoinLeaderboard } from './coin'; +import { + adjustCoinBalanceByUserId, + BonusType, + coinBonusMap, + getCoinLeaderboard, + UserCoinEntry, +} from './coin'; import { getInterviewers } from './interviewer'; import { getSuggestionPrintout, @@ -24,8 +30,13 @@ const NOTIF_CHANNEL_ID: string = vars.NOTIF_CHANNEL_ID; const OFFICE_STATUS_CHANNEL_ID: string = vars.OFFICE_STATUS_CHANNEL_ID; const OFFICE_HOURS_STATUS_API = 'https://csclub.uwaterloo.ca/~webcom/office-status.json'; const TARGET_GUILD_ID: string = vars.TARGET_GUILD_ID; -const CODEY_COIN_ROLE_ID: string = vars.CODEY_COIN_ROLE_ID; -const NUMBER_USERS_TO_ASSIGN_ROLE = 10; +const NUMBER_USERS_TO_ASSIGN_ROLE = 20; +const CODEY_COIN_ROLES: string[] = [ + vars.CODEY_COIN_T5_ROLE_ID, + vars.CODEY_COIN_T10_ROLE_ID, + vars.CODEY_COIN_T20_ROLE_ID, +]; +const CODEY_COIN_INTERVALS: number[] = [5, 10, 20]; // The last known status of the office // false if closed @@ -141,27 +152,69 @@ export const createCoffeeChatCron = (client: Client): CronJob => // Gives Codey coin role to those on the leaderboard list everyday export const assignCodeyRoleForLeaderboard = (client: Client): CronJob => new CronJob('0 0 0 */1 * *', async function () { - const leaderboard = await getCoinLeaderboard(NUMBER_USERS_TO_ASSIGN_ROLE); - const leaderboardIds: Set = new Set(leaderboard.map((entry) => entry.user_id)); const guild = client.guilds.resolve(TARGET_GUILD_ID); if (!guild) { throw new CodeyUserError(undefined, 'guild not found'); } + const members = await guild.members.fetch(); - // Removing role from previous members - const guildMembersPreviousRole = await loadRoleMembers(CODEY_COIN_ROLE_ID); - const previousIds: Set = new Set(guildMembersPreviousRole.map((member) => member.id)); - const roleName: string = await getRoleName(CODEY_COIN_ROLE_ID); - - guildMembersPreviousRole.forEach(async (member) => { - if (member && !leaderboardIds.has(member.id)) { - await updateMemberRole(member, roleName, false); + const leaderboard: UserCoinEntry[] = []; + let fetchAttempts = 0; + + // Fetch leaderboard until we have enough human members to assign roles to + while (leaderboard.length < NUMBER_USERS_TO_ASSIGN_ROLE) { + const leaderboardBuffer = await getCoinLeaderboard( + NUMBER_USERS_TO_ASSIGN_ROLE, + fetchAttempts * NUMBER_USERS_TO_ASSIGN_ROLE, + ); + + if (leaderboardBuffer.length <= 0) { + break; } - }); - leaderboardIds.forEach(async (user_id) => { - const memberToUpdate = members.get(user_id); - if (memberToUpdate && !previousIds.has(user_id)) { - await updateMemberRole(memberToUpdate, roleName, true); + + for (const entry of leaderboardBuffer) { + if (members.get(entry.user_id)?.user?.bot) { + continue; + } + leaderboard.push(entry); + if (leaderboard.length >= NUMBER_USERS_TO_ASSIGN_ROLE) { + break; + } } + + fetchAttempts++; + } + + // Create slices of the leaderboard to assign roles to + const topSlices: string[][] = []; + for (let i = 0; i < CODEY_COIN_INTERVALS.length; i++) { + topSlices.push( + leaderboard + // Slice the leaderboard into intervals -> {[0,i), [i-1, i), [i-1, end|i)} + .slice(i === 0 ? 0 : CODEY_COIN_INTERVALS[i - 1], CODEY_COIN_INTERVALS[i]) + .map((entry) => entry.user_id), + ); + } + + CODEY_COIN_ROLES.forEach(async (roleId, idx) => { + const roleName: string = await getRoleName(roleId); + const guildMembersPreviousRole = await loadRoleMembers(roleId); + const previousIds: Set = new Set(guildMembersPreviousRole.map((member) => member.id)); + const leaderboardIds = new Set(topSlices[idx]); + + // Removing role from former members + guildMembersPreviousRole.forEach(async (member) => { + if (member && !leaderboardIds.has(member.id)) { + await updateMemberRole(member, roleName, false); + } + }); + + // Adding role to new members + leaderboardIds.forEach(async (user_id) => { + const memberToUpdate = members.get(user_id); + if (memberToUpdate && !previousIds.has(user_id)) { + await updateMemberRole(memberToUpdate, roleName, true); + } + }); }); });