diff options
Diffstat (limited to 'packages/tweetdeck/src/index.ts')
| -rw-r--r-- | packages/tweetdeck/src/index.ts | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/packages/tweetdeck/src/index.ts b/packages/tweetdeck/src/index.ts new file mode 100644 index 0000000..ccc86e7 --- /dev/null +++ b/packages/tweetdeck/src/index.ts @@ -0,0 +1,242 @@ +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<string, any>, + ) => Promise<Response>, +) { + 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/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}`); |
