@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,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 EvaluatePostTitleDefinedHandles 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,109 @@
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.EvaluatePostTitleDefinedHandles = void 0;
7
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
8
+ const date_fns_1 = require("date-fns");
9
+ const lodash_1 = require("lodash");
10
+ const regjsparser_1 = require("regjsparser");
11
+ const markdown_escape_1 = __importDefault(require("markdown-escape"));
12
+ class EvaluatePostTitleDefinedHandles extends UserEvaluatorBase_js_1.UserEvaluatorBase {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.name = "Bad Post Title Defined Handles Bot";
16
+ this.shortname = "posttitledefinedhandles";
17
+ this.banContentThreshold = 1;
18
+ }
19
+ validateVariables() {
20
+ const results = [];
21
+ let regexes;
22
+ try {
23
+ regexes = this.gatherRegexes().map(r => r.regex);
24
+ }
25
+ catch (error) {
26
+ const message = error instanceof Error ? error.message : "Unknown error";
27
+ results.push({ severity: "error", message: `Invalid defined handles list: ${message}` });
28
+ return results;
29
+ }
30
+ for (const regexVal of regexes) {
31
+ let regex;
32
+ try {
33
+ regex = new RegExp(regexVal, "u");
34
+ }
35
+ catch {
36
+ results.push({ severity: "error", message: `Invalid regex in post title: ${regexVal}` });
37
+ continue;
38
+ }
39
+ if (regex.test("")) {
40
+ results.push({ severity: "error", message: `Post title regex is too greedy: ${regexVal}` });
41
+ }
42
+ }
43
+ return results;
44
+ }
45
+ gatherRegexes() {
46
+ const prefix = this.getVariable("prefix", "");
47
+ const suffix = this.getVariable("suffix", "");
48
+ const definedHandles = this.getModuleVariable("substitutions", "definedhandles", "");
49
+ if (!definedHandles) {
50
+ return [];
51
+ }
52
+ const parsed = (0, regjsparser_1.parse)(definedHandles, "u");
53
+ if (parsed.type !== "disjunction") {
54
+ return [];
55
+ }
56
+ const regexes = parsed.body.map(part => `${prefix}${part.raw}${suffix}`);
57
+ return (0, lodash_1.uniq)(regexes.map(regex => ({
58
+ evaluatorName: this.name,
59
+ regex,
60
+ flags: "u",
61
+ })));
62
+ }
63
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
64
+ preEvaluateComment(_) {
65
+ return false;
66
+ }
67
+ preEvaluatePost(post) {
68
+ if (post.crosspostParentId) {
69
+ return false;
70
+ }
71
+ const problematicTitles = this.gatherRegexes().map(r => r.regex);
72
+ return problematicTitles.some(title => new RegExp(title, "u").test(post.title));
73
+ }
74
+ preEvaluateUser(user) {
75
+ const maxCommentKarma = this.getVariable("maxCommentKarma", 2000);
76
+ const maxLinkKarma = this.getVariable("maxLinkKarma", 5000);
77
+ if (user.commentKarma > maxCommentKarma && user.linkKarma > maxLinkKarma) {
78
+ return false;
79
+ }
80
+ return true;
81
+ }
82
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
83
+ evaluate(_) {
84
+ const userPosts = this.getPosts({ since: (0, date_fns_1.subWeeks)(new Date(), 1) }).filter(post => post.isNsfw() && !post.crosspostParentId);
85
+ if (userPosts.length === 0) {
86
+ return false;
87
+ }
88
+ const regexes = this.gatherRegexes().map(r => ({ pattern: r.regex, regex: new RegExp(r.regex, r.flags) }));
89
+ if (regexes.length === 0) {
90
+ return false;
91
+ }
92
+ const nonMatchingTitles = new Set();
93
+ for (const title of userPosts.map(post => post.title)) {
94
+ if (nonMatchingTitles.has(title)) {
95
+ continue;
96
+ }
97
+ const matchedRegex = regexes.find(r => r.regex.test(title));
98
+ if (!matchedRegex) {
99
+ nonMatchingTitles.add(title);
100
+ continue;
101
+ }
102
+ this.addHitReason(`Post title "${title}" matched bannable regex: ${(0, markdown_escape_1.default)(matchedRegex.pattern)}`);
103
+ this.canAutoBan = true;
104
+ return true;
105
+ }
106
+ return false;
107
+ }
108
+ }
109
+ exports.EvaluatePostTitleDefinedHandles = EvaluatePostTitleDefinedHandles;
@@ -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 EvaluatePostTitleMulti extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ validateVariables(): ValidationIssue[];
10
+ getVariableOverrides(): Record<string, unknown>;
11
+ gatherRegexes(): EvaluatorRegex[];
12
+ preEvaluateComment(_: CommentCreate): boolean;
13
+ preEvaluatePost(post: Post): boolean;
14
+ preEvaluateUser(user: UserExtended): boolean;
15
+ evaluate(_: UserExtended): boolean;
16
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluatePostTitleMulti = void 0;
4
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
5
+ const date_fns_1 = require("date-fns");
6
+ const lodash_1 = require("lodash");
7
+ class EvaluatePostTitleMulti extends UserEvaluatorBase_js_1.UserEvaluatorBase {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.name = "Bad Post Title Multi Bot";
11
+ this.shortname = "posttitlemulti";
12
+ this.banContentThreshold = 1;
13
+ }
14
+ validateVariables() {
15
+ const regexes = this.gatherRegexes();
16
+ const results = [];
17
+ for (const regexObj of regexes) {
18
+ let regex;
19
+ try {
20
+ regex = new RegExp(regexObj.regex, regexObj.flags);
21
+ }
22
+ catch {
23
+ results.push({ severity: "error", message: `Invalid regex: ${regexObj.regex}` });
24
+ continue;
25
+ }
26
+ if (!regexObj.regex.startsWith("^")) {
27
+ results.push({ severity: "warning", message: `Regex must be anchored to start with \`^\`: ${regexObj.regex}` });
28
+ continue;
29
+ }
30
+ if (regex.test("")) {
31
+ results.push({ severity: "error", message: `Regex is too greedy: ${regexObj.regex}` });
32
+ }
33
+ }
34
+ return results;
35
+ }
36
+ getVariableOverrides() {
37
+ const regexes = this.getVariable("regexes", []);
38
+ if (regexes.some(r => Array.isArray(r))) {
39
+ return {
40
+ regexes: regexes.flat(),
41
+ };
42
+ }
43
+ else {
44
+ return {};
45
+ }
46
+ }
47
+ gatherRegexes() {
48
+ const bannableTitles = this.getVariable("regexes", []);
49
+ return bannableTitles.map(title => ({
50
+ evaluatorName: this.name,
51
+ regex: title,
52
+ flags: "u",
53
+ }));
54
+ }
55
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
56
+ preEvaluateComment(_) {
57
+ return false;
58
+ }
59
+ preEvaluatePost(post) {
60
+ if (post.crosspostParentId) {
61
+ return false;
62
+ }
63
+ const regexes = this.gatherRegexes();
64
+ return regexes.some(regexObj => new RegExp(regexObj.regex, regexObj.flags).test(post.title));
65
+ }
66
+ preEvaluateUser(user) {
67
+ return user.nsfw;
68
+ }
69
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
70
+ evaluate(_) {
71
+ const dateCutoff = this.getVariable("dateCutoffWeeks", 4);
72
+ const userPosts = this.getPosts({ since: (0, date_fns_1.subWeeks)(new Date(), dateCutoff) }).filter(post => post.nsfw && !post.crosspostParentId);
73
+ if (userPosts.length === 0) {
74
+ return false;
75
+ }
76
+ const distinctTitles = (0, lodash_1.uniq)(userPosts.map(post => post.title));
77
+ const regexes = this.gatherRegexes();
78
+ const matchedRegexes = regexes.filter(regexObj => distinctTitles.some(postTitle => new RegExp(regexObj.regex, regexObj.flags).test(postTitle)));
79
+ const matchesNeeded = this.getVariable("matchesNeeded", 4);
80
+ if (matchedRegexes.length < matchesNeeded) {
81
+ return false;
82
+ }
83
+ const regexesInOutput = this.getVariable("regexesInOutput", 5);
84
+ this.addHitReason(`User has ${matchedRegexes.length} bad post titles: ${matchedRegexes.slice(0, regexesInOutput).map(r => `\`${r.regex}\``).join(", ")}`);
85
+ return true;
86
+ }
87
+ }
88
+ exports.EvaluatePostTitleMulti = EvaluatePostTitleMulti;
@@ -0,0 +1,13 @@
1
+ import { Comment, Post } from "@devvit/public-api";
2
+ import { CommentCreate } from "@devvit/protos";
3
+ import { UserEvaluatorBase } from "./UserEvaluatorBase.js";
4
+ import { UserExtended } from "../extendedDevvit.js";
5
+ export declare class EvaluateRapidFireBot extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ preEvaluateComment(_: CommentCreate): boolean;
10
+ preEvaluatePost(_: Post): boolean;
11
+ preEvaluateUser(user: UserExtended): boolean;
12
+ evaluate(_: UserExtended, history: (Post | Comment)[]): boolean;
13
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluateRapidFireBot = void 0;
4
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
5
+ const date_fns_1 = require("date-fns");
6
+ class EvaluateRapidFireBot extends UserEvaluatorBase_js_1.UserEvaluatorBase {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.name = "Rapid Fire Bot";
10
+ this.shortname = "rapidfire";
11
+ this.banContentThreshold = 5;
12
+ }
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
+ preEvaluateComment(_) {
15
+ return false;
16
+ }
17
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
18
+ preEvaluatePost(_) {
19
+ return true;
20
+ }
21
+ preEvaluateUser(user) {
22
+ if (user.commentKarma > this.getVariable("maxcommentkarma", 500)) {
23
+ return false;
24
+ }
25
+ if (user.linkKarma > this.getVariable("maxpostkarma", 500)) {
26
+ return false;
27
+ }
28
+ if (user.createdAt < (0, date_fns_1.subDays)(new Date(), this.getVariable("maxaccountageindays", 28))) {
29
+ return false;
30
+ }
31
+ return true;
32
+ }
33
+ evaluate(_, history) {
34
+ const sortedHistory = [...history.filter(item => item.createdAt > (0, date_fns_1.subMonths)(new Date(), 1))]
35
+ .sort((a, b) => a.createdAt > b.createdAt ? -1 : 1);
36
+ const periodInMinutes = this.getVariable("periodinminutes", 1);
37
+ const numberOfItems = this.getVariable("numberofitems", 10);
38
+ for (const item of sortedHistory) {
39
+ const itemsInPeriod = sortedHistory.filter(otherItem => otherItem.createdAt < item.createdAt && otherItem.createdAt > (0, date_fns_1.subMinutes)(item.createdAt, periodInMinutes));
40
+ if (itemsInPeriod.length >= numberOfItems) {
41
+ this.addHitReason(`User has ${itemsInPeriod.length} items in a ${periodInMinutes} minute period up to ${item.id}`);
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ }
48
+ exports.EvaluateRapidFireBot = EvaluateRapidFireBot;
@@ -0,0 +1,19 @@
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 EvaluateRepostBot extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ needsOpenAiKey: boolean;
10
+ private minWordsInTitle;
11
+ private isEligiblePost;
12
+ preEvaluatePost(post: Post): boolean;
13
+ preEvaluateComment(_: CommentCreate): boolean;
14
+ preEvaluateUser(user: UserExtended): boolean;
15
+ private getImageUrl;
16
+ private postIdToUrl;
17
+ private getPostSimilarity;
18
+ evaluate(user: UserExtended): Promise<boolean>;
19
+ }
@@ -0,0 +1,167 @@
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.EvaluateRepostBot = void 0;
7
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
8
+ const evaluatorHelpers_js_1 = require("./evaluatorHelpers.js");
9
+ const date_fns_1 = require("date-fns");
10
+ const openai_1 = __importDefault(require("openai"));
11
+ const zod_1 = __importDefault(require("zod"));
12
+ const zod_js_1 = require("openai/helpers/zod.js");
13
+ const constants_js_1 = require("../constants.js");
14
+ const wordcount_1 = require("@wordpress/wordcount");
15
+ class EvaluateRepostBot extends UserEvaluatorBase_js_1.UserEvaluatorBase {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.name = "Repost Bot";
19
+ this.shortname = "repost";
20
+ this.banContentThreshold = 1;
21
+ this.needsOpenAiKey = true;
22
+ }
23
+ isEligiblePost(post) {
24
+ if (post.nsfw || post.crosspostParentId) {
25
+ return false;
26
+ }
27
+ this.minWordsInTitle ?? (this.minWordsInTitle = this.getVariable("minWordsInTitle", 2));
28
+ if ((0, wordcount_1.count)(post.title, "words") < this.minWordsInTitle) {
29
+ return false;
30
+ }
31
+ return (0, evaluatorHelpers_js_1.domainFromUrl)(post.url) === "i.redd.it" || post.gallery.length > 0;
32
+ }
33
+ preEvaluatePost(post) {
34
+ return this.isEligiblePost(post);
35
+ }
36
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
+ preEvaluateComment(_) {
38
+ return false;
39
+ }
40
+ preEvaluateUser(user) {
41
+ const maxAgeInMonths = this.getVariable("maxAgeInMonths", 1);
42
+ return user.createdAt > (0, date_fns_1.subMonths)(new Date(), maxAgeInMonths);
43
+ }
44
+ getImageUrl(post) {
45
+ if (post.gallery.length > 0) {
46
+ return post.gallery[0].url;
47
+ }
48
+ return post.url;
49
+ }
50
+ postIdToUrl(postId) {
51
+ return `https://redd.it/${postId.substring(3)}`;
52
+ }
53
+ async getPostSimilarity(postA, postB) {
54
+ const cacheKey = `bbe:RepostBot:similarity:${postA.id}:${postB.id}`;
55
+ const redis = this.context.appSlug === constants_js_1.MAIN_APP_NAME ? this.context.redis.global : this.context.redis;
56
+ const cachedResult = await redis.get(cacheKey);
57
+ if (cachedResult) {
58
+ console.log(`Repost Checks: Using cached similarity between ${this.postIdToUrl(postA.id)} and ${this.postIdToUrl(postB.id)}: ${cachedResult}`);
59
+ return parseFloat(cachedResult);
60
+ }
61
+ const openAI = new openai_1.default({ apiKey: this.openAiKey });
62
+ const responseFormat = zod_1.default.object({
63
+ similarity: zod_1.default.number().min(0).max(1),
64
+ });
65
+ const prompt = this.getVariable("prompt", "You are provided with the URLs of two images. Your task is to determine how visually similar they are. Return the similarity from 0 to 1, where 0 is completely different and 1 is identical. Only return the similarity as a number in JSON format, do not include any other text.");
66
+ const content = [
67
+ {
68
+ type: "input_text",
69
+ text: prompt,
70
+ },
71
+ {
72
+ type: "input_image",
73
+ // eslint-disable-next-line camelcase
74
+ image_url: this.getImageUrl(postA),
75
+ detail: "low",
76
+ },
77
+ {
78
+ type: "input_image",
79
+ // eslint-disable-next-line camelcase
80
+ image_url: this.getImageUrl(postB),
81
+ detail: "low",
82
+ },
83
+ ];
84
+ try {
85
+ const response = await openAI.responses.create({
86
+ model: this.getVariable("openAiModel", "gpt-5.4-nano"),
87
+ input: [
88
+ {
89
+ role: "user",
90
+ content,
91
+ },
92
+ ],
93
+ text: {
94
+ format: (0, zod_js_1.zodTextFormat)(responseFormat, "similarity"),
95
+ },
96
+ });
97
+ const result = JSON.parse(response.output_text);
98
+ await redis.set(cacheKey, result.similarity.toString(), { expiration: (0, date_fns_1.addHours)(new Date(), 1) });
99
+ console.log(`Repost Checks: Tokens used: ${response.usage?.total_tokens}, Similarity between ${this.postIdToUrl(postA.id)} and ${this.postIdToUrl(postB.id)}: ${result.similarity}`);
100
+ return result.similarity;
101
+ }
102
+ catch (error) {
103
+ const message = error instanceof Error ? error.message : String(error);
104
+ console.error(`Error getting post similarity for ${postA.id}: ${message}`);
105
+ return;
106
+ }
107
+ }
108
+ async evaluate(user) {
109
+ if (!this.preEvaluateUser(user)) {
110
+ return false;
111
+ }
112
+ const comments = this.getComments();
113
+ const maxCommentCount = this.getVariable("maxCommentCount", 5);
114
+ if (comments.length > maxCommentCount) {
115
+ return false;
116
+ }
117
+ const posts = this.getPosts();
118
+ if (posts.some(post => !this.isEligiblePost(post))) {
119
+ return false;
120
+ }
121
+ if (posts.length === 0) {
122
+ return false;
123
+ }
124
+ const maxPostCount = this.getVariable("maxPostCount", 5);
125
+ if (posts.length > maxPostCount) {
126
+ return false;
127
+ }
128
+ // Sort posts, latest first
129
+ const sortedPosts = posts.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
130
+ const latestPost = sortedPosts[0];
131
+ // Look for duplicate posts.
132
+ const postDuplicates = await this.context.reddit.searchPosts({
133
+ query: `title:"${latestPost.title}"`,
134
+ subredditName: latestPost.subredditName,
135
+ sort: "new",
136
+ limit: 100,
137
+ }).all().then(posts => posts.filter(post => this.isEligiblePost(post) && post.title === latestPost.title && post.authorName !== latestPost.authorName));
138
+ if (postDuplicates.length === 0) {
139
+ return false;
140
+ }
141
+ const maxDuplicatePostCount = this.getVariable("maxDuplicatePostCount", 50);
142
+ if (postDuplicates.length > maxDuplicatePostCount) {
143
+ return false;
144
+ }
145
+ // Sort duplicates, oldest first
146
+ postDuplicates.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
147
+ const oldestDuplicate = postDuplicates[0];
148
+ if (oldestDuplicate.createdAt > latestPost.createdAt) {
149
+ return false;
150
+ }
151
+ const minDuplicateAgeInDays = this.getVariable("minDuplicateAgeInDays", 7);
152
+ if (oldestDuplicate.createdAt > (0, date_fns_1.subDays)(new Date(), minDuplicateAgeInDays)) {
153
+ return false;
154
+ }
155
+ const similarityNeeded = this.getVariable("similarityNeeded", 0.9);
156
+ const postSimilarity = await this.getPostSimilarity(latestPost, oldestDuplicate);
157
+ if (postSimilarity === undefined) {
158
+ return false;
159
+ }
160
+ if (postSimilarity < similarityNeeded) {
161
+ return false;
162
+ }
163
+ this.addHitReason(`User's post ${this.postIdToUrl(latestPost.id)} is a repost of ${this.postIdToUrl(oldestDuplicate.id)} with similarity ${Math.round(postSimilarity * 100)}%`);
164
+ return true;
165
+ }
166
+ }
167
+ exports.EvaluateRepostBot = EvaluateRepostBot;
@@ -0,0 +1,16 @@
1
+ import { CommentCreate } from "@devvit/protos";
2
+ import { UserEvaluatorBase } from "./UserEvaluatorBase.js";
3
+ import { Post } from "@devvit/public-api";
4
+ import { UserExtended } from "@fsvreddit/fsv-devvit-helpers";
5
+ export declare class EvaluateSelfComment extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ private isSubIgnored;
10
+ private eligibleComment;
11
+ private eligiblePost;
12
+ preEvaluateComment(event: CommentCreate): boolean;
13
+ preEvaluatePost(post: Post): boolean;
14
+ preEvaluateUser(user: UserExtended): boolean;
15
+ evaluate(user: UserExtended): boolean;
16
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluateSelfComment = 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 EvaluateSelfComment extends UserEvaluatorBase_js_1.UserEvaluatorBase {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.name = "Self Comment";
12
+ this.shortname = "selfcomment";
13
+ this.banContentThreshold = 2;
14
+ }
15
+ isSubIgnored() {
16
+ const ignoredSubreddits = this.getVariable("ignoredsubs", []);
17
+ return this.context.subredditName && ignoredSubreddits.includes(this.context.subredditName);
18
+ }
19
+ eligibleComment(comment) {
20
+ return (0, tid_js_1.isLinkId)(comment.parentId)
21
+ && comment.body.split("\n\n").length <= 2;
22
+ }
23
+ eligiblePost(post) {
24
+ const domain = (0, evaluatorHelpers_js_1.domainFromUrl)(post.url);
25
+ const karmaFarmingSubs = this.getGenericVariable("karmafarminglinksubs", []);
26
+ return (domain === "i.redd.it" || domain === "v.redd.it") && !post.nsfw && karmaFarmingSubs.includes(post.subredditName);
27
+ }
28
+ preEvaluateComment(event) {
29
+ if (this.isSubIgnored()) {
30
+ return false;
31
+ }
32
+ if (!event.comment || !event.author) {
33
+ return false;
34
+ }
35
+ return event.post?.authorId === event.author.id && this.eligibleComment(event.comment);
36
+ }
37
+ preEvaluatePost(post) {
38
+ if (this.isSubIgnored()) {
39
+ return false;
40
+ }
41
+ return this.eligiblePost(post);
42
+ }
43
+ preEvaluateUser(user) {
44
+ const ageInDays = this.getVariable("ageindays", 14);
45
+ const maxKarma = this.getVariable("maxkarma", 500);
46
+ return user.createdAt > (0, date_fns_1.subDays)(new Date(), ageInDays) && user.commentKarma < maxKarma;
47
+ }
48
+ evaluate(user) {
49
+ const ignoredSubreddits = this.getVariable("ignoredsubs", []);
50
+ const posts = this.getPosts({ omitRemoved: true }).filter(post => !ignoredSubreddits.includes(post.subredditName) && !post.subredditName.toLowerCase().includes("onlyfans"));
51
+ if (posts.length === 0 || !posts.every(post => this.eligiblePost(post))) {
52
+ return false;
53
+ }
54
+ const comments = this.getComments();
55
+ if (comments.length === 0 || !comments.every(comment => this.eligibleComment(comment))) {
56
+ return false;
57
+ }
58
+ if (!posts.some(post => comments.some(comment => comment.parentId === post.id && !ignoredSubreddits.includes(post.subredditName)))) {
59
+ return false;
60
+ }
61
+ const maxCommentAge = this.getVariable("commentmaxminutes", 1);
62
+ for (const comment of comments.filter(comment => !ignoredSubreddits.includes(comment.subredditName))) {
63
+ const post = posts.find(post => post.id === comment.parentId);
64
+ if (post?.authorId !== user.id) {
65
+ return false;
66
+ }
67
+ if (comment.createdAt > (0, date_fns_1.addMinutes)(post.createdAt, maxCommentAge)) {
68
+ return false;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+ }
74
+ exports.EvaluateSelfComment = EvaluateSelfComment;
@@ -0,0 +1,16 @@
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 EvaluateSocialLinks extends UserEvaluatorBase {
6
+ name: string;
7
+ shortname: string;
8
+ banContentThreshold: number;
9
+ validateVariables(): ValidationIssue[];
10
+ getVariableOverrides(): Record<string, unknown>;
11
+ private getDomains;
12
+ preEvaluateComment(_: CommentCreate): boolean;
13
+ preEvaluatePost(post: Post): boolean;
14
+ preEvaluateUser(user: UserExtended): boolean;
15
+ evaluate(user: UserExtended): Promise<boolean>;
16
+ }