Skip to content

Commit de15882

Browse files
committed
Enhance detection functionality and reporting options
- Added ability to remove posts with an optional removal message when AI-generated content is detected. - Updated detection methods to return arrays of messages instead of single strings for better handling. - Introduced new settings for auto-check actions and removal message placeholders in the configuration. - Updated README to reflect changes in functionality and versioning.
1 parent 4aadb0a commit de15882

15 files changed

Lines changed: 120 additions & 33 deletions

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,18 @@ You can also set the app up to proactively check new posts, or posts approved ou
2828

2929
Consider setting a suitable account age and karma threshold to restrict this feature to accounts who are more likely to break rules. Posts from moderators will never be checked, and by default content from approved users will not be checked (but this can be changed).
3030

31-
If an image is detected as AI, a report like this will be made:
31+
You can configure the app to either report the post (the default option) or remove with an optional removal reason.
32+
33+
By default, a report will be made:
3234

3335
![screenshot](https://github.com/fsvreddit/image-moderator/blob/main/doc_images/screenshot.png?raw=true)
3436

3537
## Change History
3638

39+
### v1.2
40+
41+
* Added ability to remove posts if a match occurs, with an optional removal message
42+
3743
### v1.1.2
3844

3945
* Add additional detection types

src/checkNewPost.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ModAction, PostCreate } from "@devvit/protos";
22
import { Post, SettingsValues, TriggerContext, User } from "@devvit/public-api";
33
import { getSightengineResults } from "./checkSightEngineAPI.js";
44
import { postIsImage } from "./utility.js";
5-
import { AppSetting } from "./settings.js";
5+
import { AppSetting, AutoCheckActionOption } from "./settings.js";
66
import { DateTime } from "luxon";
77
import { userIsModerator } from "./moderatorChecks.js";
88
import { getModels, getRelevantDetectors } from "./detections/allDetections.js";
@@ -11,7 +11,7 @@ function getFilterKeyForPost (postId: string) {
1111
return `filtered_post:${postId}`;
1212
}
1313

14-
async function checkAndReportPost (postId: string, source: "PostCreate" | "PostApprovalAction", settings: SettingsValues, context: TriggerContext) {
14+
async function checkAndActionPost (postId: string, source: "PostCreate" | "PostApprovalAction", settings: SettingsValues, context: TriggerContext) {
1515
let post: Post | undefined;
1616
try {
1717
post = await context.reddit.getPostById(postId);
@@ -90,9 +90,9 @@ async function checkAndReportPost (postId: string, source: "PostCreate" | "PostA
9090
const detectionResults: string[] = [];
9191
for (const Detection of detectors) {
9292
const detectionInstance = new Detection(settings);
93-
const detectionResult = detectionInstance.detectProactive(result);
94-
if (detectionResult) {
95-
detectionResults.push(detectionResult);
93+
const instanceDetectionResults = detectionInstance.detectProactive(result);
94+
if (instanceDetectionResults && instanceDetectionResults.length > 0) {
95+
detectionResults.push(...instanceDetectionResults);
9696
}
9797
}
9898

@@ -101,8 +101,42 @@ async function checkAndReportPost (postId: string, source: "PostCreate" | "PostA
101101
return;
102102
}
103103

104+
const [actionToTake] = settings[AppSetting.AutoCheckAction] as AutoCheckActionOption[] | undefined ?? [AutoCheckActionOption.ReportPost];
105+
if (actionToTake === AutoCheckActionOption.ReportPost) {
106+
await reportPost(post, detectionResults, context);
107+
} else { // Remove post is the only other option
108+
await removePost(post, detectionResults, settings[AppSetting.RemovalMessagePlaceholder] as string | undefined, context);
109+
}
110+
}
111+
112+
async function reportPost (post: Post, detectionResults: string[], context: TriggerContext) {
104113
await context.reddit.report(post, { reason: detectionResults.join(", ") });
105-
console.log(`${source}: Post ${post.id} matched: ${detectionResults.join(", ")}. Reported.`);
114+
console.log(`Reported post ${post.id} for: ${detectionResults.join(", ")}`);
115+
}
116+
117+
async function removePost (post: Post, detectionResults: string[], removalMessagePlaceholder: string | undefined, context: TriggerContext) {
118+
await post.remove();
119+
console.log(`Removed post ${post.id} for: ${detectionResults.join(", ")}`);
120+
if (removalMessagePlaceholder) {
121+
const subredditName = context.subredditName ?? await context.reddit.getCurrentSubredditName();
122+
let removalMessage = removalMessagePlaceholder
123+
.replaceAll("{{subreddit}}", subredditName)
124+
.replaceAll("{{author}}", post.authorName)
125+
.replaceAll("{{reasons}}", detectionResults.map(r => `- ${r}`).join("\n"));
126+
127+
removalMessage = removalMessage.trim();
128+
removalMessage += `\n\n*I am a bot, and this action was performed automatically. Please [contact the moderators of this subreddit](/message/compose/?to=/r/${subredditName}) if you have any questions or concerns.*`;
129+
130+
const newComment = await post.addComment({
131+
text: removalMessage,
132+
});
133+
134+
await Promise.all([
135+
newComment.distinguish(true),
136+
newComment.lock(),
137+
]);
138+
console.log(`Added removal comment to post ${post.id}`);
139+
}
106140
}
107141

108142
export async function handlePostCreate (event: PostCreate, context: TriggerContext) {
@@ -119,7 +153,7 @@ export async function handlePostCreate (event: PostCreate, context: TriggerConte
119153
return;
120154
}
121155

122-
await checkAndReportPost(event.post.id, "PostCreate", settings, context);
156+
await checkAndActionPost(event.post.id, "PostCreate", settings, context);
123157
}
124158

125159
export async function handlePostApprovalAction (event: ModAction, context: TriggerContext) {
@@ -143,6 +177,6 @@ export async function handlePostApprovalAction (event: ModAction, context: Trigg
143177
return;
144178
}
145179

146-
await checkAndReportPost(event.targetPost.id, "PostApprovalAction", settings, context);
180+
await checkAndActionPost(event.targetPost.id, "PostApprovalAction", settings, context);
147181
await context.redis.del(getFilterKeyForPost(event.targetPost.id));
148182
}

src/detections/DetectDeepfake.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ export class DetectDeepfake extends DetectionBase {
3333
return Math.round(sightEngineResponse.type.deepfake * 100);
3434
}
3535

36-
public detectProactive (sightEngineResponse: SightengineResponse): string | undefined {
36+
public detectProactive (sightEngineResponse: SightengineResponse): string[] | undefined {
3737
const deepfakeLikelihood = this.getDeepfakeLikelihood(sightEngineResponse);
3838
if (!deepfakeLikelihood) {
3939
return;
4040
}
4141

4242
if (deepfakeLikelihood > this.getSetting<number>(ModuleSetting.Threshold, 80)) {
43-
return `Deepfake Likelihood: ${deepfakeLikelihood}%`;
43+
return [`Deepfake Likelihood: ${deepfakeLikelihood}%`];
4444
}
4545
}
4646

src/detections/DetectDrugs.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ test("Cannabis detection", () => {
2323
};
2424

2525
const result = detector.detectProactive(sightEngineResponse);
26-
expect(result).toBe("Drug likelihood: 99%");
26+
expect(result).toEqual(["Drug likelihood: 99%"]);
2727
});
2828

2929
test("Cannabis ignored when option is chosen", () => {
@@ -73,5 +73,5 @@ test("Other drugs detected when Allow Cannabis is chosen", () => {
7373
};
7474

7575
const result = detector.detectProactive(sightEngineResponse);
76-
expect(result).toBe("Drug likelihood: 99%");
76+
expect(result).toEqual(["Drug likelihood: 99%"]);
7777
});

src/detections/DetectDrugs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ export class DetectDrugs extends DetectionBase {
5757
return Math.round(sightEngineResponse.recreational_drug.prob * 100);
5858
}
5959

60-
public detectProactive (sightEngineResponse: SightengineResponse): string | undefined {
60+
public detectProactive (sightEngineResponse: SightengineResponse): string[] | undefined {
6161
const drugLikelihood = this.getDetectionResult(sightEngineResponse);
6262
if (drugLikelihood === undefined) {
6363
return;
6464
}
6565

6666
if (drugLikelihood > this.getSetting<number>(ModuleSetting.Threshold, 80)) {
67-
return `Drug likelihood: ${drugLikelihood}%`;
67+
return [`Drug likelihood: ${drugLikelihood}%`];
6868
}
6969
}
7070

src/detections/DetectGenAI.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ export class DetectGenAI extends DetectionBase {
3333
return Math.round(sightEngineResponse.type.ai_generated * 100);
3434
}
3535

36-
public detectProactive (sightEngineResponse: SightengineResponse): string | undefined {
36+
public detectProactive (sightEngineResponse: SightengineResponse): string[] | undefined {
3737
const aiLikelihood = this.getAILikelihood(sightEngineResponse);
3838
if (aiLikelihood === undefined) {
3939
return;
4040
}
4141

4242
if (aiLikelihood > this.getSetting<number>(ModuleSetting.Threshold, 80)) {
43-
return `AI Likelihood: ${aiLikelihood}%`;
43+
return [`AI Likelihood: ${aiLikelihood}%`];
4444
}
4545
}
4646

src/detections/DetectImageQuality.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,11 @@ export class DetectImageQuality extends DetectionBase {
3333
return Math.round(sightEngineResponse.quality.score * 100);
3434
}
3535

36-
public detectProactive (sightEngineResponse: SightengineResponse): string | undefined {
36+
public detectProactive (sightEngineResponse: SightengineResponse): string[] | undefined {
3737
const quality = this.getQualityScore(sightEngineResponse);
3838
if (quality && quality < this.getSetting(ModuleSetting.QualityThreshold, 50)) {
39-
return `Image quality is low (${quality})`;
39+
return [`Image quality is low (${quality})`];
4040
}
41-
return undefined;
4241
}
4342

4443
public detectByMenu (sightEngineResponse: SightengineResponse): string | undefined {

src/detections/DetectImageType.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ export class DetectImageType extends DetectionBase {
6464
return selectedType[0] as "photo" | "illustration";
6565
}
6666

67-
public detectProactive (sightEngineResponse: SightengineResponse): string | undefined {
67+
public detectProactive (sightEngineResponse: SightengineResponse): string[] | undefined {
6868
const detectedType = this.getImageType(sightEngineResponse);
6969
const reportableType = this.getReportableImageType();
7070

7171
if (detectedType && detectedType !== reportableType) {
72-
return `Detected image type: ${detectedType}`;
72+
return [`Detected image type: ${detectedType}`];
7373
}
7474
}
7575

src/detections/DetectMinors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ export class DetectMinors extends DetectionBase {
3131
return max(minorLikelihoods.map(likelihood => Math.round(likelihood * 100)));
3232
}
3333

34-
public detectProactive (sightEngineResponse: SightengineResponse): string | undefined {
34+
public detectProactive (sightEngineResponse: SightengineResponse): string[] | undefined {
3535
const minorLikelihood = this.getMinorLikelihood(sightEngineResponse);
3636
if (minorLikelihood && minorLikelihood > this.getSetting(ModuleSetting.MinorThreshold, 80)) {
37-
return `Likely minor: ${minorLikelihood}%`;
37+
return [`Likely minor: ${minorLikelihood}%`];
3838
}
3939
}
4040

src/detections/DetectOffensiveContent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ export class DetectOffensiveContent extends DetectionBase {
107107
return results;
108108
}
109109

110-
public detectProactive (sightEngineResponse: SightengineResponse): string | undefined {
110+
public detectProactive (sightEngineResponse: SightengineResponse): string[] | undefined {
111111
const results = this.getDetectionResults(sightEngineResponse);
112112
if (results.length > 0) {
113-
return results.join(", ");
113+
return results;
114114
}
115115
}
116116

0 commit comments

Comments
 (0)