Skip to content

Commit

Permalink
Remove web3 moderation rules from job board (#423)
Browse files Browse the repository at this point in the history
  • Loading branch information
vcarl authored Dec 2, 2024
1 parent 3192f94 commit 287ae4d
Show file tree
Hide file tree
Showing 6 changed files with 4 additions and 128 deletions.
27 changes: 1 addition & 26 deletions src/features/jobs-moderation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import {
updateJobs,
trackModeratedMessage,
failedTooFrequent,
failedWeb3Content,
failedWeb3Poster,
deleteAgedPosts,
} from "./jobs-moderation/job-mod-helpers";
import { getValidationMessage } from "./jobs-moderation/validation-messages";
Expand Down Expand Up @@ -79,9 +77,6 @@ const rulesThreadCache = new LRUCache<string, ThreadChannel>({
},
});

const freeflowHiring = "<https://discord.gg/gTWTwZPDYT>";
const freeflowForHire = "<https://vjlup8tch3g.typeform.com/to/T8w8qWzl>";

const jobModeration = async (bot: Client) => {
const jobBoard = await bot.channels.fetch(CHANNELS.jobBoard);
if (jobBoard?.type !== ChannelType.GuildText) return;
Expand Down Expand Up @@ -195,8 +190,6 @@ const jobModeration = async (bot: Client) => {
* There's a 10 minute grace period where people are allowed to re-post if they
* delete their own message.
* After 10 minutes, they must wait 6.75 days before reposting
* If it's been removed by this bot for being a web3 related post, they are
* warned twice and timed out after a third post.
*/
bot.on("messageDelete", async (message) => {
// TODO: look up audit log, early return if member was banned
Expand Down Expand Up @@ -301,29 +294,11 @@ More details & apply: https://example.com/apply
}

// Handle missing post type
let error: PostFailures | undefined = errors.find(failedTooFrequent);
const error: PostFailures | undefined = errors.find(failedTooFrequent);
if (error) {
reportUser({ reason: ReportReasons.jobFrequency, message });
}

// Handle posts that contain web3 content and posters who have been blocked
// for posting web3 roles
error = errors.find(failedWeb3Poster) || errors.find(failedWeb3Content);
if (error) {
reportUser({ reason: ReportReasons.jobCrypto, message });
if (error.count >= 3) {
await message.member?.timeout(20 * 60 * 60 * 1000);
}
const { hiring, forHire } = error;
await thread.send(
!hiring && !forHire
? `If you're hiring: ${freeflowHiring}
If you're seeking work: ${freeflowForHire}`
: hiring
? `Join FreeFlow's server to start hiring for web3: ${freeflowHiring}`
: `Apply to join FreeFlow's talent pool for web3: ${freeflowForHire}`,
);
}
await thread.send("Your post:");
await thread.send({
content: message.content,
Expand Down
21 changes: 1 addition & 20 deletions src/features/jobs-moderation/job-mod-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import {
PostFailureTooManyEmojis,
PostFailureTooManyGaps,
PostFailureTooManyLines,
PostFailureWeb3Content,
PostFailureWeb3Poster,
PostFailures,
PostType,
PostFailureLinkRequired,
Expand Down Expand Up @@ -70,11 +68,6 @@ export const failedTooManyEmojis = (
e: PostFailures,
): e is PostFailureTooManyEmojis =>
e.type === POST_FAILURE_REASONS.tooManyEmojis;
export const failedWeb3Content = (
e: PostFailures,
): e is PostFailureWeb3Content => e.type === POST_FAILURE_REASONS.web3Content;
export const failedWeb3Poster = (e: PostFailures): e is PostFailureWeb3Poster =>
e.type === POST_FAILURE_REASONS.web3Poster;

interface StoredMessage {
message: Message;
Expand Down Expand Up @@ -286,8 +279,7 @@ export const removeSpecificJob = (message: Message) => {
};

export const purgeMember = (idToRemove: string) => {
let removed = removeFromCryptoCache(idToRemove);

let removed = 0;
let index = jobBoardMessageCache.hiring.findIndex(
(x) => x.authorId === idToRemove,
);
Expand Down Expand Up @@ -322,14 +314,3 @@ export const untrackModeratedMessage = (message: Message | PartialMessage) => {
}
return false;
};

const cryptoPosters: Map<string, { count: number; last: Date }> = new Map();
export const removeFromCryptoCache = (idToClear: string) => {
if (cryptoPosters.has(idToClear)) {
cryptoPosters.delete(idToClear);
return 1;
}
return 0;
};
export const getCryptoCache = cryptoPosters.get.bind(cryptoPosters);
export const setCryptoCache = cryptoPosters.set.bind(cryptoPosters);
51 changes: 1 addition & 50 deletions src/features/jobs-moderation/validate.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Message, MessageType } from "discord.js";
import { differenceInHours } from "date-fns";

import { getLastPostAge } from "./job-mod-helpers";

import { countLines, simplifyString } from "../../helpers/string";
import { countLines } from "../../helpers/string";
import { extractEmoji } from "../../helpers/string";
import { getCryptoCache, setCryptoCache } from "./job-mod-helpers";
import { parseContent } from "./parse-content";
import {
JobPostValidator,
Expand All @@ -17,7 +15,6 @@ import {
const validate = (posts: ReturnType<typeof parseContent>, message: Message) => {
const errors: PostFailures[] = [];
errors.push(...participation(posts, message));
errors.push(...web3(posts, message));
errors.push(...formatting(posts, message));
errors.push(...links(posts, message));
return errors;
Expand Down Expand Up @@ -82,52 +79,6 @@ export const formatting: JobPostValidator = (posts, message) => {
return errors;
};

const CRYPTO_COOLDOWN = 6; // hours
const bannedWords = /(blockchain|nft|cryptocurrency|token|web3|web 3)/;

export const web3: JobPostValidator = (posts, message) => {
const now = new Date();
const lastCryptoPost = getCryptoCache(message.author.id);
// Fail posts that are sent by someone who was already blocked for posting
// web3 jobs
if (
lastCryptoPost &&
// extend duration for each repeated post
differenceInHours(now, lastCryptoPost.last) <
CRYPTO_COOLDOWN * lastCryptoPost.count
) {
const newCount = lastCryptoPost.count + 1;
setCryptoCache(message.author.id, {
...lastCryptoPost,
count: newCount,
});
return [
{
type: POST_FAILURE_REASONS.web3Poster,
count: newCount,
hiring: posts.some((p) => p.tags.includes(PostType.hiring)),
forHire: posts.some((p) => p.tags.includes(PostType.forHire)),
},
];
}

// Block posts that trigger our web3 detection
if (
posts.some((post) => bannedWords.test(simplifyString(post.description)))
) {
setCryptoCache(message.author.id, { count: 1, last: new Date() });
return [
{
type: POST_FAILURE_REASONS.web3Content,
count: 1,
hiring: posts.some((p) => p.tags.includes(PostType.hiring)),
forHire: posts.some((p) => p.tags.includes(PostType.forHire)),
},
];
}
return [];
};

export const participation: JobPostValidator = (posts, message) => {
const { members: mentions } = message.mentions;
if (
Expand Down
12 changes: 0 additions & 12 deletions src/features/jobs-moderation/validation-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import {
failedTooManyLines,
failedTooManyEmojis,
failedTooFrequent,
failedWeb3Content,
failedWeb3Poster,
failedInconsistentType,
failedTooLong,
failedTooManyGaps,
Expand All @@ -36,10 +34,6 @@ const ValidationMessages = {
`You’re posting too frequently. You last posted ${e.lastSent} days ago, please wait at least 7 days.`,
[POST_FAILURE_REASONS.replyOrMention]:
"Messages in this channel may not be replies or include @-mentions of users, to ensure the channel isn’t being used to discuss postings.",
[POST_FAILURE_REASONS.web3Content]:
"We do not allow web3 positions to be advertised here. If you continue posting, you’ll be timed out overnight.",
[POST_FAILURE_REASONS.web3Poster]:
"We do not allow posers who arrived to post web3 positions to create posts. If you continue posting, you’ll be timed out overnight.",
};

export const getValidationMessage = (reason: PostFailures): string => {
Expand Down Expand Up @@ -70,11 +64,5 @@ export const getValidationMessage = (reason: PostFailures): string => {
if (failedTooManyEmojis(reason)) {
return ValidationMessages[reason.type];
}
if (failedWeb3Content(reason)) {
return ValidationMessages[reason.type];
}
if (failedWeb3Poster(reason)) {
return ValidationMessages[reason.type];
}
return "";
};
3 changes: 0 additions & 3 deletions src/helpers/modLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export const enum ReportReasons {
jobAge = "jobAge",
jobFrequency = "jobFrequency",
jobRemoved = "jobRemoved",
jobCrypto = "jobCrypto",
lowEffortQuestionRemoved = "lowEffortQuestionRemoved",
}

Expand Down Expand Up @@ -167,8 +166,6 @@ ${reportedMessage}`;
return `${preface}, for hire post expired.`;
case ReportReasons.jobFrequency:
return `${preface}, posting too frequently.`;
case ReportReasons.jobCrypto:
return `<@${message.author.id}> posted a crypto job.`;

case ReportReasons.lowEffortQuestionRemoved:
return `
Expand Down
18 changes: 1 addition & 17 deletions src/types/jobs-moderation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ export const enum POST_FAILURE_REASONS {
tooManyGaps = "tooManyGaps",
tooFrequent = "tooFrequent",
replyOrMention = "replyOrMention",
web3Content = "web3Content",
web3Poster = "web3Poster",
// invalidContact = 'invalidContact',
// unknownLocation = 'unknownLocation',
// invalidPostType = 'invalidPostType',
Expand Down Expand Up @@ -65,18 +63,6 @@ export interface PostFailureTooFrequent {
export interface PostFailureReplyOrMention {
type: POST_FAILURE_REASONS.replyOrMention;
}
export interface PostFailureWeb3Content {
type: POST_FAILURE_REASONS.web3Content;
count: number;
hiring: boolean;
forHire: boolean;
}
export interface PostFailureWeb3Poster {
type: POST_FAILURE_REASONS.web3Poster;
count: number;
hiring: boolean;
forHire: boolean;
}
export type PostFailures =
| PostFailureMissingType
| PostFailureInconsistentType
Expand All @@ -86,6 +72,4 @@ export type PostFailures =
| PostFailureTooLong
| PostFailureTooManyLines
| PostFailureTooManyGaps
| PostFailureTooManyEmojis
| PostFailureWeb3Content
| PostFailureWeb3Poster;
| PostFailureTooManyEmojis;

0 comments on commit 287ae4d

Please sign in to comment.