A discord bot for teal.fm
discord
tealfm
music
1import type { Context } from "hono";
2
3import { Agent } from "@atproto/api";
4import { isAtprotoDid, isAtprotoDidWeb } from "@atproto/did";
5import { isValidHandle } from "@atproto/syntax";
6import { env } from "@tealfmbot/common/constants";
7import { logger } from "@tealfmbot/common/logger";
8import { deleteCookie, generateSignedCookie, getSignedCookie } from "hono/cookie";
9
10import { client } from "./client.js";
11
12export const MAX_AGE = process.env.NODE_ENV === "production" ? 60 : 0;
13
14export async function createSession(value: string) {
15 const cookie = await generateSignedCookie("__teal_fm_bot_session", value, env.COOKIE_SECRET, {
16 path: "/",
17 secure: process.env.NODE_ENV === "production",
18 httpOnly: true,
19 sameSite: "lax",
20 maxAge: 60 * 60 * 24 * 7,
21 });
22
23 return cookie;
24}
25
26export async function getSessionAgent(c: Context) {
27 c.header("Vary", "Cookie");
28 const session = await getSignedCookie(c, env.COOKIE_SECRET, "__teal_fm_bot_session");
29 if (!session) return null;
30 c.header("Cache-Control", `max-age=${MAX_AGE}, private`);
31
32 try {
33 const oauthSession = await client.restore(session);
34 return oauthSession ? new Agent(oauthSession) : null;
35 } catch (error) {
36 logger.warn({ error }, "oauth restore failed");
37 deleteCookie(c, "__teal_fm_bot_session");
38 return null;
39 }
40}
41
42export async function getSession(c: Context, e: typeof env) {
43 const session = await getSignedCookie(c, e.COOKIE_SECRET, "__teal_fm_bot_session");
44 return session;
45}
46
47export function isValidUrl(url: string) {
48 if (URL.canParse(url)) {
49 const pdsUrl = new URL(url);
50 return pdsUrl.protocol === "http:" || pdsUrl.protocol === "https:";
51 }
52 return false;
53}
54
55// https://github.com/bluesky-social/social-app/blob/main/src/lib/strings/handles.ts#L51
56export function validateIdentifier(input: string) {
57 if (typeof input !== "string") return false;
58 const results = {
59 validHandle: isValidHandle(input),
60 nonEmptyIdentifier: !input.trim(),
61 validDid: isAtprotoDid(input),
62 validUrl: isValidUrl(input),
63 validDidWeb: isAtprotoDidWeb(input),
64 };
65
66 return Object.values(results).includes(true);
67}