Files
Toot-Worker/src/index.ts
T
浪子 e2fcf08e2f fix
2026-05-14 23:39:53 +08:00

223 lines
12 KiB
TypeScript

import {
AVATAR_SVG,
HEADER_SVG,
activityObject,
actor,
followersCollection,
followingCollection,
hostMeta,
inboxHandler,
nodeInfo,
nodeInfoLinks,
outbox,
webFinger
} from "./activitypub";
import { ensureAdminUser } from "./db";
import { HttpError, cors, json, svgResponse } from "./http";
import {
accountStatuses,
accountFollowers,
accountFollowing,
addListAccounts,
authorize,
authorizeFollowRequest,
authorizePage,
bookmarkStatus,
bookmarksList,
createApp,
createList,
createPushSubscription,
createStatus,
customEmojis,
deleteList,
deletePushSubscription,
deleteScheduledStatus,
deleteStatusEndpoint,
favouriteStatus,
favouritesList,
filtersV1,
followAccount,
followRequestsList,
getAccount,
getList,
getPoll,
getPushSubscription,
getRelationships,
getScheduledStatus,
getStatusEndpoint,
hashtagInfo,
hashtagTimeline,
homeTimeline,
instance,
instanceV2,
listAccounts,
listScheduledStatuses,
listTimeline,
listsList,
lookupAccount,
markersList,
notificationClear,
notificationDismiss,
notificationsList,
pinStatus,
publishDueScheduledStatuses,
publicTimeline,
reblogStatus,
rejectFollowRequest,
removeListAccounts,
revoke,
search,
serveMedia,
statusContext,
token,
trendsTags,
unbookmarkStatus,
unfavouriteStatus,
unfollowAccount,
unpinStatus,
unreblogStatus,
updateList,
updatePushSubscription,
updateScheduledStatus,
updateCredentials,
updateMedia,
uploadMedia,
votePoll,
verifyAppCredentials,
verifyCredentials
} from "./mastodon";
import { processOutgoingDeliveries } from "./federation";
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
try {
await ensureAdminUser(env);
const response = await route(request, env);
ctx.waitUntil(processOutgoingDeliveries(env));
return response;
} catch (error) {
if (error instanceof HttpError) return json({ error: error.message }, error.status);
console.error("unhandled", error);
return json({ error: "internal_server_error" }, 500);
}
},
async scheduled(_event: ScheduledEvent, env: Env): Promise<void> {
await ensureAdminUser(env);
await publishDueScheduledStatuses(env);
await processOutgoingDeliveries(env);
}
};
async function route(request: Request, env: Env): Promise<Response> {
if (request.method === "OPTIONS") return cors(new Response(null, { status: 204 }));
const url = new URL(request.url);
const path = url.pathname.replace(/\/+$/, "") || "/";
const method = request.method.toUpperCase();
if (method === "GET" && path === "/") return nodeInfo(env);
if (method === "GET" && path === "/avatar.png") return svgResponse(AVATAR_SVG);
if (method === "GET" && path === "/header.png") return svgResponse(HEADER_SVG);
if (method === "GET" && path === "/.well-known/webfinger") return webFinger(request, env);
if (method === "GET" && path === "/.well-known/nodeinfo") return nodeInfoLinks(env);
if (method === "GET" && path === "/.well-known/host-meta") return hostMeta(env);
if (method === "GET" && path === "/nodeinfo/2.0") return nodeInfo(env);
if (method === "GET" && path === "/api/v1/instance") return instance(env);
if (method === "GET" && path === "/api/v2/instance") return instanceV2(env);
if (method === "POST" && path === "/api/v1/apps") return createApp(request, env);
if (method === "GET" && path === "/api/v1/apps/verify_credentials") return verifyAppCredentials(request, env);
if (method === "GET" && path === "/oauth/authorize") return authorizePage(request, env);
if (method === "POST" && path === "/oauth/authorize") return authorize(request, env);
if (method === "POST" && path === "/oauth/token") return token(request, env);
if (method === "POST" && path === "/oauth/revoke") return revoke(request, env);
if (method === "GET" && path === "/api/v1/accounts/verify_credentials") return verifyCredentials(request, env);
if ((method === "PATCH" || method === "POST") && path === "/api/v1/accounts/update_credentials") return updateCredentials(request, env);
if (method === "GET" && path === "/api/v1/accounts/relationships") return getRelationships(request, env);
if (method === "GET" && path === "/api/v1/accounts/search") return search(request, env);
if (method === "GET" && path === "/api/v1/accounts/lookup") return lookupAccount(request, env);
let m: RegExpMatchArray | null;
if (method === "GET" && (m = path.match(/^\/api\/v1\/accounts\/([^/]+)$/))) return getAccount(env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/accounts\/([^/]+)\/statuses$/))) return accountStatuses(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/accounts\/([^/]+)\/followers$/))) return accountFollowers(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/accounts\/([^/]+)\/following$/))) return accountFollowing(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/accounts\/([^/]+)\/follow$/))) return followAccount(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/accounts\/([^/]+)\/unfollow$/))) return unfollowAccount(request, env, decodeURIComponent(m[1]));
if (method === "GET" && path === "/api/v1/follow_requests") return followRequestsList(request, env);
if (method === "POST" && (m = path.match(/^\/api\/v1\/follow_requests\/([^/]+)\/authorize$/))) return authorizeFollowRequest(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/follow_requests\/([^/]+)\/reject$/))) return rejectFollowRequest(request, env, decodeURIComponent(m[1]));
if (method === "POST" && path === "/api/v1/statuses") return createStatus(request, env);
if (method === "GET" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)$/))) return getStatusEndpoint(request, env, decodeURIComponent(m[1]));
if (method === "DELETE" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)$/))) return deleteStatusEndpoint(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/context$/))) return statusContext(env, decodeURIComponent(m[1]), request);
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/favourite$/))) return favouriteStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/unfavourite$/))) return unfavouriteStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/reblog$/))) return reblogStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/unreblog$/))) return unreblogStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/bookmark$/))) return bookmarkStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/unbookmark$/))) return unbookmarkStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/pin$/))) return pinStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/unpin$/))) return unpinStatus(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/polls\/([^/]+)$/))) return getPoll(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/polls\/([^/]+)\/votes$/))) return votePoll(request, env, decodeURIComponent(m[1]));
if (method === "GET" && path === "/api/v1/scheduled_statuses") return listScheduledStatuses(request, env);
if (method === "GET" && (m = path.match(/^\/api\/v1\/scheduled_statuses\/([^/]+)$/))) return getScheduledStatus(request, env, decodeURIComponent(m[1]));
if ((method === "PUT" || method === "PATCH") && (m = path.match(/^\/api\/v1\/scheduled_statuses\/([^/]+)$/))) return updateScheduledStatus(request, env, decodeURIComponent(m[1]));
if (method === "DELETE" && (m = path.match(/^\/api\/v1\/scheduled_statuses\/([^/]+)$/))) return deleteScheduledStatus(request, env, decodeURIComponent(m[1]));
if (method === "GET" && path === "/api/v1/timelines/public") return publicTimeline(request, env);
if (method === "GET" && path === "/api/v1/timelines/home") return homeTimeline(request, env);
if (method === "GET" && (m = path.match(/^\/api\/v1\/timelines\/list\/([^/]+)$/))) return listTimeline(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/timelines\/tag\/([^/]+)$/))) return hashtagTimeline(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/tags\/([^/]+)$/))) return hashtagInfo(env, decodeURIComponent(m[1]));
if (method === "GET" && path === "/api/v1/lists") return listsList(request, env);
if (method === "POST" && path === "/api/v1/lists") return createList(request, env);
if (method === "GET" && (m = path.match(/^\/api\/v1\/lists\/([^/]+)$/))) return getList(request, env, decodeURIComponent(m[1]));
if ((method === "PUT" || method === "PATCH") && (m = path.match(/^\/api\/v1\/lists\/([^/]+)$/))) return updateList(request, env, decodeURIComponent(m[1]));
if (method === "DELETE" && (m = path.match(/^\/api\/v1\/lists\/([^/]+)$/))) return deleteList(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/api\/v1\/lists\/([^/]+)\/accounts$/))) return listAccounts(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/lists\/([^/]+)\/accounts$/))) return addListAccounts(request, env, decodeURIComponent(m[1]));
if (method === "DELETE" && (m = path.match(/^\/api\/v1\/lists\/([^/]+)\/accounts$/))) return removeListAccounts(request, env, decodeURIComponent(m[1]));
if (method === "GET" && path === "/api/v1/bookmarks") return bookmarksList(request, env);
if (method === "GET" && path === "/api/v1/favourites") return favouritesList(request, env);
if (method === "POST" && (path === "/api/v1/media" || path === "/api/v2/media")) return uploadMedia(request, env);
if (method === "PUT" && (m = path.match(/^\/api\/v1\/media\/([^/]+)$/))) return updateMedia(request, env, decodeURIComponent(m[1]));
if (method === "GET" && path === "/api/v1/notifications") return notificationsList(request, env);
if (method === "POST" && path === "/api/v1/notifications/clear") return notificationClear(request, env);
if (method === "POST" && (m = path.match(/^\/api\/v1\/notifications\/([^/]+)\/dismiss$/))) return notificationDismiss(request, env, decodeURIComponent(m[1]));
if (method === "GET" && (path === "/api/v2/search" || path === "/api/v1/search")) return search(request, env);
if (method === "GET" && path === "/api/v1/custom_emojis") return customEmojis(env);
if (method === "GET" && path === "/api/v1/filters") return filtersV1(request, env);
if (method === "GET" && path === "/api/v1/trends/tags") return trendsTags(env);
if (method === "GET" && path === "/api/v1/markers") return markersList(request, env);
if (method === "GET" && path === "/api/v1/push/subscription") return getPushSubscription(request, env);
if (method === "POST" && path === "/api/v1/push/subscription") return createPushSubscription(request, env);
if (method === "PUT" && path === "/api/v1/push/subscription") return updatePushSubscription(request, env);
if (method === "DELETE" && path === "/api/v1/push/subscription") return deletePushSubscription(request, env);
if (method === "GET" && (m = path.match(/^\/media\/(.+)$/))) return serveMedia(env, m[1]);
if (method === "GET" && (m = path.match(/^\/users\/([^/]+)$/))) return actor(env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/users\/([^/]+)\/outbox$/))) return outbox(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/users\/([^/]+)\/inbox$/))) return inboxHandler(request, env, decodeURIComponent(m[1]));
if (method === "POST" && path === "/inbox") return inboxHandler(request, env, null);
if (method === "GET" && (m = path.match(/^\/users\/([^/]+)\/followers$/))) return followersCollection(env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/users\/([^/]+)\/following$/))) return followingCollection(env, decodeURIComponent(m[1]));
if (method === "GET" && (m = path.match(/^\/objects\/([^/]+)$/))) return activityObject(env, decodeURIComponent(m[1]));
return json({ error: "not_found" }, 404);
}