import { handler } from "@sortug/sorlang-db"; import { serve } from "bun"; import index from "./index.html"; import { TwitterApiService } from "./lib/fetching/twitter-api"; const jsonResponse = (data: unknown, init?: ResponseInit) => Response.json(data, init); async function withTwitterService( req: Request, handler: ( service: TwitterApiService, payload: Record, ) => Promise, ) { try { const payload = await req.json(); const cookie = payload?.cookie; if (!cookie || typeof cookie !== "string") { return jsonResponse( { error: "Missing twitter auth cookie" }, { status: 400 }, ); } const service = new TwitterApiService(cookie); return await handler(service, payload); } catch (error) { console.error("Twitter API route error", error); return jsonResponse( { error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 }, ); } } const server = serve({ port: 3010, routes: { // Serve index.html for all unmatched routes. "/*": index, "/api/db": handler, "/api/hello": { async GET(req) { return Response.json({ message: "Hello, world!", method: "GET", }); }, async PUT(req) { return Response.json({ message: "Hello, world!", method: "PUT", }); }, }, "/api/hello/:name": async (req) => { const name = req.params.name; return Response.json({ message: `Hello, ${name}!`, }); }, "/api/twitter/our": { async POST(req) { return withTwitterService(req, async (service, _payload) => { return jsonResponse(await service.findOwn()); }); }, }, "/api/twitter/timeline/:mode": { async POST(req) { const { mode } = req.params; console.log("fetching tweets", mode); return withTwitterService(req, async (service, payload) => { const cursor = typeof payload.cursor === "string" ? payload.cursor : undefined; switch (mode) { case "foryou": return jsonResponse(await service.fetchForyou(cursor)); case "following": return jsonResponse(await service.fetchFollowing(cursor)); case "bookmarks": return jsonResponse(await service.fetchBookmarks(cursor)); case "list": if (!payload.listId) { return jsonResponse( { error: "Missing listId" }, { status: 400 }, ); } return jsonResponse( await service.fetchList(String(payload.listId), cursor), ); case "user": if (!payload.userId) { return jsonResponse( { error: "Missing userId" }, { status: 400 }, ); } return jsonResponse( await service.fetchUserTweets(String(payload.userId), cursor), ); case "thread": if (!payload.tweetId) { return jsonResponse( { error: "Missing tweetId" }, { status: 400 }, ); } return jsonResponse( await service.fetchThread(String(payload.tweetId), cursor), ); default: return jsonResponse( { error: `Unknown timeline mode: ${mode}` }, { status: 400 }, ); } }); }, }, "/api/twitter/lists": { async POST(req) { return withTwitterService(req, async (service) => { return jsonResponse(await service.fetchLists()); }); }, }, "/api/twitter/notifications": { async POST(req) { return withTwitterService(req, async (service, payload) => { const cursor = typeof payload.cursor === "string" ? payload.cursor : undefined; return jsonResponse(await service.fetchNotifications(cursor)); }); }, }, "/api/twitter/bookmarks/remove": { async POST(req) { return withTwitterService(req, async (service, payload) => { const tweetId = payload?.tweetId; if (!tweetId) { return jsonResponse({ error: "Missing tweetId" }, { status: 400 }); } await service.removeBookmark(String(tweetId)); return jsonResponse({ status: "ok" }); }); }, }, "/api/twitter/tweets/:tweetId/like": { async POST(req) { const { tweetId } = req.params; return withTwitterService(req, async (service, payload) => { if (!tweetId) { return jsonResponse({ error: "Missing tweetId" }, { status: 400 }); } const undo = Boolean(payload?.undo); if (undo) { await service.removeLike(tweetId); } else { await service.addLike(tweetId); } return jsonResponse({ status: "ok" }); }); }, }, "/api/twitter/tweets/:tweetId/retweet": { async POST(req) { const { tweetId } = req.params; return withTwitterService(req, async (service, payload) => { if (!tweetId) { return jsonResponse({ error: "Missing tweetId" }, { status: 400 }); } const undo = Boolean(payload?.undo); if (undo) { await service.removeRT(tweetId); } else { await service.addRT(tweetId); } return jsonResponse({ status: "ok" }); }); }, }, "/api/twitter/tweets/:tweetId/bookmark": { async POST(req) { const { tweetId } = req.params; return withTwitterService(req, async (service, payload) => { if (!tweetId) { return jsonResponse({ error: "Missing tweetId" }, { status: 400 }); } const undo = Boolean(payload?.undo); if (undo) { await service.removeBookmark(tweetId); } else { await service.addBookmark(tweetId); } return jsonResponse({ status: "ok" }); }); }, }, "/api/twitter/tweets/:tweetId/reply": { async POST(req) { const { tweetId } = req.params; return withTwitterService(req, async (service, payload) => { if (!tweetId) { return jsonResponse({ error: "Missing tweetId" }, { status: 400 }); } const text = typeof payload?.text === "string" ? payload.text.trim() : ""; if (!text) { return jsonResponse( { error: "Missing reply text" }, { status: 400 }, ); } await service.createTweet(text, { reply: tweetId }); return jsonResponse({ status: "ok" }); }); }, }, }, development: process.env.NODE_ENV !== "production" && { // Enable browser hot reloading in development hmr: true, // Echo console logs from the browser to the server console: true, }, }); console.log(`🚀 Server running at ${server.url}`);