chore: add pnpm workspace configuration with only built dependencies
This commit is contained in:
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
**Discord Moderation Watcher Bot** — A comprehensive monitoring bot that captures voice, text messages, and images from Discord servers. Records audio from voice channels, captures all text messages (new/edited/deleted) from channels and threads, and uploads attachments to external storage. All data stored in SQLite with real-time dashboard.
|
**Discord Moderation Watcher Bot** — A comprehensive monitoring bot that captures voice, text messages, and images from Discord servers. Records audio from voice channels, captures all text messages (new/edited/deleted) from channels and threads, and uploads attachments to external storage. All data stored in SQLite with real-time dashboard.
|
||||||
|
|
||||||
Built with **Bun** + **discord.js-selfbot-v13** + **@discordjs/voice** + **Express** + **WebSocket**.
|
Built with **Node.js/pnpm** + **discord.js-selfbot-v13** + **@discordjs/voice** + **Express** + **WebSocket**.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -73,28 +73,28 @@ attachments (SQLite):
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
bun install
|
pnpm install
|
||||||
|
|
||||||
# Development (auto-restart on file changes)
|
# Development (auto-restart on file changes)
|
||||||
bun run dev
|
pnpm run dev
|
||||||
|
|
||||||
# Production
|
# Production
|
||||||
bun run start
|
pnpm run start
|
||||||
|
|
||||||
# Type checking
|
# Type checking
|
||||||
bun run typecheck
|
pnpm run typecheck
|
||||||
|
|
||||||
# Linting (Biome)
|
# Linting (Biome)
|
||||||
bun run lint
|
pnpm run lint
|
||||||
|
|
||||||
# Format code (Biome)
|
# Format code (Biome)
|
||||||
bun run format
|
pnpm run format
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
bun run test
|
pnpm run test
|
||||||
|
|
||||||
# Build TypeScript
|
# Build TypeScript
|
||||||
bun run build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -136,7 +136,7 @@ All config via `.env` (see `.env.example`). Key variables:
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Tests use **Vitest** in `tests/` directory. Run with `bun run test`.
|
Tests use **Vitest** in `tests/` directory. Run with `pnpm run test`.
|
||||||
|
|
||||||
**Test Coverage:**
|
**Test Coverage:**
|
||||||
- `tests/moderation/messageStore.test.ts` — Message store CRUD operations
|
- `tests/moderation/messageStore.test.ts` — Message store CRUD operations
|
||||||
@@ -290,7 +290,7 @@ Handles SIGINT/SIGTERM/uncaughtException/unhandledRejection:
|
|||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Bot uses selfbot variant (user account) rather than standard bot token — check Discord ToS
|
- Bot uses selfbot variant (user account) rather than standard bot token — check Discord ToS
|
||||||
- Opus decoding disabled on Bun without native opus to avoid crashes
|
- Opus decoding requires native `@discordjs/opus` under Node.js
|
||||||
- OGG segments include metadata JSON for each segment (user info, timestamps, duration)
|
- OGG segments include metadata JSON for each segment (user info, timestamps, duration)
|
||||||
- WebSocket broadcasts PCM in real-time; browser can transmit audio back to Discord
|
- WebSocket broadcasts PCM in real-time; browser can transmit audio back to Discord
|
||||||
- Graceful shutdown ensures clean disconnection and resource cleanup
|
- Graceful shutdown ensures clean disconnection and resource cleanup
|
||||||
@@ -327,7 +327,7 @@ Handles SIGINT/SIGTERM/uncaughtException/unhandledRejection:
|
|||||||
|
|
||||||
- **Recorder subsystem** (`src/recorder/`):
|
- **Recorder subsystem** (`src/recorder/`):
|
||||||
- `audioStream.ts` — Subscribes to Discord audio receiver, emits Opus packets
|
- `audioStream.ts` — Subscribes to Discord audio receiver, emits Opus packets
|
||||||
- `decoder.ts` — Opus decoder with runtime checks (Bun vs Node), cooldown/rotation logic for web PCM broadcast
|
- `decoder.ts` — Opus decoder with runtime checks, cooldown/rotation logic for web PCM broadcast
|
||||||
- `segment.ts` — Manages OGG file rotation (5s default segments per user)
|
- `segment.ts` — Manages OGG file rotation (5s default segments per user)
|
||||||
- `metadata.ts` — Collects user/role info, creates segment metadata JSON
|
- `metadata.ts` — Collects user/role info, creates segment metadata JSON
|
||||||
|
|
||||||
@@ -363,28 +363,28 @@ Each segment is 5s (configurable). Metadata JSON includes user info, roles, time
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
bun install
|
pnpm install
|
||||||
|
|
||||||
# Development (auto-restart on file changes)
|
# Development (auto-restart on file changes)
|
||||||
bun run dev
|
pnpm run dev
|
||||||
|
|
||||||
# Production
|
# Production
|
||||||
bun run start
|
pnpm run start
|
||||||
|
|
||||||
# Type checking
|
# Type checking
|
||||||
bun run typecheck
|
pnpm run typecheck
|
||||||
|
|
||||||
# Linting (Biome)
|
# Linting (Biome)
|
||||||
bun run lint
|
pnpm run lint
|
||||||
|
|
||||||
# Format code (Biome)
|
# Format code (Biome)
|
||||||
bun run format
|
pnpm run format
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
bun run test
|
pnpm run test
|
||||||
|
|
||||||
# Build TypeScript
|
# Build TypeScript
|
||||||
bun run build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -404,9 +404,9 @@ All config via `.env` (see `.env.example`). Key variables:
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Tests use **Vitest** in `tests/` directory. Run with `bun run test`.
|
Tests use **Vitest** in `tests/` directory. Run with `pnpm run test`.
|
||||||
|
|
||||||
Example: `tests/decoder.test.ts` tests Opus decoder runtime detection (Bun vs Node, native opus availability).
|
Example: `tests/decoder.test.ts` tests Opus decoder runtime detection and native opus availability.
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
@@ -507,7 +507,7 @@ These will likely require:
|
|||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Bot uses selfbot variant (user account) rather than standard bot token — check Discord ToS
|
- Bot uses selfbot variant (user account) rather than standard bot token — check Discord ToS
|
||||||
- Opus decoding disabled on Bun without native opus to avoid crashes
|
- Opus decoding requires native `@discordjs/opus` under Node.js
|
||||||
- OGG segments include metadata JSON for each segment (user info, timestamps, duration)
|
- OGG segments include metadata JSON for each segment (user info, timestamps, duration)
|
||||||
- WebSocket broadcasts PCM in real-time; browser can transmit audio back to Discord
|
- WebSocket broadcasts PCM in real-time; browser can transmit audio back to Discord
|
||||||
- Graceful shutdown ensures clean disconnection and resource cleanup
|
- Graceful shutdown ensures clean disconnection and resource cleanup
|
||||||
|
|||||||
+5
-3
@@ -3,9 +3,10 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Discord bot that joins a voice channel and records audio",
|
"description": "Discord bot that joins a voice channel and records audio",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
"packageManager": "pnpm@10.25.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun --watch src/index.ts",
|
"dev": "tsx watch src/index.ts",
|
||||||
"start": "bun src/index.ts",
|
"start": "tsx src/index.ts",
|
||||||
"build": "tsc --outDir dist",
|
"build": "tsc --outDir dist",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "biome check --diagnostic-level=error .",
|
"lint": "biome check --diagnostic-level=error .",
|
||||||
@@ -39,9 +40,10 @@
|
|||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/express": "^5.0.6",
|
"@types/express": "^5.0.6",
|
||||||
"@types/fluent-ffmpeg": "^2.1.28",
|
"@types/fluent-ffmpeg": "^2.1.28",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"bun-types": "latest",
|
|
||||||
"pino-pretty": "^10.3.1",
|
"pino-pretty": "^10.3.1",
|
||||||
|
"tsx": "^4.20.6",
|
||||||
"vitest": "latest"
|
"vitest": "latest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+3626
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- '@discordjs/opus'
|
||||||
|
- better-sqlite3
|
||||||
|
- esbuild
|
||||||
+11
-1
@@ -15,10 +15,15 @@ import { getDatabase } from "./muxer-queue";
|
|||||||
const logger = createChildLogger("bot");
|
const logger = createChildLogger("bot");
|
||||||
|
|
||||||
const token = config.DISCORD_TOKEN;
|
const token = config.DISCORD_TOKEN;
|
||||||
|
logger.info({ hasToken: token.length > 0, tokenLength: token.length }, "Config loaded");
|
||||||
|
|
||||||
|
logger.info("Creating Discord client");
|
||||||
const client = new Client();
|
const client = new Client();
|
||||||
const voiceController = new VoiceController(client);
|
const voiceController = new VoiceController(client);
|
||||||
|
|
||||||
|
logger.info("Opening database");
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
logger.info("Database ready");
|
||||||
|
|
||||||
let isShuttingDown = false;
|
let isShuttingDown = false;
|
||||||
|
|
||||||
@@ -84,5 +89,10 @@ process.on("unhandledRejection", (reason, promise) => {
|
|||||||
gracefulShutdown("unhandledRejection");
|
gracefulShutdown("unhandledRejection");
|
||||||
});
|
});
|
||||||
|
|
||||||
client.login(token);
|
logger.info("Calling Discord client.login");
|
||||||
|
client.login(token).then(() => {
|
||||||
|
logger.info("Discord client.login resolved");
|
||||||
|
}).catch((error) => {
|
||||||
|
logger.error({ error }, "Discord client.login failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ const isDev = process.env.NODE_ENV !== "production";
|
|||||||
|
|
||||||
export const logger = pino({
|
export const logger = pino({
|
||||||
level: process.env.LOG_LEVEL || (isDev ? "debug" : "info"),
|
level: process.env.LOG_LEVEL || (isDev ? "debug" : "info"),
|
||||||
|
serializers: {
|
||||||
|
error: pino.stdSerializers.err,
|
||||||
|
err: pino.stdSerializers.err,
|
||||||
|
reason: (value) => value instanceof Error ? pino.stdSerializers.err(value) : value,
|
||||||
|
},
|
||||||
transport: isDev
|
transport: isDev
|
||||||
? {
|
? {
|
||||||
target: "pino-pretty",
|
target: "pino-pretty",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { Database } from "bun:sqlite";
|
import Database from "better-sqlite3";
|
||||||
import { createChildLogger } from "./logger";
|
import { createChildLogger } from "./logger";
|
||||||
|
|
||||||
const logger = createChildLogger("muxer-queue");
|
const logger = createChildLogger("muxer-queue");
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
StreamType,
|
StreamType,
|
||||||
VoiceConnection,
|
VoiceConnection,
|
||||||
} from "@discordjs/voice";
|
} from "@discordjs/voice";
|
||||||
import prism from "prism-media";
|
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
|
|
||||||
export class DiscordPlayer {
|
export class DiscordPlayer {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
import prism from "prism-media";
|
import * as prism from "prism-media";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import prism from "prism-media";
|
import * as prism from "prism-media";
|
||||||
import type { SegmentState } from "../types";
|
import type { SegmentState } from "../types";
|
||||||
|
|
||||||
export function buildSegmentPaths(
|
export function buildSegmentPaths(
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import helmet from "helmet";
|
|||||||
import http from "http";
|
import http from "http";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import pinoHttp from "pino-http";
|
import pinoHttp from "pino-http";
|
||||||
import prism from "prism-media";
|
import * as prism from "prism-media";
|
||||||
import { WebSocketServer } from "ws";
|
import { WebSocketServer } from "ws";
|
||||||
import { AppError } from "./errors";
|
import { AppError } from "./errors";
|
||||||
import { createChildLogger, logger } from "./logger";
|
import { createChildLogger, logger } from "./logger";
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": ["bun-types"],
|
"types": ["node"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user