diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index 1c97dc2980..d63c76405f 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -146,6 +146,15 @@ export interface ApHashtag { name: string } +export interface ApLike { + id: string + type: ApObject + actor: string + object: string + content: string + tag?: Array +} + export interface ApMention { type: ApObject href: string @@ -158,6 +167,7 @@ export type ApObject = 'Accept'| 'Flag'| 'Follow'| 'Hashtag'| +'Like'| 'Mention'| 'Image'| 'Read'| @@ -1356,6 +1366,8 @@ export declare function renderFollowRelay(relayId: string): Promise export declare function renderHashtag(tagName: string): ApHashtag +export declare function renderLike(reaction: Model): Promise + export declare function renderMention(user: UserLike): ApMention export declare function renderRead(userId: string, messageUri: string): ApRead diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index bd87db3932..f2f6cf834b 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -445,6 +445,7 @@ module.exports.renderFlag = nativeBinding.renderFlag module.exports.renderFollow = nativeBinding.renderFollow module.exports.renderFollowRelay = nativeBinding.renderFollowRelay module.exports.renderHashtag = nativeBinding.renderHashtag +module.exports.renderLike = nativeBinding.renderLike module.exports.renderMention = nativeBinding.renderMention module.exports.renderRead = nativeBinding.renderRead module.exports.renderRemove = nativeBinding.renderRemove diff --git a/packages/backend-rs/src/federation/activitypub/object/emoji.rs b/packages/backend-rs/src/federation/activitypub/object/emoji.rs index 0c9107c7ce..14e47168cb 100644 --- a/packages/backend-rs/src/federation/activitypub/object/emoji.rs +++ b/packages/backend-rs/src/federation/activitypub/object/emoji.rs @@ -21,8 +21,7 @@ pub struct Icon { impl ActivityPubObject for ApEmoji {} impl ApEmoji { - #[allow(dead_code)] // TODO: remove this line - fn new(emoji: emoji::Model) -> Self { + pub fn new(emoji: emoji::Model) -> Self { Self { id: misc::emoji::local_uri(&emoji.name), r#type: ApObject::Emoji, diff --git a/packages/backend-rs/src/federation/activitypub/object/like.rs b/packages/backend-rs/src/federation/activitypub/object/like.rs new file mode 100644 index 0000000000..8d3469217c --- /dev/null +++ b/packages/backend-rs/src/federation/activitypub/object/like.rs @@ -0,0 +1,74 @@ +use super::{emoji::ApEmoji, *}; +use crate::{ + config::CONFIG, + database::db_conn, + misc::{self, user}, + model::entity::{emoji, note, note_reaction}, +}; +use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter, QuerySelect, SelectColumns}; + +#[macros::errors] +pub enum Error { + #[doc = "nonexistent note"] + #[error("note {0} not found")] + NoteNotFound(String), + #[doc = "database error"] + #[error(transparent)] + Db(#[from] DbErr), +} + +#[macros::export(object, use_nullable = false)] +pub struct ApLike { + pub id: String, + pub r#type: ApObject, + pub actor: String, + pub object: String, + pub content: String, + pub tag: Option>, +} + +impl ActivityPubObject for ApLike {} + +impl ApLike { + #[allow(dead_code)] // TODO: remove this line + async fn new(reaction: note_reaction::Model) -> Result { + let db = db_conn().await?; + + let note_uri = { + let note_uri = note::Entity::find() + .select_only() + .select_column(note::Column::Uri) + .filter(note::Column::Id.eq(&reaction.note_id)) + .into_tuple::>() + .one(db) + .await?; + + match note_uri { + Some(Some(uri)) => uri, + Some(None) => misc::note::local_uri(reaction.note_id), + None => return Err(Error::NoteNotFound(reaction.note_id)), + } + }; + + let tag = emoji::Entity::find() + .filter(emoji::Column::Name.eq(reaction.reaction.replace(':', ""))) + .filter(emoji::Column::Host.is_null()) + .one(db) + .await? + .map(|emoji| vec![ApEmoji::new(emoji)]); + + Ok(Self { + id: format!("{}/likes/{}", CONFIG.url, reaction.id), + r#type: ApObject::Like, + actor: user::local_uri(reaction.user_id), + object: note_uri, + content: reaction.reaction, + tag, + }) + } +} + +#[macros::ts_export] +pub async fn render_like(reaction: note_reaction::Model) -> Result { + ApLike::new(reaction).await +} diff --git a/packages/backend-rs/src/federation/activitypub/object/mod.rs b/packages/backend-rs/src/federation/activitypub/object/mod.rs index 179674da0b..187b26fd97 100644 --- a/packages/backend-rs/src/federation/activitypub/object/mod.rs +++ b/packages/backend-rs/src/federation/activitypub/object/mod.rs @@ -4,6 +4,7 @@ pub mod emoji; pub mod flag; pub mod follow; pub mod hashtag; +pub mod like; pub mod mention; pub mod read; pub mod remove; @@ -19,6 +20,7 @@ pub enum ApObject { Flag, Follow, Hashtag, + Like, Mention, Image, Read, diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts index 7b30d1cd54..eb5ff556cc 100644 --- a/packages/backend/src/remote/activitypub/kernel/like.ts +++ b/packages/backend/src/remote/activitypub/kernel/like.ts @@ -12,11 +12,7 @@ export default async (actor: CacheableRemoteUser, activity: ILike) => { await extractEmojis(activity.tag || [], actor.host).catch(() => null); - return await create( - actor, - note, - activity._misskey_reaction || activity.content || activity.name, - ) + return await create(actor, note, activity.content || activity.name) .catch((e) => { if (e.id === "51c42bb4-931a-456b-bff7-e5a8a70dd298") { return "skip: already reacted"; diff --git a/packages/backend/src/remote/activitypub/misc/contexts.ts b/packages/backend/src/remote/activitypub/misc/contexts.ts index d0b3f56fc1..a3bc03c07b 100644 --- a/packages/backend/src/remote/activitypub/misc/contexts.ts +++ b/packages/backend/src/remote/activitypub/misc/contexts.ts @@ -550,7 +550,6 @@ export const WellKnownContext = { // Misskey misskey: "https://misskey-hub.net/ns#", _misskey_talk: "misskey:_misskey_talk", - _misskey_reaction: "misskey:_misskey_reaction", _misskey_votes: "misskey:_misskey_votes", _misskey_summary: "misskey:_misskey_summary", isCat: "misskey:isCat", diff --git a/packages/backend/src/remote/activitypub/renderer/like.ts b/packages/backend/src/remote/activitypub/renderer/like.ts index e6eab0b261..a6ffeab9e1 100644 --- a/packages/backend/src/remote/activitypub/renderer/like.ts +++ b/packages/backend/src/remote/activitypub/renderer/like.ts @@ -14,7 +14,6 @@ export const renderLike = async (noteReaction: NoteReaction, note: Note) => { actor: `${config.url}/users/${noteReaction.userId}`, object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`, content: reaction, - _misskey_reaction: reaction, } as any; if (reaction.startsWith(":")) { diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 48ca3e6244..55eaa0d437 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -7,6 +7,7 @@ import { isBlockedServer, isSelfHost, renderFollow, + renderLike, } from "backend-rs"; import { apGet } from "./request.js"; import type { IObject, ICollection, IOrderedCollection } from "./type.js"; @@ -20,7 +21,6 @@ import { } from "@/models/index.js"; import { parseUri } from "./db-resolver.js"; import renderNote from "@/remote/activitypub/renderer/note.js"; -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { renderPerson } from "@/remote/activitypub/renderer/person.js"; import renderQuestion from "@/remote/activitypub/renderer/question.js"; import renderCreate from "@/remote/activitypub/renderer/create.js"; @@ -181,7 +181,7 @@ export default class Resolver { } case "likes": { const reaction = await NoteReactions.findOneByOrFail({ id: parsed.id }); - return renderActivity(renderLike(reaction, { uri: null })); + return renderActivity(await renderLike(reaction)); } case "follows": { // if rest is a diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts index 92889380ac..13358f614f 100644 --- a/packages/backend/src/remote/activitypub/type.ts +++ b/packages/backend/src/remote/activitypub/type.ts @@ -303,7 +303,7 @@ export interface IRemove extends IActivity { export interface ILike extends IActivity { type: "Like" | "EmojiReaction" | "EmojiReact"; - _misskey_reaction?: string; + content?: string; } export interface IAnnounce extends IActivity { diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index e302cb198c..bd028bb6b2 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -14,6 +14,7 @@ import { isSelfHost, renderEmoji, renderFollow, + renderLike, } from "backend-rs"; import { Notes, @@ -23,7 +24,6 @@ import { FollowRequests, } from "@/models/index.js"; import type { ILocalUser, User } from "@/models/entities/user.js"; -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { getUserKeypair } from "@/misc/keypair-store.js"; import { checkFetch, @@ -466,7 +466,7 @@ router.get("/likes/:like", async (ctx) => { return; } - ctx.body = renderActivity(await renderLike(reaction, note)); + ctx.body = renderActivity(await renderLike(reaction)); const instanceMeta = await fetchMeta(); if (instanceMeta.secureMode || instanceMeta.privateMode) { ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index f868a4cac3..bb3bf125b8 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -1,4 +1,3 @@ -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import type { User, IRemoteUser } from "@/models/entities/user.js"; @@ -17,6 +16,7 @@ import { genIdAt, NoteEvent, publishToNoteStream, + renderLike, toDbReaction, } from "backend-rs"; import { createNotification } from "@/services/create-notification.js"; @@ -152,7 +152,7 @@ export default async ( !note.localOnly && note.visibility !== "hidden" ) { - const content = renderActivity(await renderLike(record, note)); + const content = renderActivity(await renderLike(record)); const dm = new DeliverManager(user, content); if (note.userHost != null) { const reactee = await Users.findOneBy({ id: note.userId }); diff --git a/packages/backend/src/services/note/reaction/delete.ts b/packages/backend/src/services/note/reaction/delete.ts index 00b7f18741..5da76917f5 100644 --- a/packages/backend/src/services/note/reaction/delete.ts +++ b/packages/backend/src/services/note/reaction/delete.ts @@ -1,4 +1,3 @@ -import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { renderUndo } from "@/remote/activitypub/renderer/undo.js"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import DeliverManager from "@/remote/activitypub/deliver-manager.js"; @@ -6,7 +5,12 @@ import { IdentifiableError } from "@/misc/identifiable-error.js"; import type { User, IRemoteUser } from "@/models/entities/user.js"; import type { Note } from "@/models/entities/note.js"; import { NoteReactions, Users, Notes } from "@/models/index.js"; -import { decodeReaction, NoteEvent, publishToNoteStream } from "backend-rs"; +import { + decodeReaction, + NoteEvent, + publishToNoteStream, + renderLike, +} from "backend-rs"; export default async ( user: { id: User["id"]; host: User["host"] }, @@ -55,7 +59,7 @@ export default async ( //#region 配信 if (Users.isLocalUser(user) && !note.localOnly) { const content = renderActivity( - renderUndo(await renderLike(reaction, note), user.id), + renderUndo(await renderLike(reaction), user.id), ); const dm = new DeliverManager(user, content); if (note.userHost != null) {