Televerseteleverse.

Custom Context

Extend your bot with type-safe, domain-specific context types.

Custom contexts allow you to extend the base Context class with your own properties and methods. This enables you to create domain-specific functionality, inject dependencies, and maintain type safety throughout your bot.

Why Use Custom Context?

Custom contexts provide several key benefits:

  • Type Safety: Leverage Dart's type system to catch errors at compile time
  • Dependency Injection: Pass services like databases, loggers, or configuration to your handlers
  • Code Organization: Keep related logic together in context methods
  • Reusability: Use mixins to share functionality across different bot implementations
  • Domain-Specific Logic: Add methods that make sense for your specific use case

Basic Custom Context

To create a custom context, extend the Context class and add your own methods and properties.

Defining Your Context

Your custom context must call the super constructor with update, api, and me parameters.

my_context.dart
import 'package:televerse/televerse.dart';

// Define your custom context
class MyContext extends Context {
  MyContext(super.update, super.api, super.me);

  // Add custom methods
  void doSomething() {
    print('Doing something custom!');
    print('Hello from MyContext! 👋');
  }

  // Add custom getters
  String get userName => from?.firstName ?? 'Guest';
  bool get isAdmin => from?.id == 123456789;
}

Using Your Custom Context

Pass your context constructor to the Bot via the contextFactory parameter. The generic type parameter tells the bot what type of context to expect.

main.dart
void main() async {
  // Create bot with custom context type
  final bot = Bot<MyContext>(
    'YOUR_BOT_TOKEN',
    contextFactory: MyContext.new,
  );

  // Handlers receive MyContext instances
  bot.command('start', (ctx) async {
    // ctx is MyContext, not just Context
    await ctx.reply('Hello, ${ctx.userName}!');
    
    // Call custom methods
    ctx.doSomething();
  });

  await bot.start();
}

Type Safety: Notice how ctx is typed as MyContext in the handler. This means you get full IDE autocomplete and compile-time type checking for all your custom methods!

Context with Dependencies

For more complex bots, you'll want to inject services like databases, loggers, or configuration into your context.

Defining Context with Dependencies

my_context.dart
class MyContext extends Context {
  final DatabaseService db;
  final Logger logger;
  
  MyContext(
    super.update,
    super.api,
    super.me,
    this.db,
    this.logger,
  );

  // Helper method using injected dependencies
  Future<User?> getUserFromDb() async {
    if (from == null) return null;
    return await db.getUser(from!.id);
  }
}

Creating a Factory Function

Since your context now has additional parameters, you need to create a factory function that captures these dependencies and returns a function matching the ContextFactory signature.

main.dart
void main() async {
  // Set up dependencies
  final db = DatabaseService();
  final logger = Logger();

  // Create factory function that captures dependencies
  MyContext createContext(Update update, RawAPI api, BotInfo me) {
    return MyContext(update, api, me, db, logger);
  }

  // Pass factory to bot
  final bot = Bot<MyContext>(
    'YOUR_BOT_TOKEN',
    contextFactory: createContext,
  );

  bot.command('profile', (ctx) async {
    // Access injected dependencies
    ctx.logger.info('Profile command called');
    
    final user = await ctx.getUserFromDb();
    if (user != null) {
      await ctx.reply('Your profile: ${user.name}');
    }
  });

  await bot.start();
}

ContextFactory Type: The contextFactory parameter expects a function with this signature:CTX Function(Update update, RawAPI api, BotInfo me)

Using Mixins for Modularity

Dart's mixin system allows you to compose functionality from multiple sources. This is perfect for adding reusable features to your context.

Defining Mixins

Create mixins for common functionality like internationalization, logging, or session management.

mixins.dart
// Define a mixin for internationalization
mixin I18nMixin {
  String translate(String key, {String locale = 'en'}) {
    final translations = {
      'en': {
        'hello': 'Hello',
        'goodbye': 'Goodbye',
      },
      'es': {
        'hello': 'Hola',
        'goodbye': 'Adiós',
      },
    };
    
    return translations[locale]?[key] ?? key;
  }
}

// Define a mixin for logging
mixin LoggingMixin {
  void logCommand(String command) {
    print('[LOG] Command executed: $command');
  }
}

Applying Mixins to Context

Use the with keyword to apply mixins to your custom context.

my_context.dart
// Use mixins with custom context
class MyContext extends Context with I18nMixin, LoggingMixin {
  MyContext(super.update, super.api, super.me);
  
  String get userLocale {
    // Get user's language from Telegram
    return from?.languageCode ?? 'en';
  }
}

Using Mixin Methods

main.dart
void main() async {
  final bot = Bot<MyContext>(
    'YOUR_BOT_TOKEN',
    contextFactory: MyContext.new,
  );

  bot.command('start', (ctx) async {
    // Use mixin methods
    ctx.logCommand('start');
    
    // Translate based on user's locale
    final greeting = ctx.translate('hello', locale: ctx.userLocale);
    await ctx.reply(greeting);
  });

  await bot.start();
}

Type Safety Benefits

The generic type system ensures that your context type is consistent throughout the middleware chain.

type_safety.dart
// This won't compile - type safety!
final bot = Bot<MyContext>(
  'TOKEN',
  // Error: Context is not MyContext
  contextFactory: Context.new, // ❌ Type error
);

// This works - correct type
final bot = Bot<MyContext>(
  'TOKEN',
  contextFactory: MyContext.new, // ✅ Correct
);

Advanced Patterns

Using Custom Context with Bot.fromAPI

You can also use custom contexts when creating a bot from an existing RawAPI instance.

main.dart
void main() async {
  // Create and configure API with transformers
  final api = RawAPI('YOUR_BOT_TOKEN');
  
  // Create bot from API with custom context
  final bot = Bot.fromAPI<MyContext>(
    api,
    contextFactory: MyContext.new,
  );

  await bot.start();
}

Best Practices

  • Keep contexts focused: Don't turn your context into a dumping ground for unrelated functionality.
  • Use mixins for reusability: Extract common patterns into mixins that can be shared across projects.
  • Inject dependencies properly: Use factory functions to inject services rather than accessing globals.
  • Leverage type safety: Let Dart's type system help you catch errors early.
  • Document your context: Add dartdoc comments to explain what each property and method does.
  • Consider async factories: If you need to fetch data during context creation, your factory can return Future<CTX>.

See Also

  • Context- Learn about the base Context class and its built-in properties.
  • Bot Class- Understand how the Bot class uses generic types.