TSCord

repository·main·Indexed 43 days ago

https://github.com/barthofu/tscord

A fully-featured Discord bot template and framework written in TypeScript. Built on discord.js v14 and discordx, it provides a structured environment with support for slash commands, context menus, buttons, modals, and event listeners. Key features include Mikro-ORM for database persistence, built-in guards (middleware), i18n localization, a local state store, and a scheduler for cron jobs. It includes a CLI for project scaffolding and an ecosystem featuring a dashboard, website, and plugin system.

Tokens
12.8K
Snippets
50
Records
60
Agent score
56%

What's inside TSCord

  1. Overview of TSCord features and architecture

    main

    TSCord is a TypeScript-based Discord bot template built on top of discordx and discord.js v14. It is designed to provide a structured framework for building extensible bots.

    Core Capabilities

    • Advanced Handlers: Support for slash commands, context menus, buttons, modals, select menus, message commands, and Discord event listeners.
    • Guards (Middleware): Functions that act as middleware for handlers. Built-in guards include:
      • Rate limiter
      • Maintenance mode
      • Disabling commands
      • Guild only (no DMs)
      • NSFW only
      • Regex-based message content matching
    • Database & Persistence: Multiple database support via Mikro-ORM with a built-in migrations system.
    • State & Logic: Local store for global state management, a scheduler for cron jobs, and custom event handlers.
    • Observability: Advanced logging (with file and Discord channel support), built-in statistics, and an internal API for external service interaction.
    • Internationalization: Fully-typed localization (i18n).

    Developer Experience

    • Patterns: Built around Dependency Injection and Singleton patterns.
    • Development Tools: Hot Module Replacement (HMR) for events and commands, VSCode debugging setup, and strict ESLint configuration.
    • Deployment: Support for Docker, PM2, and GitHub Actions CI/CD.
  2. Explore the TSCord ecosystem

    main

    TSCord extends beyond the core bot template through several specialized packages:

    • Dashboard: A ready-to-use, fancy dashboard for managing your bot.
    • Website: A customizable static homepage for your bot.
    • CLI: A command-line interface for initializing projects, generating files, and managing plugins.
    • Plugins: An extensible ecosystem of plugins to add functionality to your bot.
  3. Initialize a new TSCord bot project

    main

    You can quickly scaffold a new Discord bot project using the TSCord CLI. This command initializes a new bot template with boilerplate features included.

    Run the following command in your terminal:

    npx tscord init bot my-bot

    Replace my-bot with your desired project name.

  4. Use the Stats service to track bot metrics

    main

    The Stats service provides methods to record and retrieve various bot metrics, including user interactions, command usage, and system health. It is designed to be used within a tscord application to build dashboards or monitoring tools.

    Core Functionalities:

    • Interaction Tracking: Record Discord interactions (slash commands, context menus, etc.) and simple command messages.
    • Metric Retrieval: Fetch total stats (users, guilds, commands), top commands, top guilds, and user activity distributions.
    • Time-series Data: Retrieve and cumulate stats over a specific number of days.
    • System Monitoring: Monitor process usage (CPU/RAM), host health, and Discord websocket latency.
  5. Hot reload commands and events

    main

    In development mode, the application supports hot reloading to apply changes to commands and events without restarting the entire process. The reload(client) function performs these steps:

    1. State Preservation: Identifies instances marked with @keptInstances and stores them.
    2. Cleanup: Removes existing events from the client, clears MetadataStorage, and clears all services in DIService.engine.
    3. Dependency Re-registration: Re-registers the kept instances and the Client instance into the tsyringe container.
    4. File Re-import: Re-loads files matching the importPattern ({events,commands}/**/*.{ts,js}).
    5. Re-build: Rebuilds MetadataStorage, re-initializes application commands, and re-initializes events.
    6. Service Re-initialization: Re-loads plugins and re-initializes the Database service.
    async function reload(client: Client) {
    	const store = await resolveDependency(Store)
    	store.set('botHasBeenReloaded', true)
    
    	const logger = await resolveDependency(Logger)
    	logger.startSpinner('Hot reloading...')
    
    	client.removeEvents()
    
    	const instancesToKeep: Map<constructor<any>, any> = new Map()
    	for (const target of keptInstances) {
    		const instance = await resolveDependency(target)
    		instancesToKeep.set(target, instance)
    	}
    
    	MetadataStorage.clear()
    	DIService.engine.clearAllServices()
    
    	for (const [target, instance] of instancesToKeep) {
    		container.registerInstance(target, instance)
    	}
    
    	container.registerInstance(Client, client)
    
    	await loadFiles(importPattern)
    
    	await MetadataStorage.instance.build()
    	await client.initApplicationCommands()
    	client.initEvents()
    
    	const pluginManager = await resolveDependency(PluginsManager)
    	await pluginManager.loadPlugins()
    
    	const db = await resolveDependency(Database)
    	await db.initialize()
    
    	logger.log(chalk.whiteBright('Hot reloaded\n'))
    }
  6. Initialize the TSCord application

    main

    The application is initialized via the init() function. This process performs the following sequence:

    1. Environment Validation: Checks required environment variables via checkEnvironmentVariables().
    2. Error Handling: Initializes the ErrorHandler service.
    3. Plugin Management: Loads plugins, syncs translations, imports commands/events, and initializes plugin services and main files.
    4. Database Initialization: Initializes the Database service.
    5. Client Setup: Configures the DIService engine using tsyringeDependencyRegistryEngine and instantiates the Client with clientConfig().
    6. File Loading: Imports all commands and events located in the {events,commands} directories.
    7. Data Table & Context: Initializes the data table and wraps the execution in a MikroORM RequestContext using the database's Entity Manager (db.orm.em).
    8. Authentication: Logs in to Discord using env.BOT_TOKEN.
    9. Post-Login Tasks:
      • Hot Reloading: In development mode, starts a chokidar watcher to trigger reload(client) on file changes.
      • API Server: Starts the Server service if apiConfig.enabled is true.
      • Image Sync: Syncs images with Imgur if env.IMGUR_CLIENT_ID and generalConfig.automaticUploadImagesToImgur are configured.
      • Ready Event: Monitors the Store for a ready state to emit the templateReady event via EventManager once all system properties are initialized.
    async function init() {
      // ... initialization logic ...
      client.login(env.BOT_TOKEN)
        .then(async () => {
          // ... post-login setup ...
        })
        .catch((err) => {
          console.error(err);
          process.exit(1);
        });
    }
  7. Deploy the application using Docker Compose

    main

    The docker-compose.yml file provides a template for running the TSCord application in a containerized environment. The primary app service builds from the local context using the Dockerfile located at ./.docker/app/Dockerfile and loads configuration from a .env file.

    Application Service Configuration

    • Container Name: my_bot
    • Ports: Maps host port 4000 to container port 4000.
    • Volumes:
      • ./database:/app/database: Persists database files.
      • ./logs/:/app/logs: Persists application logs.
      • ./assets:/app/assets: Persists application assets.
    • Restart Policy: unless-stopped
    services:
      app:
        container_name: my_bot
        build:
          context: ./
          dockerfile: ./.docker/app/Dockerfile
        env_file:
          - .env
        ports:
          - 4000:4000
        volumes:
            - ./database:/app/database
            - ./logs/:/app/logs
            - ./assets:/app/assets
        restart: unless-stopped
  8. Configure import sorting with simple-import-sort

    main

    The project uses eslint-plugin-simple-import-sort for organized import statements. This is preferred over the standard import/order or sort-imports rules.

    To use it, register the plugin and configure the simple-import-sort/imports rule with specific groups. The default grouping used in this project is:

    1. Side effect imports (^\u0000)
    2. Node.js builtins (^node:)
    3. External packages (^@?\w)
    4. Internal packages (^@/\w)
    5. Other imports (^\w)
    6. TypeScript import assignments (^\u0001, ^\u0002)

    Note: When using this, you should disable sort-imports and import/order to avoid conflicts.

    {
      plugins: {
        'simple-import-sort': pluginSimpleImportSort,
      },
      rules: {
        'sort-imports': 'off',
        'import/order': 'off',
        'simple-import-sort/imports': [
          'error',
          {
            groups: [
              ['^\u0000'],
              ['^node:'],
              ['^@?\w'],
              ['^@/\w'],
              ['^\w'],
              ['^\u0001', '^\u0002'],
            ],
          },
        ],
      },
    }
  9. Configure MikroORM in TSCord

    main

    TSCord uses MikroORM for database management. The configuration is defined in mikro-orm.config.ts and is environment-aware, utilizing process.env.NODE_ENV to select the appropriate database settings.

    Key configuration details:

    • Environment Selection: It defaults to 'development' if NODE_ENV is not set.
    • Entity Discovery: Entities are automatically discovered from two locations:
      • src/entities/*.{ts,js}
      • src/plugins/*/entities/*.{ts,js} (this allows plugins to register their own entities).
    • Base Configuration: The core database settings (like connection strings and driver details) are imported from ./src/configs/database based on the current environment.
    // Example of how the configuration is structured internally
    export default async () => {
    	return defineConfig({
    		...mikroORMConfig[process.env.NODE_ENV || 'development'] as Options<DatabaseDriver>,
    		entities: [`src/entities/*.{ts,js}`, `src/plugins/*/entities/*.{ts,js}`],
    	})
    }
  10. Configure ESLint with @antfu/eslint-config

    main

    The project uses @antfu/eslint-config as its primary linting engine. You can customize the linting behavior by passing an options object to the antfu function.

    Key configuration areas include:

    • stylistic: Controls code style preferences like indentation, quotes, and semicolons.
    • jsonc & yaml: Boolean flags to enable/disable linting for JSONC and YAML files.
    • linterOptions: Configuration for the ESLint engine itself (e.g., reportUnusedDisableDirectives).
    • ignores: An array of directory patterns to exclude from linting.
    • plugins: Custom ESLint plugins to register.
    • rules: Specific rule overrides for TypeScript (ts/), imports, and other categories.
    const antfu = require('@antfu/eslint-config').default
    
    module.exports = antfu(
      {
        stylistic: {
          indent: 'tab',
          quotes: 'single',
          semi: false,
        },
        jsonc: false,
        yaml: false,
        linterOptions: {
          reportUnusedDisableDirectives: false,
        },
        ignores: ['node_modules', 'build', 'logs', 'database'],
      },
      {
        // Additional plugin and rule configurations
      }
    )
  11. Configure MikroORM environments

    main

    The project uses a Config structure to manage different MikroORM Options for production and development environments.

    By default, the project is configured to use SQLite in production. The configuration includes settings for:

    • driver: The database driver (e.g., SqliteDriver).
    • dbName: The filename/path of the database.
    • highlighter: An instance of SqlHighlighter for SQL formatting.
    • migrations: Configuration for migration files, including path, emit type (e.g., 'js'), and snapshot status.
    • extensions: An array of MikroORM extensions, such as Migrator and EntityGenerator.

    If no development configuration is explicitly provided, the system defaults to using the production configuration for development as well.

    export const mikroORMConfig = {
    	production: {
    		driver: SqliteDriver,
    		dbName: `./database/db.sqlite`,
    		highlighter: new SqlHighlighter(),
    		debug: false,
    		migrations: {
    			path: './database/migrations',
    			emit: 'js',
    			snapshot: true,
    		},
    		extensions: [Migrator, EntityGenerator],
    	},
    	development: { /* ... */ }
    };
  12. Configure PM2 for TSCord deployment

    main

    When deploying a TSCord application using PM2, you can use a pm2.config.json file to define the application process. The configuration should specify the application name, the entry script (typically npm), and the arguments required to build and start the bot.

    {
        "apps": [
            {
                "name": "My Discord Bot",
                "script": "npm",
                "args" : "run build:start"
            }
        ]
    }