@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,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvaluateWorldTraveller = void 0;
4
+ const UserEvaluatorBase_js_1 = require("./UserEvaluatorBase.js");
5
+ const constants_js_1 = require("../constants.js");
6
+ const lodash_1 = require("lodash");
7
+ const date_fns_1 = require("date-fns");
8
+ class EvaluateWorldTraveller extends UserEvaluatorBase_js_1.UserEvaluatorBase {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.name = "World Traveller";
12
+ this.shortname = "worldtraveler";
13
+ this.canAutoBan = true;
14
+ this.banContentThreshold = 10;
15
+ }
16
+ validateVariables() {
17
+ const subGroups = this.getWTSubGroups();
18
+ const results = [];
19
+ const allSubs = new Set();
20
+ const duplicateSubs = new Set();
21
+ for (const group of subGroups) {
22
+ for (const sub of group.subs) {
23
+ if (allSubs.has(sub)) {
24
+ duplicateSubs.add(sub);
25
+ }
26
+ else {
27
+ allSubs.add(sub);
28
+ }
29
+ }
30
+ }
31
+ if (duplicateSubs.size > 0) {
32
+ results.push({
33
+ severity: "warning",
34
+ message: `Subreddits found in more than one group: ${Array.from(duplicateSubs).join(", ")}`,
35
+ });
36
+ }
37
+ return results;
38
+ }
39
+ getWTSubGroups() {
40
+ const subGroups = this.getVariable("subgroups", []);
41
+ return subGroups.map(group => ({ group, subs: group.split(",").map(s => s.trim()) }));
42
+ }
43
+ getSubList() {
44
+ return this.getWTSubGroups().map(group => group.subs).flat();
45
+ }
46
+ isInEligibleSubreddit() {
47
+ if (!this.context.subredditName) {
48
+ return false;
49
+ }
50
+ return this.context.subredditName.startsWith(constants_js_1.CONTROL_SUBREDDIT) || this.getSubList().includes(this.context.subredditName);
51
+ }
52
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
53
+ preEvaluateComment(_) {
54
+ return false;
55
+ }
56
+ preEvaluatePost(post) {
57
+ if (!post.nsfw) {
58
+ return false;
59
+ }
60
+ return this.getSubList().includes(post.subredditName);
61
+ }
62
+ preEvaluateUser(user) {
63
+ return user.nsfw && this.isInEligibleSubreddit();
64
+ }
65
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
66
+ evaluate(_) {
67
+ const nsfwOnly = this.getVariable("nsfwonly", false);
68
+ const relevantHistory = this.getPosts({ since: (0, date_fns_1.subMonths)(new Date(), 1) })
69
+ .filter(post => nsfwOnly ? post.nsfw : true)
70
+ .filter(post => this.getSubList().includes(post.subredditName));
71
+ const distinctSubreddits = (0, lodash_1.uniq)(relevantHistory.map(item => item.subredditName));
72
+ const distinctSubGroups = (0, lodash_1.uniq)(this.getWTSubGroups()
73
+ .filter(group => distinctSubreddits.some(sub => group.subs.includes(sub)))
74
+ .map(group => group.group));
75
+ const requiredSubCount = this.getVariable("distinctgroups", 5);
76
+ return distinctSubGroups.length >= requiredSubCount;
77
+ }
78
+ }
79
+ exports.EvaluateWorldTraveller = EvaluateWorldTraveller;
@@ -0,0 +1,67 @@
1
+ import { CommentCreate, CommentUpdate } from "@devvit/protos";
2
+ import { Comment, Post, TriggerContext, UserSocialLink } from "@devvit/public-api";
3
+ import { UserExtended } from "@fsvreddit/fsv-devvit-helpers";
4
+ interface HistoryOptions {
5
+ since?: Date;
6
+ omitRemoved?: boolean;
7
+ edited?: boolean;
8
+ }
9
+ export interface ValidationIssue {
10
+ severity: "error" | "warning";
11
+ message: string;
12
+ }
13
+ export interface EvaluatorRegex {
14
+ evaluatorName: string;
15
+ subName?: string;
16
+ regex: string;
17
+ flags?: string;
18
+ }
19
+ export interface HitReasonDetailed {
20
+ reason: string;
21
+ details: {
22
+ key: string;
23
+ value: string;
24
+ }[];
25
+ }
26
+ export type HitReason = string | HitReasonDetailed;
27
+ export declare abstract class UserEvaluatorBase {
28
+ protected context: TriggerContext;
29
+ private variables;
30
+ abstract name: string;
31
+ abstract shortname: string;
32
+ socialLinks: UserSocialLink[] | undefined;
33
+ readonly needsOpenAiKey: boolean;
34
+ protected openAiKey: string | undefined;
35
+ banContentThreshold: number;
36
+ canAutoBan: boolean;
37
+ protected history: (Post | Comment)[];
38
+ private userPosts;
39
+ private userComments;
40
+ protected ignoredSubs: Set<string>;
41
+ constructor(context: TriggerContext, history: (Post | Comment)[], socialLinks: UserSocialLink[] | undefined, variables: Record<string, unknown>);
42
+ setHistory(history: (Post | Comment)[]): void;
43
+ setSocialLinks(socialLinks: UserSocialLink[]): void;
44
+ setOpenAiKey(key: string): void;
45
+ evaluatorDisabled(): boolean;
46
+ validateVariables(): ValidationIssue[];
47
+ gatherRegexes(): EvaluatorRegex[];
48
+ getSubGroups(): string[] | undefined;
49
+ getVariableOverrides(): Record<string, unknown>;
50
+ protected getAllVariables(filter?: string): Record<string, unknown>;
51
+ protected getVariable<Type>(name: string, defaultValue: Type): Type;
52
+ protected getGenericVariable<Type>(name: string, defaultValue: Type): Type;
53
+ protected getModuleVariable<Type>(module: string, name: string, defaultValue: Type): Type;
54
+ protected getSocialLinks(username: string): Promise<UserSocialLink[]>;
55
+ protected addHitReason(reason: HitReason): void;
56
+ hitReasons: HitReason[] | undefined;
57
+ abstract preEvaluateComment(event: CommentCreate): boolean | Promise<boolean>;
58
+ preEvaluateCommentEdit(event: CommentUpdate): boolean | Promise<boolean>;
59
+ abstract preEvaluatePost(post: Post): boolean | Promise<boolean>;
60
+ preEvaluatePostEdit(post: Post): boolean | Promise<boolean>;
61
+ abstract preEvaluateUser(user: UserExtended): boolean | Promise<boolean>;
62
+ abstract evaluate(user: UserExtended): boolean | Promise<boolean>;
63
+ private getContent;
64
+ protected getComments(options?: HistoryOptions): Comment[];
65
+ protected getPosts(options?: HistoryOptions): Post[];
66
+ }
67
+ export {};
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserEvaluatorBase = void 0;
4
+ const tid_js_1 = require("@devvit/public-api/types/tid.js");
5
+ const evaluatorHelpers_js_1 = require("./evaluatorHelpers.js");
6
+ class UserEvaluatorBase {
7
+ constructor(context, history, socialLinks, variables) {
8
+ this.variables = {};
9
+ this.needsOpenAiKey = false;
10
+ this.banContentThreshold = 10;
11
+ this.canAutoBan = true;
12
+ this.hitReasons = undefined;
13
+ this.context = context;
14
+ this.socialLinks = socialLinks;
15
+ this.variables = variables;
16
+ this.ignoredSubs = new Set(this.getGenericVariable("ignoredsubs", []));
17
+ this.history = history;
18
+ }
19
+ setHistory(history) {
20
+ this.history = history;
21
+ this.userPosts = undefined;
22
+ this.userComments = undefined;
23
+ }
24
+ setSocialLinks(socialLinks) {
25
+ this.socialLinks = socialLinks;
26
+ }
27
+ setOpenAiKey(key) {
28
+ this.openAiKey = key;
29
+ }
30
+ evaluatorDisabled() {
31
+ return this.getVariable("killswitch", false);
32
+ }
33
+ validateVariables() {
34
+ return [];
35
+ }
36
+ gatherRegexes() {
37
+ return [];
38
+ }
39
+ getSubGroups() {
40
+ return;
41
+ }
42
+ getVariableOverrides() {
43
+ return {};
44
+ }
45
+ getAllVariables(filter) {
46
+ const result = {};
47
+ const root = this.shortname + ":" + (filter ?? "");
48
+ for (const key in this.variables) {
49
+ if (key.startsWith(root)) {
50
+ result[key] = this.variables[key];
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+ getVariable(name, defaultValue) {
56
+ return this.variables[`${this.shortname}:${name}`] ?? defaultValue;
57
+ }
58
+ getGenericVariable(name, defaultValue) {
59
+ return this.variables[`generic:${name}`] ?? defaultValue;
60
+ }
61
+ getModuleVariable(module, name, defaultValue) {
62
+ return this.variables[`${module}:${name}`] ?? defaultValue;
63
+ }
64
+ async getSocialLinks(username) {
65
+ this.socialLinks ?? (this.socialLinks = await (0, evaluatorHelpers_js_1.getSocialLinksWithCache)(username, this.context));
66
+ return this.socialLinks;
67
+ }
68
+ addHitReason(reason) {
69
+ if (!this.hitReasons) {
70
+ this.hitReasons = [reason];
71
+ }
72
+ else {
73
+ this.hitReasons.push(reason);
74
+ }
75
+ }
76
+ preEvaluateCommentEdit(event) {
77
+ return this.preEvaluateComment(event);
78
+ }
79
+ preEvaluatePostEdit(post) {
80
+ return this.preEvaluatePost(post);
81
+ }
82
+ getContent(history, options) {
83
+ if (!options) {
84
+ return history;
85
+ }
86
+ const filteredHistory = history.filter((item) => {
87
+ if (options.since && item.createdAt < options.since) {
88
+ return false;
89
+ }
90
+ if (options.omitRemoved && item.body === "[removed]") {
91
+ return false;
92
+ }
93
+ if (options.edited !== undefined) {
94
+ return item.edited === options.edited;
95
+ }
96
+ return true;
97
+ });
98
+ return filteredHistory;
99
+ }
100
+ getComments(options) {
101
+ this.userComments ?? (this.userComments = this.history.filter(item => (0, tid_js_1.isCommentId)(item.id) && !this.ignoredSubs.has(item.subredditName)));
102
+ return this.getContent(this.userComments, options);
103
+ }
104
+ getPosts(options) {
105
+ this.userPosts ?? (this.userPosts = this.history.filter(item => (0, tid_js_1.isLinkId)(item.id) && !this.ignoredSubs.has(item.subredditName)));
106
+ return this.getContent(this.userPosts, options);
107
+ }
108
+ }
109
+ exports.UserEvaluatorBase = UserEvaluatorBase;
@@ -0,0 +1,3 @@
1
+ import { TriggerContext, UserSocialLink } from "@devvit/public-api";
2
+ export declare function domainFromUrl(url: string): string | undefined;
3
+ export declare function getSocialLinksWithCache(username: string, context: TriggerContext): Promise<UserSocialLink[]>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.domainFromUrl = domainFromUrl;
4
+ exports.getSocialLinksWithCache = getSocialLinksWithCache;
5
+ const date_fns_1 = require("date-fns");
6
+ const devvit_helpers_1 = require("devvit-helpers");
7
+ function domainFromUrl(url) {
8
+ if (!url || url.startsWith("/")) {
9
+ // Reddit internal link or crosspost
10
+ return;
11
+ }
12
+ const hostname = new URL(url).hostname;
13
+ const trimmedHostname = hostname.startsWith("www.") ? hostname.substring(4) : hostname;
14
+ return trimmedHostname;
15
+ }
16
+ async function getSocialLinksWithCache(username, context) {
17
+ const cacheKey = `socialLinks:${username}`;
18
+ const cachedValue = await context.redis.get(cacheKey);
19
+ if (cachedValue) {
20
+ return JSON.parse(cachedValue);
21
+ }
22
+ const userSocialLinks = await (0, devvit_helpers_1.getUserSocialLinks)(username, context.metadata);
23
+ await context.redis.set(cacheKey, JSON.stringify(userSocialLinks), { expiration: (0, date_fns_1.addMinutes)(new Date(), 5) });
24
+ return userSocialLinks;
25
+ }
@@ -0,0 +1,26 @@
1
+ export * from "./EvaluateBadDisplayname.js";
2
+ export * from "./EvaluateBadDisplaynameDefinedHandles.js";
3
+ export * from "./EvaluateBadUsername.js";
4
+ export * from "./EvaluateBioText.js";
5
+ export * from "./EvaluateBioTextDefinedHandles.js";
6
+ export * from "./EvaluateCommentPhrase.js";
7
+ export * from "./EvaluateDomainSharer.js";
8
+ export * from "./EvaluateInconsistentAgeBot.js";
9
+ export * from "./EvaluateInconsistentGenderBot.js";
10
+ export * from "./EvaluateLinkReuse.js";
11
+ export * from "./EvaluateObfuscatedBioKeywords.js";
12
+ export * from "./EvaluatePinnedPostTitle.js";
13
+ export * from "./EvaluatePostTitle.js";
14
+ export * from "./EvaluatePostTitleDefinedHandles.js";
15
+ export * from "./EvaluatePostTitleMulti.js";
16
+ export * from "./EvaluateSelfComment.js";
17
+ export * from "./EvaluateSocialLinks.js";
18
+ export * from "./EvaluateSuspiciousFirstPost.js";
19
+ export * from "./EvaluateTGGroup.js";
20
+ export * from "./EvaluateWarmupBot.js";
21
+ export * from "./EvaluateBotGroupAdvanced.js";
22
+ export * from "./EvaluateBotGroupAdvancedInternal.js";
23
+ export * from "./EvaluateTitleCopyBot.js";
24
+ export * from "./EvaluateWorldTraveller.js";
25
+ export * from "./EvaluateBotGroupSubmitters.js";
26
+ export * from "./UserEvaluatorBase.js";
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./EvaluateBadDisplayname.js"), exports);
18
+ __exportStar(require("./EvaluateBadDisplaynameDefinedHandles.js"), exports);
19
+ __exportStar(require("./EvaluateBadUsername.js"), exports);
20
+ __exportStar(require("./EvaluateBioText.js"), exports);
21
+ __exportStar(require("./EvaluateBioTextDefinedHandles.js"), exports);
22
+ __exportStar(require("./EvaluateCommentPhrase.js"), exports);
23
+ __exportStar(require("./EvaluateDomainSharer.js"), exports);
24
+ __exportStar(require("./EvaluateInconsistentAgeBot.js"), exports);
25
+ __exportStar(require("./EvaluateInconsistentGenderBot.js"), exports);
26
+ __exportStar(require("./EvaluateLinkReuse.js"), exports);
27
+ __exportStar(require("./EvaluateObfuscatedBioKeywords.js"), exports);
28
+ __exportStar(require("./EvaluatePinnedPostTitle.js"), exports);
29
+ __exportStar(require("./EvaluatePostTitle.js"), exports);
30
+ __exportStar(require("./EvaluatePostTitleDefinedHandles.js"), exports);
31
+ __exportStar(require("./EvaluatePostTitleMulti.js"), exports);
32
+ __exportStar(require("./EvaluateSelfComment.js"), exports);
33
+ __exportStar(require("./EvaluateSocialLinks.js"), exports);
34
+ __exportStar(require("./EvaluateSuspiciousFirstPost.js"), exports);
35
+ __exportStar(require("./EvaluateTGGroup.js"), exports);
36
+ __exportStar(require("./EvaluateWarmupBot.js"), exports);
37
+ __exportStar(require("./EvaluateBotGroupAdvanced.js"), exports);
38
+ __exportStar(require("./EvaluateBotGroupAdvancedInternal.js"), exports);
39
+ __exportStar(require("./EvaluateTitleCopyBot.js"), exports);
40
+ __exportStar(require("./EvaluateWorldTraveller.js"), exports);
41
+ __exportStar(require("./EvaluateBotGroupSubmitters.js"), exports);
42
+ __exportStar(require("./UserEvaluatorBase.js"), exports);
@@ -0,0 +1,34 @@
1
+ import { JSONValue, TriggerContext, User } from "@devvit/public-api";
2
+ import { Document } from "yaml";
3
+ export declare function getUserOrUndefined(username: string, context: TriggerContext): Promise<User | undefined>;
4
+ export interface DuplicateKeyInfo {
5
+ key: string;
6
+ path: string;
7
+ line: number;
8
+ col: number;
9
+ }
10
+ export declare function findDuplicateKeysInDoc(doc: Document.Parsed): DuplicateKeyInfo[];
11
+ export declare function yamlToVariables(input: string, extraSubstitutions?: Record<string, string | string[]>): Record<string, JSONValue>;
12
+ export interface AdditionalUserInfo {
13
+ createdAt: Date;
14
+ userDescription?: string;
15
+ displayName?: string;
16
+ nsfw: boolean;
17
+ commentKarma: number;
18
+ linkKarma: number;
19
+ hasRedditPremium: boolean;
20
+ hasVerifiedEmail: boolean;
21
+ isSubredditModerator: boolean;
22
+ }
23
+ export interface AdditionalUserInfo {
24
+ createdAt: Date;
25
+ userDescription?: string;
26
+ displayName?: string;
27
+ nsfw: boolean;
28
+ commentKarma: number;
29
+ linkKarma: number;
30
+ hasRedditPremium: boolean;
31
+ hasVerifiedEmail: boolean;
32
+ isSubredditModerator: boolean;
33
+ }
34
+ export declare function getAdditionalUserInfo(username: string, context: TriggerContext): Promise<AdditionalUserInfo | undefined>;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getUserOrUndefined = getUserOrUndefined;
4
+ exports.findDuplicateKeysInDoc = findDuplicateKeysInDoc;
5
+ exports.yamlToVariables = yamlToVariables;
6
+ exports.getAdditionalUserInfo = getAdditionalUserInfo;
7
+ const fsv_devvit_helpers_1 = require("@fsvreddit/fsv-devvit-helpers");
8
+ const date_fns_1 = require("date-fns");
9
+ const yaml_1 = require("yaml");
10
+ async function getUserOrUndefined(username, context) {
11
+ let user;
12
+ try {
13
+ user = await context.reddit.getUserByUsername(username);
14
+ }
15
+ catch {
16
+ //
17
+ }
18
+ return user;
19
+ }
20
+ function getSubstitutionsListFromYaml(yaml) {
21
+ const yamlDocuments = (0, yaml_1.parseAllDocuments)(yaml);
22
+ const substitutions = {};
23
+ for (const document of yamlDocuments) {
24
+ const json = document.toJSON();
25
+ if (json?.name !== "substitutions") {
26
+ continue;
27
+ }
28
+ for (const key in json) {
29
+ if (key !== "name") {
30
+ substitutions[key] = json[key];
31
+ }
32
+ }
33
+ }
34
+ return substitutions;
35
+ }
36
+ function findDuplicateKeysInDoc(doc) {
37
+ const duplicates = [];
38
+ function walk(node, path) {
39
+ // Only YAMLMap nodes have .items
40
+ if (!(node instanceof yaml_1.YAMLMap)) {
41
+ return;
42
+ }
43
+ const seen = new Map();
44
+ for (const pair of node.items) {
45
+ const keyNode = pair.key;
46
+ const key = keyNode?.value;
47
+ if (typeof key !== "string") {
48
+ continue;
49
+ }
50
+ const childPath = [...path, key];
51
+ if (seen.has(key)) {
52
+ const [line, col] = keyNode?.range ?? [0, 0];
53
+ duplicates.push({
54
+ key,
55
+ path: childPath.join("."),
56
+ line,
57
+ col,
58
+ });
59
+ }
60
+ else {
61
+ seen.set(key, true);
62
+ }
63
+ walk(pair.value, childPath);
64
+ }
65
+ }
66
+ walk(doc.contents, []);
67
+ return duplicates;
68
+ }
69
+ function yamlToVariables(input, extraSubstitutions = {}) {
70
+ const substitutionsList = { ...getSubstitutionsListFromYaml(input), ...extraSubstitutions };
71
+ let yaml = input;
72
+ for (const key in substitutionsList) {
73
+ if (typeof substitutionsList[key] === "string") {
74
+ yaml = yaml.replaceAll(`{{${key}}}`, substitutionsList[key]);
75
+ }
76
+ else {
77
+ // Handle array substitutions
78
+ yaml = yaml.replaceAll(`{{${key}}}`, JSON.stringify(substitutionsList[key]));
79
+ }
80
+ }
81
+ const variables = {};
82
+ const yamlDocuments = (0, yaml_1.parseAllDocuments)(yaml);
83
+ const modulesSeen = new Set();
84
+ const errors = [];
85
+ let index = 0;
86
+ for (const doc of yamlDocuments) {
87
+ const json = doc.toJSON();
88
+ if (!json) {
89
+ // Empty document
90
+ continue;
91
+ }
92
+ const root = json.name;
93
+ if (!root) {
94
+ console.error(`Evaluator Variables: Error parsing evaluator variables from wiki. Missing root name on document ${index}.`);
95
+ continue;
96
+ }
97
+ const duplicates = findDuplicateKeysInDoc(doc);
98
+ if (duplicates.length > 0) {
99
+ for (const duplicate of duplicates) {
100
+ const error = `Duplicate key "${duplicate.key}" found at path "${duplicate.path}" in module "${root}".`;
101
+ errors.push(error);
102
+ }
103
+ }
104
+ if (modulesSeen.has(root)) {
105
+ errors.push(`Module name ${root} is present more than once`);
106
+ }
107
+ else {
108
+ modulesSeen.add(root);
109
+ }
110
+ for (const key in json) {
111
+ if (key !== "name") {
112
+ variables[`${root}:${key}`] = json[key];
113
+ }
114
+ }
115
+ index++;
116
+ }
117
+ variables.errors = errors;
118
+ return variables;
119
+ }
120
+ async function getAdditionalUserInfo(username, context) {
121
+ const cacheKey = `bbe:additionalUserInfo:${username}`;
122
+ const cachedData = await context.redis.get(cacheKey);
123
+ if (cachedData) {
124
+ const parsedData = JSON.parse(cachedData);
125
+ return {
126
+ createdAt: new Date(parsedData.createdAt),
127
+ userDescription: parsedData.userDescription,
128
+ displayName: parsedData.displayName,
129
+ nsfw: parsedData.nsfw,
130
+ commentKarma: parsedData.commentKarma,
131
+ linkKarma: parsedData.linkKarma,
132
+ hasRedditPremium: parsedData.hasRedditPremium,
133
+ hasVerifiedEmail: parsedData.hasVerifiedEmail,
134
+ isSubredditModerator: parsedData.isSubredditModerator,
135
+ };
136
+ }
137
+ const user = await (0, fsv_devvit_helpers_1.getUserExtended)(username, context);
138
+ if (!user) {
139
+ return;
140
+ }
141
+ const dataToCache = {
142
+ createdAt: user.createdAt.getTime(),
143
+ userDescription: user.userDescription,
144
+ displayName: user.displayName,
145
+ nsfw: user.nsfw,
146
+ commentKarma: user.commentKarma,
147
+ linkKarma: user.linkKarma,
148
+ hasRedditPremium: user.isGold,
149
+ hasVerifiedEmail: user.hasVerifiedEmail,
150
+ isSubredditModerator: user.isModerator,
151
+ };
152
+ await context.redis.set(cacheKey, JSON.stringify(dataToCache), { expiration: (0, date_fns_1.addMinutes)(new Date(), 10) });
153
+ return {
154
+ createdAt: user.createdAt,
155
+ userDescription: user.userDescription,
156
+ displayName: user.displayName,
157
+ nsfw: user.nsfw,
158
+ commentKarma: user.commentKarma,
159
+ linkKarma: user.linkKarma,
160
+ hasRedditPremium: user.isGold,
161
+ hasVerifiedEmail: user.hasVerifiedEmail,
162
+ isSubredditModerator: user.isModerator,
163
+ };
164
+ }
@@ -0,0 +1,3 @@
1
+ import fsvconfig from "@fsvreddit/eslint-config";
2
+
3
+ export default fsvconfig;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@fsvreddit/bot-bouncer-evaluation",
3
+ "version": "0.7.40",
4
+ "description": "Evaluation code for Bot Bouncer, a Dev Platform app designed to hunt bots on Reddit",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "test": "npm run test:unit",
8
+ "test:unit": "vitest --run",
9
+ "lint": "eslint . --ext .ts",
10
+ "build": "tsc --project tsconfig.build.json",
11
+ "prepare": "npm run build"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/fsvreddit/bot-bouncer-evaluation.git"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "author": "fsv",
21
+ "license": "BSD-3-Clause",
22
+ "dependencies": {
23
+ "@fsvreddit/fsv-devvit-helpers": "^0.1.13",
24
+ "@wordpress/wordcount": "^4.53.0",
25
+ "date-fns": "^4.1.0",
26
+ "devvit-helpers": "^0.11.0",
27
+ "lodash": "^4.17.21",
28
+ "markdown-escape": "^2.0.0",
29
+ "openai": "^7.5.0",
30
+ "regjsparser": "^0.13.0",
31
+ "yaml": "^2.8.0",
32
+ "zod": "^4.4.3"
33
+ },
34
+ "devDependencies": {
35
+ "@fsvreddit/eslint-config": "^0.1.0",
36
+ "@types/lodash": "^4.17.17",
37
+ "@types/markdown-escape": "^1.1.3",
38
+ "eslint": "^10.0.2",
39
+ "typescript": "^5.8.3",
40
+ "vitest": "^4.0.15"
41
+ },
42
+ "keywords": [
43
+ "reddit",
44
+ "devvit"
45
+ ],
46
+ "peerDependencies": {
47
+ "@devvit/protos": "^0.14.1",
48
+ "@devvit/public-api": "^0.14.1"
49
+ }
50
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": [
4
+ "vitest.config.ts",
5
+ "node_modules",
6
+ "tests",
7
+ "dist",
8
+ "eslint.config.mjs"
9
+ ],
10
+ "compilerOptions": {
11
+ "rootDir": "src",
12
+ "outDir": "dist",
13
+ }
14
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "extends": "@devvit/public-api/devvit.tsconfig.json",
3
+ "compilerOptions": {
4
+ "target": "ES2020",
5
+ "lib": [
6
+ "ES2022",
7
+ "WebWorker"
8
+ ],
9
+ "resolveJsonModule": true,
10
+ "declaration": true,
11
+ "outDir": "dist",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "module": "NodeNext",
15
+ "moduleResolution": "NodeNext",
16
+ },
17
+ "exclude": [
18
+ "**/node_modules",
19
+ "dist",
20
+ "**/.*/",
21
+ "eslint.config.mjs"
22
+ ]
23
+ }