Save emojis attached to actor objects

This commit is contained in:
silverpill 2023-03-03 22:23:52 +00:00
parent 4204350375
commit bd53e147ca
3 changed files with 144 additions and 88 deletions

View file

@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Allow to add notes to generated invite codes.
- Added `registration.default_role` configuration option.
- Save emojis attached to actor objects.
### Deprecated

View file

@ -5,10 +5,13 @@ use mitra_config::Instance;
use crate::activitypub::{
actors::types::Actor,
fetcher::fetchers::fetch_file,
handlers::create::handle_emoji,
receiver::HandlerError,
vocabulary::{EMOJI, HASHTAG},
};
use crate::database::DatabaseClient;
use crate::models::{
posts::validators::EMOJIS_MAX_NUM,
profiles::queries::{create_profile, update_profile},
profiles::types::{
DbActorProfile,
@ -78,10 +81,38 @@ async fn fetch_actor_images(
(maybe_avatar, maybe_banner)
}
fn parse_tags(actor: &Actor) -> () {
for tag_value in &actor.tag {
log::debug!("found actor tag: {}", tag_value);
async fn parse_tags(
db_client: &impl DatabaseClient,
instance: &Instance,
media_dir: &Path,
actor: &Actor,
) -> Result<(), HandlerError> {
let mut emojis = vec![];
for tag_value in actor.tag.clone() {
let tag_type = tag_value["type"].as_str().unwrap_or(HASHTAG);
if tag_type == EMOJI {
if emojis.len() >= EMOJIS_MAX_NUM {
log::warn!("too many emojis");
continue;
};
match handle_emoji(
db_client,
instance,
media_dir,
tag_value,
).await? {
Some(emoji) => {
if !emojis.contains(&emoji.id) {
emojis.push(emoji.id);
};
},
None => continue,
};
} else {
log::warn!("skipping actor tag of type {}", tag_type);
};
};
Ok(())
}
pub async fn create_remote_profile(
@ -103,7 +134,12 @@ pub async fn create_remote_profile(
).await;
let (identity_proofs, payment_options, extra_fields) =
actor.parse_attachments();
parse_tags(&actor);
parse_tags(
db_client,
instance,
media_dir,
&actor,
).await?;
let mut profile_data = ProfileCreateData {
username: actor.preferred_username.clone(),
hostname: Some(actor_address.hostname),
@ -153,7 +189,12 @@ pub async fn update_remote_profile(
).await;
let (identity_proofs, payment_options, extra_fields) =
actor.parse_attachments();
parse_tags(&actor);
parse_tags(
db_client,
instance,
media_dir,
&actor,
).await?;
let mut profile_data = ProfileUpdateData {
display_name: actor.name.clone(),
bio: actor.summary.clone(),

View file

@ -1,10 +1,11 @@
use std::collections::HashMap;
use std::path::Path;
use chrono::Utc;
use serde_json::{Value as JsonValue};
use uuid::Uuid;
use mitra_config::Config;
use mitra_config::{Config, Instance};
use mitra_utils::{
html::clean_html,
urls::get_hostname,
@ -32,7 +33,7 @@ use crate::models::{
get_emoji_by_remote_object_id,
update_emoji,
},
emojis::types::EmojiImage,
emojis::types::{DbEmoji, EmojiImage},
emojis::validators::{
validate_emoji_name,
EMOJI_MAX_SIZE,
@ -230,6 +231,91 @@ pub fn get_object_links(
links
}
pub async fn handle_emoji(
db_client: &impl DatabaseClient,
instance: &Instance,
media_dir: &Path,
tag_value: JsonValue,
) -> Result<Option<DbEmoji>, HandlerError> {
let tag: EmojiTag = match serde_json::from_value(tag_value) {
Ok(tag) => tag,
Err(error) => {
log::warn!("invalid emoji tag: {}", error);
return Ok(None);
},
};
let tag_name = tag.name.trim_matches(':');
if validate_emoji_name(tag_name).is_err() {
log::warn!("invalid emoji name");
return Ok(None);
};
let maybe_emoji_id = match get_emoji_by_remote_object_id(
db_client,
&tag.id,
).await {
Ok(emoji) => {
if emoji.updated_at >= tag.updated {
// Emoji already exists and is up to date
return Ok(Some(emoji));
};
if emoji.emoji_name != tag_name {
log::warn!("emoji name can't be changed");
return Ok(None);
};
Some(emoji.id)
},
Err(DatabaseError::NotFound("emoji")) => None,
Err(other_error) => return Err(other_error.into()),
};
let (file_name, file_size, maybe_media_type) = match fetch_file(
instance,
&tag.icon.url,
tag.icon.media_type.as_deref(),
EMOJI_MAX_SIZE,
media_dir,
).await {
Ok(file) => file,
Err(error) => {
log::warn!("failed to fetch emoji: {}", error);
return Ok(None);
},
};
let media_type = match maybe_media_type {
Some(media_type) if EMOJI_MEDIA_TYPES.contains(&media_type.as_str()) => {
media_type
},
_ => {
log::warn!(
"unexpected emoji media type: {:?}",
maybe_media_type,
);
return Ok(None);
},
};
log::info!("downloaded emoji {}", tag.icon.url);
let image = EmojiImage { file_name, file_size, media_type };
let emoji = if let Some(emoji_id) = maybe_emoji_id {
update_emoji(
db_client,
&emoji_id,
image,
&tag.updated,
).await?
} else {
let hostname = get_hostname(&tag.id)
.map_err(|_| ValidationError("invalid emoji ID"))?;
create_emoji(
db_client,
tag_name,
Some(&hostname),
image,
Some(&tag.id),
&tag.updated,
).await?
};
Ok(Some(emoji))
}
pub async fn get_object_tags(
config: &Config,
db_client: &impl DatabaseClient,
@ -361,97 +447,25 @@ pub async fn get_object_tags(
links.push(linked.id);
};
} else if tag_type == EMOJI {
let tag: EmojiTag = match serde_json::from_value(tag_value) {
Ok(tag) => tag,
Err(error) => {
log::warn!("invalid emoji tag: {}", error);
continue;
},
};
if emojis.len() >= EMOJIS_MAX_NUM {
log::warn!("too many emojis");
continue;
};
let tag_name = tag.name.trim_matches(':');
if validate_emoji_name(tag_name).is_err() {
log::warn!("invalid emoji name");
continue;
};
let maybe_emoji_id = match get_emoji_by_remote_object_id(
match handle_emoji(
db_client,
&tag.id,
).await {
Ok(emoji) => {
if emoji.updated_at >= tag.updated {
// Emoji already exists and is up to date
if !emojis.contains(&emoji.id) {
emojis.push(emoji.id);
};
continue;
};
if emoji.emoji_name != tag_name {
log::warn!("emoji name can't be changed");
continue;
};
Some(emoji.id)
},
Err(DatabaseError::NotFound("emoji")) => None,
Err(other_error) => return Err(other_error.into()),
};
let (file_name, file_size, maybe_media_type) = match fetch_file(
&instance,
&tag.icon.url,
tag.icon.media_type.as_deref(),
EMOJI_MAX_SIZE,
&media_dir,
).await {
Ok(file) => file,
Err(error) => {
log::warn!("failed to fetch emoji: {}", error);
continue;
tag_value,
).await? {
Some(emoji) => {
if !emojis.contains(&emoji.id) {
emojis.push(emoji.id);
};
},
};
let media_type = match maybe_media_type {
Some(media_type) if EMOJI_MEDIA_TYPES.contains(&media_type.as_str()) => {
media_type
},
_ => {
log::warn!(
"unexpected emoji media type: {:?}",
maybe_media_type,
);
continue;
},
};
log::info!("downloaded emoji {}", tag.icon.url);
let image = EmojiImage { file_name, file_size, media_type };
let emoji = if let Some(emoji_id) = maybe_emoji_id {
update_emoji(
db_client,
&emoji_id,
image,
&tag.updated,
).await?
} else {
let hostname = get_hostname(&tag.id)
.map_err(|_| ValidationError("invalid emoji ID"))?;
create_emoji(
db_client,
tag_name,
Some(&hostname),
image,
Some(&tag.id),
&tag.updated,
).await?
};
if !emojis.contains(&emoji.id) {
emojis.push(emoji.id);
None => continue,
};
} else {
log::warn!(
"skipping tag of type {}",
tag_type,
);
log::warn!("skipping tag of type {}", tag_type);
};
};
if let Some(ref object_id) = object.quote_url {