BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
1import { DEFAULT_MODERATION_DECISION } from "$/lib/moderation";
2import type {
3 DistributionChannel,
4 ModerationContext,
5 ModerationLabel,
6 ModerationLabelerPolicyDefinition,
7 ModerationLabelVisibility,
8 ModerationReasonType,
9 ModerationUiDecision,
10 ReportSubjectInput,
11 StoredModerationPrefs,
12} from "$/lib/types";
13import { invoke } from "@tauri-apps/api/core";
14import * as logger from "@tauri-apps/plugin-log";
15
16async function getModerationPrefs() {
17 return invoke<StoredModerationPrefs>("get_moderation_prefs");
18}
19
20async function setAdultContentEnabled(enabled: boolean) {
21 return invoke<void>("set_adult_content_enabled", { enabled });
22}
23
24async function setLabelPreference(labelerDid: string, label: string, visibility: ModerationLabelVisibility) {
25 return invoke<void>("set_label_preference", { labelerDid, label, visibility });
26}
27
28async function subscribeLabeler(did: string) {
29 return invoke<void>("subscribe_labeler", { did });
30}
31
32async function unsubscribeLabeler(did: string) {
33 return invoke<void>("unsubscribe_labeler", { did });
34}
35
36async function getLabelerPolicyDefinitions() {
37 return invoke<ModerationLabelerPolicyDefinition[]>("get_labeler_policy_definitions");
38}
39
40async function moderateContent(labels: ModerationLabel[], context: ModerationContext): Promise<ModerationUiDecision> {
41 if (labels.length === 0) {
42 return DEFAULT_MODERATION_DECISION;
43 }
44
45 try {
46 return await invoke<ModerationUiDecision>("moderate_content", { labelsJson: JSON.stringify(labels), context });
47 } catch (error) {
48 logger.warn("moderation decision failed", {
49 keyValues: { context, error: String(error), labels: String(labels.length) },
50 });
51 return DEFAULT_MODERATION_DECISION;
52 }
53}
54
55async function createReport(subject: ReportSubjectInput, reasonType: ModerationReasonType, reason?: string) {
56 return invoke<number>("create_report", { reason: reason?.trim() ? reason.trim() : null, reasonType, subject });
57}
58
59async function getDistributionChannel(): Promise<DistributionChannel> {
60 const value = await invoke<string>("get_distribution_channel");
61 if (value === "github" || value === "mac_app_store" || value === "microsoft_store") {
62 return value;
63 }
64
65 return "github";
66}
67
68async function blockActor(did: string) {
69 return invoke<{ uri: string; cid: string }>("block_actor", { did });
70}
71
72export const ModerationController = {
73 getModerationPrefs,
74 setAdultContentEnabled,
75 setLabelPreference,
76 subscribeLabeler,
77 unsubscribeLabeler,
78 getLabelerPolicyDefinitions,
79 moderateContent,
80 createReport,
81 getDistributionChannel,
82 blockActor,
83};
84
85export const MODERATION_REASON_OPTIONS: Array<{ label: string; value: ModerationReasonType }> = [
86 { label: "Spam", value: "com.atproto.moderation.defs#reasonSpam" },
87 { label: "Violation", value: "com.atproto.moderation.defs#reasonViolation" },
88 { label: "Misleading", value: "com.atproto.moderation.defs#reasonMisleading" },
89 { label: "Sexual", value: "com.atproto.moderation.defs#reasonSexual" },
90 { label: "Rude", value: "com.atproto.moderation.defs#reasonRude" },
91 { label: "Other", value: "com.atproto.moderation.defs#reasonOther" },
92];