Skip to content

Commit

Permalink
feat: Add expired/actor_target check to has_muted_word (#211)
Browse files Browse the repository at this point in the history
* Add expired/actor_target check to has_muted_word

* Fix tests
  • Loading branch information
sugyan authored Aug 13, 2024
1 parent e65ab97 commit 59da997
Show file tree
Hide file tree
Showing 5 changed files with 778 additions and 226 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bsky-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ anyhow.workspace = true
async-trait.workspace = true
atrium-api.workspace = true
atrium-xrpc-client = { workspace = true, optional = true }
chrono.workspace = true
psl = { version = "2.1.42", optional = true }
regex.workspace = true
serde = { workspace = true, features = ["derive"] }
Expand Down
37 changes: 31 additions & 6 deletions bsky-sdk/src/moderation/mutewords.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Muteword checking logic.
use atrium_api::app::bsky::{actor::defs::MutedWord, richtext::facet::MainFeaturesItem};
use atrium_api::types::Union;
use atrium_api::app::bsky::actor::defs::{MutedWord, ProfileViewBasic};
use atrium_api::app::bsky::richtext::facet;
use atrium_api::types::{string::Language, Union};
use regex::Regex;
use std::sync::OnceLock;

Expand All @@ -27,9 +28,10 @@ const LANGUAGE_EXCEPTIONS: [&str; 5] = [
pub fn has_muted_word(
muted_words: &[MutedWord],
text: &str,
facets: &Option<Vec<atrium_api::app::bsky::richtext::facet::Main>>,
outline_tags: &Option<Vec<String>>,
langs: &Option<Vec<atrium_api::types::string::Language>>,
facets: Option<&Vec<facet::Main>>,
outline_tags: Option<&Vec<String>>,
langs: Option<&Vec<Language>>,
actor: Option<&ProfileViewBasic>,
) -> bool {
let exception = langs
.as_ref()
Expand All @@ -47,7 +49,7 @@ pub fn has_muted_word(
.iter()
.flat_map(|facet| {
facet.features.iter().filter_map(|feature| {
if let Union::Refs(MainFeaturesItem::Tag(tag)) = feature {
if let Union::Refs(facet::MainFeaturesItem::Tag(tag)) = feature {
Some(&tag.tag)
} else {
None
Expand All @@ -61,6 +63,29 @@ pub fn has_muted_word(
for mute in muted_words {
let muted_word = mute.value.to_lowercase();
let post_text = text.to_lowercase();

// check if expired
if let Some(expires_at) = &mute.expires_at {
if expires_at.as_ref() < &chrono::Utc::now().fixed_offset() {
continue;
}
}
// check if actor target
if let Some(actor_target) = &mute.actor_target {
if actor_target == "exclude-following"
&& actor
.and_then(|actor| {
actor
.viewer
.as_ref()
.and_then(|viewer| viewer.following.as_ref())
})
.is_some()
{
continue;
}
}

// `content` applies to tags as well
if tags.contains(&muted_word) {
return true;
Expand Down
203 changes: 190 additions & 13 deletions bsky-sdk/src/moderation/subjects/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use super::super::types::{LabelTarget, SubjectPost};
use super::super::Moderator;
use atrium_api::app::bsky::actor::defs::MutedWord;
use atrium_api::app::bsky::embed::record::{ViewBlocked, ViewRecord, ViewRecordRefs};
use atrium_api::app::bsky::embed::record_with_media::{MainMediaRefs, ViewMediaRefs};
use atrium_api::app::bsky::feed::defs::PostViewEmbedRefs;
use atrium_api::app::bsky::feed::post::{self, RecordEmbedRefs};
use atrium_api::types::{TryFromUnknown, Union};

impl Moderator {
Expand Down Expand Up @@ -137,20 +139,195 @@ fn check_muted_words(subject: &SubjectPost, muted_words: &[MutedWord]) -> bool {
if muted_words.is_empty() {
return false;
}
let Ok(post) =
atrium_api::app::bsky::feed::post::Record::try_from_unknown(subject.data.record.clone())
else {
return false;
};
if has_muted_word(
muted_words,
&post.text,
&post.facets,
&post.tags,
&post.langs,
) {
return true;

let post_author = &subject.author;

if let Ok(post) =
atrium_api::app::bsky::feed::post::Record::try_from_unknown(subject.record.clone())
{
// post text
if has_muted_word(
muted_words,
&post.text,
post.facets.as_ref(),
post.tags.as_ref(),
post.langs.as_ref(),
Some(post_author),
) {
return true;
}

if let Some(Union::Refs(RecordEmbedRefs::AppBskyEmbedImagesMain(images))) = &post.embed {
// post images
for image in &images.images {
if has_muted_word(
muted_words,
&image.alt,
None,
None,
post.langs.as_ref(),
Some(post_author),
) {
return true;
}
}
}
}

match &subject.embed {
// quote post
Some(Union::Refs(PostViewEmbedRefs::AppBskyEmbedRecordView(view))) => {
if let Union::Refs(ViewRecordRefs::ViewRecord(view_record)) = &view.record {
if let Ok(record) = post::Record::try_from_unknown(view_record.value.clone()) {
let embedded_post = record;
let embed_author = &view_record.author;
// quoted post text
if has_muted_word(
muted_words,
&embedded_post.text,
embedded_post.facets.as_ref(),
embedded_post.tags.as_ref(),
embedded_post.langs.as_ref(),
Some(embed_author),
) {
return true;
}
match &embedded_post.embed {
// quoted post's images
Some(Union::Refs(RecordEmbedRefs::AppBskyEmbedImagesMain(main))) => {
for image in &main.images {
if has_muted_word(
muted_words,
&image.alt,
None,
None,
embedded_post.langs.as_ref(),
Some(embed_author),
) {
return true;
}
}
}
// quoted post's link card
Some(Union::Refs(RecordEmbedRefs::AppBskyEmbedExternalMain(main))) => {
let external = &main.external;
if has_muted_word(
muted_words,
&format!("{} {}", external.title, external.description),
None,
None,
None,
Some(embed_author),
) {
return true;
}
}
Some(Union::Refs(RecordEmbedRefs::AppBskyEmbedRecordWithMediaMain(
main,
))) => match &main.media {
Union::Refs(MainMediaRefs::AppBskyEmbedExternalMain(main)) => {
let external = &main.external;
if has_muted_word(
muted_words,
&format!("{} {}", external.title, external.description),
None,
None,
None,
Some(embed_author),
) {
return true;
}
}
Union::Refs(MainMediaRefs::AppBskyEmbedImagesMain(main)) => {
for image in &main.images {
if has_muted_word(
muted_words,
&image.alt,
None,
None,
embedded_post.langs.as_ref(),
Some(embed_author),
) {
return true;
}
}
}
_ => {}
},
_ => {}
}
}
}
}
// link card
Some(Union::Refs(PostViewEmbedRefs::AppBskyEmbedExternalView(view))) => {
let external = &view.external;
if has_muted_word(
muted_words,
&format!("{} {}", external.title, external.description),
None,
None,
None,
Some(post_author),
) {
return true;
}
}
// quote post with media
Some(Union::Refs(PostViewEmbedRefs::AppBskyEmbedRecordWithMediaView(view))) => {
if let Union::Refs(ViewRecordRefs::ViewRecord(view_record)) = &view.record.record {
let embed_author = &view_record.author;
// quoted post text
if let Ok(record) = post::Record::try_from_unknown(view_record.value.clone()) {
let post = record;
if has_muted_word(
muted_words,
&post.text,
post.facets.as_ref(),
post.tags.as_ref(),
post.langs.as_ref(),
Some(embed_author),
) {
return true;
}
}
// quoted post media
match &view.media {
Union::Refs(ViewMediaRefs::AppBskyEmbedExternalView(view)) => {
let external = &view.external;
if has_muted_word(
muted_words,
&format!("{} {}", external.title, external.description),
None,
None,
None,
Some(embed_author),
) {
return true;
}
}
Union::Refs(ViewMediaRefs::AppBskyEmbedImagesView(view)) => {
let langs = post::Record::try_from_unknown(view_record.value.clone())
.ok()
.and_then(|record| record.data.langs);
for image in &view.images {
if has_muted_word(
muted_words,
&image.alt,
None,
None,
langs.as_ref(),
Some(embed_author),
) {
return true;
}
}
}
_ => {}
}
}
}
_ => {}
}
false
}
Loading

0 comments on commit 59da997

Please sign in to comment.