BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
1import type { ModerationLabel, ModerationUiDecision } from "$/lib/types";
2
3/**
4 * Official Bluesky labeler DID (@moderation.bsky.app)
5 */
6export const BUILTIN_LABELER_DID = "did:plc:ar7c4by46qjdydhdevvrndac";
7
8export const DEFAULT_MODERATION_DECISION: ModerationUiDecision = {
9 alert: false,
10 blur: "none",
11 filter: false,
12 inform: false,
13 noOverride: false,
14};
15
16export type ModerationLabelSummary = { key: string; source: string; value: string };
17
18export function asModerationLabels(value: unknown): ModerationLabel[] {
19 const record = asRecord(value);
20 const labels = record?.["labels"];
21 if (!Array.isArray(labels)) {
22 return [];
23 }
24
25 return labels.filter((label): label is ModerationLabel => isRecordLike(label));
26}
27
28export function collectModerationLabels(...values: unknown[]): ModerationLabel[] {
29 const labels = values.flatMap((value) => asModerationLabels(value));
30 if (labels.length <= 1) {
31 return labels;
32 }
33
34 const deduped: ModerationLabel[] = [];
35 const seen = new Set<string>();
36
37 for (const label of labels) {
38 const source = typeof label.src === "string" ? label.src : "";
39 const value = typeof label.val === "string" ? label.val : "";
40 const uri = typeof label.uri === "string" ? label.uri : "";
41 const key = `${source}|${value}|${uri}`;
42 if (seen.has(key)) {
43 continue;
44 }
45
46 seen.add(key);
47 deduped.push(label);
48 }
49
50 return deduped;
51}
52
53export function moderationLabelsKey(labels: ModerationLabel[]): string {
54 if (labels.length === 0) {
55 return "";
56 }
57
58 const tokens = labels.map((label) => {
59 const source = typeof label.src === "string" ? label.src.trim() : "";
60 const value = typeof label.val === "string" ? label.val.trim() : "";
61 const uri = typeof label.uri === "string" ? label.uri.trim() : "";
62 return `${source}|${value}|${uri}`;
63 });
64
65 return tokens.toSorted().join(";");
66}
67
68export function summarizeModerationLabels(labels: ModerationLabel[], limit = 3): ModerationLabelSummary[] {
69 if (labels.length === 0) {
70 return [];
71 }
72
73 const summaries: ModerationLabelSummary[] = [];
74 const seen = new Set<string>();
75
76 for (const label of labels) {
77 const value = toLabelDisplayValue(label.val);
78 if (!value) {
79 continue;
80 }
81
82 const source = toSourceDisplayValue(label.src);
83 const key = `${source}|${value}`;
84 if (seen.has(key)) {
85 continue;
86 }
87
88 seen.add(key);
89 summaries.push({ key, source, value });
90
91 if (summaries.length >= limit) {
92 break;
93 }
94 }
95
96 return summaries;
97}
98
99function toLabelDisplayValue(value: unknown): string | null {
100 if (typeof value !== "string") {
101 return null;
102 }
103
104 const normalized = value.trim();
105 if (!normalized) {
106 return null;
107 }
108
109 if (normalized.startsWith("!")) {
110 return `Not ${normalized.slice(1)}`;
111 }
112
113 return normalized;
114}
115
116function toSourceDisplayValue(source: unknown): string {
117 if (typeof source !== "string") {
118 return "Unknown";
119 }
120
121 const normalized = source.trim();
122 if (!normalized) {
123 return "Unknown";
124 }
125
126 if (!normalized.startsWith("did:")) {
127 return normalized;
128 }
129
130 if (normalized.length <= 22) {
131 return normalized;
132 }
133
134 return `${normalized.slice(0, 16)}...${normalized.slice(-6)}`;
135}
136
137function asRecord(value: unknown): Record<string, unknown> | null {
138 if (!isRecordLike(value)) {
139 return null;
140 }
141
142 return value;
143}
144
145function isRecordLike(value: unknown): value is Record<string, unknown> {
146 return !!value && typeof value === "object" && !Array.isArray(value);
147}