{"_id":"@dsqr/discord","_rev":"3-43e80e53211169614320bc36950bf7e3","name":"@dsqr/discord","dist-tags":{"beta":"0.0.1-beta.0","latest":"0.0.1-beta.2"},"versions":{"0.0.1-beta.0":{"name":"@dsqr/discord","version":"0.0.1-beta.0","_id":"@dsqr/discord@0.0.1-beta.0","maintainers":[{"name":"daveved","email":"dave.w.dennis@gmail.com"}],"dist":{"shasum":"4963cef7d18d4932b09e3e482a27459e1fa89e02","tarball":"https://registry.npmjs.org/@dsqr/discord/-/discord-0.0.1-beta.0.tgz","fileCount":26,"integrity":"sha512-LV5UvLPzyD7uSssDj/+LrL3NRh+pYqzzzYwLZG+oh4GUlBSGCvshg/u8JDx+UcFUljYv/CaO/YmBEZYb21N8Wg==","signatures":[{"sig":"MEUCIGIBmZ2KPnW30NNbu+Fwd9exvBHgNX0lxD4lNWYg3YbvAiEA+o6FWX2WhP5QWjqDyAVK7PwM/WUMgpoI/oxumCSBZmw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22429},"type":"module","exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js"},"./*":{"types":"./dist/types/*.d.ts","import":"./dist/esm/*.js"}},"gitHead":"612b68d21386a38cfa08f2421e2241df674a6cf4","scripts":{"build":"bun run scripts/build.ts"},"_npmUser":{"name":"daveved","email":"dave.w.dennis@gmail.com"},"_npmVersion":"10.9.2","description":"","directories":{},"sideEffects":false,"_nodeVersion":"22.13.1","dependencies":{"ai":"^4.1.41","zod":"^3.24.2","dotenv":"^16.4.7","discord.js":"^14.18.0","@ai-sdk/perplexity":"^1.0.0"},"_hasShrinkwrap":false,"peerDependencies":{"typescript":"^5.6.3"},"_npmOperationalInternal":{"tmp":"tmp/discord_0.0.1-beta.0_1741063959989_0.4151118138261112","host":"s3://npm-registry-packages-npm-production"}},"0.0.1-beta.1":{"name":"@dsqr/discord","version":"0.0.1-beta.1","_id":"@dsqr/discord@0.0.1-beta.1","maintainers":[{"name":"daveved","email":"dave.w.dennis@gmail.com"}],"dist":{"shasum":"a7e1d776274351d4b502983ea6c0a39b0bb558dd","tarball":"https://registry.npmjs.org/@dsqr/discord/-/discord-0.0.1-beta.1.tgz","fileCount":26,"integrity":"sha512-AmIC/+BbDARc83rSrD/gwHcVazG1gj70CINZt64kabJxtzBaCvdo5UOYiPo/i7T75bkTMNC07Q4bKjsj15/gwA==","signatures":[{"sig":"MEUCIHoXwjxJLOzOfbi0ZXCd9/vlukUkuWcz0ojQL5RC3VShAiEAgdCxABGMkyt0qiFTEQUe+PkiFVkRcstMDqnF9fiDm9c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22387},"type":"module","exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js"},"./*":{"types":"./dist/types/*.d.ts","import":"./dist/esm/*.js"}},"gitHead":"22de9f99aecf61bc46e5006d61cef7bfd9e2ead9","scripts":{"build":"bun run scripts/build.ts"},"_npmUser":{"name":"daveved","email":"dave.w.dennis@gmail.com"},"_npmVersion":"10.9.2","description":"","directories":{},"sideEffects":false,"_nodeVersion":"22.13.1","dependencies":{"zod":"^3.24.2","dotenv":"^16.4.7","discord.js":"^14.18.0"},"_hasShrinkwrap":false,"peerDependencies":{"typescript":"^5.6.3"},"_npmOperationalInternal":{"tmp":"tmp/discord_0.0.1-beta.1_1741227398524_0.2131311704533514","host":"s3://npm-registry-packages-npm-production"}},"0.0.1-beta.2":{"name":"@dsqr/discord","version":"0.0.1-beta.2","type":"module","sideEffects":false,"scripts":{"build":"bun run scripts/build.ts"},"exports":{".":{"import":"./dist/esm/index.js","types":"./dist/types/index.d.ts"},"./*":{"import":"./dist/esm/*.js","types":"./dist/types/*.d.ts"}},"peerDependencies":{"typescript":"^5.6.3"},"dependencies":{"discord.js":"^14.18.0","dotenv":"^16.4.7","zod":"^3.24.2"},"_id":"@dsqr/discord@0.0.1-beta.2","gitHead":"8a6217095d98d4091f324c6a88775d4c0cfe5d3b","description":"<div align=\"center\">","_nodeVersion":"22.13.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-gQklyeVGV6+sWr57G54LwIU/DnKSIQ0Q3Tp8mibopX8a0EJgGveRCz5+D/e9gXHAFxDv89UPHIndNhA3dh+5bg==","shasum":"8ad0b75bb5814f179bd786c0fdecc97c46a2ed34","tarball":"https://registry.npmjs.org/@dsqr/discord/-/discord-0.0.1-beta.2.tgz","fileCount":26,"unpackedSize":38025,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDpA2aidjsUguupQyb5Er1p9ibMUlQMNKTlkDpAB9h2CwIhAID3k6YRfCv/QhTt8PuWoYPguFbTp2xxvKRx+GUSFQR2"}]},"_npmUser":{"name":"daveved","email":"dave.w.dennis@gmail.com"},"directories":{},"maintainers":[{"name":"daveved","email":"dave.w.dennis@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/discord_0.0.1-beta.2_1741408755818_0.5803428877089247"},"_hasShrinkwrap":false}},"time":{"created":"2025-03-04T04:52:39.927Z","modified":"2025-03-08T04:39:16.215Z","0.0.1-beta.0":"2025-03-04T04:52:40.184Z","0.0.1-beta.1":"2025-03-06T02:16:38.691Z","0.0.1-beta.2":"2025-03-08T04:39:15.998Z"},"maintainers":[{"name":"daveved","email":"dave.w.dennis@gmail.com"}],"readme":"<div align=\"center\">\n\n# DSQR Discord\n\n[![Typescript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)\n[![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://www.discord.com/)\n\n</div>\n\nThe DSQR discord package provides a simple and efficient way to get started with a Discord bot. It handles the initial plumbing, allowing you to build from the ground up to support all your needs.\n\n- **Quick Start**: Get a bot running with minimal code, leveraging Discord.js and Bun's SQLite (or other databases upon request).\n- **Customizable**: Add your own intents, commands, event handlers, and lifecycle callbacks.\n- **Database Included**: Built-in SQLite support with guild tracking, fully extensible for custom tables and queries.\n- **Type Safety**: Written in TypeScript with Zod validation for configuration.\n- **Lifecycle Hooks**: Comprehensive callbacks for startup, readiness, shutdown, commands, errors, and more.\n\n## ⇁ TOC\n- [The Problems](#-the-problems)\n- [The Solutions](#-the-solutions)\n- [Installation](#-installation)\n- [Getting Started](#-getting-started)\n- [Custom Event Handlers](#-custom-event-handlers)\n- [Extending the Database](#-extending-the-database)\n- [Lifecycle Callbacks](#-lifecycle-callbacks)\n- [Handling Errors and Shutdowns](#-handling-errors-and-shutdowns)\n- [Nix Development Setup](#-nix-development-setup)\n- [Tips for Success](#-tips-for-success)\n\n## ⇁ The Problems\n1. Setting up a Discord bot from scratch involves tedious boilerplate: initializing the client, managing events, registering commands, and handling persistence.\n2. You need a starting point that's simple yet flexible enough to scale—whether it's custom commands, event handling, or database storage.\n3. Managing environment variables and ensuring they're valid can be a hassle without a clear, type-safe approach.\n\n## ⇁ The Solutions\n1. DSQR Discord provides a pre-configured Discord.js client with built-in event handlers and SQLite integration, reducing setup time.\n2. A modular design lets you customize intents, commands, event handlers, and lifecycle callbacks while exposing the full database for expansion.\n3. A Zod-based configuration system validates environment variables, making setup reliable and straightforward.\n\n## ⇁ Installation\nInstall using npm (or your preferred package manager):\n```\nbun add @dsqr/discord @ai-sdk/perplexity\n```\n\nIf you're using Nix for development, see the [Nix Development Setup](#-nix-development-setup) section below.\n\n## ⇁ Getting Started\n\nDSQR Discord makes it incredibly easy to set up a Discord bot. The package handles all the complex boilerplate like client initialization, command registration, event handling, and database setup.\n\n### How It Works\n\nWhen you start your bot with `bot.start()`, DSQR Discord will:\n\n1. Set up default event handlers (which can be overridden via `eventHandlers`)\n2. Register your slash commands with Discord\n3. Log in to Discord using your bot token\n4. Call your `onStart` callback if provided\n5. Automatically track guild membership in the database\n\nThe bot comes with sensible defaults but is fully customizable:\n\n### Prerequisites\n- A Discord bot token and client ID from the [Discord Developer Portal](https://discord.com/developers/applications).\n- An `.env` file with required variables (see below).\n- For the `ChatbotCommand`, a Perplexity API key (optional, add `PERPLEXITY_API_KEY=your-key` to `.env`).\n\n### Basic Setup\n1. **Set Up Environment Variables**:\n   Create an `.env` file in your project root:\n   ```\n   DISCORD_BOT_TOKEN=your-bot-token\n   DISCORD_CLIENT_ID=your-client-id\n   DISCORD_DB_PATH=dsqr.local.db  # Optional, defaults to this\n   PERPLEXITY_API_KEY=your-perplexity-key  # Optional, for ChatbotCommand\n   ```\n\n2. **Create Your Bot**:\n   Here's a simple example with commands using `satisfies`:\n   ```typescript\n   // bot.ts\n   import { local, dsqrDiscord, DsqrDiscordConfig } from \"@dsqr/discord\"\n   import { GatewayIntentBits } from \"discord.js\"\n   import { PingPongCommand } from \"./commands/ping.ts\"\n   import { ChatbotCommand } from \"./commands/chat.ts\"\n\n   // Load config from .env\n   const config = local.getConfig()\n\n   const botConfig = {\n     botToken: config.discord.botToken,\n     clientId: config.discord.clientId,\n     database: { type: \"sqlite\", filename: config.discord.dbPath },\n     intents: [GatewayIntentBits.GuildMessages],\n     commands: [new PingPongCommand(), new ChatbotCommand()],\n     callbacks: {\n       onStart: () => console.log(\"Bot started!\"),\n       onReady: (client) => console.log(`Ready as ${client.user.tag}`),\n       onError: (error) => console.error(\"Error:\", error.message),\n     },\n   } satisfies DsqrDiscordConfig\n\n   const bot = dsqrDiscord(botConfig)\n   bot.start()\n   ```\n\n3. **Run Your Bot**:\n   With Bun:\n   ```\n   bun run bot.ts\n   ```\n\n### Adding Commands\nHere are two example commands:\n\n- **PingPong Command**:\n  ```typescript\n  // commands/ping.ts\n  import { Command } from \"@dsqr/discord\"\n  import { SlashCommandBuilder } from \"discord.js\"\n\n  export class PingPongCommand implements Command {\n    name = \"ping\"\n    slashCommandConfig = new SlashCommandBuilder()\n      .setName(this.name)\n      .setDescription(\"Replies with Pong!\")\n\n    async execute(interaction) {\n      await interaction.reply(\"Pong!\");\n    }\n  }\n  ```\n\n- **Simple Chatbot Command** with Perplexity:\n  ```typescript\n  // commands/chat.ts\n  import { Command } from \"@dsqr/discord\"\n  import { SlashCommandBuilder, ChatInputCommandInteraction } from \"discord.js\"\n  import { createPerplexity } from \"@ai-sdk/perplexity\"\n\n  const perplexity = createPerplexity({\n    apiKey: process.env.PERPLEXITY_API_KEY ?? \"\",\n  })\n\n  export class ChatbotCommand implements Command {\n    name = \"chat\"\n    slashCommandConfig = new SlashCommandBuilder()\n      .setName(this.name)\n      .setDescription(\"Chat with an AI assistant\")\n      .addStringOption((option) =>\n        option\n          .setName(\"message\")\n          .setDescription(\"Your message\")\n          .setRequired(true)\n      )\n\n    async execute(interaction: ChatInputCommandInteraction) {\n      await interaction.deferReply();\n      const message = interaction.options.getString(\"message\");\n      if (!message) return interaction.editReply(\"Please provide a message.\");\n\n      try {\n        const response = await perplexity(\"sonar-pro\").chat({\n          messages: [{ role: \"user\", content: message }],\n        });\n        await interaction.editReply(response.choices[0].message.content);\n      } catch (error) {\n        console.error(\"Chat error:\", error);\n        await interaction.editReply(\"Sorry, I couldn't respond. Try again later.\");\n      }\n    }\n  }\n  ```\n\n## ⇁ Custom Event Handlers\nYou can override default event handlers or add new ones, and use the exposed `db.database` for custom queries. DSQR Discord automatically handles the following events: ClientReady, Error, GuildCreate, GuildDelete, and InteractionCreate, but you can customize any of these or add additional event handlers.\n\nHere's an example logging messages to a custom table:\n\n```typescript\n// bot.ts\nimport { local, dsqrDiscord, DsqrDiscordConfig } from \"@dsqr/discord\"\nimport { GatewayIntentBits, Events } from \"discord.js\"\nimport { PingPongCommand } from \"./commands/ping.ts\"\n\nconst config = local.getConfig()\n\nconst botConfig = {\n  botToken: config.discord.botToken,\n  clientId: config.discord.clientId,\n  database: { type: \"sqlite\", filename: config.discord.dbPath },\n  intents: [GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent],\n  commands: [new PingPongCommand()],\n  eventHandlers: {\n    [Events.MessageCreate]: (message) => {\n      if (message.author.bot) return;\n      const query = \"INSERT INTO message_logs (guild_id, user_id, message) VALUES (?, ?, ?)\";\n      message.client.dsqrDiscord.db.database.run(query, [\n        message.guildId,\n        message.author.id,\n        message.content,\n      ]);\n      console.log(`Logged message from ${message.author.tag}`);\n    },\n  },\n  callbacks: {\n    onStart: () => console.log(\"Bot started!\"),\n  },\n} satisfies DsqrDiscordConfig\n\nconst bot = dsqrDiscord(botConfig)\nbot.start()\n```\n\nNote: This assumes a `message_logs` table exists (see \"Extending the Database\").\n\nNote: You need `GatewayIntentBits.MessageContent` to read message content, which is a privileged intent that must be enabled in the Discord Developer Portal.\n\n## ⇁ Extending the Database\nThe `db` object exposes `getGuild`, `getAllGuilds`, and the raw `database` instance, letting you run custom queries or add tables. Here's how to extend the database:\n\n```typescript\n// database/sqlite.ts\nimport { Database, Statement } from \"bun:sqlite\"\n\nexport interface SqliteDatabase {\n  insertGuild: Statement\n  removeGuild: Statement\n  getGuild: Statement\n  getAllGuilds: Statement\n  database: Database\n}\n\nexport function sqliteDatabase(filename: string = \"dsqr.local.db\"): SqliteDatabase {\n  const database = new Database(filename, { create: true })\n\n  database.exec(`\n    CREATE TABLE IF NOT EXISTS guilds (\n      guild_id TEXT PRIMARY KEY,\n      name TEXT NOT NULL,\n      owner_id TEXT,\n      created_at INTEGER DEFAULT (strftime('%s', 'now'))\n    );\n    CREATE TABLE IF NOT EXISTS message_logs (\n      id INTEGER PRIMARY KEY AUTOINCREMENT,\n      guild_id TEXT,\n      user_id TEXT,\n      message TEXT,\n      timestamp INTEGER DEFAULT (strftime('%s', 'now'))\n    );\n  `)\n\n  return {\n    insertGuild: database.prepare(\"INSERT OR REPLACE INTO guilds (guild_id, name, owner_id) VALUES ($guildId, $name, $ownerId)\"),\n    removeGuild: database.prepare(\"DELETE FROM guilds WHERE guild_id = $guildId\"),\n    getGuild: database.prepare(\"SELECT * FROM guilds WHERE guild_id = $guildId\"),\n    getAllGuilds: database.prepare(\"SELECT * FROM guilds\"),\n    database,\n  }\n}\n```\n\nUse it in your bot:\n```typescript\nbot.db.database.run(\"INSERT INTO message_logs (guild_id, message) VALUES (?, ?)\", [\"123\", \"Test log\"]);\n```\n\n## ⇁ Lifecycle Callbacks\n\nDSQR Discord provides several callback hooks to customize how your bot responds to different lifecycle events:\n\n```typescript\ncallbacks: {\n  // When the bot starts up\n  onStart: () => console.log(\"Bot started!\"),\n  \n  // When bot successfully connects to Discord\n  onReady: (client) => console.log(`Ready as ${client.user.tag}`),\n  \n  // When the bot shuts down (via bot.stop() or SIGINT)\n  onShutdown: () => console.log(\"Bot shutting down, goodbye!\"),\n  \n  // When any error occurs in the bot\n  onError: (error) => console.error(\"Bot error:\", error.message),\n  \n  // After a command executes successfully\n  onCommandSuccess: (interaction) => {\n    console.log(`Command /${interaction.commandName} used by ${interaction.user.tag}`);\n  },\n  \n  // When a command throws an error\n  onCommandError: (error, interaction) => {\n    console.error(`Error in /${interaction.commandName}:`, error.message);\n  }\n}\n```\n\nAll callbacks are optional - implement only the ones you need. These hooks are perfect for:\n\n- Logging and monitoring\n- Metrics collection\n- User experience tracking\n- Database operations\n- Custom error handling\n- Graceful resource cleanup\n\n## ⇁ Handling Errors and Shutdowns\n\nDSQR Discord provides automatic error handling and graceful shutdowns:\n\n1. **Error handling**: All errors are logged and passed to your `onError` callback if provided\n2. **Command errors**: Command execution errors are caught and passed to your `onCommandError` callback\n3. **Graceful shutdown**: The bot automatically handles SIGINT signals, closing the database connection and destroying the client\n4. **Custom shutdown**: You can manually shut down the bot with `bot.stop()`\n\n## ⇁ Nix Development Setup\n\nDSQR Discord works well in a Nix development environment. Create a `flake.nix` file in your project root:\n\n```nix\n{\n  description = \"Discord bot with Bun and Vercel AI SDK\";\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixos-unstable\";\n    flake-utils.url = \"github:numtide/flake-utils\";\n  };\n  outputs = { self, nixpkgs, flake-utils }:\n    flake-utils.lib.eachDefaultSystem (system:\n      let\n        pkgs = nixpkgs.legacyPackages.${system};\n      in\n      {\n        devShells.default = pkgs.mkShell {\n          buildInputs = with pkgs; [\n            bun\n            nodejs_22\n            git\n          ];\n          shellHook = ''\n            echo \"🦉🦉🦉🦉🦉🦉🦉🦉🦉🦉🦉\"\n          '';\n        };\n        packages.default = pkgs.writeScriptBin \"start-bot\" ''\n          #!/bin/sh\n          bun run packages/bot/src/index.ts\n        '';\n      });\n}\n```\n\nThis setup provides:\n\n1. A development shell with Bun, Node.js 22, and Git\n2. A default package that runs your bot with Bun\n3. A fun owl-filled shell greeting\n\nTo use this setup:\n\n```bash\n# Enter the development environment\nnix develop\n\n# Or run the bot directly (if you've enabled flakes)\nnix run\n```\n\n## ⇁ Tips for Success\n\n- Start with the minimal configuration (token, client ID, database) and add features as needed\n- Use TypeScript's `satisfies` keyword with `DsqrDiscordConfig` to get type checking\n- Add only the intents your bot needs for better security\n- Implement lifecycle callbacks that make sense for your use case\n- Access the Discord.js client directly via `bot.client` when needed\n- Use the built-in database for simple persistence needs\n- Consider using environment variables with the built-in Zod validation\n- Remember that your existing code will continue to work as the library is backward compatible","readmeFilename":"README.md","description":"<div align=\"center\">"}