1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum LabelPreference {
9 Ignore,
10 Warn,
11 Hide,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum UiContext {
17 ProfileList,
18 ProfileView,
19 ContentList,
20 ContentView,
21 ContentMedia,
22 Avatar,
23 Banner,
24 DisplayName,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum LabelBlurs {
31 Content,
32 Media,
33 None,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "lowercase")]
39pub enum LabelSeverity {
40 Alert,
41 Inform,
42 None,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum LabelFlag {
48 NoOverride,
49 Adult,
50 Unauthed,
51 NoSelf,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum BehaviorValue {
57 Blur,
58 Alert,
59 Inform,
60}
61
62#[derive(Debug, Clone, Default)]
64pub struct ModerationBehavior {
65 pub profile_list: Option<BehaviorValue>,
66 pub profile_view: Option<BehaviorValue>,
67 pub content_list: Option<BehaviorValue>,
68 pub content_view: Option<BehaviorValue>,
69 pub content_media: Option<BehaviorValue>,
70 pub avatar: Option<BehaviorValue>,
71 pub banner: Option<BehaviorValue>,
72 pub display_name: Option<BehaviorValue>,
73}
74
75impl ModerationBehavior {
76 pub fn get(&self, ctx: UiContext) -> Option<BehaviorValue> {
77 match ctx {
78 UiContext::ProfileList => self.profile_list,
79 UiContext::ProfileView => self.profile_view,
80 UiContext::ContentList => self.content_list,
81 UiContext::ContentView => self.content_view,
82 UiContext::ContentMedia => self.content_media,
83 UiContext::Avatar => self.avatar,
84 UiContext::Banner => self.banner,
85 UiContext::DisplayName => self.display_name,
86 }
87 }
88}
89
90#[derive(Debug, Clone)]
92pub struct LabelValueDefinition {
93 pub identifier: String,
94 pub configurable: bool,
95 pub default_setting: LabelPreference,
96 pub flags: Vec<LabelFlag>,
97 pub severity: LabelSeverity,
98 pub blurs: LabelBlurs,
99 pub behaviors: LabelBehaviors,
100}
101
102#[derive(Debug, Clone)]
104pub struct LabelBehaviors {
105 pub account: ModerationBehavior,
106 pub profile: ModerationBehavior,
107 pub content: ModerationBehavior,
108}
109
110#[derive(Debug, Clone)]
112pub enum ModerationCauseSource {
113 User,
114 List { uri: String, name: String },
115 Labeler { did: String },
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum LabelTarget {
121 Account,
122 Profile,
123 Content,
124}
125
126#[derive(Debug, Clone)]
128pub enum ModerationCause {
129 Blocking {
130 source: ModerationCauseSource,
131 priority: u8,
132 downgraded: bool,
133 },
134 BlockedBy {
135 source: ModerationCauseSource,
136 priority: u8,
137 downgraded: bool,
138 },
139 BlockOther {
140 source: ModerationCauseSource,
141 priority: u8,
142 downgraded: bool,
143 },
144 Label {
145 source: ModerationCauseSource,
146 label: LabelData,
147 label_def: LabelValueDefinition,
148 target: LabelTarget,
149 setting: LabelPreference,
150 no_override: bool,
151 priority: u8,
152 downgraded: bool,
153 },
154 Muted {
155 source: ModerationCauseSource,
156 priority: u8,
157 downgraded: bool,
158 },
159 MuteWord {
160 source: ModerationCauseSource,
161 priority: u8,
162 downgraded: bool,
163 },
164 Hidden {
165 source: ModerationCauseSource,
166 priority: u8,
167 downgraded: bool,
168 },
169}
170
171impl ModerationCause {
172 pub fn priority(&self) -> u8 {
173 match self {
174 Self::Blocking { priority, .. }
175 | Self::BlockedBy { priority, .. }
176 | Self::BlockOther { priority, .. }
177 | Self::Label { priority, .. }
178 | Self::Muted { priority, .. }
179 | Self::MuteWord { priority, .. }
180 | Self::Hidden { priority, .. } => *priority,
181 }
182 }
183
184 pub fn is_downgraded(&self) -> bool {
185 match self {
186 Self::Blocking { downgraded, .. }
187 | Self::BlockedBy { downgraded, .. }
188 | Self::BlockOther { downgraded, .. }
189 | Self::Label { downgraded, .. }
190 | Self::Muted { downgraded, .. }
191 | Self::MuteWord { downgraded, .. }
192 | Self::Hidden { downgraded, .. } => *downgraded,
193 }
194 }
195
196 pub fn set_downgraded(&mut self) {
197 match self {
198 Self::Blocking { downgraded, .. }
199 | Self::BlockedBy { downgraded, .. }
200 | Self::BlockOther { downgraded, .. }
201 | Self::Label { downgraded, .. }
202 | Self::Muted { downgraded, .. }
203 | Self::MuteWord { downgraded, .. }
204 | Self::Hidden { downgraded, .. } => *downgraded = true,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct LabelData {
212 pub src: String,
213 pub uri: String,
214 pub val: String,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub neg: Option<bool>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct MutedWord {
223 pub value: String,
224 pub targets: Vec<String>,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub actor_target: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub expires_at: Option<String>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct ModerationPrefsLabeler {
234 pub did: String,
235 pub labels: std::collections::HashMap<String, LabelPreference>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct ModerationPrefs {
242 pub adult_content_enabled: bool,
243 pub labels: std::collections::HashMap<String, LabelPreference>,
244 pub labelers: Vec<ModerationPrefsLabeler>,
245 pub muted_words: Vec<MutedWord>,
246 pub hidden_posts: Vec<String>,
247}
248
249#[derive(Debug, Clone)]
251pub struct ModerationOpts {
252 pub user_did: Option<String>,
253 pub prefs: ModerationPrefs,
254 pub label_defs: std::collections::HashMap<String, Vec<LabelValueDefinition>>,
255}
256
257#[derive(Debug, Clone, Default)]
259pub struct ModerationUi {
260 pub no_override: bool,
261 pub filters: Vec<ModerationCause>,
262 pub blurs: Vec<ModerationCause>,
263 pub alerts: Vec<ModerationCause>,
264 pub informs: Vec<ModerationCause>,
265}
266
267impl ModerationUi {
268 pub fn filter(&self) -> bool {
269 !self.filters.is_empty()
270 }
271 pub fn blur(&self) -> bool {
272 !self.blurs.is_empty()
273 }
274 pub fn alert(&self) -> bool {
275 !self.alerts.is_empty()
276 }
277 pub fn inform(&self) -> bool {
278 !self.informs.is_empty()
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn label_preference_serde() {
288 let pref = LabelPreference::Hide;
289 let json = serde_json::to_string(&pref).unwrap();
290 assert_eq!(json, "\"hide\"");
291 let parsed: LabelPreference = serde_json::from_str(&json).unwrap();
292 assert_eq!(parsed, LabelPreference::Hide);
293 }
294
295 #[test]
296 fn moderation_ui_helpers() {
297 let mut ui = ModerationUi::default();
298 assert!(!ui.filter());
299 assert!(!ui.blur());
300 assert!(!ui.alert());
301 assert!(!ui.inform());
302
303 ui.filters.push(ModerationCause::Hidden {
304 source: ModerationCauseSource::User,
305 priority: 6,
306 downgraded: false,
307 });
308 assert!(ui.filter());
309 }
310
311 #[test]
312 fn cause_priority_and_downgrade() {
313 let mut cause = ModerationCause::Muted {
314 source: ModerationCauseSource::User,
315 priority: 6,
316 downgraded: false,
317 };
318 assert_eq!(cause.priority(), 6);
319 assert!(!cause.is_downgraded());
320 cause.set_downgraded();
321 assert!(cause.is_downgraded());
322 }
323
324 #[test]
325 fn muted_word_serde() {
326 let word = MutedWord {
327 value: "test".into(),
328 targets: vec!["content".into()],
329 actor_target: Some("exclude-following".into()),
330 expires_at: None,
331 };
332 let json = serde_json::to_string(&word).unwrap();
333 assert!(json.contains("\"actorTarget\""));
334 let parsed: MutedWord = serde_json::from_str(&json).unwrap();
335 assert_eq!(parsed.value, "test");
336 }
337
338 #[test]
339 fn behavior_get_context() {
340 let behavior = ModerationBehavior {
341 content_list: Some(BehaviorValue::Blur),
342 content_view: Some(BehaviorValue::Alert),
343 ..Default::default()
344 };
345 assert_eq!(
346 behavior.get(UiContext::ContentList),
347 Some(BehaviorValue::Blur)
348 );
349 assert_eq!(
350 behavior.get(UiContext::ContentView),
351 Some(BehaviorValue::Alert)
352 );
353 assert_eq!(behavior.get(UiContext::Avatar), None);
354 }
355}