< Summary

Information
Class: TeleFlow.Telegram.TelegramServiceCollectionExtensions
Assembly: TeleFlow.Framework
File(s): /_/src/TeleFlow.Framework/TelegramServiceCollectionExtensions.cs
Line coverage
85%
Covered lines: 79
Uncovered lines: 13
Coverable lines: 92
Total lines: 227
Line coverage: 85.8%
Branch coverage
85%
Covered branches: 17
Total branches: 20
Branch coverage: 85%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/_/src/TeleFlow.Framework/TelegramServiceCollectionExtensions.cs

#LineLine coverage
 1using System.Reflection;
 2using Microsoft.Extensions.DependencyInjection;
 3using Microsoft.Extensions.DependencyInjection.Extensions;
 4using TeleFlow.Framework.Application;
 5using TeleFlow.Framework.Callbacks;
 6using TeleFlow.Framework.Dispatching;
 7using TeleFlow.Framework.States;
 8using TeleFlow.Framework.Updates;
 9using TeleFlow.Telegram.Internal;
 10using TeleFlow.Telegram.Internal.Handlers;
 11using TeleFlow.Telegram.Internal.Options;
 12
 13namespace TeleFlow.Telegram;
 14
 15/// <summary>
 16/// Registers Telegram bot framework services, handler metadata, and Telegram-specific extension points.
 17/// </summary>
 18public static class TelegramServiceCollectionExtensions
 19{
 20    private const string ReflectionAssemblyRegistrationObsoleteDiagnosticId = "TLF900";
 21
 22    private const string ReflectionAssemblyRegistrationObsoleteMessage =
 23        "Reflection-based assembly handler registration is deprecated and will be removed before 1.0. " +
 24        "Use AddTelegramHandlersFromAssembly with IWF.TeleFlow.Generators, or register handlers explicitly with AddTeleg
 25
 26    public static IServiceCollection AddTelegramBot(
 27        this IServiceCollection services,
 28        Action<TelegramBotOptions> configure)
 29    {
 48130        ArgumentNullException.ThrowIfNull(services);
 48131        ArgumentNullException.ThrowIfNull(configure);
 32
 48133        EnsureNotRegistered<TelegramBotOptions>(services, nameof(AddTelegramBot));
 34
 48135        var options = new TelegramBotOptions();
 48136        configure(options);
 48137        TelegramBotOptionsValidator.Validate(options);
 48038        var stateKeyOptions = TelegramStateKeyOptions.FromToken(options.Token);
 39
 48040        services.AddTelegramClient(clientOptions =>
 48041        {
 48042            clientOptions.Token = options.Token;
 48043            clientOptions.BotUsername = options.BotUsername;
 48044            clientOptions.BaseUrl = options.BaseUrl;
 48045            clientOptions.Environment = options.Environment;
 48046            clientOptions.Defaults = options.Defaults;
 48047            clientOptions.RetryAfter = options.RetryAfter;
 96048        });
 49
 47150        services.AddSingleton(options);
 47151        services.AddSingleton(stateKeyOptions);
 47152        services.AddSingleton<TelegramBotIdentity>();
 47153        services.AddUpdateContextAccessor();
 47154        services.TryAddSingleton(options.RoleFilter);
 47155        services.AddSingleton<TelegramContextFactory>();
 47156        services.TryAddScoped<ITelegramCurrentUpdateAccessor, TelegramCurrentUpdateAccessor>();
 47157        services.TryAddSingleton<IStateStorageKeyBuilder, DefaultStateStorageKeyBuilder>();
 47158        services.TryAddSingleton<IStateKeyFactory, TelegramStateKeyFactory>();
 47159        services.TryAddSingleton<ICallbackDataSerializer, JsonCallbackDataSerializer>();
 47160        services.TryAddSingleton<ITelegramChatMemberStatusResolver, TelegramChatMemberStatusResolver>();
 47161        services.TryAddSingleton<ITelegramChatMemberStatusCache, MemoryTelegramChatMemberStatusCache>();
 47162        services.TryAddEnumerable(ServiceDescriptor.Singleton<ITeleFlowRuntimeValidator, TelegramRuntimeDependencyValida
 63
 47164        return services;
 65    }
 66
 67    public static IServiceCollection AddTelegramChatMemberStatusResolver<TResolver>(
 68        this IServiceCollection services)
 69        where TResolver : class, ITelegramChatMemberStatusResolver
 70    {
 071        ArgumentNullException.ThrowIfNull(services);
 72
 073        services.RemoveAll<ITelegramChatMemberStatusResolver>();
 074        services.AddSingleton<ITelegramChatMemberStatusResolver, TResolver>();
 075        return services;
 76    }
 77
 78    public static IServiceCollection AddTelegramChatMemberStatusCache<TCache>(
 79        this IServiceCollection services)
 80        where TCache : class, ITelegramChatMemberStatusCache
 81    {
 082        ArgumentNullException.ThrowIfNull(services);
 83
 084        services.RemoveAll<ITelegramChatMemberStatusCache>();
 085        services.AddSingleton<ITelegramChatMemberStatusCache, TCache>();
 086        return services;
 87    }
 88
 89    public static IServiceCollection AddAutoCallbackAnswer(
 90        this IServiceCollection services,
 91        Action<TelegramAutoAnswerCallbackOptions>? configure = null)
 92    {
 693        ArgumentNullException.ThrowIfNull(services);
 94
 695        EnsureTelegramBotRegistered(services, nameof(AddAutoCallbackAnswer));
 96
 697        var options = new TelegramAutoAnswerCallbackOptions();
 698        configure?.Invoke(options);
 699        ValidateAutoAnswerCallbackOptions(options);
 100
 6101        services.RemoveAll<TelegramAutoAnswerCallbackOptions>();
 6102        services.AddSingleton(options);
 6103        return services;
 104    }
 105
 106    /// <summary>
 107    /// Registers all generated Telegram handler metadata from an assembly.
 108    /// This API requires build-time metadata from <c>IWF.TeleFlow.Generators</c> and never falls back to assembly scann
 109    /// </summary>
 110    public static IServiceCollection AddTelegramHandlersFromAssembly(
 111        this IServiceCollection services,
 112        Assembly assembly)
 113    {
 31114        ArgumentNullException.ThrowIfNull(services);
 31115        ArgumentNullException.ThrowIfNull(assembly);
 116
 31117        EnsureTelegramBotRegistered(services, nameof(AddTelegramHandlersFromAssembly));
 118
 31119        var generatedRegistrars = TelegramHandlerAssemblyScanner.GetGeneratedRegistrars(assembly);
 120
 31121        if (generatedRegistrars.Length > 0)
 122        {
 30123            TelegramHandlerServiceRegistrar.RegisterGeneratedHandlers(services, assembly, generatedRegistrars);
 29124            return services;
 125        }
 126
 1127        throw new InvalidOperationException(
 1128            $"Assembly '{assembly.FullName}' does not contain generated Telegram handler metadata. " +
 1129            $"Use {nameof(AddTelegramHandler)}<THandler>() or {nameof(AddTelegramModule)}<TModule>() for explicit direct
 130    }
 131
 132    /// <summary>
 133    /// Registers all Telegram handler types discovered by scanning an assembly at startup.
 134    /// This API is a deprecated migration path and should not be used by new applications.
 135    /// </summary>
 136    [Obsolete(
 137        ReflectionAssemblyRegistrationObsoleteMessage,
 138        DiagnosticId = ReflectionAssemblyRegistrationObsoleteDiagnosticId)]
 139    public static IServiceCollection AddTelegramHandlersFromAssemblyReflection(
 140        this IServiceCollection services,
 141        Assembly assembly)
 142    {
 13143        ArgumentNullException.ThrowIfNull(services);
 13144        ArgumentNullException.ThrowIfNull(assembly);
 145
 13146        EnsureTelegramBotRegistered(services, nameof(AddTelegramHandlersFromAssemblyReflection));
 13147        TelegramHandlerRegistrationValidator.EnsureAssemblyCanRegister(
 13148            services,
 13149            assembly,
 13150            TelegramHandlerRegistrationMode.Reflection);
 151
 12152        var handlerTypes = TelegramHandlerAssemblyScanner.GetHandlerTypes(assembly);
 153
 11154        TelegramHandlerServiceRegistrar.RegisterReflectionHandlers(services, assembly, handlerTypes);
 10155        return services;
 156    }
 157
 158    /// <summary>
 159    /// Registers one explicitly named handler type by building its metadata at startup without scanning its assembly.
 160    /// </summary>
 161    public static IServiceCollection AddTelegramHandler<THandler>(this IServiceCollection services)
 162        where THandler : class
 163    {
 334164        ArgumentNullException.ThrowIfNull(services);
 165
 334166        EnsureTelegramBotRegistered(services, nameof(AddTelegramHandler));
 334167        TelegramHandlerServiceRegistrar.RegisterHandlerTypes(services, [typeof(THandler)]);
 168
 307169        return services;
 170    }
 171
 172    /// <summary>
 173    /// Registers one explicitly named Telegram module type. Generated metadata is used when available; otherwise
 174    /// metadata is built at startup for the named module type only.
 175    /// </summary>
 176    public static IServiceCollection AddTelegramModule<TModule>(this IServiceCollection services)
 177        where TModule : class
 178    {
 8179        ArgumentNullException.ThrowIfNull(services);
 180
 8181        EnsureTelegramBotRegistered(services, nameof(AddTelegramModule));
 182
 8183        var moduleType = typeof(TModule);
 8184        var generatedRegistrars = TelegramHandlerAssemblyScanner.GetGeneratedRegistrars(moduleType.Assembly);
 185
 8186        if (generatedRegistrars.Length > 0 &&
 8187            TelegramHandlerServiceRegistrar.TryRegisterGeneratedHandlerType(services, moduleType, generatedRegistrars))
 188        {
 2189            return services;
 190        }
 191
 5192        TelegramHandlerServiceRegistrar.RegisterModuleType(services, moduleType);
 193
 4194        return services;
 195    }
 196
 197    private static void EnsureTelegramBotRegistered(IServiceCollection services, string apiName)
 198    {
 4836199        if (services.All(static descriptor => descriptor.ServiceType != typeof(TelegramBotOptions)))
 200        {
 0201            throw new InvalidOperationException(
 0202                $"{nameof(AddTelegramBot)} must be called before {apiName}.");
 203        }
 392204    }
 205
 206    private static void EnsureNotRegistered<TService>(IServiceCollection services, string apiName)
 207    {
 1741208        if (services.Any(descriptor => descriptor.ServiceType == typeof(TService)))
 209        {
 0210            throw new InvalidOperationException(
 0211                $"{apiName} cannot be called more than once for service '{typeof(TService).Name}'.");
 212        }
 481213    }
 214
 215    private static void ValidateAutoAnswerCallbackOptions(TelegramAutoAnswerCallbackOptions options)
 216    {
 6217        ArgumentNullException.ThrowIfNull(options);
 218
 6219        if (options.Text is not null && string.IsNullOrWhiteSpace(options.Text))
 220        {
 0221            throw new InvalidOperationException("Auto callback answer text must not be empty.");
 222        }
 223
 6224        options.Text = options.Text?.Trim();
 6225    }
 226
 227}