Skip to content

Commit b2743d1

Browse files
authored
Add recurring messages to some channels (#124)
* Slight refactor of implementation Presume to post in Reactiflux, log to #bot-log, small naming/etc changes, add channel ID and frequency constants * Add messages * Install date-fns, remove node-cron * Add schedule helper This assumes all timers start on sunday at midnight, and resumes them on startup by scheduling for the next intended run from there * Swap out cron for interval * Disable "use before define" and "no explicit any" * Move `interval` next to channel definition Also removes messageConfig from args and just uses the constant * git WIP * Remove debugging steps, wait until bot is ready * Clean up message contents
1 parent 4f600c7 commit b2743d1

File tree

7 files changed

+190
-116
lines changed

7 files changed

+190
-116
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ module.exports = {
1313
commonjs: true,
1414
},
1515
rules: {
16+
"@typescript-eslint/no-explicit-any": "off",
17+
"@typescript-eslint/no-use-before-define": "off",
1618
"@typescript-eslint/explicit-function-return-type": "off",
1719
"@typescript-eslint/camelcase": "off",
1820
},

package-lock.json

Lines changed: 19 additions & 52 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
"license": "MIT",
1818
"dependencies": {
1919
"@types/node-cron": "^2.0.3",
20+
"date-fns": "^2.27.0",
2021
"discord.js": "^13.4.0",
2122
"dotenv": "^6.1.0",
2223
"gists": "^2.0.0",
23-
"node-cron": "^2.0.3",
2424
"node-fetch": "^2.2.0",
2525
"query-string": "^6.2.0"
2626
},

src/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ export const modRoleId = "&102870499406647296";
22

33
export const staffRoles = ["mvp", "moderator", "admin", "admins"];
44

5+
export const enum CHANNELS {
6+
"helpReact" = "103696749012467712",
7+
"helpJs" = "565213527673929729",
8+
"random" = "103325358643752960",
9+
"jobBoard" = "103882387330457600",
10+
}
11+
512
export const enum ReportReasons {
613
userWarn = "userWarn",
714
userDelete = "userDelete",

src/features/scheduled-messages.ts

Lines changed: 125 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,152 @@
1-
import * as discord from "discord.js";
2-
import cron from "node-cron";
1+
import type * as discord from "discord.js";
2+
import { CHANNELS } from "../constants";
3+
import { logger } from "./log";
4+
import { scheduleTask } from "../helpers/schedule";
35

4-
export const MESSAGE_SCHEDULE: MessageConfig[] = [
6+
const HOURLY = 60 * 60 * 1000;
7+
// By keeping these off 24 hr, we can make sure they show up at all timezones. If
8+
// it were 24 hours, for instance, it would consistently show up in the middle of
9+
// the night for some timezones.
10+
const DAILY = 20 * HOURLY;
11+
const FREQUENCY = {
12+
often: 9 * HOURLY,
13+
daily: DAILY,
14+
moreThanWeekly: 2 * DAILY,
15+
weekly: 6 * DAILY,
16+
};
17+
18+
type MessageConfig = {
19+
postTo: {
20+
guildId?: discord.Snowflake;
21+
interval: number;
22+
channelId: discord.Snowflake;
23+
}[];
24+
message: discord.MessageOptions;
25+
};
26+
const MESSAGE_SCHEDULE: MessageConfig[] = [
527
/* Example:
628
{
7-
cronExpression: "0,30 * * * *", // https://crontab.guru/#0,30_*_*_*_*
8-
guilds: [
29+
// Find Discord channel IDs: https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-
30+
postTo: [
931
{
10-
// getting these IDs: https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-
11-
id: "102860784329052160", // Reactiflux's server ID
12-
channelIds: ["103696749012467712"] // #help-react
32+
id: "102860784329052160", // Reactiflux's server ID, optional
33+
interval: FREQUENCY.weekly, // Frequency the bot should post by
34+
channelIds: [ CHANNELS.helpReact ] // Add channel IDs to constants first!
1335
}
1436
],
1537
message: {
16-
embed: {
17-
title: "Example Message",
18-
description: "This message is posted every 0th and 30th minute of the hour"
19-
}
38+
content: "A message to post, any type of message discord.js understands"
2039
}
2140
}
2241
*/
23-
];
42+
{
43+
postTo: [{ interval: FREQUENCY.daily, channelId: CHANNELS.jobBoard }],
44+
message: {
45+
content: `Messages must start with [FORHIRE]/[HIRING]. Check the channel description for a full list of tags and rules!
2446
25-
export type MessageConfig = {
26-
cronExpression: string;
27-
guilds: { id: discord.Snowflake; channelIds: discord.Snowflake[] }[];
28-
message: discord.MessageOptions;
29-
};
47+
* Job postings may only be posted every 7 days.
48+
* Posts should be reasonably descriptive.
49+
* Jobs are paid — unpaid, equity-only, or similar are not allowed.
50+
* We don't allow "small gigs".
51+
52+
Moderators may remove posts at any time, with or without warning. Repeat violators of these rules will be removed from the server permanently.
53+
`,
54+
},
55+
},
56+
{
57+
postTo: [{ interval: FREQUENCY.often, channelId: CHANNELS.helpJs }],
58+
message: {
59+
content: `This channel is good for specific questions about syntax, debugging a small (< ~50 lines of code) snippet of JS, without React involved. Question not getting answered? Maybe it's hard to answer, check out these resources for how to ask a good question:
60+
61+
How to ask for programming help <http://wp.me/p2oIwo-26>
62+
How do I ask a good question <https://stackoverflow.com/help/how-to-ask>
63+
`,
64+
},
65+
},
66+
{
67+
postTo: [{ interval: FREQUENCY.often, channelId: CHANNELS.helpReact }],
68+
message: {
69+
content: `This channel is good for specific questions about React, how React's features work, or debugging a small (< ~50 lines of code) snippet of JS that uses React. Question not getting answered? Maybe it's hard to answer, check out these resources for how to ask a good question:
70+
71+
How to ask for programming help <http://wp.me/p2oIwo-26>
72+
How do I ask a good question <https://stackoverflow.com/help/how-to-ask>
73+
`,
74+
},
75+
},
76+
{
77+
postTo: [
78+
{ interval: FREQUENCY.moreThanWeekly, channelId: CHANNELS.helpReact },
79+
],
80+
message: {
81+
content: `Check our the other channels too! This is our highest-traffic channel, which may mean your question gets missed as other discussions happen.
82+
83+
#help-js For questions about pure Javscript problems.
84+
#help-styling For questions about CSS or other visual problems.
85+
#help-backend For questions about issues with your server code.
86+
#code-review Get deeper review of a snippet of code.
87+
#jobs-advice If you have a question about your job or career, ask it in here.
88+
#general-tech Discussion of non-JS code, or that new laptop you're deciding on.
89+
#tooling for questions about building, linting, generating, or otherwise processing your code.
90+
91+
Looking for work? Trying to hire? Check out #job-board, or <https://reactiflux.com/jobs>
92+
93+
Has someone been really helpful? Shoutout who and what in #thanks! We keep an eye in there as one way to find new MVPs. Give us all the reactions in there too!
94+
95+
Please remember our Code of Conduct: <https://reactiflux.com/conduct>
96+
and our guidelines for promotion: <https://reactiflux.com/promotion>
97+
98+
If you see anything that violates our rules, help alert the mods by reacting to it with 👎
99+
`,
100+
},
101+
},
102+
{
103+
postTo: [
104+
{ interval: FREQUENCY.moreThanWeekly, channelId: CHANNELS.random },
105+
],
106+
message: {
107+
content: `Have you read our Code of Conduct? <https://reactiflux.com/conduct> Don't make dad angry.
108+
109+
If something crosses a line, give it a 👎, or if you'd prefer to remain anonymous, let mods know with the form at <https://reactiflux.com/contact>`,
110+
},
111+
},
112+
];
30113

31114
export const messages: MessageConfig[] = [];
32115

33-
const isTextChannel = (
34-
channel: discord.Channel,
35-
): channel is discord.TextChannel | discord.DMChannel | discord.NewsChannel => {
36-
return (
37-
channel.type === "GUILD_TEXT" ||
38-
channel.type === "GUILD_NEWS" ||
39-
channel.type === "GUILD_STORE"
40-
);
116+
export const scheduleMessages = (bot: discord.Client) => {
117+
bot.on("ready", () => {
118+
MESSAGE_SCHEDULE.forEach((messageConfig) =>
119+
sendMessage(bot, messageConfig),
120+
);
121+
});
41122
};
42123

43124
const sendMessage = async (
44125
bot: discord.Client,
45126
messageConfig: MessageConfig,
46127
) => {
47-
for (const { id, channelIds } of messageConfig.guilds) {
48-
const guild = await bot.guilds.fetch(id);
49-
50-
for (const channelId of channelIds) {
51-
const channel = guild.channels.resolve(channelId);
128+
const { message, postTo } = messageConfig;
129+
postTo.forEach(
130+
async ({ guildId = "102860784329052160", channelId, interval }) => {
131+
const channel = await bot.channels.fetch(channelId);
52132

53133
if (channel === null) {
54-
console.log(
55-
`Failed to send a scheduled message: channel ${channelId} does not exist in guild ${id}.`,
134+
logger.log(
135+
"scheduled",
136+
`Failed to send a scheduled message: channel ${channelId} does not exist in guild ${guildId}.`,
56137
);
57-
} else if (!isTextChannel(channel)) {
58-
console.log(
59-
`Failed to send a scheduled message: channel ${channelId} in guild ${id} is not a text channel.`,
138+
return;
139+
}
140+
if (!channel.isText()) {
141+
logger.log(
142+
"scheduled",
143+
`Failed to send a scheduled message: channel ${channelId} in guild ${guildId} is not a text channel.`,
60144
);
61-
} else {
62-
channel.send(messageConfig.message);
145+
return;
63146
}
64-
}
65-
}
66-
};
67-
68-
export const scheduleMessage = (
69-
bot: discord.Client,
70-
messageConfig: MessageConfig,
71-
) => {
72-
return cron.schedule(messageConfig.cronExpression, () =>
73-
sendMessage(bot, messageConfig),
74-
);
75-
};
76-
77-
export const scheduleMessages = (
78-
bot: discord.Client,
79-
messageConfigs: MessageConfig[],
80-
) => {
81-
const scheduledTasks = messageConfigs.map((messageConfig) =>
82-
scheduleMessage(bot, messageConfig),
147+
scheduleTask(interval, () => {
148+
channel.send(message);
149+
});
150+
},
83151
);
84-
return scheduledTasks;
85152
};

0 commit comments

Comments
 (0)