A discord bot for teal.fm
discord tealfm music

add pino-http

besaid.zone 83d97e11 1c4ba171

verified
+1 -1
.oxfmtrc.json
··· 1 1 { 2 2 "$schema": "./node_modules/oxfmt/configuration_schema.json", 3 - "ignorePatterns": ["*.json", "pnpm-lock.yml"], 3 + "ignorePatterns": ["*.json", "pnpm-lock.yaml"], 4 4 "experimentalSortImports": { 5 5 "order": "asc" 6 6 }
+6 -2
PLAN.md
··· 1 1 # rough notes on how I think this should work 2 2 3 3 - we start of with no accounts 4 - - `/teal auth` sends user a link to log in with atproto account 4 + - `/auth <did or handle>` sends user a link to log in with atproto account 5 5 - after auth success, we take did and send http request to tap instance to start backfilling for repo 6 6 - user can now use bot commands 7 7 8 8 ## planned commands 9 9 10 - - `teal auth` sends user a link to log in with atproto account 10 + - `auth <did or handle>` sends user a link to log in with atproto account 11 11 - `top <artist>`: top 10 listeners for artist (total amount of plays across all songs / albums) 12 12 - `recent`: most recent play 13 13 14 14 ## web interface for account management...maybe..? 15 15 16 16 - link/unlink account (unlinking would basically delete an account and all their plays) 17 + 18 + ## authentication flow 19 + 20 + 1.
+13 -7
apps/bot/commands/auth.ts
··· 1 - import { CommandInteraction, SlashCommandBuilder } from "discord.js"; 2 1 import { logger } from "@tealfmbot/common/logger.ts"; 2 + import { ChatInputCommandInteraction, InteractionContextType, SlashCommandBuilder } from "discord.js"; 3 3 4 4 export default { 5 5 data: new SlashCommandBuilder() 6 6 .setName("auth") 7 - .setDescription( 8 - "Authenticate your account with the teal.fm bot to start tracking your listens", 9 - ), 10 - async execute(interaction: CommandInteraction) { 11 - await interaction.reply("placeholder"); 12 - logger.info("auth command sent"); 7 + .setDescription("Authenticate your account with the teal.fm bot to start tracking your listens") 8 + .addStringOption((option) => 9 + option 10 + .setName("identifier") 11 + .setDescription("e.g. 'handle.bsky.social or did:plc...'") 12 + .setRequired(true) 13 + .setMinLength(1), 14 + ).setContexts(InteractionContextType.Guild), 15 + async execute(interaction: ChatInputCommandInteraction) { 16 + const identifier = interaction.options.getString("identifier") 17 + await interaction.reply(`hello ${identifier}`) 18 + logger.info(`starting authentication process for ${identifier} at ${new Date().toJSON()}`); 13 19 }, 14 20 };
+1 -1
apps/bot/commands/ping.ts
··· 1 - import { CommandInteraction, SlashCommandBuilder } from "discord.js"; 2 1 import { logger } from "@tealfmbot/common/logger.ts"; 2 + import { CommandInteraction, SlashCommandBuilder } from "discord.js"; 3 3 4 4 export default { 5 5 data: new SlashCommandBuilder().setName("ping").setDescription("replies with pong"),
+1 -1
apps/bot/commands/top.ts
··· 1 - import { CommandInteraction, SlashCommandBuilder } from "discord.js"; 2 1 import { logger } from "@tealfmbot/common/logger.ts"; 2 + import { CommandInteraction, SlashCommandBuilder } from "discord.js"; 3 3 4 4 export default { 5 5 data: new SlashCommandBuilder()
+3 -3
apps/bot/deploy-commands.ts
··· 1 - import { REST, Routes } from "discord.js"; 2 - import fs from "node:fs"; 3 - import path from "node:path"; 4 1 import { 5 2 DISCORD_APPLICATION_ID, 6 3 DISCORD_BOT_TOKEN, 7 4 DISCORD_GUILD_ID, 8 5 } from "@tealfmbot/common/constants.ts"; 6 + import { REST, Routes } from "discord.js"; 7 + import fs from "node:fs"; 8 + import path from "node:path"; 9 9 10 10 const commands = []; 11 11 const commandPaths = fs.globSync("commands/**/*.ts");
+2 -2
apps/bot/main.ts
··· 1 + import { DISCORD_BOT_TOKEN } from "@tealfmbot/common/constants.ts"; 2 + import { logger } from "@tealfmbot/common/logger.ts"; 1 3 import { Client, Collection, Events, GatewayIntentBits, MessageFlags } from "discord.js"; 2 4 import fs from "node:fs"; 3 5 import path from "node:path"; 4 - import { DISCORD_BOT_TOKEN } from "@tealfmbot/common/constants.ts"; 5 - import { logger } from "@tealfmbot/common/logger.ts"; 6 6 7 7 const client = new Client({ intents: [GatewayIntentBits.Guilds] }); 8 8
+19 -2
apps/web/index.ts
··· 1 + import { serve, type HttpBindings } from "@hono/node-server"; 2 + import { logger } from "@tealfmbot/common/logger.js"; 1 3 import { Hono } from "hono"; 2 - import { serve, type HttpBindings } from "@hono/node-server"; 4 + import pinoHttpLogger from "pino-http" 5 + 6 + type Variables = { 7 + logger: typeof logger 8 + } 9 + 10 + const app = new Hono<{ Bindings: HttpBindings, Variables: Variables }>(); 11 + 12 + app.use(async (c, next) => { 13 + await new Promise<void>((resolve) => pinoHttpLogger({ 14 + autoLogging: false, 15 + })(c.env.incoming, c.env.outgoing, () => resolve())); 3 16 4 - const app = new Hono<{ Bindings: HttpBindings }>(); 17 + c.set("logger", c.env.incoming.log) 18 + 19 + await next(); 20 + }) 5 21 6 22 app.get("/", (c) => { 23 + c.var.logger.info("test log") 7 24 return c.text("yo!!"); 8 25 }); 9 26
+2 -1
apps/web/package.json
··· 12 12 "@hono/node-server": "^1.19.7", 13 13 "@tealfmbot/common": "workspace:*", 14 14 "@tealfmbot/tsconfig": "workspace:*", 15 - "hono": "^4.11.3" 15 + "hono": "^4.11.3", 16 + "pino-http": "^11.0.0" 16 17 }, 17 18 "devDependencies": { 18 19 "@types/node": "^25.0.3",
+1 -1
packages/common/constants.ts
··· 1 - import { loadEnvFile } from "node:process"; 2 1 import path from "node:path"; 2 + import { loadEnvFile } from "node:process"; 3 3 4 4 loadEnvFile(path.join(import.meta.dirname, "../../.env")); 5 5
+3 -2
packages/database/db.ts
··· 1 1 import type { DB } from "kysely-codegen"; 2 + 3 + import { DATABASE_URL } from "@tealfmbot/common/constants.ts"; 4 + import { Kysely, PostgresDialect } from "kysely"; 2 5 import { Pool } from "pg"; 3 - import { Kysely, PostgresDialect } from "kysely"; 4 - import { DATABASE_URL } from "@tealfmbot/common/constants.ts"; 5 6 6 7 const dialect = new PostgresDialect({ 7 8 pool: new Pool({
+5 -4
packages/database/migrator.ts
··· 1 + import type { DB } from "kysely-codegen"; 2 + 3 + import { DATABASE_URL } from "@tealfmbot/common/constants.ts"; 4 + import { Kysely, Migrator, PostgresDialect, FileMigrationProvider } from "kysely"; 5 + import fs from "node:fs/promises"; 1 6 import path from "node:path"; 2 7 import { Pool } from "pg"; 3 - import fs from "node:fs/promises"; 4 - import type { DB } from "kysely-codegen"; 5 - import { Kysely, Migrator, PostgresDialect, FileMigrationProvider } from "kysely"; 6 - import { DATABASE_URL } from "@tealfmbot/common/constants.ts"; 7 8 8 9 async function migrateToLatest() { 9 10 const db = new Kysely<DB>({
+19
pnpm-lock.yaml
··· 79 79 hono: 80 80 specifier: ^4.11.3 81 81 version: 4.11.3 82 + pino-http: 83 + specifier: ^11.0.0 84 + version: 11.0.0 82 85 devDependencies: 83 86 '@types/node': 84 87 specifier: ^25.0.3 ··· 636 639 function-bind@1.1.2: 637 640 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 638 641 642 + get-caller-file@2.0.5: 643 + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 644 + engines: {node: 6.* || 8.* || >= 10.*} 645 + 639 646 get-tsconfig@4.13.0: 640 647 resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} 641 648 ··· 920 927 921 928 pino-abstract-transport@3.0.0: 922 929 resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} 930 + 931 + pino-http@11.0.0: 932 + resolution: {integrity: sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==} 923 933 924 934 pino-pretty@13.1.3: 925 935 resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} ··· 1567 1577 1568 1578 function-bind@1.1.2: {} 1569 1579 1580 + get-caller-file@2.0.5: {} 1581 + 1570 1582 get-tsconfig@4.13.0: 1571 1583 dependencies: 1572 1584 resolve-pkg-maps: 1.0.0 ··· 1817 1829 pino-abstract-transport@3.0.0: 1818 1830 dependencies: 1819 1831 split2: 4.2.0 1832 + 1833 + pino-http@11.0.0: 1834 + dependencies: 1835 + get-caller-file: 2.0.5 1836 + pino: 10.1.0 1837 + pino-std-serializers: 7.0.0 1838 + process-warning: 5.0.0 1820 1839 1821 1840 pino-pretty@13.1.3: 1822 1841 dependencies: