< Summary

Information
Class: TeleFlow.Telegram.Internal.Handlers.TelegramFilterEvaluator
Assembly: TeleFlow.Framework
File(s): /_/src/TeleFlow.Framework/Internal/Handlers/TelegramFilterEvaluator.cs
Line coverage
97%
Covered lines: 124
Uncovered lines: 3
Coverable lines: 127
Total lines: 262
Line coverage: 97.6%
Branch coverage
91%
Covered branches: 109
Total branches: 119
Branch coverage: 91.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
MatchesAsync()100%1212100%
MatchesBuiltInFilter(...)93.61%4747100%
MatchesMessageBuiltInFilter(...)82.14%282895.23%
MatchesRoleRequirementsAsync()83.33%121286.66%
ResolveStatusAsync()100%66100%
MatchesOptionalId(...)100%22100%
ContainsString(...)100%44100%
ContainsLong(...)100%44100%
StartsWithAnyPrefix(...)100%44100%
ResolveUncachedStatusAsync(...)100%11100%

File(s)

/_/src/TeleFlow.Framework/Internal/Handlers/TelegramFilterEvaluator.cs

#LineLine coverage
 1using Microsoft.Extensions.DependencyInjection;
 2using TeleFlow.Annotations;
 3using TeleFlow.Telegram.Internal;
 4using TeleFlow.Telegram.Schema.Types;
 5
 6namespace TeleFlow.Telegram.Internal.Handlers;
 7
 8internal static class TelegramFilterEvaluator
 9{
 10    public static async ValueTask<bool> MatchesAsync(
 11        TelegramUpdateContext context,
 12        TelegramRouteFilterPlan filterPlan,
 13        CancellationToken cancellationToken)
 14    {
 55115        ArgumentNullException.ThrowIfNull(filterPlan);
 16
 55117        var builtInFilters = filterPlan.BuiltInFilters;
 55118        var facts = builtInFilters.Count > 0
 55119            ? TelegramFilterContextFacts.From(context)
 55120            : default;
 21
 135422        for (var index = 0; index < builtInFilters.Count; index++)
 23        {
 33024            var filter = builtInFilters[index];
 25
 33026            if (!MatchesBuiltInFilter(context, facts, filter))
 27            {
 20428                return false;
 29            }
 30        }
 31
 34732        var customFilters = filterPlan.CustomFilters;
 33
 70834        for (var index = 0; index < customFilters.Count; index++)
 35        {
 2936            var customFilter = customFilters[index];
 37
 2938            if (!await customFilter.MatchesAsync(context, cancellationToken).ConfigureAwait(false))
 39            {
 2040                return false;
 41            }
 42        }
 43
 32544        if (!await MatchesRoleRequirementsAsync(context, filterPlan.RoleRequirements, cancellationToken).ConfigureAwait(
 45        {
 346            return false;
 47        }
 48
 32249        return true;
 54950    }
 51
 52    private static bool MatchesBuiltInFilter(
 53        TelegramUpdateContext context,
 54        TelegramFilterContextFacts facts,
 55        TelegramFilterDescriptor filter)
 56    {
 33057        return filter.Kind switch
 33058        {
 3859            TelegramFilterKind.ChatType => facts.DestinationChat is not null &&
 3860                                           ContainsString(
 3861                                               filter.StringValues,
 3862                                               facts.DestinationChat.Type,
 3863                                               StringComparison.Ordinal),
 2064            TelegramFilterKind.ChatId => facts.DestinationChat is not null &&
 2065                                         ContainsLong(filter.LongValues, facts.DestinationChat.Id),
 1666            TelegramFilterKind.ChatUsername => facts.DestinationChat?.Username is { } username &&
 1667                                               ContainsString(
 1668                                                   filter.StringValues,
 1669                                                   username,
 1670                                                   StringComparison.OrdinalIgnoreCase),
 2071            TelegramFilterKind.FromUser => facts.SenderUser is { IsBot: false } user &&
 2072                                           MatchesOptionalId(filter.LongValues, user.Id),
 1173            TelegramFilterKind.FromBot => facts.SenderUser is { IsBot: true } bot &&
 1174                                          MatchesOptionalId(filter.LongValues, bot.Id),
 375            TelegramFilterKind.FromPremiumUser => facts.SenderUser?.IsPremium == true,
 776            TelegramFilterKind.SenderChatType => facts.SenderChat is not null &&
 777                                                 ContainsString(
 778                                                     filter.StringValues,
 779                                                     facts.SenderChat.Type,
 780                                                     StringComparison.Ordinal),
 881            TelegramFilterKind.MessageThreadId => facts.MessageThreadId is { } messageThreadId &&
 882                                                  ContainsLong(filter.LongValues, messageThreadId),
 1283            TelegramFilterKind.HasMessageThread => facts.MessageThreadId is not null,
 1384            TelegramFilterKind.HasCallbackData => context is CallbackQueryContext callbackContext &&
 1385                                                  !string.IsNullOrWhiteSpace(callbackContext.TelegramCallbackQuery.Data)
 4586            TelegramFilterKind.CallbackDataPrefix => context is CallbackQueryContext callbackContext &&
 4587                                                     callbackContext.TelegramCallbackQuery.Data is { } data &&
 4588                                                     StartsWithAnyPrefix(data, filter.StringValues),
 13789            _ => context is MessageContext messageContext &&
 13790                 MatchesMessageBuiltInFilter(messageContext.TelegramMessage, filter)
 33091        };
 92    }
 93
 94    private static bool MatchesMessageBuiltInFilter(
 95        Message message,
 96        TelegramFilterDescriptor filter)
 97    {
 13798        return filter.Kind switch
 13799        {
 21100            TelegramFilterKind.HasText => !string.IsNullOrWhiteSpace(message.Text),
 3101            TelegramFilterKind.HasPhoto => message.Photo is { Count: > 0 },
 2102            TelegramFilterKind.HasDocument => message.Document is not null,
 29103            TelegramFilterKind.HasCaption => !string.IsNullOrWhiteSpace(message.Caption),
 12104            TelegramFilterKind.HasVideo => message.Video is not null,
 11105            TelegramFilterKind.HasAnimation => message.Animation is not null,
 10106            TelegramFilterKind.HasAudio => message.Audio is not null,
 9107            TelegramFilterKind.HasVoice => message.Voice is not null,
 8108            TelegramFilterKind.HasVideoNote => message.VideoNote is not null,
 7109            TelegramFilterKind.HasSticker => message.Sticker is not null,
 6110            TelegramFilterKind.HasContact => message.Contact is not null,
 5111            TelegramFilterKind.HasLocation => message.Location is not null,
 4112            TelegramFilterKind.HasVenue => message.Venue is not null,
 3113            TelegramFilterKind.HasPoll => message.Poll is not null,
 2114            TelegramFilterKind.HasDice => message.Dice is not null,
 2115            TelegramFilterKind.IsReply => message.ReplyToMessage is not null,
 3116            TelegramFilterKind.ReplyToBot => message.ReplyToMessage?.From?.IsBot == true,
 0117            _ => false
 137118        };
 119    }
 120
 121    private static async ValueTask<bool> MatchesRoleRequirementsAsync(
 122        TelegramUpdateContext context,
 123        IReadOnlyList<TelegramRoleRequirementDescriptor> roleRequirements,
 124        CancellationToken cancellationToken)
 125    {
 325126        if (roleRequirements.Count == 0)
 127        {
 312128            return true;
 129        }
 130
 13131        if (!TelegramRoleFilterIdentityResolver.TryResolve(context, out var identity))
 132        {
 1133            return false;
 134        }
 135
 12136        var options = context.Services.GetRequiredService<TelegramRoleFilterOptions>();
 12137        var status = await ResolveStatusAsync(context, identity, options, cancellationToken).ConfigureAwait(false);
 138
 12139        if (status is null)
 140        {
 0141            return false;
 142        }
 143
 12144        if (!TelegramMemberStatusSetValidator.IsValid(status.Value))
 145        {
 0146            throw new InvalidOperationException("Telegram role resolver returned an invalid member status set.");
 147        }
 148
 50149        foreach (var requirement in roleRequirements)
 150        {
 14151            if ((status.Value & requirement.AllowedStatuses) == 0)
 152            {
 2153                return false;
 154            }
 155        }
 156
 10157        return true;
 325158    }
 159
 160    private static async ValueTask<TelegramMemberStatusSet?> ResolveStatusAsync(
 161        TelegramUpdateContext context,
 162        TelegramRoleFilterIdentity identity,
 163        TelegramRoleFilterOptions options,
 164        CancellationToken cancellationToken)
 165    {
 12166        if (options.CacheEnabled)
 167        {
 10168            var cache = context.Services.GetRequiredService<ITelegramChatMemberStatusCache>();
 10169            var cachedStatus = await cache.GetAsync(
 10170                identity.ChatId,
 10171                identity.UserId,
 10172                cancellationToken).ConfigureAwait(false);
 173
 10174            if (cachedStatus is not null)
 175            {
 1176                return cachedStatus;
 177            }
 178
 9179            var resolvedStatus = await ResolveUncachedStatusAsync(context, identity, cancellationToken).ConfigureAwait(f
 180
 9181            if (resolvedStatus is not null)
 182            {
 9183                await cache.SetAsync(
 9184                    identity.ChatId,
 9185                    identity.UserId,
 9186                    resolvedStatus.Value,
 9187                    options.CacheTtl,
 9188                    cancellationToken).ConfigureAwait(false);
 189            }
 190
 9191            return resolvedStatus;
 192        }
 193
 2194        return await ResolveUncachedStatusAsync(context, identity, cancellationToken).ConfigureAwait(false);
 12195    }
 196
 197    private static bool MatchesOptionalId(
 198        IReadOnlyList<long> ids,
 199        long id)
 200    {
 19201        return ids.Count == 0 || ContainsLong(ids, id);
 202    }
 203
 204    private static bool ContainsString(
 205        IReadOnlyList<string> values,
 206        string value,
 207        StringComparison comparison)
 208    {
 96209        for (var index = 0; index < values.Count; index++)
 210        {
 39211            if (string.Equals(values[index], value, comparison))
 212            {
 30213                return true;
 214            }
 215        }
 216
 9217        return false;
 218    }
 219
 220    private static bool ContainsLong(
 221        IReadOnlyList<long> values,
 222        long value)
 223    {
 70224        for (var index = 0; index < values.Count; index++)
 225        {
 27226            if (values[index] == value)
 227            {
 19228                return true;
 229            }
 230        }
 231
 8232        return false;
 233    }
 234
 235    private static bool StartsWithAnyPrefix(
 236        string value,
 237        IReadOnlyList<string> prefixes)
 238    {
 144239        for (var index = 0; index < prefixes.Count; index++)
 240        {
 44241            if (value.StartsWith(prefixes[index], StringComparison.Ordinal))
 242            {
 16243                return true;
 244            }
 245        }
 246
 28247        return false;
 248    }
 249
 250    private static ValueTask<TelegramMemberStatusSet?> ResolveUncachedStatusAsync(
 251        TelegramUpdateContext context,
 252        TelegramRoleFilterIdentity identity,
 253        CancellationToken cancellationToken)
 254    {
 11255        var resolver = context.Services.GetRequiredService<ITelegramChatMemberStatusResolver>();
 11256        return resolver.ResolveAsync(
 11257            context,
 11258            identity.ChatId,
 11259            identity.UserId,
 11260            cancellationToken);
 261    }
 262}