This commit is contained in:
浪子
2026-05-14 11:47:25 +08:00
parent 01880d39a0
commit 5b01f18719
11 changed files with 512 additions and 29 deletions
+57 -3
View File
@@ -1,17 +1,21 @@
import {
deleteActorFromCache,
deleteCachedStatus,
exportUserPublicKeyPem,
findFavourite,
findReblog,
getCachedStatusByObjectId,
getStatus,
getStatusByObjectId,
getUserByUsername,
recordNotification,
upsertActorCache
upsertActorCache,
upsertCachedStatus
} from "./db";
import {
deliverToInboxes,
isDuplicateActivity,
isFollowedByAnyLocalUser,
notifyForLocalStatus,
objectAsJson,
objectIdString,
@@ -360,11 +364,12 @@ async function handleDelete(ctx: InboxContext): Promise<Response> {
await env.DB.prepare("DELETE FROM favourites WHERE actor = ?").bind(actorId).run();
await env.DB.prepare("DELETE FROM reblogs WHERE actor = ?").bind(actorId).run();
await env.DB.prepare("DELETE FROM notifications WHERE actor = ?").bind(actorId).run();
await env.DB.prepare("DELETE FROM cached_statuses WHERE actor = ?").bind(actorId).run();
await deleteActorFromCache(env, actorId);
return new Response(null, { status: 202 });
}
await env.DB.prepare("DELETE FROM favourites WHERE actor = ? AND activity_id LIKE ?").bind(actorId, `%${target}%`).run();
await deleteCachedStatus(env, target);
return new Response(null, { status: 202 });
}
@@ -374,6 +379,13 @@ async function handleUpdate(ctx: InboxContext): Promise<Response> {
if (!obj) return new Response(null, { status: 202 });
if (String(obj.type ?? "") === "Person" && obj.id === actorId) {
await upsertActorCache(env, obj as unknown as RemoteActor);
return new Response(null, { status: 202 });
}
if (String(obj.type ?? "") === "Note" && typeof obj.id === "string") {
const existing = await getCachedStatusByObjectId(env, obj.id);
if (existing && existing.actor === actorId) {
await cacheRemoteNote(env, actorId, obj);
}
}
return new Response(null, { status: 202 });
}
@@ -381,7 +393,7 @@ async function handleUpdate(ctx: InboxContext): Promise<Response> {
async function handleCreate(ctx: InboxContext): Promise<Response> {
const { env, activity, actorId } = ctx;
const obj = objectAsJson(activity.body.object);
if (!obj) return new Response(null, { status: 202 });
if (!obj || String(obj.type ?? "") !== "Note") return new Response(null, { status: 202 });
const recipients = collectRecipients(activity.body, obj);
const localActorIds = new Set<string>();
@@ -391,6 +403,12 @@ async function handleCreate(ctx: InboxContext): Promise<Response> {
if (m) localActorIds.add(m[1]);
}
const isPublic = recipients.includes(PUBLIC_COLLECTION);
const followed = await isFollowedByAnyLocalUser(env, actorId);
if (followed && (isPublic || localActorIds.size > 0)) {
await cacheRemoteNote(env, actorId, obj);
}
for (const username of localActorIds) {
const localUser = await getUserByUsername(env, username);
if (!localUser) continue;
@@ -405,6 +423,42 @@ async function handleCreate(ctx: InboxContext): Promise<Response> {
return new Response(null, { status: 202 });
}
async function cacheRemoteNote(env: Env, actorId: string, note: Json): Promise<void> {
if (typeof note.id !== "string") return;
const cachedId = note.id;
const stored = await upsertCachedStatus(env, {
id: cachedId,
object_id: cachedId,
actor: actorId,
content: typeof note.content === "string" ? note.content : "",
summary: typeof note.summary === "string" ? note.summary : "",
sensitive: note.sensitive ? 1 : 0,
language: typeof note.contentMap === "object" && note.contentMap ? Object.keys(note.contentMap as Json)[0] ?? "en" : "en",
in_reply_to: typeof note.inReplyTo === "string" ? note.inReplyTo : null,
url: typeof note.url === "string" ? note.url : cachedId,
published: typeof note.published === "string" ? note.published : new Date().toISOString()
});
if (!stored) return;
await env.DB.prepare("DELETE FROM cached_status_attachments WHERE cached_status_id = ?").bind(stored.id).run();
const attachments = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : [];
let position = 0;
for (const raw of attachments) {
if (!raw || typeof raw !== "object") continue;
const att = raw as Json;
const url = typeof att.url === "string" ? att.url
: (att.url && typeof att.url === "object" && typeof (att.url as Json).href === "string") ? String((att.url as Json).href)
: null;
if (!url) continue;
const mime = typeof att.mediaType === "string" ? att.mediaType : "application/octet-stream";
const description = typeof att.name === "string" ? att.name
: typeof att.summary === "string" ? att.summary : null;
await env.DB.prepare(
"INSERT OR REPLACE INTO cached_status_attachments (cached_status_id, position, url, preview_url, mime_type, description) VALUES (?, ?, ?, ?, ?, ?)"
).bind(stored.id, position, url, null, mime, description).run();
position++;
}
}
function collectRecipients(activity: Json, object: Json): string[] {
const fields: unknown[] = [activity.to, activity.cc, activity.bto, activity.bcc, object.to, object.cc];
const out = new Set<string>();