Skip to content

Commit df591b1

Browse files
authored
Adjust jobs moderation bot (#335)
* Git commit double lifespan of forhire posts * Fix negative values * Stop deleting edited messages. Too many false positives * Support optional arg in job board validators * Add dummy env values for test env * Revise feedback message * Require that HIRING posts include a link
1 parent 120169b commit df591b1

File tree

7 files changed

+116
-10
lines changed

7 files changed

+116
-10
lines changed

src/features/jobs-moderation.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ const jobModeration = async (bot: Client) => {
176176
if (errors) {
177177
if (posts.some((p) => p.tags.includes(PostType.forHire))) {
178178
reportUser({ reason: ReportReasons.jobCircumvent, message });
179-
await newMessage.delete();
179+
// await newMessage.delete();
180180
} else {
181181
await handleErrors(channel, message, errors);
182182
}
@@ -276,15 +276,14 @@ ${errors.map((e) => `- ${getValidationMessage(e)}`).join("\n")}`,
276276
await thread.send({
277277
content: `Hey <@${
278278
message.author.id
279-
}>, your message does not meet our requirements to be posted to the board. This thread acts as a REPL where you can test out new posts against our validation rules.
279+
}>, your message does not meet our requirements to be posted to the board. This thread acts as a REPL where you can test out new posts against our validation rules. It was removed for these reasons:
280280
281-
You can view our guidance for job posts here: <https://www.reactiflux.com/promotion#job-board>. It was removed for these reasons:
281+
${errors.map((e) => `- ${getValidationMessage(e)}`).join("\n")}
282282
283-
${errors.map((e) => `- ${getValidationMessage(e)}`).join("\n")}`,
283+
View our [guidance for job posts](<https://www.reactiflux.com/promotion#job-board>).`,
284284
embeds: [
285285
{
286-
title: "Job Board Rules",
287-
description: `Here's an example of a valid post:
286+
description: `Here's an example of a good HIRING post:
288287
\`\`\`
289288
HIRING | REMOTE | FULL-TIME
290289

src/features/jobs-moderation/job-mod-helpers.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
PostFailureWeb3Poster,
3131
PostFailures,
3232
PostType,
33+
PostFailureLinkRequired,
3334
} from "../../types/jobs-moderation";
3435

3536
export class RuleViolation extends Error {
@@ -56,6 +57,9 @@ export const failedReplyOrMention = (
5657
e.type === POST_FAILURE_REASONS.replyOrMention;
5758
export const failedTooLong = (e: PostFailures): e is PostFailureTooLong =>
5859
e.type === POST_FAILURE_REASONS.tooLong;
60+
export const failedLinkRequired = (
61+
e: PostFailures,
62+
): e is PostFailureLinkRequired => e.type === POST_FAILURE_REASONS.linkRequired;
5963
export const failedTooManyLines = (
6064
e: PostFailures,
6165
): e is PostFailureTooManyLines => e.type === POST_FAILURE_REASONS.tooManyLines;
@@ -139,7 +143,7 @@ export const loadJobs = async (bot: Client, channel: TextChannel) => {
139143
};
140144

141145
const POST_INTERVAL = 7;
142-
const FORHIRE_AGE_LIMIT = 1.25 * 24;
146+
const FORHIRE_AGE_LIMIT = 1.25 * 48;
143147

144148
export const deleteAgedPosts = async () => {
145149
// Delete all `forhire` messages that are older than the age limit
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { describe, expect, it } from "vitest";
2+
import { PostType, POST_FAILURE_REASONS } from "../../types/jobs-moderation";
3+
import { links } from "./validate";
4+
5+
const makePost = (type: PostType, description: string) => [
6+
{ tags: [type], description },
7+
];
8+
9+
describe("links", () => {
10+
it("requires links", () => {
11+
expect(
12+
links(
13+
makePost(
14+
PostType.hiring,
15+
"Hiring for some role and stuff\nDM me to apply",
16+
),
17+
undefined,
18+
),
19+
).toContainEqual({ type: POST_FAILURE_REASONS.linkRequired });
20+
expect(
21+
links(
22+
makePost(
23+
PostType.hiring,
24+
"Hiring for some role and stuff\nDM me to apply\nhttps://example.com",
25+
),
26+
undefined,
27+
),
28+
).toHaveLength(0);
29+
expect(
30+
links(
31+
makePost(PostType.forHire, "advertising my own skills for hire"),
32+
undefined,
33+
),
34+
).toHaveLength(0);
35+
});
36+
it("doesn't choke on weird urls", () => {
37+
const urls = [
38+
"https://www.example.com",
39+
"https://subdomain.example.com",
40+
"https://www.sub.example.com",
41+
"https://www.example.com/path/to/page",
42+
"https://www.example.com/?query=param",
43+
"https://www.example.com/path/to/page?query=param",
44+
"https://www.example.com/#section",
45+
"https://www.example.com/path/to/page#section",
46+
"https://www.example.com/path/to/page?query=param#section",
47+
"https://xn--bcher-kva.example/",
48+
"https://192.168.0.1",
49+
"https://[2001:db8::ff00:42:8329]",
50+
"https://www.example.co.uk",
51+
"https://www.example.travel",
52+
"https://www.example.com:8080",
53+
"https://www.example.com:8443",
54+
"https://www.example.com/images/pic.jpg",
55+
"https://www.example.com/files/document.pdf",
56+
"https://www.example.com/path%20with%20spaces",
57+
];
58+
expect.assertions(urls.length);
59+
urls.forEach((url) => {
60+
expect(
61+
links(
62+
makePost(
63+
PostType.hiring,
64+
`Hiring for some role and stuff\nDM me to apply\n${url}`,
65+
),
66+
undefined,
67+
),
68+
).toHaveLength(0);
69+
});
70+
});
71+
});

src/features/jobs-moderation/validate.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const validate = (posts: ReturnType<typeof parseContent>, message: Message) => {
1919
errors.push(...participation(posts, message));
2020
errors.push(...web3(posts, message));
2121
errors.push(...formatting(posts, message));
22+
errors.push(...links(posts, message));
2223
return errors;
2324
};
2425
export default validate;
@@ -63,7 +64,7 @@ export const formatting: JobPostValidator = (posts, message) => {
6364
if (lineCount > maxLines) {
6465
errors.push({
6566
type: POST_FAILURE_REASONS.tooManyLines,
66-
overage: maxLines - lineCount,
67+
overage: lineCount - maxLines,
6768
});
6869
}
6970
const maxChars = isForHire ? 350 : 1800;
@@ -145,3 +146,19 @@ export const participation: JobPostValidator = (posts, message) => {
145146
}
146147
return [];
147148
};
149+
150+
const urlRegex = /(https):\/\/[^\s/$.?#].[^\s]*/g;
151+
export const links: JobPostValidator<false> = (posts) => {
152+
const errors: PostFailures[] = [];
153+
posts.forEach(({ tags, description }) => {
154+
if (!tags.includes(PostType.hiring)) {
155+
return;
156+
}
157+
const urls = description.match(urlRegex);
158+
if (!urls) {
159+
errors.push({ type: POST_FAILURE_REASONS.linkRequired });
160+
}
161+
});
162+
163+
return errors;
164+
};

src/features/jobs-moderation/validation-messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
failedInconsistentType,
1717
failedTooLong,
1818
failedTooManyGaps,
19+
failedLinkRequired,
1920
} from "./job-mod-helpers";
2021

2122
const ValidationMessages = {
@@ -26,6 +27,7 @@ const ValidationMessages = {
2627
[POST_FAILURE_REASONS.tooManyEmojis]: "Your post has too many emojis.",
2728
[POST_FAILURE_REASONS.tooLong]: (e: PostFailureTooLong) =>
2829
`Your post is too long, please shorten it by ${e.overage} characters.`,
30+
[POST_FAILURE_REASONS.linkRequired]: `Hiring posts must include a link, either to the company website or a page to apply for the job. Make sure it includes \`https://\` so Discord makes it clickable.`,
2931
[POST_FAILURE_REASONS.tooManyLines]: (e: PostFailureTooManyLines) =>
3032
`Your post has too many lines, please shorten it by ${e.overage} lines.`,
3133
[POST_FAILURE_REASONS.tooManyGaps]:
@@ -56,6 +58,9 @@ export const getValidationMessage = (reason: PostFailures): string => {
5658
if (failedTooLong(reason)) {
5759
return ValidationMessages[reason.type](reason);
5860
}
61+
if (failedLinkRequired(reason)) {
62+
return ValidationMessages[reason.type];
63+
}
5964
if (failedTooManyLines(reason)) {
6065
return ValidationMessages[reason.type](reason);
6166
}

src/helpers/env.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ console.log("Running as", isProd() ? "PRODUCTION" : "TEST", "environment");
88

99
let ok = true;
1010
const getEnv = (key: string, optional = false) => {
11+
if (process.env.VITEST) {
12+
return "dummystring";
13+
}
1114
const value = process.env[key];
1215
if (!value && !optional) {
1316
console.log(`Add a ${key} value to .env`);

src/types/jobs-moderation.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@ export enum PostType {
1111
forHire = "forhire",
1212
}
1313

14-
export type JobPostValidator = (
14+
export type JobPostValidator<WithMessage = true> = (
1515
posts: Post[],
16-
message: Message<boolean>,
16+
message: WithMessage extends true
17+
? Message<boolean>
18+
: Message<boolean> | undefined,
1719
) => PostFailures[];
1820

1921
export const enum POST_FAILURE_REASONS {
2022
missingType = "missingType",
2123
inconsistentType = "inconsistentType",
2224
tooManyEmojis = "tooManyEmojis",
2325
tooLong = "tooLong",
26+
linkRequired = "linkRequired",
2427
tooManyLines = "tooManyLines",
2528
tooManyGaps = "tooManyGaps",
2629
tooFrequent = "tooFrequent",
@@ -49,6 +52,9 @@ export interface PostFailureTooLong {
4952
type: POST_FAILURE_REASONS.tooLong;
5053
overage: number;
5154
}
55+
export interface PostFailureLinkRequired {
56+
type: POST_FAILURE_REASONS.linkRequired;
57+
}
5258
export interface PostFailureTooManyGaps {
5359
type: POST_FAILURE_REASONS.tooManyGaps;
5460
}
@@ -76,6 +82,7 @@ export type PostFailures =
7682
| PostFailureInconsistentType
7783
| PostFailureTooFrequent
7884
| PostFailureReplyOrMention
85+
| PostFailureLinkRequired
7986
| PostFailureTooLong
8087
| PostFailureTooManyLines
8188
| PostFailureTooManyGaps

0 commit comments

Comments
 (0)