修复关注
This commit is contained in:
+120
-14
@@ -21,6 +21,8 @@ import {
|
||||
findOutgoingFollow,
|
||||
findPin,
|
||||
findReblog,
|
||||
getActorByLocalId,
|
||||
getActorFromCache,
|
||||
getAdminUser,
|
||||
getAppByClientId,
|
||||
getStatus,
|
||||
@@ -55,6 +57,7 @@ import {
|
||||
} from "./http";
|
||||
import type { ParsedBody } from "./http";
|
||||
import type {
|
||||
ActorCache,
|
||||
CachedStatus,
|
||||
Follow,
|
||||
Media,
|
||||
@@ -416,6 +419,8 @@ function extractFieldsAttributes(body: ParsedBody): { name: string; value: strin
|
||||
export async function getAccount(env: Env, accountId: string): Promise<Response> {
|
||||
const local = await getUserByIdOrUsername(env, accountId);
|
||||
if (local) return json(await accountJson(env, local));
|
||||
const byLocalId = await getActorByLocalId(env, accountId);
|
||||
if (byLocalId) return json(remoteAccountJson(byLocalId));
|
||||
if (accountId.startsWith("http://") || accountId.startsWith("https://")) {
|
||||
const cache = await resolveRemoteActor(env, accountId);
|
||||
if (cache) return json(remoteAccountJson(cache));
|
||||
@@ -441,20 +446,90 @@ export async function lookupAccount(request: Request, env: Env): Promise<Respons
|
||||
}
|
||||
|
||||
export async function accountStatuses(request: Request, env: Env, accountId: string): Promise<Response> {
|
||||
const user = await getUserByIdOrUsername(env, accountId);
|
||||
if (!user) return json({ error: "Record not found" }, 404);
|
||||
const url = new URL(request.url);
|
||||
const limit = clampLimit(url.searchParams.get("limit"), 20, 40);
|
||||
const excludeReplies = url.searchParams.get("exclude_replies") === "true";
|
||||
const where: string[] = ["user_id = ?"];
|
||||
|
||||
const user = await getUserByIdOrUsername(env, accountId);
|
||||
if (user) {
|
||||
const excludeReplies = url.searchParams.get("exclude_replies") === "true";
|
||||
const where: string[] = ["user_id = ?"];
|
||||
const binds: unknown[] = [user.id];
|
||||
if (excludeReplies) where.push("in_reply_to_id IS NULL");
|
||||
pagedAppend(where, binds, url);
|
||||
const sql = `SELECT * FROM statuses WHERE ${where.join(" AND ")} ORDER BY created_at DESC LIMIT ?`;
|
||||
binds.push(limit);
|
||||
const rows = await env.DB.prepare(sql).bind(...binds).all<Status>();
|
||||
const items = await serializeStatuses(env, rows.results, request, new Map([[user.id, user]]));
|
||||
return withPagination(json(items), request, rows.results.map((row) => row.id));
|
||||
}
|
||||
|
||||
const remote = await getActorByLocalId(env, accountId)
|
||||
?? (accountId.startsWith("http://") || accountId.startsWith("https://") ? await resolveRemoteActor(env, accountId) : null);
|
||||
if (remote) {
|
||||
const rows = await env.DB.prepare(
|
||||
"SELECT * FROM cached_statuses WHERE actor = ? ORDER BY published DESC LIMIT ?"
|
||||
).bind(remote.id, limit).all<CachedStatus>();
|
||||
const items = await Promise.all(rows.results.map((row) => cachedStatusToMastodon(env, row)));
|
||||
return json(items);
|
||||
}
|
||||
|
||||
return json({ error: "Record not found" }, 404);
|
||||
}
|
||||
|
||||
export async function accountFollowers(request: Request, env: Env, accountId: string): Promise<Response> {
|
||||
const user = await getUserByIdOrUsername(env, accountId);
|
||||
if (!user) return remoteAccountListFallback(env, accountId);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const limit = clampLimit(url.searchParams.get("limit"), 20, 80);
|
||||
const where: string[] = ["local_user_id = ?", "accepted = 1"];
|
||||
const binds: unknown[] = [user.id];
|
||||
if (excludeReplies) where.push("in_reply_to_id IS NULL");
|
||||
pagedAppend(where, binds, url);
|
||||
const sql = `SELECT * FROM statuses WHERE ${where.join(" AND ")} ORDER BY created_at DESC LIMIT ?`;
|
||||
binds.push(limit);
|
||||
const rows = await env.DB.prepare(sql).bind(...binds).all<Status>();
|
||||
const items = await serializeStatuses(env, rows.results, request, new Map([[user.id, user]]));
|
||||
return withPagination(json(items), request, rows.results.map((row) => row.id));
|
||||
pagedAppendForTable(where, binds, url, "follows");
|
||||
const rows = await env.DB.prepare(
|
||||
`SELECT * FROM follows WHERE ${where.join(" AND ")} ORDER BY created_at DESC LIMIT ?`
|
||||
).bind(...binds, limit).all<Follow>();
|
||||
|
||||
const accounts = await actorIdsToAccounts(env, rows.results.map((row) => row.follower_actor));
|
||||
return withPagination(json(accounts), request, rows.results.map((row) => row.id));
|
||||
}
|
||||
|
||||
export async function accountFollowing(request: Request, env: Env, accountId: string): Promise<Response> {
|
||||
const user = await getUserByIdOrUsername(env, accountId);
|
||||
if (!user) return remoteAccountListFallback(env, accountId);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const limit = clampLimit(url.searchParams.get("limit"), 20, 80);
|
||||
const where: string[] = ["local_user_id = ?", "accepted = 1"];
|
||||
const binds: unknown[] = [user.id];
|
||||
pagedAppendForTable(where, binds, url, "outgoing_follows");
|
||||
const rows = await env.DB.prepare(
|
||||
`SELECT * FROM outgoing_follows WHERE ${where.join(" AND ")} ORDER BY created_at DESC LIMIT ?`
|
||||
).bind(...binds, limit).all<{ id: string; target_actor: string }>();
|
||||
|
||||
const accounts = await actorIdsToAccounts(env, rows.results.map((row) => row.target_actor));
|
||||
return withPagination(json(accounts), request, rows.results.map((row) => row.id));
|
||||
}
|
||||
|
||||
async function remoteAccountListFallback(env: Env, accountId: string): Promise<Response> {
|
||||
const remote = await getActorByLocalId(env, accountId)
|
||||
?? (accountId.startsWith("http://") || accountId.startsWith("https://") ? await resolveRemoteActor(env, accountId) : null);
|
||||
if (remote) return json([]);
|
||||
return json({ error: "Record not found" }, 404);
|
||||
}
|
||||
|
||||
async function actorIdsToAccounts(env: Env, actorIds: string[]): Promise<Record<string, unknown>[]> {
|
||||
const accounts = await Promise.all(actorIds.map((actorId) => accountFromActorId(env, actorId)));
|
||||
return accounts.filter((account): account is Record<string, unknown> => Boolean(account));
|
||||
}
|
||||
|
||||
async function accountFromActorId(env: Env, actorId: string): Promise<Record<string, unknown> | null> {
|
||||
if (actorId.startsWith(baseUrl(env))) {
|
||||
const match = actorId.match(/\/users\/([^/?#]+)$/);
|
||||
const user = match ? await getUserByUsername(env, match[1]) : null;
|
||||
return user ? accountJson(env, user) : null;
|
||||
}
|
||||
const cache = await resolveRemoteActor(env, actorId) ?? await getActorFromCache(env, actorId);
|
||||
return cache ? remoteAccountJson(cache) : null;
|
||||
}
|
||||
|
||||
export async function createStatus(request: Request, env: Env): Promise<Response> {
|
||||
@@ -1305,11 +1380,11 @@ async function accountJson(env: Env, user: User): Promise<Record<string, unknown
|
||||
};
|
||||
}
|
||||
|
||||
function remoteAccountJson(cache: { id: string; preferred_username: string | null; name: string | null; summary: string | null; icon_url: string | null; fetched_at: string }): Record<string, unknown> {
|
||||
function remoteAccountJson(cache: ActorCache): Record<string, unknown> {
|
||||
const host = (() => { try { return new URL(cache.id).host; } catch { return "remote"; } })();
|
||||
const username = cache.preferred_username ?? cache.id.split("/").pop() ?? "user";
|
||||
return {
|
||||
id: cache.id,
|
||||
id: cache.local_id ?? cache.id,
|
||||
username,
|
||||
acct: `${username}@${host}`,
|
||||
display_name: cache.name ?? username,
|
||||
@@ -1376,7 +1451,19 @@ type AccountTarget = { kind: "local"; userId: string; actorId: string } | { kind
|
||||
async function resolveAccountTarget(env: Env, key: string): Promise<AccountTarget | null> {
|
||||
const local = await getUserByIdOrUsername(env, key);
|
||||
if (local) return { kind: "local", userId: local.id, actorId: actorUrl(env, local) };
|
||||
if (key.startsWith("http://") || key.startsWith("https://")) return { kind: "remote", actorId: key };
|
||||
|
||||
const byLocalId = await getActorByLocalId(env, key);
|
||||
if (byLocalId) return { kind: "remote", actorId: byLocalId.id };
|
||||
|
||||
if (key.startsWith("http://") || key.startsWith("https://")) {
|
||||
if (key.startsWith(baseUrl(env))) {
|
||||
const match = key.match(/\/users\/([^/?#]+)$/);
|
||||
const u = match ? await getUserByUsername(env, match[1]) : null;
|
||||
if (u) return { kind: "local", userId: u.id, actorId: actorUrl(env, u) };
|
||||
}
|
||||
await resolveRemoteActor(env, key);
|
||||
return { kind: "remote", actorId: key };
|
||||
}
|
||||
if (key.includes("@")) {
|
||||
const resolved = await resolveAcct(env, key);
|
||||
if (!resolved) return null;
|
||||
@@ -1385,6 +1472,7 @@ async function resolveAccountTarget(env: Env, key: string): Promise<AccountTarge
|
||||
const localUser = match ? await getUserByUsername(env, match[1]) : null;
|
||||
if (localUser) return { kind: "local", userId: localUser.id, actorId: resolved.actorId };
|
||||
}
|
||||
await resolveRemoteActor(env, resolved.actorId);
|
||||
return { kind: "remote", actorId: resolved.actorId };
|
||||
}
|
||||
return null;
|
||||
@@ -1589,6 +1677,24 @@ function pagedAppend(where: string[], binds: unknown[], url: URL): void {
|
||||
}
|
||||
}
|
||||
|
||||
function pagedAppendForTable(where: string[], binds: unknown[], url: URL, table: "follows" | "outgoing_follows"): void {
|
||||
const maxId = url.searchParams.get("max_id");
|
||||
if (maxId) {
|
||||
where.push(`created_at < (SELECT created_at FROM ${table} WHERE id = ?)`);
|
||||
binds.push(maxId);
|
||||
}
|
||||
const sinceId = url.searchParams.get("since_id");
|
||||
if (sinceId) {
|
||||
where.push(`created_at > (SELECT created_at FROM ${table} WHERE id = ?)`);
|
||||
binds.push(sinceId);
|
||||
}
|
||||
const minId = url.searchParams.get("min_id");
|
||||
if (minId) {
|
||||
where.push(`created_at > (SELECT created_at FROM ${table} WHERE id = ?)`);
|
||||
binds.push(minId);
|
||||
}
|
||||
}
|
||||
|
||||
function withPagination(response: Response, request: Request, ids: string[]): Response {
|
||||
if (ids.length === 0) return response;
|
||||
const url = new URL(request.url);
|
||||
|
||||
Reference in New Issue
Block a user