@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.
- package/README.md +1 -0
- package/dist/allEvaluators.d.ts +16 -0
- package/dist/allEvaluators.js +72 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +6 -0
- package/dist/extendedDevvit.d.ts +36 -0
- package/dist/extendedDevvit.js +66 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +23 -0
- package/dist/userEvaluation/EvaluateBadDisplayname.d.ts +18 -0
- package/dist/userEvaluation/EvaluateBadDisplayname.js +81 -0
- package/dist/userEvaluation/EvaluateBadDisplaynameDefinedHandles.d.ts +16 -0
- package/dist/userEvaluation/EvaluateBadDisplaynameDefinedHandles.js +89 -0
- package/dist/userEvaluation/EvaluateBadUsername.d.ts +16 -0
- package/dist/userEvaluation/EvaluateBadUsername.js +75 -0
- package/dist/userEvaluation/EvaluateBioText.d.ts +18 -0
- package/dist/userEvaluation/EvaluateBioText.js +96 -0
- package/dist/userEvaluation/EvaluateBioTextDefinedHandles.d.ts +16 -0
- package/dist/userEvaluation/EvaluateBioTextDefinedHandles.js +99 -0
- package/dist/userEvaluation/EvaluateBotGroupAdvanced.d.ts +124 -0
- package/dist/userEvaluation/EvaluateBotGroupAdvanced.js +1360 -0
- package/dist/userEvaluation/EvaluateBotGroupAdvancedInternal.d.ts +6 -0
- package/dist/userEvaluation/EvaluateBotGroupAdvancedInternal.js +13 -0
- package/dist/userEvaluation/EvaluateBotGroupSubmitters.d.ts +8 -0
- package/dist/userEvaluation/EvaluateBotGroupSubmitters.js +23 -0
- package/dist/userEvaluation/EvaluateCommentPhrase.d.ts +16 -0
- package/dist/userEvaluation/EvaluateCommentPhrase.js +86 -0
- package/dist/userEvaluation/EvaluateDomainSharer.d.ts +18 -0
- package/dist/userEvaluation/EvaluateDomainSharer.js +138 -0
- package/dist/userEvaluation/EvaluateFirstCommentEmDash.d.ts +17 -0
- package/dist/userEvaluation/EvaluateFirstCommentEmDash.js +103 -0
- package/dist/userEvaluation/EvaluateFirstPostWithSelfComment.d.ts +14 -0
- package/dist/userEvaluation/EvaluateFirstPostWithSelfComment.js +53 -0
- package/dist/userEvaluation/EvaluateInconsistentAgeBot.d.ts +17 -0
- package/dist/userEvaluation/EvaluateInconsistentAgeBot.js +91 -0
- package/dist/userEvaluation/EvaluateInconsistentGenderBot.d.ts +17 -0
- package/dist/userEvaluation/EvaluateInconsistentGenderBot.js +99 -0
- package/dist/userEvaluation/EvaluateLinkReuse.d.ts +17 -0
- package/dist/userEvaluation/EvaluateLinkReuse.js +159 -0
- package/dist/userEvaluation/EvaluateMixedBot.d.ts +15 -0
- package/dist/userEvaluation/EvaluateMixedBot.js +82 -0
- package/dist/userEvaluation/EvaluateObfuscatedBioKeywords.d.ts +16 -0
- package/dist/userEvaluation/EvaluateObfuscatedBioKeywords.js +79 -0
- package/dist/userEvaluation/EvaluatePinnedPostTitle.d.ts +15 -0
- package/dist/userEvaluation/EvaluatePinnedPostTitle.js +77 -0
- package/dist/userEvaluation/EvaluatePostTitle.d.ts +16 -0
- package/dist/userEvaluation/EvaluatePostTitle.js +91 -0
- package/dist/userEvaluation/EvaluatePostTitleDefinedHandles.d.ts +15 -0
- package/dist/userEvaluation/EvaluatePostTitleDefinedHandles.js +109 -0
- package/dist/userEvaluation/EvaluatePostTitleMulti.d.ts +16 -0
- package/dist/userEvaluation/EvaluatePostTitleMulti.js +88 -0
- package/dist/userEvaluation/EvaluateRapidFireBot.d.ts +13 -0
- package/dist/userEvaluation/EvaluateRapidFireBot.js +48 -0
- package/dist/userEvaluation/EvaluateRepostBot.d.ts +19 -0
- package/dist/userEvaluation/EvaluateRepostBot.js +167 -0
- package/dist/userEvaluation/EvaluateSelfComment.d.ts +16 -0
- package/dist/userEvaluation/EvaluateSelfComment.js +74 -0
- package/dist/userEvaluation/EvaluateSocialLinks.d.ts +16 -0
- package/dist/userEvaluation/EvaluateSocialLinks.js +101 -0
- package/dist/userEvaluation/EvaluateSuspiciousFirstPost.d.ts +15 -0
- package/dist/userEvaluation/EvaluateSuspiciousFirstPost.js +68 -0
- package/dist/userEvaluation/EvaluateTGGroup.d.ts +16 -0
- package/dist/userEvaluation/EvaluateTGGroup.js +73 -0
- package/dist/userEvaluation/EvaluateTextInNsfwImages.d.ts +14 -0
- package/dist/userEvaluation/EvaluateTextInNsfwImages.js +169 -0
- package/dist/userEvaluation/EvaluateTitleCopyBot.d.ts +13 -0
- package/dist/userEvaluation/EvaluateTitleCopyBot.js +74 -0
- package/dist/userEvaluation/EvaluateWarmupBot.d.ts +15 -0
- package/dist/userEvaluation/EvaluateWarmupBot.js +89 -0
- package/dist/userEvaluation/EvaluateWorldTraveller.d.ts +18 -0
- package/dist/userEvaluation/EvaluateWorldTraveller.js +79 -0
- package/dist/userEvaluation/UserEvaluatorBase.d.ts +67 -0
- package/dist/userEvaluation/UserEvaluatorBase.js +109 -0
- package/dist/userEvaluation/evaluatorHelpers.d.ts +3 -0
- package/dist/userEvaluation/evaluatorHelpers.js +25 -0
- package/dist/userEvaluation/index.d.ts +26 -0
- package/dist/userEvaluation/index.js +42 -0
- package/dist/utility.d.ts +34 -0
- package/dist/utility.js +164 -0
- package/eslint.config.mjs +3 -0
- package/package.json +50 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EvaluateSocialLinks = void 0;
|
|
4
|
+
const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
|
|
5
|
+
const date_fns_1 = require("date-fns");
|
|
6
|
+
const evaluatorHelpers_js_1 = require("./evaluatorHelpers.js");
|
|
7
|
+
const lodash_1 = require("lodash");
|
|
8
|
+
class EvaluateSocialLinks extends UserEvaluatorBase_js_1.UserEvaluatorBase {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
this.name = "Social Links Bot";
|
|
12
|
+
this.shortname = "sociallinks";
|
|
13
|
+
this.banContentThreshold = 0;
|
|
14
|
+
}
|
|
15
|
+
validateVariables() {
|
|
16
|
+
const results = [];
|
|
17
|
+
const badLinks = this.getVariable("badlinks", []);
|
|
18
|
+
for (const link of badLinks) {
|
|
19
|
+
try {
|
|
20
|
+
(0, evaluatorHelpers_js_1.domainFromUrl)(link);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
results.push({ severity: "error", message: `Invalid URL in badlinks: ${link}` });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
getVariableOverrides() {
|
|
29
|
+
const badLinks = this.getVariable("badlinks", []);
|
|
30
|
+
if (badLinks.some(link => Array.isArray(link))) {
|
|
31
|
+
return {
|
|
32
|
+
badlinks: badLinks.flat(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
getDomains() {
|
|
40
|
+
const domains = new Set(this.getGenericVariable("redditdomains", []));
|
|
41
|
+
domains.add("redgifs.com");
|
|
42
|
+
domains.add("instagram.com");
|
|
43
|
+
domains.add("i.redd.it");
|
|
44
|
+
const badLinks = this.getVariable("badlinks", []);
|
|
45
|
+
for (const link of badLinks) {
|
|
46
|
+
try {
|
|
47
|
+
const domain = (0, evaluatorHelpers_js_1.domainFromUrl)(link);
|
|
48
|
+
if (domain) {
|
|
49
|
+
domains.add(domain);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
//
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return Array.from(domains);
|
|
57
|
+
}
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
59
|
+
preEvaluateComment(_) {
|
|
60
|
+
if (this.socialLinks) {
|
|
61
|
+
const badSocialLinks = this.getVariable("badlinks", []);
|
|
62
|
+
return this.socialLinks.some(link => badSocialLinks.some(badLink => link.outboundUrl.startsWith(badLink)));
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
preEvaluatePost(post) {
|
|
67
|
+
return post.nsfw;
|
|
68
|
+
}
|
|
69
|
+
preEvaluateUser(user) {
|
|
70
|
+
const badSocialLinks = this.getVariable("badlinks", []);
|
|
71
|
+
if (badSocialLinks.length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const accountEligible = (user.commentKarma < 500 && user.createdAt > (0, date_fns_1.subMonths)(new Date(), 2))
|
|
75
|
+
|| user.createdAt < (0, date_fns_1.subYears)(new Date(), 5)
|
|
76
|
+
|| user.nsfw;
|
|
77
|
+
return accountEligible;
|
|
78
|
+
}
|
|
79
|
+
async evaluate(user) {
|
|
80
|
+
const badSocialLinks = this.getVariable("badlinks", []);
|
|
81
|
+
if (badSocialLinks.length === 0) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const badLinksFromPosts = badSocialLinks.filter(badLink => this.getPosts().some(post => post.url.startsWith(badLink) && !post.crosspostParentId));
|
|
85
|
+
if (badLinksFromPosts.length > 0) {
|
|
86
|
+
this.addHitReason(`User has bad links in posts: ${(0, lodash_1.uniq)(badLinksFromPosts).join(", ")}`);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
const userSocialLinks = await this.getSocialLinks(user.username);
|
|
90
|
+
if (userSocialLinks.length === 0) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const badSocialLinksFound = userSocialLinks.filter(link => badSocialLinks.some(badLink => link.outboundUrl.startsWith(badLink)));
|
|
94
|
+
if (badSocialLinksFound.length > 0) {
|
|
95
|
+
this.addHitReason(`User has bad social links: ${(0, lodash_1.uniq)(badSocialLinksFound.map(link => link.outboundUrl)).join(", ")}`);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.EvaluateSocialLinks = EvaluateSocialLinks;
|
|
@@ -0,0 +1,15 @@
|
|
|
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 EvaluateSuspiciousFirstPost extends UserEvaluatorBase {
|
|
6
|
+
name: string;
|
|
7
|
+
shortname: string;
|
|
8
|
+
banContentThreshold: number;
|
|
9
|
+
private subList;
|
|
10
|
+
private eligiblePost;
|
|
11
|
+
preEvaluateComment(_: CommentCreate): boolean;
|
|
12
|
+
preEvaluatePost(post: Post): boolean;
|
|
13
|
+
preEvaluateUser(user: UserExtended): boolean;
|
|
14
|
+
evaluate(_: UserExtended): boolean;
|
|
15
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
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.EvaluateSuspiciousFirstPost = void 0;
|
|
7
|
+
const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
|
|
8
|
+
const date_fns_1 = require("date-fns");
|
|
9
|
+
const evaluatorHelpers_js_1 = require("./evaluatorHelpers.js");
|
|
10
|
+
const markdown_escape_1 = __importDefault(require("markdown-escape"));
|
|
11
|
+
class EvaluateSuspiciousFirstPost extends UserEvaluatorBase_js_1.UserEvaluatorBase {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.name = "Suspicious First Post";
|
|
15
|
+
this.shortname = "suspiciousfirstpost";
|
|
16
|
+
this.banContentThreshold = 1;
|
|
17
|
+
}
|
|
18
|
+
subList() {
|
|
19
|
+
return this.getVariable("subreddits", []);
|
|
20
|
+
}
|
|
21
|
+
eligiblePost(post) {
|
|
22
|
+
if (!this.subList().includes(post.subredditName)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const domain = (0, evaluatorHelpers_js_1.domainFromUrl)(post.url);
|
|
26
|
+
return (domain === "i.redd.it" || domain === "v.redd.it")
|
|
27
|
+
|| post.url.startsWith("https://www.reddit.com/gallery/");
|
|
28
|
+
}
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
30
|
+
preEvaluateComment(_) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
preEvaluatePost(post) {
|
|
34
|
+
return this.eligiblePost(post);
|
|
35
|
+
}
|
|
36
|
+
preEvaluateUser(user) {
|
|
37
|
+
const maxAgeInDays = this.getVariable("maxageindays", 14);
|
|
38
|
+
const maxCommentKarma = this.getVariable("maxcommentkarma", 50);
|
|
39
|
+
return user.createdAt > (0, date_fns_1.subDays)(new Date(), maxAgeInDays) && user.commentKarma < maxCommentKarma;
|
|
40
|
+
}
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
42
|
+
evaluate(_) {
|
|
43
|
+
const comments = this.getComments();
|
|
44
|
+
if (comments.length > 1) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const posts = this.getPosts({ omitRemoved: false });
|
|
48
|
+
if (posts.length === 0) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (posts.length > 1) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (!posts.every(post => this.eligiblePost(post))) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (comments.length > 0) {
|
|
58
|
+
const commentDate = comments[0].createdAt;
|
|
59
|
+
const postDate = posts[0].createdAt;
|
|
60
|
+
if (commentDate > postDate) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.addHitReason(`Sole post in ${(0, markdown_escape_1.default)(posts[0].subredditName)}`);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.EvaluateSuspiciousFirstPost = EvaluateSuspiciousFirstPost;
|
|
@@ -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 EvaluateTGGroup extends UserEvaluatorBase {
|
|
6
|
+
name: string;
|
|
7
|
+
shortname: string;
|
|
8
|
+
banContentThreshold: number;
|
|
9
|
+
validateVariables(): ValidationIssue[];
|
|
10
|
+
gatherRegexes(): EvaluatorRegex[];
|
|
11
|
+
private eligiblePost;
|
|
12
|
+
preEvaluateComment(_: CommentCreate): boolean;
|
|
13
|
+
preEvaluatePost(post: Post): boolean;
|
|
14
|
+
preEvaluateUser(user: UserExtended): boolean;
|
|
15
|
+
evaluate(_: UserExtended): boolean;
|
|
16
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EvaluateTGGroup = 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 EvaluateTGGroup extends UserEvaluatorBase_js_1.UserEvaluatorBase {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.name = "Telegram Group Bot";
|
|
11
|
+
this.shortname = "tg-group";
|
|
12
|
+
this.banContentThreshold = 1;
|
|
13
|
+
}
|
|
14
|
+
validateVariables() {
|
|
15
|
+
const results = [];
|
|
16
|
+
const regexes = this.getVariable("bodyregex", []);
|
|
17
|
+
for (const regexVal of regexes) {
|
|
18
|
+
let regex;
|
|
19
|
+
try {
|
|
20
|
+
regex = new RegExp(regexVal);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
results.push({ severity: "error", message: `Invalid regex in TG Group: ${regexVal}` });
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (regex.test("")) {
|
|
27
|
+
results.push({ severity: "error", message: `TG Group regex is too greedy: ${regexVal}` });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
}
|
|
32
|
+
gatherRegexes() {
|
|
33
|
+
const bodyRegexes = this.getVariable("bodyregex", []);
|
|
34
|
+
return (0, lodash_1.uniq)([
|
|
35
|
+
...bodyRegexes.map(bodyRegex => ({
|
|
36
|
+
evaluatorName: this.name,
|
|
37
|
+
regex: bodyRegex,
|
|
38
|
+
})),
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
eligiblePost(post) {
|
|
42
|
+
if (!post.body) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const titleStrings = this.getVariable("titlestrings", []);
|
|
46
|
+
const bodyRegexes = this.getVariable("bodyregex", []);
|
|
47
|
+
return post.nsfw
|
|
48
|
+
&& titleStrings.some(titleString => post.title.includes(titleString))
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
50
|
+
&& bodyRegexes.some(bodyRegex => new RegExp(bodyRegex).test(post.body));
|
|
51
|
+
}
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
53
|
+
preEvaluateComment(_) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
preEvaluatePost(post) {
|
|
57
|
+
return this.eligiblePost(post);
|
|
58
|
+
}
|
|
59
|
+
preEvaluateUser(user) {
|
|
60
|
+
return user.createdAt > (0, date_fns_1.subMonths)(new Date(), 1)
|
|
61
|
+
|| user.commentKarma < 100
|
|
62
|
+
|| user.linkKarma < 50;
|
|
63
|
+
}
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
65
|
+
evaluate(_) {
|
|
66
|
+
const recentPosts = this.getPosts({ since: (0, date_fns_1.subWeeks)(new Date(), 1) }).filter(post => post.nsfw);
|
|
67
|
+
if (recentPosts.length === 0) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return recentPosts.some(post => this.eligiblePost(post));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.EvaluateTGGroup = EvaluateTGGroup;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EvaluatorRegex, ValidationIssue } from "./UserEvaluatorBase";
|
|
2
|
+
import { EvaluateBotGroupAdvanced } from "./EvaluateBotGroupAdvanced";
|
|
3
|
+
import { UserExtended } from "@fsvreddit/fsv-devvit-helpers";
|
|
4
|
+
export declare class EvaluateTextInNsfwImages extends EvaluateBotGroupAdvanced {
|
|
5
|
+
name: string;
|
|
6
|
+
shortname: string;
|
|
7
|
+
banContentThreshold: number;
|
|
8
|
+
readonly needsOpenAiKey = true;
|
|
9
|
+
gatherRegexes(): EvaluatorRegex[];
|
|
10
|
+
validateVariables(): ValidationIssue[];
|
|
11
|
+
private getImageUrl;
|
|
12
|
+
private getTextFromImage;
|
|
13
|
+
evaluate(user: UserExtended): Promise<boolean>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
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.EvaluateTextInNsfwImages = void 0;
|
|
7
|
+
const EvaluateBotGroupAdvanced_1 = require("./EvaluateBotGroupAdvanced");
|
|
8
|
+
const date_fns_1 = require("date-fns");
|
|
9
|
+
const evaluatorHelpers_1 = require("./evaluatorHelpers");
|
|
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_1 = require("../constants");
|
|
14
|
+
class EvaluateTextInNsfwImages extends EvaluateBotGroupAdvanced_1.EvaluateBotGroupAdvanced {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.name = "Text in NSFW Images Bot";
|
|
18
|
+
this.shortname = "nsfwtext";
|
|
19
|
+
this.banContentThreshold = 0;
|
|
20
|
+
this.needsOpenAiKey = true;
|
|
21
|
+
}
|
|
22
|
+
gatherRegexes() {
|
|
23
|
+
const regexesFromSuper = super.gatherRegexes();
|
|
24
|
+
const imageTextRegexes = this.getVariable("imageTextRegexes", []);
|
|
25
|
+
const requiredPostTitleRegexes = this.getVariable("requiredPostTitleRegexes", []);
|
|
26
|
+
const allRegexes = [...imageTextRegexes, ...requiredPostTitleRegexes];
|
|
27
|
+
return [
|
|
28
|
+
...regexesFromSuper,
|
|
29
|
+
...allRegexes.map(regex => ({
|
|
30
|
+
evaluatorName: this.name,
|
|
31
|
+
regex,
|
|
32
|
+
flags: "u",
|
|
33
|
+
})),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
validateVariables() {
|
|
37
|
+
const issues = super.validateVariables();
|
|
38
|
+
const imageTextRegexes = this.getVariable("imageTextRegexes", []);
|
|
39
|
+
if (!Array.isArray(imageTextRegexes)) {
|
|
40
|
+
issues.push({ severity: "error", message: "imageTextRegexes must be an array." });
|
|
41
|
+
}
|
|
42
|
+
for (const regex of imageTextRegexes) {
|
|
43
|
+
try {
|
|
44
|
+
new RegExp(regex, "u");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
issues.push({ severity: "error", message: `Invalid regex in imageTextRegexes: ${regex}` });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const requiredPostTitleRegexes = this.getVariable("requiredPostTitleRegexes", []);
|
|
51
|
+
if (!Array.isArray(requiredPostTitleRegexes)) {
|
|
52
|
+
issues.push({ severity: "error", message: "requiredPostTitleRegexes must be an array." });
|
|
53
|
+
}
|
|
54
|
+
for (const regex of requiredPostTitleRegexes) {
|
|
55
|
+
try {
|
|
56
|
+
new RegExp(regex, "u");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
issues.push({ severity: "error", message: `Invalid regex in requiredPostTitleRegexes: ${regex}` });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return issues;
|
|
63
|
+
}
|
|
64
|
+
getImageUrl(post) {
|
|
65
|
+
if ((0, evaluatorHelpers_1.domainFromUrl)(post.url) === "i.redd.it") {
|
|
66
|
+
return post.url;
|
|
67
|
+
}
|
|
68
|
+
if (post.gallery.length > 0) {
|
|
69
|
+
return post.gallery[0].url;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async getTextFromImage(url) {
|
|
73
|
+
if (!this.openAiKey) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const resultCacheKey = `imageText:${url}`;
|
|
77
|
+
const redis = this.context.subredditName === constants_1.MAIN_APP_NAME ? this.context.redis.global : this.context.redis;
|
|
78
|
+
const cachedResult = await redis.get(resultCacheKey);
|
|
79
|
+
if (cachedResult) {
|
|
80
|
+
console.log(`OpenAI Checks: Using cached result for image ${url}, Extracted Text: ${cachedResult}`);
|
|
81
|
+
return JSON.parse(cachedResult);
|
|
82
|
+
}
|
|
83
|
+
const openAIClient = new openai_1.default({
|
|
84
|
+
apiKey: this.openAiKey,
|
|
85
|
+
});
|
|
86
|
+
const responseFormat = zod_1.default.object({
|
|
87
|
+
extractedText: zod_1.default.string().optional().nullable(),
|
|
88
|
+
});
|
|
89
|
+
const prompt = this.getVariable("prompt", "You are given a list of image URLs. For each image, extract any text that appears in the image. If no text is present, return null.");
|
|
90
|
+
const content = [
|
|
91
|
+
{
|
|
92
|
+
type: "input_text",
|
|
93
|
+
text: prompt,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: "input_image",
|
|
97
|
+
// eslint-disable-next-line camelcase
|
|
98
|
+
image_url: url,
|
|
99
|
+
detail: "low",
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
const model = this.getVariable("openAiModel", "gpt-5.4-nano");
|
|
103
|
+
let response;
|
|
104
|
+
try {
|
|
105
|
+
response = await openAIClient.responses.create({
|
|
106
|
+
model,
|
|
107
|
+
input: [
|
|
108
|
+
{
|
|
109
|
+
role: "user",
|
|
110
|
+
content,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
text: {
|
|
114
|
+
format: (0, zod_js_1.zodTextFormat)(responseFormat, "extracted_text"),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
120
|
+
console.error(`OpenAI Checks: API request failed for image ${url} with error: ${errorMessage}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const result = JSON.parse(response.output_text);
|
|
124
|
+
console.log(`OpenAI Checks: Tokens used: ${response.usage?.total_tokens}, Model: ${model}, Image URL: ${url}, Extracted Text: ${result.extractedText}`);
|
|
125
|
+
await redis.set(resultCacheKey, JSON.stringify(result.extractedText), { expiration: (0, date_fns_1.addHours)(new Date(), 1) });
|
|
126
|
+
if (!result.extractedText) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
return result.extractedText;
|
|
130
|
+
}
|
|
131
|
+
async evaluate(user) {
|
|
132
|
+
if (!this.openAiKey) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const imageTextRegexes = this.getVariable("imageTextRegexes", []).map(regex => new RegExp(regex, "iu"));
|
|
136
|
+
if (imageTextRegexes.length === 0) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const posts = this.getPosts();
|
|
140
|
+
const recentNsfwPosts = posts.filter(post => post.nsfw && this.getImageUrl(post) && post.createdAt > (0, date_fns_1.subWeeks)(new Date(), 1));
|
|
141
|
+
if (recentNsfwPosts.length === 0) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
recentNsfwPosts.sort((a, b) => (0, date_fns_1.compareDesc)(a.createdAt, b.createdAt));
|
|
145
|
+
const anyGroupMatches = await super.evaluate(user);
|
|
146
|
+
if (!anyGroupMatches) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
// Clear hit reasons from the super.evaluate call, as we want to only report hits from this specific evaluation
|
|
150
|
+
this.hitReasons = undefined;
|
|
151
|
+
const mostRecentNsfwPost = recentNsfwPosts[0];
|
|
152
|
+
const imageUrl = this.getImageUrl(mostRecentNsfwPost);
|
|
153
|
+
if (!imageUrl || imageUrl.endsWith(".gif") || imageUrl.endsWith(".mp4")) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const extractedText = await this.getTextFromImage(imageUrl);
|
|
157
|
+
if (!extractedText) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
for (const regex of imageTextRegexes) {
|
|
161
|
+
if (regex.test(extractedText)) {
|
|
162
|
+
this.addHitReason(`Matched regex "${regex.source}" in extracted text from image: "${extractedText}"`);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
exports.EvaluateTextInNsfwImages = EvaluateTextInNsfwImages;
|
|
@@ -0,0 +1,13 @@
|
|
|
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 EvaluateTitleCopyBot extends UserEvaluatorBase {
|
|
6
|
+
name: string;
|
|
7
|
+
shortname: string;
|
|
8
|
+
banContentThreshold: number;
|
|
9
|
+
preEvaluateComment(event: CommentCreate): boolean;
|
|
10
|
+
preEvaluatePost(_: Post): boolean;
|
|
11
|
+
preEvaluateUser(user: UserExtended): boolean;
|
|
12
|
+
evaluate(_: UserExtended): Promise<boolean>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EvaluateTitleCopyBot = 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
|
+
class EvaluateTitleCopyBot extends UserEvaluatorBase_js_1.UserEvaluatorBase {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.name = "Title Copy Bot";
|
|
11
|
+
this.shortname = "titlecopy";
|
|
12
|
+
this.banContentThreshold = 5;
|
|
13
|
+
}
|
|
14
|
+
preEvaluateComment(event) {
|
|
15
|
+
if (!event.comment?.body || !event.post?.title) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const tlcOnly = this.getVariable("tlcOnly", false);
|
|
19
|
+
if (tlcOnly && !(0, tid_js_1.isLinkId)(event.comment.parentId)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const minCommentLength = this.getVariable("minCommentLength", 1);
|
|
23
|
+
if (event.comment.body.trim().length < minCommentLength) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return event.comment.body.trim() === event.post.title.trim();
|
|
27
|
+
}
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
preEvaluatePost(_) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
preEvaluateUser(user) {
|
|
33
|
+
return user.createdAt > (0, date_fns_1.subDays)(new Date(), this.getVariable("maxAccountAgeDays", 180));
|
|
34
|
+
}
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
36
|
+
async evaluate(_) {
|
|
37
|
+
const ignoredSubs = this.getVariable("ignoredSubs", []);
|
|
38
|
+
const comments = this.getComments().filter(comment => !ignoredSubs.includes(comment.subredditName));
|
|
39
|
+
if (comments.some(comment => comment.body.includes("\n"))) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const tlcOnly = this.getVariable("tlcOnly", false);
|
|
43
|
+
if (tlcOnly && comments.some(comment => !(0, tid_js_1.isLinkId)(comment.parentId))) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const minCommentLength = this.getVariable("minCommentLength", 1);
|
|
47
|
+
if (comments.some(comment => comment.body.trim().length < minCommentLength)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const commentsToCheck = this.getVariable("numCommentsToCheck", 5);
|
|
51
|
+
const mostRecentComments = comments.slice(0, commentsToCheck);
|
|
52
|
+
if (mostRecentComments.length < commentsToCheck) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (this.getPosts().length > 0) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
for (const comment of mostRecentComments) {
|
|
59
|
+
const post = await this.context.reddit.getPostById(comment.postId);
|
|
60
|
+
if (comment.body.trim() !== post.title.trim()) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.addHitReason({
|
|
65
|
+
reason: "User's recent comments copy the post title verbatim, with no other comments or posts.",
|
|
66
|
+
details: mostRecentComments.map(comment => ({
|
|
67
|
+
key: `Comment ${comment.id}`,
|
|
68
|
+
value: comment.body.trim(),
|
|
69
|
+
})),
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.EvaluateTitleCopyBot = EvaluateTitleCopyBot;
|
|
@@ -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 EvaluateWarmupBot extends UserEvaluatorBase {
|
|
6
|
+
name: string;
|
|
7
|
+
shortname: string;
|
|
8
|
+
banContentThreshold: number;
|
|
9
|
+
preEvaluateComment(_: CommentCreate): boolean;
|
|
10
|
+
validateVariables(): ValidationIssue[];
|
|
11
|
+
gatherRegexes(): EvaluatorRegex[];
|
|
12
|
+
preEvaluatePost(post: Post): boolean;
|
|
13
|
+
preEvaluateUser(user: UserExtended): boolean;
|
|
14
|
+
evaluate(user: UserExtended): Promise<boolean>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EvaluateWarmupBot = void 0;
|
|
4
|
+
const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
|
|
5
|
+
const lodash_1 = require("lodash");
|
|
6
|
+
const devvit_helpers_1 = require("devvit-helpers");
|
|
7
|
+
class EvaluateWarmupBot extends UserEvaluatorBase_js_1.UserEvaluatorBase {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.name = "Warmup Bot";
|
|
11
|
+
this.shortname = "warmupbot";
|
|
12
|
+
this.banContentThreshold = 0;
|
|
13
|
+
}
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
+
preEvaluateComment(_) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
validateVariables() {
|
|
19
|
+
const regexes = this.gatherRegexes().map(r => r.regex);
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const regexVal of regexes) {
|
|
22
|
+
let regex;
|
|
23
|
+
try {
|
|
24
|
+
regex = new RegExp(regexVal);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
results.push({ severity: "error", message: `Invalid rege: ${regexVal}` });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (regex.test("")) {
|
|
31
|
+
results.push({ severity: "error", message: `Regex is too greedy: ${regexVal}` });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
gatherRegexes() {
|
|
37
|
+
const subredditRegexes = this.getVariable("subredditRegexes", []);
|
|
38
|
+
const postTitleRegexes = this.getVariable("postTitleRegexes", []);
|
|
39
|
+
return [...subredditRegexes, ...postTitleRegexes].map(regex => ({
|
|
40
|
+
evaluatorName: this.name,
|
|
41
|
+
regex,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
preEvaluatePost(post) {
|
|
45
|
+
const postTitleRegexes = this.getVariable("postTitleRegexes", []);
|
|
46
|
+
if (postTitleRegexes.length === 0) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return postTitleRegexes.some(regex => new RegExp(regex).test(post.title));
|
|
50
|
+
}
|
|
51
|
+
preEvaluateUser(user) {
|
|
52
|
+
return user.isModerator;
|
|
53
|
+
}
|
|
54
|
+
async evaluate(user) {
|
|
55
|
+
const subredditRegexes = this.getVariable("subredditRegexes", []);
|
|
56
|
+
if (subredditRegexes.length === 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const postTitleRegexes = this.getVariable("postTitleRegexes", []);
|
|
60
|
+
if (postTitleRegexes.length === 0) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const userPosts = this.getPosts();
|
|
64
|
+
const userComments = this.getComments();
|
|
65
|
+
const history = [...userPosts, ...userComments];
|
|
66
|
+
if (!userPosts.some(post => postTitleRegexes.some(regex => new RegExp(regex).test(post.title)))) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const userSubreddits = (0, lodash_1.uniq)(history.map(item => item.subredditName));
|
|
70
|
+
const matchedSubreddits = userSubreddits.filter(subreddit => subredditRegexes.some(regex => new RegExp(regex).test(subreddit)));
|
|
71
|
+
if (matchedSubreddits.length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// Check to see if the user is a mod of any of the matched subreddits
|
|
75
|
+
let moddedSubreddit;
|
|
76
|
+
for (const subreddit of matchedSubreddits) {
|
|
77
|
+
if (await (0, devvit_helpers_1.isModerator)(this.context.reddit, subreddit, user.username)) {
|
|
78
|
+
moddedSubreddit = subreddit;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!moddedSubreddit) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
this.addHitReason(`User is a moderator of matched subreddit: ${moddedSubreddit}`);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.EvaluateWarmupBot = EvaluateWarmupBot;
|
|
@@ -0,0 +1,18 @@
|
|
|
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 EvaluateWorldTraveller extends UserEvaluatorBase {
|
|
6
|
+
name: string;
|
|
7
|
+
shortname: string;
|
|
8
|
+
canAutoBan: boolean;
|
|
9
|
+
banContentThreshold: number;
|
|
10
|
+
validateVariables(): ValidationIssue[];
|
|
11
|
+
private getWTSubGroups;
|
|
12
|
+
private getSubList;
|
|
13
|
+
private isInEligibleSubreddit;
|
|
14
|
+
preEvaluateComment(_: CommentCreate): boolean;
|
|
15
|
+
preEvaluatePost(post: Post): boolean;
|
|
16
|
+
preEvaluateUser(user: UserExtended): boolean;
|
|
17
|
+
evaluate(_: UserExtended): boolean;
|
|
18
|
+
}
|