Replace client-side tag URLs with collection IDs

This commit is contained in:
silverpill 2023-01-13 21:28:26 +00:00
parent be67972760
commit d09770913b
8 changed files with 55 additions and 19 deletions

View file

@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- Make `delete-emoji` command accept emoji name and hostname instead of ID. - Make `delete-emoji` command accept emoji name and hostname instead of ID.
- Replaced client-side tag URLs with collection IDs.
### Security ### Security

View file

@ -11,6 +11,7 @@ use crate::activitypub::{
local_actor_subscribers, local_actor_subscribers,
local_emoji_id, local_emoji_id,
local_object_id, local_object_id,
local_tag_collection,
}, },
types::{Attachment, EmojiTag, EmojiTagImage, LinkTag, SimpleTag}, types::{Attachment, EmojiTag, EmojiTagImage, LinkTag, SimpleTag},
vocabulary::*, vocabulary::*,
@ -25,7 +26,6 @@ use crate::models::{
users::types::User, users::types::User,
}; };
use crate::utils::files::get_file_url; use crate::utils::files::get_file_url;
use crate::web_client::urls::get_tag_page_url;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Serialize)] #[derive(Serialize)]
@ -136,11 +136,11 @@ pub fn build_note(
tags.push(Tag::SimpleTag(tag)); tags.push(Tag::SimpleTag(tag));
}; };
for tag_name in &post.tags { for tag_name in &post.tags {
let tag_page_url = get_tag_page_url(instance_url, tag_name); let tag_href = local_tag_collection(instance_url, tag_name);
let tag = SimpleTag { let tag = SimpleTag {
name: format!("#{}", tag_name), name: format!("#{}", tag_name),
tag_type: HASHTAG.to_string(), tag_type: HASHTAG.to_string(),
href: tag_page_url, href: tag_href,
}; };
tags.push(Tag::SimpleTag(tag)); tags.push(Tag::SimpleTag(tag));
}; };
@ -327,7 +327,11 @@ mod tests {
username: "author".to_string(), username: "author".to_string(),
..Default::default() ..Default::default()
}; };
let post = Post { author, ..Default::default() }; let post = Post {
author,
tags: vec!["test".to_string()],
..Default::default()
};
let note = build_note(INSTANCE_HOSTNAME, INSTANCE_URL, &post); let note = build_note(INSTANCE_HOSTNAME, INSTANCE_URL, &post);
assert_eq!( assert_eq!(
@ -345,6 +349,13 @@ mod tests {
assert_eq!(note.cc, vec![ assert_eq!(note.cc, vec![
local_actor_followers(INSTANCE_URL, "author"), local_actor_followers(INSTANCE_URL, "author"),
]); ]);
assert_eq!(note.tag.len(), 1);
let tag = match note.tag[0] {
Tag::SimpleTag(ref tag) => tag,
_ => panic!(),
};
assert_eq!(tag.name, "#test");
assert_eq!(tag.href, "https://example.com/collections/tags/test");
} }
#[test] #[test]

View file

@ -24,6 +24,7 @@ impl LocalActorCollection {
} }
} }
// Mastodon and Pleroma use the same actor ID format
pub fn local_actor_id(instance_url: &str, username: &str) -> String { pub fn local_actor_id(instance_url: &str, username: &str) -> String {
format!("{}/users/{}", instance_url, username) format!("{}/users/{}", instance_url, username)
} }
@ -65,6 +66,10 @@ pub fn local_emoji_id(instance_url: &str, emoji_name: &str) -> String {
format!("{}/objects/emojis/{}", instance_url, emoji_name) format!("{}/objects/emojis/{}", instance_url, emoji_name)
} }
pub fn local_tag_collection(instance_url: &str, tag_name: &str) -> String {
format!("{}/collections/tags/{}", instance_url, tag_name)
}
pub fn parse_local_actor_id( pub fn parse_local_actor_id(
instance_url: &str, instance_url: &str,
actor_id: &str, actor_id: &str,

View file

@ -18,7 +18,11 @@ use crate::models::{
posts::queries::{get_post_by_id, get_posts_by_author}, posts::queries::{get_post_by_id, get_posts_by_author},
users::queries::get_user_by_name, users::queries::get_user_by_name,
}; };
use crate::web_client::urls::{get_post_page_url, get_profile_page_url}; use crate::web_client::urls::{
get_post_page_url,
get_profile_page_url,
get_tag_page_url,
};
use super::actors::types::{get_local_actor, get_instance_actor}; use super::actors::types::{get_local_actor, get_instance_actor};
use super::builders::create_note::{ use super::builders::create_note::{
build_emoji_tag, build_emoji_tag,
@ -364,6 +368,18 @@ pub async fn emoji_view(
Ok(response) Ok(response)
} }
#[get("/collections/tags/{tag_name}")]
pub async fn tag_view(
config: web::Data<Config>,
tag_name: web::Path<String>,
) -> Result<HttpResponse, HttpError> {
let page_url = get_tag_page_url(&config.instance_url(), &tag_name);
let response = HttpResponse::Found()
.append_header(("Location", page_url))
.finish();
Ok(response)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::http::{ use actix_web::http::{

View file

@ -159,6 +159,7 @@ async fn main() -> std::io::Result<()> {
.service(activitypub::instance_actor_scope()) .service(activitypub::instance_actor_scope())
.service(activitypub::object_view) .service(activitypub::object_view)
.service(activitypub::emoji_view) .service(activitypub::emoji_view)
.service(activitypub::tag_view)
.service(atom::get_atom_feed) .service(atom::get_atom_feed)
.service(nodeinfo::get_nodeinfo) .service(nodeinfo::get_nodeinfo)
.service(nodeinfo::get_nodeinfo_2_0) .service(nodeinfo::get_nodeinfo_2_0)

View file

@ -285,7 +285,7 @@ pub async fn search(
posts, posts,
).await?; ).await?;
let hashtags = tags.into_iter() let hashtags = tags.into_iter()
.map(Tag::from_tag_name) .map(|tag_name| Tag::from_tag_name(&config.instance_url(), tag_name))
.collect(); .collect();
Ok(SearchResults { accounts, statuses, hashtags }) Ok(SearchResults { accounts, statuses, hashtags })
} }

View file

@ -2,6 +2,7 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use crate::activitypub::identifiers::local_tag_collection;
use crate::mastodon_api::accounts::types::Account; use crate::mastodon_api::accounts::types::Account;
use crate::mastodon_api::media::types::Attachment; use crate::mastodon_api::media::types::Attachment;
use crate::models::{ use crate::models::{
@ -39,11 +40,11 @@ pub struct Tag {
} }
impl Tag { impl Tag {
pub fn from_tag_name(tag_name: String) -> Self { pub fn from_tag_name(instance_url: &str, tag_name: String) -> Self {
let tag_url = local_tag_collection(instance_url, &tag_name);
Tag { Tag {
name: tag_name, name: tag_name,
// TODO: add link to tag page url: tag_url,
url: "".to_string(),
} }
} }
} }
@ -108,7 +109,7 @@ impl Status {
.map(|item| Mention::from_profile(item, instance_url)) .map(|item| Mention::from_profile(item, instance_url))
.collect(); .collect();
let tags: Vec<Tag> = post.tags.into_iter() let tags: Vec<Tag> = post.tags.into_iter()
.map(Tag::from_tag_name) .map(|tag_name| Tag::from_tag_name(instance_url, tag_name))
.collect(); .collect();
let emojis: Vec<CustomEmoji> = post.emojis.into_iter() let emojis: Vec<CustomEmoji> = post.emojis.into_iter()
.map(|emoji| CustomEmoji::from_db(instance_url, emoji)) .map(|emoji| CustomEmoji::from_db(instance_url, emoji))

View file

@ -1,7 +1,7 @@
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use crate::activitypub::identifiers::local_tag_collection;
use crate::errors::ValidationError; use crate::errors::ValidationError;
use crate::web_client::urls::get_tag_page_url;
use super::links::is_inside_code_block; use super::links::is_inside_code_block;
const HASHTAG_RE: &str = r"(?m)(?P<before>^|\s|>|[\(])#(?P<tag>[^\s<]+)"; const HASHTAG_RE: &str = r"(?m)(?P<before>^|\s|>|[\(])#(?P<tag>[^\s<]+)";
@ -45,11 +45,11 @@ pub fn replace_hashtags(instance_url: &str, text: &str, tags: &[String]) -> Stri
let tag_name = tag.to_lowercase(); let tag_name = tag.to_lowercase();
let after = secondary_caps["after"].to_string(); let after = secondary_caps["after"].to_string();
if tags.contains(&tag_name) { if tags.contains(&tag_name) {
let tag_page_url = get_tag_page_url(instance_url, &tag_name); let tag_url = local_tag_collection(instance_url, &tag_name);
return format!( return format!(
r#"{}<a class="hashtag" href="{}">#{}</a>{}"#, r#"{}<a class="hashtag" href="{}">#{}</a>{}"#,
before, before,
tag_page_url, tag_url,
tag, tag,
after, after,
); );
@ -101,13 +101,14 @@ mod tests {
let output = replace_hashtags(INSTANCE_URL, TEXT_WITH_TAGS, &tags); let output = replace_hashtags(INSTANCE_URL, TEXT_WITH_TAGS, &tags);
let expected_output = concat!( let expected_output = concat!(
r#"@user1@server1 some text <a class="hashtag" href="https://example.com/tag/testtag">#TestTag</a>."#, "\n", r#"@user1@server1 some text <a class="hashtag" href="https://example.com/collections/tags/testtag">#TestTag</a>."#, "\n",
r#"<a class="hashtag" href="https://example.com/tag/tag1">#TAG1</a> <a class="hashtag" href="https://example.com/tag/tag1">#tag1</a> "#, r#"<a class="hashtag" href="https://example.com/collections/tags/tag1">#TAG1</a> "#,
r#"<a class="hashtag" href="https://example.com/collections/tags/tag1">#tag1</a> "#,
r#"#test_underscore #test*special "#, r#"#test_underscore #test*special "#,
r#"more text (<a class="hashtag" href="https://example.com/tag/tag2">#tag2</a>) text "#, r#"more text (<a class="hashtag" href="https://example.com/collections/tags/tag2">#tag2</a>) text "#,
r#"<a class="hashtag" href="https://example.com/tag/tag3">#tag3</a>, "#, r#"<a class="hashtag" href="https://example.com/collections/tags/tag3">#tag3</a>, "#,
r#"<a class="hashtag" href="https://example.com/tag/tag4">#tag4</a>:<br>"#, r#"<a class="hashtag" href="https://example.com/collections/tags/tag4">#tag4</a>:<br>"#,
r#"end with <a class="hashtag" href="https://example.com/tag/tag5">#tag5</a>"#, r#"end with <a class="hashtag" href="https://example.com/collections/tags/tag5">#tag5</a>"#,
); );
assert_eq!(output, expected_output); assert_eq!(output, expected_output);
} }