修复远程缓存
This commit is contained in:
+95
-15
@@ -35,7 +35,7 @@ import {
|
||||
PUBLIC_COLLECTION,
|
||||
SECURITY_CONTEXT
|
||||
} from "./types";
|
||||
import type { ActorCache, Json, RemoteActor, Status, User } from "./types";
|
||||
import type { ActorCache, CachedStatus, CachedStatusMention, CachedStatusTag, Json, RemoteActor, Status, User } from "./types";
|
||||
import {
|
||||
actorUrl,
|
||||
activityUrl,
|
||||
@@ -419,7 +419,7 @@ async function handleUpdate(ctx: InboxContext): Promise<Response> {
|
||||
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);
|
||||
await cacheRemoteNote(env, actorId, obj, activity.body, existing);
|
||||
}
|
||||
}
|
||||
return new Response(null, { status: 202 });
|
||||
@@ -439,28 +439,27 @@ async function handleCreate(ctx: InboxContext): Promise<Response> {
|
||||
}
|
||||
|
||||
const isPublic = recipients.includes(PUBLIC_COLLECTION);
|
||||
const isFollowersOnly = recipients.some((recipient) => isFollowersCollection(actorId, recipient));
|
||||
const followed = await isFollowedByAnyLocalUser(env, actorId);
|
||||
if (followed && (isPublic || localActorIds.size > 0)) {
|
||||
await cacheRemoteNote(env, actorId, obj);
|
||||
if ((followed && (isPublic || isFollowersOnly)) || localActorIds.size > 0) {
|
||||
await cacheRemoteNote(env, actorId, obj, activity.body);
|
||||
}
|
||||
|
||||
for (const username of localActorIds) {
|
||||
const localUser = await getUserByUsername(env, username);
|
||||
if (!localUser) continue;
|
||||
const inReplyTo = typeof obj.inReplyTo === "string" ? obj.inReplyTo : null;
|
||||
let statusId: string | null = null;
|
||||
if (inReplyTo) {
|
||||
const parent = await getStatusByObjectId(env, inReplyTo);
|
||||
if (parent) statusId = parent.id;
|
||||
}
|
||||
await recordNotification(env, localUser.id, "mention", actorId, statusId);
|
||||
await recordNotification(env, localUser.id, "mention", actorId, typeof obj.id === "string" ? obj.id : null);
|
||||
}
|
||||
return new Response(null, { status: 202 });
|
||||
}
|
||||
|
||||
async function cacheRemoteNote(env: Env, actorId: string, note: Json): Promise<void> {
|
||||
async function cacheRemoteNote(env: Env, actorId: string, note: Json, activity: Json = {}, fallback?: CachedStatus): Promise<void> {
|
||||
if (typeof note.id !== "string") return;
|
||||
const cachedId = note.id;
|
||||
const recipients = collectRecipients(activity, note);
|
||||
const mentions = note.tag === undefined ? parseJsonArray<CachedStatusMention>(fallback?.mentions_json) : extractRemoteMentions(note);
|
||||
const tags = note.tag === undefined ? parseJsonArray<CachedStatusTag>(fallback?.tags_json) : extractRemoteHashtags(note);
|
||||
const localRecipients = recipients.length === 0 ? parseJsonArray<string>(fallback?.local_recipients_json) : collectLocalRecipients(env, recipients);
|
||||
const stored = await upsertCachedStatus(env, {
|
||||
id: cachedId,
|
||||
object_id: cachedId,
|
||||
@@ -469,9 +468,13 @@ async function cacheRemoteNote(env: Env, actorId: string, note: Json): Promise<v
|
||||
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",
|
||||
visibility: inferRemoteVisibility(actorId, activity, note, fallback?.visibility ?? "public"),
|
||||
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()
|
||||
published: typeof note.published === "string" ? note.published : new Date().toISOString(),
|
||||
mentions_json: JSON.stringify(mentions),
|
||||
tags_json: JSON.stringify(tags),
|
||||
local_recipients_json: JSON.stringify(localRecipients)
|
||||
});
|
||||
if (!stored) return;
|
||||
await env.DB.prepare("DELETE FROM cached_status_attachments WHERE cached_status_id = ?").bind(stored.id).run();
|
||||
@@ -494,8 +497,85 @@ async function cacheRemoteNote(env: Env, actorId: string, note: Json): Promise<v
|
||||
}
|
||||
}
|
||||
|
||||
function inferRemoteVisibility(actorId: string, activity: Json, object: Json, fallback: string): string {
|
||||
const to = collectRecipientFields(activity.to, activity.bto, object.to, object.bto);
|
||||
const cc = collectRecipientFields(activity.cc, activity.bcc, object.cc, object.bcc);
|
||||
const recipients = new Set([...to, ...cc]);
|
||||
if (recipients.size === 0) return fallback;
|
||||
if (to.has(PUBLIC_COLLECTION)) return "public";
|
||||
if (cc.has(PUBLIC_COLLECTION)) return "unlisted";
|
||||
if ([...recipients].some((recipient) => isFollowersCollection(actorId, recipient))) return "private";
|
||||
return "direct";
|
||||
}
|
||||
|
||||
function isFollowersCollection(actorId: string, recipient: string): boolean {
|
||||
return recipient === `${actorId}/followers` || recipient.endsWith("/followers");
|
||||
}
|
||||
|
||||
function collectLocalRecipients(env: Env, recipients: string[]): string[] {
|
||||
return recipients.filter((recipient) => recipient.startsWith(baseUrl(env)) && /\/users\/[^/?#]+$/.test(recipient));
|
||||
}
|
||||
|
||||
function extractRemoteMentions(note: Json): CachedStatusMention[] {
|
||||
const mentions: CachedStatusMention[] = [];
|
||||
for (const tag of noteTagObjects(note)) {
|
||||
if (String(tag.type ?? "") !== "Mention") continue;
|
||||
const url = stringValue(tag.href) ?? stringValue(tag.id);
|
||||
if (!url) continue;
|
||||
const acct = mentionAcct(tag, url);
|
||||
mentions.push({ actor: url, acct, url });
|
||||
}
|
||||
return mentions;
|
||||
}
|
||||
|
||||
function extractRemoteHashtags(note: Json): CachedStatusTag[] {
|
||||
const tags: CachedStatusTag[] = [];
|
||||
for (const tag of noteTagObjects(note)) {
|
||||
const name = stringValue(tag.name);
|
||||
if (String(tag.type ?? "") !== "Hashtag" && !name?.startsWith("#")) continue;
|
||||
if (!name) continue;
|
||||
tags.push({ name: name.replace(/^#/, "").toLowerCase(), url: stringValue(tag.href) ?? stringValue(tag.id) });
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
function noteTagObjects(note: Json): Json[] {
|
||||
const tags = Array.isArray(note.tag) ? note.tag : note.tag ? [note.tag] : [];
|
||||
return tags.filter((tag): tag is Json => Boolean(tag) && typeof tag === "object");
|
||||
}
|
||||
|
||||
function mentionAcct(tag: Json, url: string): string {
|
||||
const name = stringValue(tag.name)?.replace(/^@/, "");
|
||||
if (name) return name;
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const username = parsed.pathname.split("/").filter(Boolean).pop() ?? parsed.host;
|
||||
return `${username}@${parsed.host}`;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
function stringValue(value: unknown): string | null {
|
||||
return typeof value === "string" && value ? value : null;
|
||||
}
|
||||
|
||||
function parseJsonArray<T>(value: string | null | undefined): T[] {
|
||||
if (!value) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(value) as unknown;
|
||||
return Array.isArray(parsed) ? parsed as T[] : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function collectRecipients(activity: Json, object: Json): string[] {
|
||||
const fields: unknown[] = [activity.to, activity.cc, activity.bto, activity.bcc, object.to, object.cc];
|
||||
const fields: unknown[] = [activity.to, activity.cc, activity.bto, activity.bcc, object.to, object.cc, object.bto, object.bcc];
|
||||
return [...collectRecipientFields(...fields)];
|
||||
}
|
||||
|
||||
function collectRecipientFields(...fields: unknown[]): Set<string> {
|
||||
const out = new Set<string>();
|
||||
for (const field of fields) {
|
||||
if (Array.isArray(field)) {
|
||||
@@ -506,7 +586,7 @@ function collectRecipients(activity: Json, object: Json): string[] {
|
||||
out.add(field);
|
||||
}
|
||||
}
|
||||
return [...out];
|
||||
return out;
|
||||
}
|
||||
|
||||
async function localUserFromTarget(env: Env, actorId: string | null): Promise<User | null> {
|
||||
|
||||
Reference in New Issue
Block a user