an independent Bluesky client using Constellation, PDS Queries, and other services
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
client
app
1import * as ATPAPI from "@atproto/api";
2
3import {
4 type HydratedLabelValueDefinition,
5 type labelpref,
6 useAutoLabelConfig,
7} from "~/providers/AutoLabelProvider";
8import { useQueryLabels } from "~/utils/useQuery";
9
10export function normalizeLabels(
11 labels: ATPAPI.ComAtprotoLabelDefs.Label[],
12): ATPAPI.ComAtprotoLabelDefs.Label[] {
13 const map = new Map<string, ATPAPI.ComAtprotoLabelDefs.Label>();
14
15 for (const label of labels) {
16 const key = `${label.src}::${label.uri}::${label.val}`;
17
18 if (label.neg) {
19 map.delete(key);
20 } else {
21 map.set(key, label);
22 }
23 }
24
25 return [...map.values()];
26}
27
28function computeVerdict(
29 prefs: labelpref[],
30 labels: ATPAPI.ComAtprotoLabelDefs.Label[],
31): ATPAPI.AppBskyActorDefs.ContentLabelPref["visibility"] {
32 for (const pref of prefs) {
33 // check if any label matches this pref
34 const hit = labels.some((l) => l.src === pref.did && l.val === pref.label);
35
36 if (!hit) continue;
37
38 if (pref.visibility === "hide") {
39 return "hide";
40 }
41
42 if (pref.visibility === "warn") {
43 return "warn";
44 }
45 }
46
47 return "ignore";
48}
49
50export function useAutoLabels({
51 subjects,
52 type,
53}: {
54 subjects: string[];
55 type: "post" | "profile" | "account";
56}): {
57 results: Map<
58 string,
59 {
60 labelVerdict: ATPAPI.AppBskyActorDefs.ContentLabelPref["visibility"];
61 labels: ATPAPI.ComAtprotoLabelDefs.Label[];
62 }
63 >;
64 hydratedLabelDefs: Map<
65 string,
66 HydratedLabelValueDefinition
67 >;
68} {
69 const {
70 activeLabelerDids,
71 mergedPrefContentLabels,
72 hydratedLabelDefs,
73 isLoading: configLoading,
74 isError: configError,
75 sendError,
76 } = useAutoLabelConfig();
77
78 const results = new Map<
79 string,
80 {
81 labelVerdict: ATPAPI.AppBskyActorDefs.ContentLabelPref["visibility"];
82 labels: ATPAPI.ComAtprotoLabelDefs.Label[];
83 }
84 >();
85
86 // const res = useQueryLabelMerge({
87 // s: subjects,
88 // l: activeLabelerDids,
89 // strict: false,
90 // });
91 const res = useQueryLabels(subjects,activeLabelerDids);
92 if (configLoading || res.isLoading) {
93 for (const subject of subjects) {
94 const subjectLabels = [] as ATPAPI.ComAtprotoLabelDefs.Label[];
95 results.set(subject, {
96 labelVerdict: "loading",
97 labels: subjectLabels,
98 });
99 }
100 return {
101 results,
102 hydratedLabelDefs,
103 };
104 }
105 if (configError || res.isError || res.data?.error) {
106 if (res.isError) {
107 sendError({
108 did: "!all",
109 message: "queryFailure"
110 })
111 } else
112 if (res.data?.error) {
113 console.log("fuck shit !internal-IDK-unknown, ", res.data)
114 res.data?.error?.forEach((err)=>{
115 sendError({
116 did: err.s,
117 message: err.e || "!internal-uAL-unknown, len: " + res.data.error?.length + ". label-length" + res.data.labels.length
118 })
119 })
120 }
121 console.error("Error fetching auto-label configuration or subject labels",{
122 configError: configError,
123 isLoading: res.isLoading,
124 isError: res.isError,
125 data: res.data,
126 //error: res.error,
127 });
128
129 for (const subject of subjects) {
130 const subjectLabels = [] as ATPAPI.ComAtprotoLabelDefs.Label[];
131 results.set(subject, {
132 labelVerdict: "error",
133 labels: subjectLabels,
134 });
135 }
136 return {
137 results,
138 hydratedLabelDefs,
139 };
140 }
141
142 if (res.data && (res.data.labels.length === 0) ) {
143 // early return because wow you did it theres no labels to be computed! wonderful! wow!
144 // group labels by subject (uri)
145
146 for (const subject of subjects) {
147 const subjectLabels = [] as ATPAPI.ComAtprotoLabelDefs.Label[];
148 results.set(subject, {
149 labelVerdict: "ignore",
150 labels: subjectLabels,
151 });
152 }
153 return {
154 results,
155 hydratedLabelDefs,
156 };
157 }
158
159 const effectiveLabels = normalizeLabels(res.data?.labels ?? []);
160
161 // group labels by subject (uri)
162 const labelsBySubject = new Map<
163 string,
164 ATPAPI.ComAtprotoLabelDefs.Label[]
165 >();
166
167 for (const label of effectiveLabels) {
168 const arr = labelsBySubject.get(label.uri) ?? [];
169 arr.push(label);
170 labelsBySubject.set(label.uri, arr);
171 }
172
173 for (const subject of subjects) {
174 const subjectLabels = labelsBySubject.get(subject) ?? [];
175 const verdict = computeVerdict(
176 mergedPrefContentLabels,
177 subjectLabels,
178 );
179 results.set(subject, {
180 labelVerdict: verdict,
181 labels: subjectLabels,
182 });
183 }
184
185 return {
186 results,
187 hydratedLabelDefs,
188 };
189}
190
191export function getGetHydratedLabelDefs(
192 hydratedLabelDefs: Map<
193 string,
194 HydratedLabelValueDefinition
195 >,
196) {
197 function getHydratedLabelDefs(did: string, label: string) {
198 return hydratedLabelDefs.get(did + "::" + label);
199 }
200 return getHydratedLabelDefs;
201}