223 lines
12 KiB
TypeScript
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);
|
|
}
|