diff --git a/visibility-filtering/rules/context.rs b/visibility-filtering/rules/context.rs index 5b9b939a..1e27921f 100644 --- a/visibility-filtering/rules/context.rs +++ b/visibility-filtering/rules/context.rs @@ -257,6 +257,23 @@ impl TakedownPredicates<'_> { self.in_viewer_country(local_laws_takedown_country) } + /// TES global takedowns that are not country-coded and are not the DMCA + /// arm already consumed by `legal_in_viewer_country`. + #[inline] + pub fn is_global(&self) -> bool { + self.ctx + .candidate + .tweet_features + .takedown_reasons + .iter() + .any(|reason| { + matches!( + reason, + TakedownReason::HatefulImagery | TakedownReason::Unknown + ) + }) + } + #[inline] fn in_viewer_country(&self, extractor: fn(&TakedownReason) -> Option<&str>) -> bool { let viewer_country = self.ctx.viewer.country_code.as_deref(); diff --git a/visibility-filtering/rules/golden_corpus.rs b/visibility-filtering/rules/golden_corpus.rs index cc4da723..0354cf3c 100644 --- a/visibility-filtering/rules/golden_corpus.rs +++ b/visibility-filtering/rules/golden_corpus.rs @@ -621,6 +621,30 @@ fn tweet_shape_cases() -> Vec { expected_action: Allow, expected_decided_by: None, }, + Case { + name: "hateful_imagery_takedown_drops_for_any_viewer", + level: TimelineHome, + viewer: viewer(VIEWER_ID), + candidate: takedown_candidate(TakedownReason::HatefulImagery), + expected_action: Drop(FilteredReason::UnspecifiedReason), + expected_decided_by: Some("DropGlobalTakendownPostRule"), + }, + Case { + name: "unknown_takedown_drops_for_any_viewer", + level: TimelineHomeRecommendations, + viewer: viewer(VIEWER_ID), + candidate: takedown_candidate(TakedownReason::Unknown), + expected_action: Drop(FilteredReason::UnspecifiedReason), + expected_decided_by: Some("DropGlobalTakendownPostRule"), + }, + Case { + name: "hateful_imagery_takedown_allows_author", + level: TimelineHome, + viewer: author_viewer(), + candidate: takedown_candidate(TakedownReason::HatefulImagery), + expected_action: Allow, + expected_decided_by: None, + }, ] } diff --git a/visibility-filtering/rules/registry.rs b/visibility-filtering/rules/registry.rs index af79ac33..a80eb1b2 100644 --- a/visibility-filtering/rules/registry.rs +++ b/visibility-filtering/rules/registry.rs @@ -226,6 +226,7 @@ rust_vf: "DropStaleTweetsRule", "DropLegalTakendownPostRule", "DropLocalLawsTakendownPostRule", + "DropGlobalTakendownPostRule", "SensitiveViewerLoggedOutDropRule", "SensitiveViewerUnderageDropRule", "SensitiveViewerNoStatedAgeDropRule", diff --git a/visibility-filtering/rules/tweet_rules.rs b/visibility-filtering/rules/tweet_rules.rs index 15db052e..1210e9ec 100644 --- a/visibility-filtering/rules/tweet_rules.rs +++ b/visibility-filtering/rules/tweet_rules.rs @@ -270,6 +270,13 @@ fn drop_local_laws_takendown_post(context: &RuleContext<'_>) -> VfAction { VfAction::Allow } +fn drop_global_takendown_post(context: &RuleContext<'_>) -> VfAction { + if !context.viewer().is_author() && context.takedown().is_global() { + return VfAction::Drop(FilteredReason::UnspecifiedReason); + } + VfAction::Allow +} + fn drop_geo_restricted_media(context: &RuleContext<'_>) -> VfAction { if context.takedown().media_restricted_in_viewer_country() { VfAction::Drop(FilteredReason::UnspecifiedReason) @@ -293,6 +300,10 @@ pub(super) const TES_HOME_DROPS: &[RuleSpec] = &[ name: "DropLocalLawsTakendownPostRule", evaluate: drop_local_laws_takendown_post, }, + RuleSpec::Custom { + name: "DropGlobalTakendownPostRule", + evaluate: drop_global_takendown_post, + }, ]; pub(super) const FILTER_ALL: &[RuleSpec] = &[RuleSpec::Tweet { @@ -865,6 +876,9 @@ mod tests { ]); assert_allows(legal, &viewer_with_country("de"), &non_country); assert_allows(local, &viewer_with_country("de"), &non_country); + let global = tes_spec("DropGlobalTakendownPostRule"); + assert_drops(global, &viewer_with_country("de"), &non_country, &reason); + assert_drops(global, &viewer(VIEWER_ID), &non_country, &reason); } #[test] @@ -908,6 +922,18 @@ mod tests { let mut author_dmca = dmca.clone(); author_dmca.author_id = VIEWER_ID; assert_allows(legal, &viewer(VIEWER_ID), &author_dmca); + + let global = tes_spec("DropGlobalTakendownPostRule"); + assert_allows(global, &viewer(VIEWER_ID), &dmca); + let hateful = takedown_candidate(vec![TakedownReason::HatefulImagery]); + assert_drops(global, &viewer_with_country("de"), &hateful, &reason); + assert_drops(global, &viewer(VIEWER_ID), &hateful, &reason); + assert_allows(legal, &viewer(VIEWER_ID), &hateful); + let unknown = takedown_candidate(vec![TakedownReason::Unknown]); + assert_drops(global, &viewer(VIEWER_ID), &unknown, &reason); + let mut author_hateful = hateful.clone(); + author_hateful.author_id = VIEWER_ID; + assert_allows(global, &viewer(VIEWER_ID), &author_hateful); } #[test]