Initial toot-worker implementation

This commit is contained in:
浪子
2026-05-14 09:59:58 +08:00
commit 01880d39a0
19 changed files with 4952 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
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,
authorize,
authorizeFollowRequest,
authorizePage,
bookmarkStatus,
createApp,
createStatus,
customEmojis,
deleteStatusEndpoint,
favouriteStatus,
filtersV1,
followAccount,
followRequestsList,
getAccount,
getRelationships,
getStatusEndpoint,
homeTimeline,
instance,
instanceV2,
markersList,
notificationClear,
notificationDismiss,
notificationsList,
publicTimeline,
pushSubscription,
reblogStatus,
rejectFollowRequest,
revoke,
search,
serveMedia,
statusContext,
token,
trendsTags,
unfavouriteStatus,
unfollowAccount,
unreblogStatus,
updateCredentials,
updateMedia,
uploadMedia,
verifyAppCredentials,
verifyCredentials
} from "./mastodon";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
try {
await ensureAdminUser(env);
return await route(request, env);
} 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 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);
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 === "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 bookmarkStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/pin$/))) return bookmarkStatus(request, env, decodeURIComponent(m[1]));
if (method === "POST" && (m = path.match(/^\/api\/v1\/statuses\/([^/]+)\/unpin$/))) return bookmarkStatus(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 === "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 === "POST" && path === "/api/v1/push/subscription") return pushSubscription();
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);
}