Skip to content

Commit

Permalink
Add recurring messages to some channels (#124)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
vcarl authored Dec 30, 2021
1 parent 4f600c7 commit b2743d1
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 116 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module.exports = {
commonjs: true,
},
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/camelcase": "off",
},
Expand Down
71 changes: 19 additions & 52 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"license": "MIT",
"dependencies": {
"@types/node-cron": "^2.0.3",
"date-fns": "^2.27.0",
"discord.js": "^13.4.0",
"dotenv": "^6.1.0",
"gists": "^2.0.0",
"node-cron": "^2.0.3",
"node-fetch": "^2.2.0",
"query-string": "^6.2.0"
},
Expand Down
7 changes: 7 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ export const modRoleId = "&102870499406647296";

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

export const enum CHANNELS {
"helpReact" = "103696749012467712",
"helpJs" = "565213527673929729",
"random" = "103325358643752960",
"jobBoard" = "103882387330457600",
}

export const enum ReportReasons {
userWarn = "userWarn",
userDelete = "userDelete",
Expand Down
183 changes: 125 additions & 58 deletions src/features/scheduled-messages.ts
Original file line number Diff line number Diff line change
@@ -1,85 +1,152 @@
import * as discord from "discord.js";
import cron from "node-cron";
import type * as discord from "discord.js";
import { CHANNELS } from "../constants";
import { logger } from "./log";
import { scheduleTask } from "../helpers/schedule";

export const MESSAGE_SCHEDULE: MessageConfig[] = [
const HOURLY = 60 * 60 * 1000;
// By keeping these off 24 hr, we can make sure they show up at all timezones. If
// it were 24 hours, for instance, it would consistently show up in the middle of
// the night for some timezones.
const DAILY = 20 * HOURLY;
const FREQUENCY = {
often: 9 * HOURLY,
daily: DAILY,
moreThanWeekly: 2 * DAILY,
weekly: 6 * DAILY,
};

type MessageConfig = {
postTo: {
guildId?: discord.Snowflake;
interval: number;
channelId: discord.Snowflake;
}[];
message: discord.MessageOptions;
};
const MESSAGE_SCHEDULE: MessageConfig[] = [
/* Example:
{
cronExpression: "0,30 * * * *", // https://crontab.guru/#0,30_*_*_*_*
guilds: [
// Find Discord channel IDs: https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-
postTo: [
{
// getting these IDs: https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-
id: "102860784329052160", // Reactiflux's server ID
channelIds: ["103696749012467712"] // #help-react
id: "102860784329052160", // Reactiflux's server ID, optional
interval: FREQUENCY.weekly, // Frequency the bot should post by
channelIds: [ CHANNELS.helpReact ] // Add channel IDs to constants first!
}
],
message: {
embed: {
title: "Example Message",
description: "This message is posted every 0th and 30th minute of the hour"
}
content: "A message to post, any type of message discord.js understands"
}
}
*/
];
{
postTo: [{ interval: FREQUENCY.daily, channelId: CHANNELS.jobBoard }],
message: {
content: `Messages must start with [FORHIRE]/[HIRING]. Check the channel description for a full list of tags and rules!
export type MessageConfig = {
cronExpression: string;
guilds: { id: discord.Snowflake; channelIds: discord.Snowflake[] }[];
message: discord.MessageOptions;
};
* Job postings may only be posted every 7 days.
* Posts should be reasonably descriptive.
* Jobs are paid — unpaid, equity-only, or similar are not allowed.
* We don't allow "small gigs".
Moderators may remove posts at any time, with or without warning. Repeat violators of these rules will be removed from the server permanently.
`,
},
},
{
postTo: [{ interval: FREQUENCY.often, channelId: CHANNELS.helpJs }],
message: {
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:
How to ask for programming help <http://wp.me/p2oIwo-26>
How do I ask a good question <https://stackoverflow.com/help/how-to-ask>
`,
},
},
{
postTo: [{ interval: FREQUENCY.often, channelId: CHANNELS.helpReact }],
message: {
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:
How to ask for programming help <http://wp.me/p2oIwo-26>
How do I ask a good question <https://stackoverflow.com/help/how-to-ask>
`,
},
},
{
postTo: [
{ interval: FREQUENCY.moreThanWeekly, channelId: CHANNELS.helpReact },
],
message: {
content: `Check our the other channels too! This is our highest-traffic channel, which may mean your question gets missed as other discussions happen.
#help-js For questions about pure Javscript problems.
#help-styling For questions about CSS or other visual problems.
#help-backend For questions about issues with your server code.
#code-review Get deeper review of a snippet of code.
#jobs-advice If you have a question about your job or career, ask it in here.
#general-tech Discussion of non-JS code, or that new laptop you're deciding on.
#tooling for questions about building, linting, generating, or otherwise processing your code.
Looking for work? Trying to hire? Check out #job-board, or <https://reactiflux.com/jobs>
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!
Please remember our Code of Conduct: <https://reactiflux.com/conduct>
and our guidelines for promotion: <https://reactiflux.com/promotion>
If you see anything that violates our rules, help alert the mods by reacting to it with 👎
`,
},
},
{
postTo: [
{ interval: FREQUENCY.moreThanWeekly, channelId: CHANNELS.random },
],
message: {
content: `Have you read our Code of Conduct? <https://reactiflux.com/conduct> Don't make dad angry.
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>`,
},
},
];

export const messages: MessageConfig[] = [];

const isTextChannel = (
channel: discord.Channel,
): channel is discord.TextChannel | discord.DMChannel | discord.NewsChannel => {
return (
channel.type === "GUILD_TEXT" ||
channel.type === "GUILD_NEWS" ||
channel.type === "GUILD_STORE"
);
export const scheduleMessages = (bot: discord.Client) => {
bot.on("ready", () => {
MESSAGE_SCHEDULE.forEach((messageConfig) =>
sendMessage(bot, messageConfig),
);
});
};

const sendMessage = async (
bot: discord.Client,
messageConfig: MessageConfig,
) => {
for (const { id, channelIds } of messageConfig.guilds) {
const guild = await bot.guilds.fetch(id);

for (const channelId of channelIds) {
const channel = guild.channels.resolve(channelId);
const { message, postTo } = messageConfig;
postTo.forEach(
async ({ guildId = "102860784329052160", channelId, interval }) => {
const channel = await bot.channels.fetch(channelId);

if (channel === null) {
console.log(
`Failed to send a scheduled message: channel ${channelId} does not exist in guild ${id}.`,
logger.log(
"scheduled",
`Failed to send a scheduled message: channel ${channelId} does not exist in guild ${guildId}.`,
);
} else if (!isTextChannel(channel)) {
console.log(
`Failed to send a scheduled message: channel ${channelId} in guild ${id} is not a text channel.`,
return;
}
if (!channel.isText()) {
logger.log(
"scheduled",
`Failed to send a scheduled message: channel ${channelId} in guild ${guildId} is not a text channel.`,
);
} else {
channel.send(messageConfig.message);
return;
}
}
}
};

export const scheduleMessage = (
bot: discord.Client,
messageConfig: MessageConfig,
) => {
return cron.schedule(messageConfig.cronExpression, () =>
sendMessage(bot, messageConfig),
);
};

export const scheduleMessages = (
bot: discord.Client,
messageConfigs: MessageConfig[],
) => {
const scheduledTasks = messageConfigs.map((messageConfig) =>
scheduleMessage(bot, messageConfig),
scheduleTask(interval, () => {
channel.send(message);
});
},
);
return scheduledTasks;
};
Loading

0 comments on commit b2743d1

Please sign in to comment.