use super::{
Blur, LabelCause, LabelPref, LabelTarget, Labeled, LabelerDefs, ModerationDecision,
ModerationPrefs,
};
use jacquard_api::com_atproto::label::{Label, LabelValue};
use jacquard_common::bos::BosStr;
use jacquard_common::types::string::{Datetime, Did};
use smol_str::SmolStr;
pub fn moderate<S: BosStr, T: Labeled<S>>(
item: &T,
prefs: &ModerationPrefs,
defs: &LabelerDefs,
accepted_labelers: &[Did],
) -> ModerationDecision {
let mut decision = ModerationDecision::none();
let now = Datetime::now();
for label in item.labels() {
if let Some(exp) = &label.exp {
if exp <= &now {
continue;
}
}
if !accepted_labelers.is_empty()
&& !accepted_labelers
.iter()
.any(|d| d.as_ref() == label.src.as_ref())
{
continue;
}
if label.neg.unwrap_or(false) {
decision.causes.retain(|cause| {
!(cause.label.as_str() == label.val.as_ref()
&& cause.source.as_ref() == label.src.as_ref())
});
continue;
}
apply_label(label, prefs, defs, &mut decision);
}
if let Some(self_labels) = item.self_labels() {
for self_label in self_labels.values {
let pref = prefs
.labels
.iter()
.find(|(k, _)| k.as_str() == self_label.val.as_ref())
.map(|(_, v)| v);
match pref {
Some(LabelPref::Hide) => {
decision.filter = true;
}
Some(LabelPref::Warn) | None => {
if decision.blur == Blur::None {
decision.blur = Blur::Content;
}
decision.inform = true;
}
Some(LabelPref::Ignore) => {
}
}
}
}
decision
}
fn apply_label<S: BosStr>(
label: &Label<S>,
prefs: &ModerationPrefs,
defs: &LabelerDefs,
decision: &mut ModerationDecision,
) {
let label_val = label.val.as_ref();
let pref = prefs
.labelers
.iter()
.find(|(k, _)| k.as_str() == label.src.as_ref())
.and_then(|(_, labeler_prefs)| {
labeler_prefs
.iter()
.find(|(k, _)| k.as_str() == label_val)
.map(|(_, v)| v)
})
.or_else(|| {
prefs
.labels
.iter()
.find(|(k, _)| k.as_str() == label_val)
.map(|(_, v)| v)
});
let def = defs.find_def(&label.src, label_val);
if let Some(def) = def {
if def.adult_only.unwrap_or(false) && !prefs.adult_content_enabled {
decision.filter = true;
decision.no_override = true;
decision.causes.push(LabelCause {
label: LabelValue::from_value(SmolStr::new(label_val)),
source: Did::new_owned(label.src.as_ref()).expect("label.src must be a valid DID"),
target: determine_target(label),
});
return;
}
}
match pref.copied() {
Some(LabelPref::Hide) => {
decision.filter = true;
decision.causes.push(LabelCause {
label: LabelValue::from_value(SmolStr::new(label_val)),
source: Did::new_owned(label.src.as_ref()).expect("label.src must be a valid DID"),
target: determine_target(label),
});
}
Some(LabelPref::Warn) => {
apply_warning(label, def, decision);
}
Some(LabelPref::Ignore) => {
}
None => {
apply_default(label, def, decision);
}
}
}
fn apply_warning<S: BosStr>(
label: &Label<S>,
def: Option<&jacquard_api::com_atproto::label::LabelValueDefinition>,
decision: &mut ModerationDecision,
) {
let label_val = label.val.as_ref();
let blur = if let Some(def) = def {
match def.blurs.as_ref() {
"content" => Blur::Content,
"media" => Blur::Media,
_ => Blur::None,
}
} else {
match label_val {
"porn" | "sexual" | "nudity" | "nsfl" | "gore" => Blur::Media,
_ => Blur::Content,
}
};
decision.blur = match (decision.blur, blur) {
(Blur::Content, _) | (_, Blur::Content) => Blur::Content,
(Blur::Media, _) | (_, Blur::Media) => Blur::Media,
_ => Blur::None,
};
if let Some(def) = def {
match def.severity.as_ref() {
"alert" => decision.alert = true,
"inform" => decision.inform = true,
_ => {}
}
} else {
decision.alert = true;
}
decision.causes.push(LabelCause {
label: LabelValue::from_value(SmolStr::new(label_val)),
source: Did::new_owned(label.src.as_ref()).expect("label.src must be a valid DID"),
target: determine_target(label),
});
}
fn apply_default<S: BosStr>(
label: &Label<S>,
def: Option<&jacquard_api::com_atproto::label::LabelValueDefinition>,
decision: &mut ModerationDecision,
) {
let label_val = label.val.as_ref();
if let Some(def) = def {
if let Some(default_setting) = &def.default_setting {
match default_setting.as_ref() {
"hide" => {
decision.filter = true;
decision.causes.push(LabelCause {
label: LabelValue::from_value(SmolStr::new(label_val)),
source: Did::new_owned(label.src.as_ref())
.expect("label.src must be a valid DID"),
target: determine_target(label),
});
return;
}
"warn" => {
apply_warning(label, Some(def), decision);
return;
}
"ignore" => return,
_ => {}
}
}
}
if label_val.starts_with('!') {
match label_val {
"!hide" => {
decision.filter = true;
decision.no_override = true;
decision.causes.push(LabelCause {
label: LabelValue::from_value(SmolStr::new(label_val)),
source: Did::new_owned(label.src.as_ref())
.expect("label.src must be a valid DID"),
target: determine_target(label),
});
}
"!warn" => {
apply_warning(label, def, decision);
}
"!no-unauthenticated" => {
decision.inform = true;
}
_ => {}
}
} else {
match label_val {
"porn" | "nsfl" => {
decision.filter = true;
decision.causes.push(LabelCause {
label: LabelValue::from_value(SmolStr::new(label_val)),
source: Did::new_owned(label.src.as_ref())
.expect("label.src must be a valid DID"),
target: determine_target(label),
});
}
"sexual" | "nudity" | "gore" => {
apply_warning(label, def, decision);
}
_ => {
decision.inform = true;
decision.causes.push(LabelCause {
label: LabelValue::from_value(SmolStr::new(label_val)),
source: Did::new_owned(label.src.as_ref())
.expect("label.src must be a valid DID"),
target: determine_target(label),
});
}
}
}
}
fn determine_target<S: BosStr>(label: &Label<S>) -> LabelTarget {
if Did::<SmolStr>::new_owned(label.uri.as_ref()).is_ok() {
LabelTarget::Account
} else {
LabelTarget::Content
}
}
pub fn moderate_all<'a, S: BosStr, T: Labeled<S>>(
items: &'a [T],
prefs: &ModerationPrefs,
defs: &LabelerDefs,
accepted_labelers: &[Did],
) -> Vec<(&'a T, ModerationDecision)> {
items
.iter()
.map(|item| (item, moderate(item, prefs, defs, accepted_labelers)))
.collect()
}
pub trait ModerationIterExt<'a, S: BosStr, T: Labeled<S> + 'a>:
Iterator<Item = &'a T> + Sized
{
fn with_moderation(
self,
prefs: &'a ModerationPrefs,
defs: &'a LabelerDefs,
accepted_labelers: &'a [Did],
) -> impl Iterator<Item = (&'a T, ModerationDecision)> {
self.map(move |item| (item, moderate::<S, T>(item, prefs, defs, accepted_labelers)))
}
fn filter_moderated(
self,
prefs: &'a ModerationPrefs,
defs: &'a LabelerDefs,
accepted_labelers: &'a [Did],
) -> impl Iterator<Item = &'a T> {
self.filter(move |item| !moderate::<S, T>(*item, prefs, defs, accepted_labelers).filter)
}
}
impl<'a, S: BosStr, T: Labeled<S> + 'a, I: Iterator<Item = &'a T>> ModerationIterExt<'a, S, T>
for I
{
}