Skip to content

Commit 5793d8d

Browse files
typeofwebkamiloox
andauthored
feat: Automatically create roles based on karma (#77)
* Create roles * Fix after code review * CR * Roles update * Add setInterval Co-authored-by: kamiloox <mrkamiloox@gmail.com>
1 parent 5ee2ca1 commit 5793d8d

File tree

6 files changed

+122
-8
lines changed

6 files changed

+122
-8
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ jspm_packages/
6464

6565
# dotenv environment variables file
6666
.env
67-
.env.dev
6867

6968
package-lock.json
7069
*.tsbuildinfo

src/app.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createHttpServer } from './http-server';
1010
import { InvalidUsageError } from './types';
1111
import { getWeekNumber } from './utils';
1212
import { getStatsCollection, initDb } from './db';
13+
import { updateKarmaRoles } from './cron/roles';
1314

1415
const MESSAGE_COLLECTOR_CACHE_S = 60 * 60;
1516
const messageCollectorCache = new Cache({ stdTTL: MESSAGE_COLLECTOR_CACHE_S });
@@ -195,6 +196,17 @@ init().catch((err) => errors.push(err));
195196

196197
const httpServer = createHttpServer(client, errors, warnings, debugs);
197198

199+
const updateRoles = () => {
200+
updateKarmaRoles()
201+
.then(() => console.log(`Successfully updated roles`))
202+
.catch((err) => errors.push(err));
203+
};
204+
198205
httpServer.listen(getConfig('PORT'), () => {
199206
console.log(`Server running!`);
207+
208+
updateRoles();
209+
setInterval(() => {
210+
updateRoles();
211+
}, 1000 * 60 * 60 * 24 * 1);
200212
});

src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/* eslint-disable @typescript-eslint/no-unsafe-call */
2+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
3+
require('dotenv').config({ path: '.env' });
4+
15
export function getConfig(name: 'ENV'): 'production' | 'staging' | 'development' | 'test';
26
export function getConfig(name: 'NODE_ENV'): 'production' | 'development';
37
export function getConfig(name: string): string;

src/cron/roles.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import Discord from 'discord.js';
2+
3+
import { getConfig } from '../config';
4+
import { initDb, getKarmaCollection } from '../db';
5+
import { offsetDateByWeeks } from '../utils';
6+
7+
interface MemberTotalKarma {
8+
readonly _id: string;
9+
readonly total: number;
10+
}
11+
12+
const TYPE_OF_WEB_GUILD_ID = '440163731704643589';
13+
const ROLE_NAME = 'AKTYWNI';
14+
15+
const createKarmaRole = (guild: Discord.Guild) => {
16+
return guild.roles.create({
17+
data: {
18+
name: ROLE_NAME,
19+
color: 'DARK_VIVID_PINK',
20+
mentionable: false,
21+
hoist: true,
22+
},
23+
reason: 'Most active',
24+
});
25+
};
26+
27+
const fetchKarmaRole = async (guild: Discord.Guild) => {
28+
const roles = await guild.roles.fetch(undefined, false);
29+
return roles.cache.find((role) => role.name === ROLE_NAME);
30+
};
31+
32+
const fetchOrCreateKarmaRole = async (guild: Discord.Guild) => {
33+
const role = await fetchKarmaRole(guild);
34+
if (!role) {
35+
return createKarmaRole(guild);
36+
}
37+
return role;
38+
};
39+
40+
const giveRole = async (guild: Discord.Guild, role: Discord.Role, memberId: string) => {
41+
const member = await guild.members.fetch(memberId);
42+
if (!member) {
43+
console.error(`Member with id ${memberId} doesn't exists!`);
44+
return;
45+
}
46+
47+
console.debug(`Adding role ${role.name} to member ${member.displayName}!`);
48+
return member.roles.add(role);
49+
};
50+
51+
const removeRole = (member: Discord.GuildMember, role: Discord.Role) => {
52+
console.debug(`Removing role ${role.name} from member ${member.displayName}!`);
53+
return member.roles.remove(role.id);
54+
};
55+
56+
const getBestMembers = async (fromDate: Date, toDate = new Date()) => {
57+
const db = await initDb();
58+
const karmaCollection = getKarmaCollection(db);
59+
60+
const agg = karmaCollection
61+
.aggregate<MemberTotalKarma>([
62+
{ $match: { createdAt: { $gte: fromDate, $lte: toDate } } },
63+
{ $group: { _id: '$to', total: { $sum: '$value' } } },
64+
{ $sort: { total: -1 } },
65+
{ $limit: 10 },
66+
])
67+
.toArray();
68+
69+
return agg;
70+
};
71+
72+
const assignMembersRoles = async (guild: Discord.Guild, role: Discord.Role) => {
73+
const startDate = offsetDateByWeeks(new Date(), 2);
74+
const bestKarmaMembers = await getBestMembers(startDate);
75+
76+
return Promise.all(bestKarmaMembers.map(({ _id }) => giveRole(guild, role, _id)));
77+
};
78+
79+
const removeMembersRoles = (role: Discord.Role) => {
80+
if (role.members.size > 0) {
81+
return Promise.all(role.members.map((member) => removeRole(member, role)));
82+
}
83+
84+
console.error(`Members with role don't exists!`);
85+
return;
86+
};
87+
88+
export const updateKarmaRoles = async () => {
89+
const client = new Discord.Client();
90+
await client.login(getConfig('DISCORD_BOT_TOKEN'));
91+
92+
const guild = await client.guilds.fetch(TYPE_OF_WEB_GUILD_ID);
93+
const karmaRole = await fetchOrCreateKarmaRole(guild);
94+
95+
await removeMembersRoles(karmaRole);
96+
await assignMembersRoles(guild, karmaRole);
97+
};

src/index.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1 @@
1-
/* eslint-disable @typescript-eslint/no-unsafe-call */
2-
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
3-
if (process.env.NODE_ENV === 'production') {
4-
require('dotenv').config({ path: '.env' });
5-
} else {
6-
require('dotenv').config({ path: '.env.dev' });
7-
}
81
require('./app');

src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,12 @@ export function getDateForWeekNumber(year: number, weekNo: number): Date {
2424
const yearStart = new Date(year, 0, 1);
2525
return new Date((weekNo * 7 - 1) * 86400000 + Number(yearStart));
2626
}
27+
28+
// Offset date by n weeks: e.g offsetDateByWeek(new Date(), 2) will return current date minus two weeks
29+
export const offsetDateByWeeks = (d: Date, weeks: number): Date => {
30+
const offsetDate = new Date(d);
31+
const daysCount = weeks * 7;
32+
offsetDate.setDate(offsetDate.getDate() - daysCount);
33+
34+
return offsetDate;
35+
};

0 commit comments

Comments
 (0)