summaryrefslogtreecommitdiff
path: root/packages/tweetdeck/src/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tweetdeck/src/index.ts')
-rw-r--r--packages/tweetdeck/src/index.ts242
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}`);