@fsvreddit/bot-bouncer-evaluation 0.7.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +1 -0
  2. package/dist/allEvaluators.d.ts +16 -0
  3. package/dist/allEvaluators.js +72 -0
  4. package/dist/constants.d.ts +3 -0
  5. package/dist/constants.js +6 -0
  6. package/dist/extendedDevvit.d.ts +36 -0
  7. package/dist/extendedDevvit.js +66 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.js +23 -0
  10. package/dist/userEvaluation/EvaluateBadDisplayname.d.ts +18 -0
  11. package/dist/userEvaluation/EvaluateBadDisplayname.js +81 -0
  12. package/dist/userEvaluation/EvaluateBadDisplaynameDefinedHandles.d.ts +16 -0
  13. package/dist/userEvaluation/EvaluateBadDisplaynameDefinedHandles.js +89 -0
  14. package/dist/userEvaluation/EvaluateBadUsername.d.ts +16 -0
  15. package/dist/userEvaluation/EvaluateBadUsername.js +75 -0
  16. package/dist/userEvaluation/EvaluateBioText.d.ts +18 -0
  17. package/dist/userEvaluation/EvaluateBioText.js +96 -0
  18. package/dist/userEvaluation/EvaluateBioTextDefinedHandles.d.ts +16 -0
  19. package/dist/userEvaluation/EvaluateBioTextDefinedHandles.js +99 -0
  20. package/dist/userEvaluation/EvaluateBotGroupAdvanced.d.ts +124 -0
  21. package/dist/userEvaluation/EvaluateBotGroupAdvanced.js +1360 -0
  22. package/dist/userEvaluation/EvaluateBotGroupAdvancedInternal.d.ts +6 -0
  23. package/dist/userEvaluation/EvaluateBotGroupAdvancedInternal.js +13 -0
  24. package/dist/userEvaluation/EvaluateBotGroupSubmitters.d.ts +8 -0
  25. package/dist/userEvaluation/EvaluateBotGroupSubmitters.js +23 -0
  26. package/dist/userEvaluation/EvaluateCommentPhrase.d.ts +16 -0
  27. package/dist/userEvaluation/EvaluateCommentPhrase.js +86 -0
  28. package/dist/userEvaluation/EvaluateDomainSharer.d.ts +18 -0
  29. package/dist/userEvaluation/EvaluateDomainSharer.js +138 -0
  30. package/dist/userEvaluation/EvaluateFirstCommentEmDash.d.ts +17 -0
  31. package/dist/userEvaluation/EvaluateFirstCommentEmDash.js +103 -0
  32. package/dist/userEvaluation/EvaluateFirstPostWithSelfComment.d.ts +14 -0
  33. package/dist/userEvaluation/EvaluateFirstPostWithSelfComment.js +53 -0
  34. package/dist/userEvaluation/EvaluateInconsistentAgeBot.d.ts +17 -0
  35. package/dist/userEvaluation/EvaluateInconsistentAgeBot.js +91 -0
  36. package/dist/userEvaluation/EvaluateInconsistentGenderBot.d.ts +17 -0
  37. package/dist/userEvaluation/EvaluateInconsistentGenderBot.js +99 -0
  38. package/dist/userEvaluation/EvaluateLinkReuse.d.ts +17 -0
  39. package/dist/userEvaluation/EvaluateLinkReuse.js +159 -0
  40. package/dist/userEvaluation/EvaluateMixedBot.d.ts +15 -0
  41. package/dist/userEvaluation/EvaluateMixedBot.js +82 -0
  42. package/dist/userEvaluation/EvaluateObfuscatedBioKeywords.d.ts +16 -0
  43. package/dist/userEvaluation/EvaluateObfuscatedBioKeywords.js +79 -0
  44. package/dist/userEvaluation/EvaluatePinnedPostTitle.d.ts +15 -0
  45. package/dist/userEvaluation/EvaluatePinnedPostTitle.js +77 -0
  46. package/dist/userEvaluation/EvaluatePostTitle.d.ts +16 -0
  47. package/dist/userEvaluation/EvaluatePostTitle.js +91 -0
  48. package/dist/userEvaluation/EvaluatePostTitleDefinedHandles.d.ts +15 -0
  49. package/dist/userEvaluation/EvaluatePostTitleDefinedHandles.js +109 -0
  50. package/dist/userEvaluation/EvaluatePostTitleMulti.d.ts +16 -0
  51. package/dist/userEvaluation/EvaluatePostTitleMulti.js +88 -0
  52. package/dist/userEvaluation/EvaluateRapidFireBot.d.ts +13 -0
  53. package/dist/userEvaluation/EvaluateRapidFireBot.js +48 -0
  54. package/dist/userEvaluation/EvaluateRepostBot.d.ts +19 -0
  55. package/dist/userEvaluation/EvaluateRepostBot.js +167 -0
  56. package/dist/userEvaluation/EvaluateSelfComment.d.ts +16 -0
  57. package/dist/userEvaluation/EvaluateSelfComment.js +74 -0
  58. package/dist/userEvaluation/EvaluateSocialLinks.d.ts +16 -0
  59. package/dist/userEvaluation/EvaluateSocialLinks.js +101 -0
  60. package/dist/userEvaluation/EvaluateSuspiciousFirstPost.d.ts +15 -0
  61. package/dist/userEvaluation/EvaluateSuspiciousFirstPost.js +68 -0
  62. package/dist/userEvaluation/EvaluateTGGroup.d.ts +16 -0
  63. package/dist/userEvaluation/EvaluateTGGroup.js +73 -0
  64. package/dist/userEvaluation/EvaluateTextInNsfwImages.d.ts +14 -0
  65. package/dist/userEvaluation/EvaluateTextInNsfwImages.js +169 -0
  66. package/dist/userEvaluation/EvaluateTitleCopyBot.d.ts +13 -0
  67. package/dist/userEvaluation/EvaluateTitleCopyBot.js +74 -0
  68. package/dist/userEvaluation/EvaluateWarmupBot.d.ts +15 -0
  69. package/dist/userEvaluation/EvaluateWarmupBot.js +89 -0
  70. package/dist/userEvaluation/EvaluateWorldTraveller.d.ts +18 -0
  71. package/dist/userEvaluation/EvaluateWorldTraveller.js +79 -0
  72. package/dist/userEvaluation/UserEvaluatorBase.d.ts +67 -0
  73. package/dist/userEvaluation/UserEvaluatorBase.js +109 -0
  74. package/dist/userEvaluation/evaluatorHelpers.d.ts +3 -0
  75. package/dist/userEvaluation/evaluatorHelpers.js +25 -0
  76. package/dist/userEvaluation/index.d.ts +26 -0
  77. package/dist/userEvaluation/index.js +42 -0
  78. package/dist/utility.d.ts +34 -0
  79. package/dist/utility.js +164 -0
  80. package/eslint.config.mjs +3 -0
  81. package/package.json +50 -0
  82. package/tsconfig.build.json +14 -0
  83. package/tsconfig.json +23 -0
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluateInconsistentGenderBot = void 0;
4
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
5
+ const lodash_1 = require("lodash");
6
+ const date_fns_1 = require("date-fns");
7
+ const regjsparser_1 = require("regjsparser");
8
+ class EvaluateInconsistentGenderBot extends UserEvaluatorBase_js_1.UserEvaluatorBase {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.name = "Inconsistent Gender Bot";
12
+ this.shortname = "inconsistentgender";
13
+ this.banContentThreshold = 6;
14
+ this.canAutoBan = true;
15
+ }
16
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
+ preEvaluateComment(_event) {
18
+ return false;
19
+ }
20
+ getGenderFromTitle(title) {
21
+ const genderRegexes = this.gatherRegexes().map(r => new RegExp(r.regex, r.flags));
22
+ for (const regex of genderRegexes) {
23
+ const match = regex.exec(title);
24
+ if (match?.[1]) {
25
+ return match[1].toUpperCase();
26
+ }
27
+ }
28
+ return;
29
+ }
30
+ gatherRegexes() {
31
+ const regexes = this.getVariable("genderregexes", []);
32
+ return regexes.map(regex => ({
33
+ evaluatorName: this.name,
34
+ regex,
35
+ flags: "i",
36
+ }));
37
+ }
38
+ validateVariables() {
39
+ const regexes = this.gatherRegexes();
40
+ const results = [];
41
+ for (const regexObj of regexes) {
42
+ try {
43
+ new RegExp(regexObj.regex, regexObj.flags);
44
+ const parsed = (0, regjsparser_1.parse)(regexObj.regex, "i");
45
+ if (parsed.type !== "alternative") {
46
+ results.push({ severity: "error", message: `No capturing group found in regex: ${regexObj.regex}` });
47
+ continue;
48
+ }
49
+ const capturingGroups = parsed.body.filter(part => part.type === "group" && part.behavior === "normal");
50
+ if (capturingGroups.length !== 1) {
51
+ results.push({ severity: "error", message: `Expected exactly one capturing group in regex: ${regexObj.regex}` });
52
+ continue;
53
+ }
54
+ }
55
+ catch {
56
+ results.push({ severity: "error", message: `Invalid regex: ${regexObj.regex}` });
57
+ }
58
+ }
59
+ return results;
60
+ }
61
+ preEvaluatePost(post) {
62
+ return this.getGenderFromTitle(post.title) !== undefined && post.isNsfw() && !post.crosspostParentId;
63
+ }
64
+ preEvaluateUser(user) {
65
+ return user.commentKarma < 100;
66
+ }
67
+ evaluate(user) {
68
+ const nsfwPosts = this.getPosts({ since: (0, date_fns_1.subWeeks)(new Date(), 2) })
69
+ .filter(post => post.isNsfw() && !post.subredditName.toLowerCase().includes("roleplay") && !post.crosspostParentId);
70
+ if (nsfwPosts.length < 4) {
71
+ return false;
72
+ }
73
+ const genderGroups = (0, lodash_1.countBy)((0, lodash_1.compact)(nsfwPosts.map(post => this.getGenderFromTitle(post.title))));
74
+ const gendersFound = Object.entries(genderGroups)
75
+ .map(([gender, count]) => ({ gender, count }))
76
+ .filter(item => item.count > 1)
77
+ .map(item => item.gender);
78
+ if (gendersFound.some(gender => gender.length !== 1)) {
79
+ console.log(`Found invalid genders on post title: ${gendersFound.join(", ")}`);
80
+ return false;
81
+ }
82
+ if (gendersFound.length < 2) {
83
+ return false;
84
+ }
85
+ if (nsfwPosts.some(post => post.subredditName === "bodyswap" || post.subredditName.toLowerCase().includes("roleplay") || post.subredditName.toLowerCase().includes("penpals"))) {
86
+ return false;
87
+ }
88
+ if (user.userDescription?.includes("shared") || user.userDescription?.includes("couple") || nsfwPosts.some(post => post.title.toLowerCase().includes("couple"))) {
89
+ this.canAutoBan = false;
90
+ }
91
+ const sharedRegex = /([0-9]{2}M|M[0-9]{2}) and ([0-9]{2}F|F[0-9]{2})/;
92
+ if (nsfwPosts.some(post => sharedRegex.test(post.title))) {
93
+ this.canAutoBan = false;
94
+ }
95
+ this.addHitReason(`Inconsistent Gender Bot: Found ${gendersFound.length} different genders in ${nsfwPosts.length} posts`);
96
+ return true;
97
+ }
98
+ }
99
+ exports.EvaluateInconsistentGenderBot = EvaluateInconsistentGenderBot;
@@ -0,0 +1,17 @@
1
+ import { Post } from "@devvit/public-api";
2
+ import { CommentCreate } from "@devvit/protos";
3
+ import { UserEvaluatorBase, ValidationIssue } from "./UserEvaluatorBase.js";
4
+ import { UserExtended } from "@fsvreddit/fsv-devvit-helpers";
5
+ export declare class EvaluateLinkReuse extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ private getLinkRegexes;
10
+ validateVariables(): ValidationIssue[];
11
+ preEvaluatePost(post: Post): boolean;
12
+ preEvaluateComment(_event: CommentCreate): Promise<boolean>;
13
+ preEvaluateUser(user: UserExtended): boolean;
14
+ private getDistinctUsersForLink;
15
+ private isEligibleUserForEvaluator;
16
+ evaluate(user: UserExtended): Promise<boolean>;
17
+ }
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluateLinkReuse = void 0;
4
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
5
+ const constants_js_1 = require("../constants.js");
6
+ const date_fns_1 = require("date-fns");
7
+ const index_js_1 = require("../index.js");
8
+ class EvaluateLinkReuse extends UserEvaluatorBase_js_1.UserEvaluatorBase {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.name = "Link Reuse Bot";
12
+ this.shortname = "linkreuse";
13
+ this.banContentThreshold = 1;
14
+ }
15
+ getLinkRegexes() {
16
+ return this.getVariable("linkRegexes", []);
17
+ }
18
+ validateVariables() {
19
+ const results = [];
20
+ const linkRegexes = this.getLinkRegexes();
21
+ if (linkRegexes.length === 0) {
22
+ results.push({ severity: "error", message: "No link regexes defined" });
23
+ }
24
+ for (const regex of linkRegexes) {
25
+ try {
26
+ const regexp = new RegExp(regex);
27
+ if (regexp.test("")) {
28
+ results.push({ severity: "error", message: `Link regex in linkreuse matches empty string: ${regex}` });
29
+ }
30
+ }
31
+ catch {
32
+ results.push({ severity: "error", message: `Invalid link regex in linkreuse: ${regex}` });
33
+ }
34
+ }
35
+ const socialLinkRegexes = this.getVariable("socialLinkRegexes", []);
36
+ if (socialLinkRegexes.length === 0) {
37
+ results.push({ severity: "error", message: "No social link regexes defined" });
38
+ }
39
+ for (const regex of socialLinkRegexes) {
40
+ try {
41
+ const regexp = new RegExp(regex);
42
+ if (regexp.test("")) {
43
+ results.push({ severity: "error", message: `Social link regex in linkreuse matches empty string: ${regex}` });
44
+ }
45
+ }
46
+ catch {
47
+ results.push({ severity: "error", message: `Invalid social link regex in linkreuse: ${regex}` });
48
+ }
49
+ }
50
+ return results;
51
+ }
52
+ preEvaluatePost(post) {
53
+ const linkRegexes = this.getLinkRegexes();
54
+ return linkRegexes.some(regex => new RegExp(regex).test(post.url))
55
+ && !post.crosspostParentId;
56
+ }
57
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await
58
+ async preEvaluateComment(_event) {
59
+ return false;
60
+ }
61
+ preEvaluateUser(user) {
62
+ return user.nsfw;
63
+ }
64
+ async getDistinctUsersForLink(link) {
65
+ const redis = this.context.appSlug === constants_js_1.MAIN_APP_NAME ? this.context.redis.global : this.context.redis;
66
+ const cacheKey = `bbe:linkreuse:distinctUsers:${link}`;
67
+ const cachedResults = await redis.get(cacheKey);
68
+ if (cachedResults !== undefined) {
69
+ return JSON.parse(cachedResults);
70
+ }
71
+ const results = await this.context.reddit.searchPosts({
72
+ query: `url:"${link}"`,
73
+ sort: "new",
74
+ limit: 100,
75
+ }).all();
76
+ const distinctUsers = Array.from(new Set(results.filter(post => post.url === link && post.authorName !== "[deleted]").map(post => post.authorName)));
77
+ await redis.set(cacheKey, JSON.stringify(distinctUsers), { expiration: (0, date_fns_1.addHours)(new Date(), 1) });
78
+ return distinctUsers;
79
+ }
80
+ async isEligibleUserForEvaluator(username, isSubject = false) {
81
+ let socialLinks;
82
+ if (isSubject) {
83
+ socialLinks = await this.getSocialLinks(username);
84
+ }
85
+ else {
86
+ socialLinks = await (0, index_js_1.getSocialLinksWithCache)(username, this.context);
87
+ }
88
+ if (socialLinks.length === 0) {
89
+ return false;
90
+ }
91
+ const socialLinkRegexes = this.getVariable("socialLinkRegexes", []);
92
+ return socialLinks.some(link => socialLinkRegexes.some(regex => new RegExp(regex).test(link.outboundUrl)));
93
+ }
94
+ async evaluate(user) {
95
+ const linkRegexes = this.getLinkRegexes();
96
+ const requiredLinks = this.getVariable("requiredLinks", 3);
97
+ const reuseThreshold = this.getVariable("reuseThreshold", 3);
98
+ const daysToCheck = this.getVariable("daysToCheck", 7);
99
+ const matchingPosts = this.getPosts()
100
+ .filter(post => post.createdAt > (0, date_fns_1.subDays)(new Date(), daysToCheck) && linkRegexes.some(regex => new RegExp(regex).test(post.url)) && !post.crosspostParentId)
101
+ .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
102
+ .slice(0, 10);
103
+ if (matchingPosts.length < requiredLinks) {
104
+ return false;
105
+ }
106
+ const distinctLinks = Array.from(new Set(matchingPosts.map(post => post.url)));
107
+ const reuses = await Promise.all(distinctLinks.map(async (link) => {
108
+ const distinctUsers = await this.getDistinctUsersForLink(link);
109
+ return { link, distinctUsers };
110
+ }));
111
+ const reusedOverThreshold = reuses.filter(({ distinctUsers }) => distinctUsers.length >= reuseThreshold);
112
+ if (reusedOverThreshold.length < requiredLinks) {
113
+ return false;
114
+ }
115
+ if (!(await this.isEligibleUserForEvaluator(user.username, true))) {
116
+ return false;
117
+ }
118
+ const eligibleUsers = new Map();
119
+ // Now we need to check if any of the distinct users are eligible for this evaluator
120
+ const filteredReuses = [];
121
+ for (const { link, distinctUsers } of reusedOverThreshold) {
122
+ const eligibleDistinctUsers = [];
123
+ for (const username of distinctUsers) {
124
+ if (username === user.username) {
125
+ eligibleDistinctUsers.push(username);
126
+ continue;
127
+ }
128
+ const isEligibleFromCache = eligibleUsers.get(username);
129
+ if (isEligibleFromCache !== undefined) {
130
+ if (isEligibleFromCache) {
131
+ eligibleDistinctUsers.push(username);
132
+ continue;
133
+ }
134
+ else {
135
+ const isEligible = await this.isEligibleUserForEvaluator(username);
136
+ eligibleUsers.set(username, isEligible);
137
+ if (isEligible) {
138
+ eligibleDistinctUsers.push(username);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ filteredReuses.push({ link, distinctUsers: eligibleDistinctUsers });
144
+ }
145
+ const filteredReusedOverThreshold = filteredReuses.filter(({ distinctUsers }) => distinctUsers.length >= reuseThreshold);
146
+ if (filteredReusedOverThreshold.length < requiredLinks) {
147
+ return false;
148
+ }
149
+ this.addHitReason({
150
+ reason: `User has ${filteredReusedOverThreshold.length} links reused by at least ${reuseThreshold} distinct users`,
151
+ details: filteredReusedOverThreshold.map(({ link, distinctUsers }) => ({
152
+ key: link,
153
+ value: `Also used by by: ${distinctUsers.filter(username => username !== user.username).map(username => `u/${username}`).join(", ")}`,
154
+ })),
155
+ });
156
+ return true;
157
+ }
158
+ }
159
+ exports.EvaluateLinkReuse = EvaluateLinkReuse;
@@ -0,0 +1,15 @@
1
+ import { CommentCreate } from "@devvit/protos";
2
+ import { UserEvaluatorBase } from "./UserEvaluatorBase.js";
3
+ import { Comment, Post } from "@devvit/public-api";
4
+ import { UserExtended } from "../extendedDevvit.js";
5
+ export declare class EvaluateMixedBot extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ private readonly emDashRegex;
9
+ private eligibleComment;
10
+ private eligiblePost;
11
+ preEvaluateComment(event: CommentCreate): boolean;
12
+ preEvaluatePost(post: Post): boolean;
13
+ preEvaluateUser(user: UserExtended): boolean;
14
+ evaluate(user: UserExtended, history: (Post | Comment)[]): boolean;
15
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluateMixedBot = void 0;
4
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
5
+ const date_fns_1 = require("date-fns");
6
+ const tid_js_1 = require("@devvit/public-api/types/tid.js");
7
+ const evaluatorHelpers_js_1 = require("./evaluatorHelpers.js");
8
+ class EvaluateMixedBot extends UserEvaluatorBase_js_1.UserEvaluatorBase {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.name = "Mixed Bot";
12
+ this.shortname = "mixed-bot";
13
+ this.emDashRegex = /\w—\w/i;
14
+ }
15
+ eligibleComment(comment) {
16
+ const isEligible = (0, tid_js_1.isLinkId)(comment.parentId)
17
+ && comment.body.split("\n\n").length <= 3
18
+ && (comment.body.slice(0, 25).includes(",")
19
+ || comment.body.slice(0, 25).includes(".")
20
+ || this.emDashRegex.test(comment.body)
21
+ || !comment.body.includes("\n")
22
+ || comment.body === comment.body.toLowerCase()
23
+ || comment.body.length < 50
24
+ || comment.body.includes("\n\n\n\n"));
25
+ return isEligible;
26
+ }
27
+ eligiblePost(post) {
28
+ if (post.subredditName === "WhatIsMyCQS") {
29
+ return true;
30
+ }
31
+ if (!post.url) {
32
+ return false;
33
+ }
34
+ if (post.url.startsWith("/")) {
35
+ return false;
36
+ }
37
+ const redditDomains = this.getGenericVariable("redditdomains", []);
38
+ const domain = (0, evaluatorHelpers_js_1.domainFromUrl)(post.url);
39
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
40
+ return (domain && redditDomains.includes(domain)) || post.subredditName === "WhatIsMyCQS";
41
+ }
42
+ preEvaluateComment(event) {
43
+ if (!event.comment) {
44
+ return false;
45
+ }
46
+ return this.eligibleComment(event.comment);
47
+ }
48
+ preEvaluatePost(post) {
49
+ return this.eligiblePost(post);
50
+ }
51
+ preEvaluateUser(user) {
52
+ return (user.createdAt < (0, date_fns_1.subYears)(new Date(), 5) || user.createdAt > (0, date_fns_1.subMonths)(new Date(), 6)) && user.commentKarma < 1000;
53
+ }
54
+ evaluate(user, history) {
55
+ if (history.length > 90) {
56
+ return false;
57
+ }
58
+ const olderContentCount = history.filter(item => item.createdAt < (0, date_fns_1.subYears)(new Date(), 5)).length;
59
+ if (user.createdAt > (0, date_fns_1.subYears)(new Date(), 5) && olderContentCount > 5) {
60
+ return false;
61
+ }
62
+ const posts = this.getPosts(history, { since: (0, date_fns_1.subMonths)(new Date(), 1), omitRemoved: true });
63
+ const comments = this.getComments(history, { since: (0, date_fns_1.subMonths)(new Date(), 1) });
64
+ if (posts.length === 0 || comments.length === 0) {
65
+ return false;
66
+ }
67
+ if (!posts.every(post => this.eligiblePost(post))) {
68
+ return false;
69
+ }
70
+ if (!comments.every(comment => this.eligibleComment(comment))) {
71
+ return false;
72
+ }
73
+ if (!posts.some(post => post.title === post.title.toLowerCase() || post.title === post.title.toUpperCase())) {
74
+ return false;
75
+ }
76
+ if (!comments.some(comment => this.emDashRegex.test(comment.body))) {
77
+ return false;
78
+ }
79
+ return true;
80
+ }
81
+ }
82
+ exports.EvaluateMixedBot = EvaluateMixedBot;
@@ -0,0 +1,16 @@
1
+ import { Post } from "@devvit/public-api";
2
+ import { CommentCreate } from "@devvit/protos";
3
+ import { UserEvaluatorBase } from "./UserEvaluatorBase.js";
4
+ import { UserExtended } from "@fsvreddit/fsv-devvit-helpers";
5
+ export declare class EvaluateObfuscatedBioKeywords extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ private getKeywords;
10
+ private getAllowedTerms;
11
+ private bioTextMatches;
12
+ preEvaluateComment(event: CommentCreate): boolean;
13
+ preEvaluatePost(_: Post): boolean;
14
+ preEvaluateUser(user: UserExtended): boolean;
15
+ evaluate(_user: UserExtended): boolean;
16
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluateObfuscatedBioKeywords = void 0;
4
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
5
+ class EvaluateObfuscatedBioKeywords extends UserEvaluatorBase_js_1.UserEvaluatorBase {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.name = "Obfuscated Bio Keywords Bot";
9
+ this.shortname = "obfuscatedbiowords";
10
+ this.banContentThreshold = 0;
11
+ }
12
+ getKeywords() {
13
+ return this.getVariable("keywords", []);
14
+ }
15
+ getAllowedTerms() {
16
+ return this.getVariable("allowedterms", []);
17
+ }
18
+ bioTextMatches(bio) {
19
+ if (!bio) {
20
+ return false;
21
+ }
22
+ const keywords = this.getKeywords();
23
+ const allowedTerms = this.getAllowedTerms();
24
+ for (const keyword of keywords) {
25
+ // eslint-disable-next-line @typescript-eslint/no-misused-spread
26
+ const keywordLetters = [...keyword.toLowerCase()];
27
+ for (let i = 0; i < keywordLetters.length; i++) {
28
+ keywordLetters[i] = keywordLetters[i].replace("i", "[ie1]");
29
+ keywordLetters[i] = keywordLetters[i].replace("o", "[o0]");
30
+ keywordLetters[i] = keywordLetters[i].replace("a", "[a4@]");
31
+ keywordLetters[i] = keywordLetters[i].replace("e", "[ei3]");
32
+ keywordLetters[i] = keywordLetters[i].replace("s", "[s5]");
33
+ keywordLetters[i] = keywordLetters[i].replace("t", "[t7]");
34
+ keywordLetters[i] = keywordLetters[i].replace("b", "[b6]");
35
+ keywordLetters[i] = keywordLetters[i].replace("g", "[g9]");
36
+ keywordLetters[i] = keywordLetters[i].replace("l", "[l1]");
37
+ keywordLetters[i] = keywordLetters[i].replace("z", "[z2]");
38
+ }
39
+ const regexComponents = [
40
+ keywordLetters[0] + ".?" + keywordLetters.slice(1).join(".{0,2}"),
41
+ keywordLetters.join("."),
42
+ keywordLetters.join(".{2}"),
43
+ ];
44
+ const regexText = "(?:" + regexComponents.join("|") + ")";
45
+ const regex = new RegExp("\\b" + regexText + "\\b", "i");
46
+ const matches = bio.match(regex);
47
+ if (matches?.length !== 1) {
48
+ continue;
49
+ }
50
+ if (matches[0].toLowerCase() === keyword.toLowerCase()) {
51
+ continue;
52
+ }
53
+ if (allowedTerms.includes(matches[0].toLowerCase())) {
54
+ continue;
55
+ }
56
+ this.addHitReason(`Bio text matched obfuscated keyword: ${keyword} (matched "${matches[0]}")`);
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ preEvaluateComment(event) {
62
+ return this.bioTextMatches(event.author?.description);
63
+ }
64
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
65
+ preEvaluatePost(_) {
66
+ return this.getKeywords().length > 0;
67
+ }
68
+ preEvaluateUser(user) {
69
+ if (user.commentKarma > 1000 || user.linkKarma > 5000) {
70
+ return false;
71
+ }
72
+ return this.bioTextMatches(user.userDescription);
73
+ }
74
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
75
+ evaluate(_user) {
76
+ return true;
77
+ }
78
+ }
79
+ exports.EvaluateObfuscatedBioKeywords = EvaluateObfuscatedBioKeywords;
@@ -0,0 +1,15 @@
1
+ import { Post } from "@devvit/public-api";
2
+ import { CommentCreate } from "@devvit/protos";
3
+ import { EvaluatorRegex, UserEvaluatorBase, ValidationIssue } from "./UserEvaluatorBase.js";
4
+ import { UserExtended } from "@fsvreddit/fsv-devvit-helpers";
5
+ export declare class EvaluatePinnedPostTitles extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ validateVariables(): ValidationIssue[];
10
+ gatherRegexes(): EvaluatorRegex[];
11
+ preEvaluateComment(_: CommentCreate): boolean;
12
+ preEvaluatePost(post: Post): boolean;
13
+ preEvaluateUser(user: UserExtended): boolean;
14
+ evaluate(_: UserExtended): boolean;
15
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EvaluatePinnedPostTitles = void 0;
7
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
8
+ const evaluatorHelpers_js_1 = require("./evaluatorHelpers.js");
9
+ const markdown_escape_1 = __importDefault(require("markdown-escape"));
10
+ const lodash_1 = require("lodash");
11
+ class EvaluatePinnedPostTitles extends UserEvaluatorBase_js_1.UserEvaluatorBase {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.name = "Sticky Post Title Bot";
15
+ this.shortname = "pinnedpost";
16
+ this.banContentThreshold = 1;
17
+ }
18
+ validateVariables() {
19
+ const results = [];
20
+ const regexes = this.gatherRegexes();
21
+ for (const regexVal of regexes) {
22
+ let regex;
23
+ try {
24
+ regex = new RegExp(regexVal.regex, regexVal.flags);
25
+ }
26
+ catch {
27
+ results.push({ severity: "error", message: `Invalid regex in sticky post title: ${regexVal.regex}` });
28
+ continue;
29
+ }
30
+ if (regex.test("")) {
31
+ results.push({ severity: "error", message: `Sticky post title regex is too greedy: ${regexVal.regex}` });
32
+ }
33
+ }
34
+ return results;
35
+ }
36
+ gatherRegexes() {
37
+ const bannableTitles = this.getVariable("bantext", []);
38
+ return (0, lodash_1.uniq)(bannableTitles.map(title => ({
39
+ evaluatorName: this.name,
40
+ regex: title,
41
+ flags: "u",
42
+ })));
43
+ }
44
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
+ preEvaluateComment(_) {
46
+ return false;
47
+ }
48
+ preEvaluatePost(post) {
49
+ const domain = (0, evaluatorHelpers_js_1.domainFromUrl)(post.url);
50
+ return domain === "reddit.com" && !post.crosspostParentId;
51
+ }
52
+ preEvaluateUser(user) {
53
+ const maxCommentKarma = this.getVariable("maxCommentKarma", 2000);
54
+ const maxLinkKarma = this.getVariable("maxLinkKarma", 5000);
55
+ if (user.commentKarma > maxCommentKarma || user.linkKarma > maxLinkKarma) {
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
61
+ evaluate(_) {
62
+ const stickyPosts = this.getPosts().filter(post => post.stickied && !post.crosspostParentId);
63
+ if (stickyPosts.length === 0) {
64
+ return false;
65
+ }
66
+ const regexes = this.gatherRegexes().map(r => new RegExp(r.regex, r.flags));
67
+ const matchedBanRegex = regexes.find(regex => stickyPosts.some(post => regex.test(post.title)));
68
+ if (matchedBanRegex) {
69
+ const matchedPost = stickyPosts.find(post => matchedBanRegex.test(post.title));
70
+ this.addHitReason(`Sticky post title "${matchedPost?.title}" matched regex: ${(0, markdown_escape_1.default)(matchedBanRegex.source)}`);
71
+ this.canAutoBan = true;
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ }
77
+ exports.EvaluatePinnedPostTitles = EvaluatePinnedPostTitles;
@@ -0,0 +1,16 @@
1
+ import { Post } from "@devvit/public-api";
2
+ import { CommentCreate } from "@devvit/protos";
3
+ import { EvaluatorRegex, UserEvaluatorBase, ValidationIssue } from "./UserEvaluatorBase.js";
4
+ import { UserExtended } from "@fsvreddit/fsv-devvit-helpers";
5
+ export declare class EvaluatePostTitle extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ validateVariables(): ValidationIssue[];
10
+ gatherRegexes(): EvaluatorRegex[];
11
+ preEvaluateComment(_: CommentCreate): boolean;
12
+ private getTitles;
13
+ preEvaluatePost(post: Post): boolean;
14
+ preEvaluateUser(user: UserExtended): boolean;
15
+ evaluate(_: UserExtended): boolean;
16
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EvaluatePostTitle = void 0;
7
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
8
+ const markdown_escape_1 = __importDefault(require("markdown-escape"));
9
+ const date_fns_1 = require("date-fns");
10
+ const lodash_1 = require("lodash");
11
+ class EvaluatePostTitle extends UserEvaluatorBase_js_1.UserEvaluatorBase {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.name = "Bad Post Title Bot";
15
+ this.shortname = "posttitle";
16
+ this.banContentThreshold = 1;
17
+ }
18
+ validateVariables() {
19
+ const results = [];
20
+ const regexes = this.gatherRegexes().map(r => r.regex);
21
+ for (const regexVal of regexes) {
22
+ let regex;
23
+ try {
24
+ regex = new RegExp(regexVal, "u");
25
+ }
26
+ catch {
27
+ results.push({ severity: "error", message: `Invalid regex in post title: ${regexVal}` });
28
+ continue;
29
+ }
30
+ if (regex.test("")) {
31
+ results.push({ severity: "error", message: `Post title regex is too greedy: ${regexVal}` });
32
+ }
33
+ }
34
+ return results;
35
+ }
36
+ gatherRegexes() {
37
+ const bannableTitles = this.getTitles();
38
+ return (0, lodash_1.uniq)(bannableTitles.map(title => ({
39
+ evaluatorName: this.name,
40
+ regex: title,
41
+ flags: "u",
42
+ })));
43
+ }
44
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
+ preEvaluateComment(_) {
46
+ return false;
47
+ }
48
+ getTitles() {
49
+ const prefixesToIgnore = this.getVariable("ignoredRegexPrefixes", []);
50
+ return this.getVariable("bantext", []).filter(title => !prefixesToIgnore.some(prefix => title.startsWith(prefix)));
51
+ }
52
+ preEvaluatePost(post) {
53
+ if (post.crosspostParentId) {
54
+ return false;
55
+ }
56
+ const bannableTitles = this.getTitles();
57
+ return bannableTitles.some(title => new RegExp(title, "u").test(post.title));
58
+ }
59
+ preEvaluateUser(user) {
60
+ const maxCommentKarma = this.getVariable("maxCommentKarma", 2000);
61
+ const maxLinkKarma = this.getVariable("maxLinkKarma", 5000);
62
+ if (user.commentKarma > maxCommentKarma && user.linkKarma > maxLinkKarma) {
63
+ return false;
64
+ }
65
+ return true;
66
+ }
67
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
68
+ evaluate(_) {
69
+ const userPosts = this.getPosts({ since: (0, date_fns_1.subWeeks)(new Date(), 1) }).filter(post => post.nsfw && !post.crosspostParentId);
70
+ if (userPosts.length === 0) {
71
+ return false;
72
+ }
73
+ const bannableTitles = this.gatherRegexes().map(title => ({ pattern: title.regex, regex: new RegExp(title.regex, title.flags) }));
74
+ const nonMatchingTitles = new Set();
75
+ for (const title of userPosts.map(post => post.title)) {
76
+ if (nonMatchingTitles.has(title)) {
77
+ continue;
78
+ }
79
+ const matchedBanRegex = bannableTitles.find(bannable => bannable.regex.test(title));
80
+ if (!matchedBanRegex) {
81
+ nonMatchingTitles.add(title);
82
+ continue;
83
+ }
84
+ this.addHitReason(`Post title "${title}" matched bannable regex: ${(0, markdown_escape_1.default)(matchedBanRegex.pattern)}`);
85
+ this.canAutoBan = true;
86
+ return true;
87
+ }
88
+ return false;
89
+ }
90
+ }
91
+ exports.EvaluatePostTitle = EvaluatePostTitle;