* Fixed Result behaviour
* Added AsWClient * Updated examples
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Telegrator.WideHostBuilderExtensions.AddTelegratorInternal(Microsoft.Extensions.DependencyInjection.IServiceCollection,Microsoft.Extensions.Configuration.IConfiguration,System.Collections.Generic.IDictionary{System.Object,System.Object},Telegrator.Core.IHandlersCollection@,Telegrator.TelegratorOptions)">
|
||||
<member name="M:Telegrator.WideHostBuilderExtensions.AddWideTelegratorInternal(Microsoft.Extensions.DependencyInjection.IServiceCollection,Microsoft.Extensions.Configuration.IConfiguration,System.Collections.Generic.IDictionary{System.Object,System.Object},Telegrator.Core.IHandlersCollection@,Telegrator.TelegratorOptions)">
|
||||
<summary>
|
||||
Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
|
||||
</summary>
|
||||
|
||||
+6
-1
@@ -6377,11 +6377,16 @@
|
||||
Represents handler results, allowing to communicate with router and control aspect execution
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Telegrator.Result.InterruptRouter">
|
||||
<member name="P:Telegrator.Result.Success">
|
||||
<summary>
|
||||
Tell router to stop describing
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Telegrator.Result.RouteNext">
|
||||
<summary>
|
||||
Tell router to continue describing
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Telegrator.Result.NextType">
|
||||
<summary>
|
||||
Exact type that router should search
|
||||
|
||||
@@ -4,12 +4,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core;
|
||||
@@ -58,6 +53,12 @@ public static class HandlersExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static WTelegramBotClient AsWClient(this ITelegramBotClient client)
|
||||
{
|
||||
return client as WTelegramBotClient
|
||||
?? throw new InvalidCastException("Client is not assignable to `WTelegram.Bot.WTelegramBotClient`");
|
||||
}
|
||||
|
||||
public static WUpdate AsWUpdate(this Update update)
|
||||
{
|
||||
return update as WUpdate
|
||||
@@ -75,7 +76,7 @@ public static class WideHostBuilderExtensions
|
||||
/// </summary>
|
||||
public static IHostApplicationBuilder AddWideTelegrator(this IHostApplicationBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null, Action<ITelegramBotHostBuilder>? action = null)
|
||||
{
|
||||
AddTelegratorInternal(builder.Services, builder.Configuration, builder.Properties, ref handlers, options);
|
||||
AddWideTelegratorInternal(builder.Services, builder.Configuration, builder.Properties, ref handlers, options);
|
||||
action?.Invoke(new TelegramBotHostBuilder(builder, handlers));
|
||||
return builder;
|
||||
}
|
||||
@@ -85,14 +86,14 @@ public static class WideHostBuilderExtensions
|
||||
/// </summary>
|
||||
public static IHostApplicationBuilder AddWideTelegrator(this IHostApplicationBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null)
|
||||
{
|
||||
AddTelegratorInternal(builder.Services, builder.Configuration, builder.Properties, ref handlers, options);
|
||||
AddWideTelegratorInternal(builder.Services, builder.Configuration, builder.Properties, ref handlers, options);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
|
||||
/// </summary>
|
||||
internal static void AddTelegratorInternal(IServiceCollection services, IConfiguration configuration, IDictionary<object, object> properties, [NotNull] ref IHandlersCollection? handlers, TelegratorOptions? options = null)
|
||||
internal static void AddWideTelegratorInternal(IServiceCollection services, IConfiguration configuration, IDictionary<object, object> properties, [NotNull] ref IHandlersCollection? handlers, TelegratorOptions? options = null)
|
||||
{
|
||||
if (services.Any(srvc => srvc.ServiceType == typeof(HostedUpdateReceiver)))
|
||||
throw new InvalidOperationException("`HostedUpdateReceiver` found in services. WideHost extension is not compatible with long-polling receiving. Please, remove `AddTelegrator` invocation from your WebApp configuration.");
|
||||
@@ -123,7 +124,7 @@ public static class WideHostBuilderExtensions
|
||||
services.AddSingleton(handlers);
|
||||
properties.Add(HostBuilderExtensions.HandlersCollectionPropertyKey, handlers);
|
||||
|
||||
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<WTelegramBotClientOptions>)))
|
||||
if (!services.Any(srvc => srvc.ServiceType == typeof(IOptions<WTelegramBotClientOptions>)))
|
||||
{
|
||||
// For now, there's no way to configure this from IConfiguration, use `ConfigureWideTelegram` instead
|
||||
throw new MissingMemberException("No options of type 'WTelegramBotClientOptions' was registered. This configuration is runtime required! Use `ConfigureWideTelegram` to register options.");
|
||||
@@ -163,7 +164,13 @@ public static class WideBotServiceCollectionExtensions
|
||||
}
|
||||
|
||||
private static WTelegramBotClient TypedTelegramBotClientFactory(HttpClient httpClient, IServiceProvider provider)
|
||||
=> new WTelegramBotClient(provider.GetRequiredService<IOptions<WTelegramBotClientOptions>>().Value, httpClient);
|
||||
{
|
||||
ILogger<WTelegramBotClient> logger = provider.GetRequiredService<ILogger<WTelegramBotClient>>();
|
||||
WTelegramBotClient client = new WTelegramBotClient(provider.GetRequiredService<IOptions<WTelegramBotClientOptions>>().Value, httpClient);
|
||||
|
||||
WTelegram.Helpers.Log = (lvl, str) => logger.Log((LogLevel)lvl, str);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -125,7 +125,8 @@ public sealed class DescriptorFiltersSet
|
||||
if (!anyErrors)
|
||||
return Result.Ok();
|
||||
|
||||
return formReport ? Result.Next() : Result.Fault();
|
||||
return formReport
|
||||
? Result.Next() : Result.Fault();
|
||||
}
|
||||
|
||||
private static bool ExecuteFilter<T>(IFilter<T> filter, FilterExecutionContext<T> context, out Exception? exception) where T : class
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
@@ -21,6 +24,11 @@ public static class HandlerInspector
|
||||
/// <returns></returns>
|
||||
public static string? GetDisplayName(MemberInfo handlerType)
|
||||
{
|
||||
if (handlerType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(handlerType));
|
||||
}
|
||||
|
||||
return handlerType.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName;
|
||||
}
|
||||
|
||||
@@ -31,11 +39,26 @@ public static class HandlerInspector
|
||||
/// <returns>The handler attribute.</returns>
|
||||
public static UpdateHandlerAttributeBase GetHandlerAttribute(MemberInfo handlerType)
|
||||
{
|
||||
// Getting polling handler attribute
|
||||
IEnumerable<UpdateHandlerAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<UpdateHandlerAttributeBase>();
|
||||
if (handlerType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(handlerType));
|
||||
}
|
||||
|
||||
//
|
||||
return handlerAttrs.Single();
|
||||
List<UpdateHandlerAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<UpdateHandlerAttributeBase>().ToList();
|
||||
|
||||
if (handlerAttrs.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to register handler '{handlerType.Name}': Missing required attribute derived from '{nameof(UpdateHandlerAttributeBase)}'.");
|
||||
}
|
||||
|
||||
if (handlerAttrs.Count > 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to register handler '{handlerType.Name}': Multiple handler attributes found. A handler must have exactly one attribute derived from '{nameof(UpdateHandlerAttributeBase)}'.");
|
||||
}
|
||||
|
||||
return handlerAttrs[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,10 +68,16 @@ public static class HandlerInspector
|
||||
/// <returns>The state keeper attribute, or null if not present.</returns>
|
||||
public static IFilter<Update>? GetStateKeeperAttribute(MemberInfo handlerType)
|
||||
{
|
||||
// Getting polling handler attribute
|
||||
Attribute stateAttr = handlerType.GetCustomAttribute(typeof(StateAttribute<,>));
|
||||
if (handlerType == null)
|
||||
throw new ArgumentNullException(nameof(handlerType));
|
||||
|
||||
Attribute? stateAttr = handlerType.GetCustomAttributes()
|
||||
.FirstOrDefault(attr =>
|
||||
{
|
||||
Type type = attr.GetType();
|
||||
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(StateAttribute<,>);
|
||||
});
|
||||
|
||||
//
|
||||
return stateAttr as IFilter<Update>;
|
||||
}
|
||||
|
||||
@@ -60,12 +89,19 @@ public static class HandlerInspector
|
||||
/// <returns>An enumerable of filter attributes.</returns>
|
||||
public static IEnumerable<IFilter<Update>> GetFilterAttributes(MemberInfo handlerType, UpdateType validUpdType)
|
||||
{
|
||||
//
|
||||
IEnumerable<UpdateFilterAttributeBase> filters = handlerType.GetCustomAttributes<UpdateFilterAttributeBase>();
|
||||
if (handlerType == null)
|
||||
throw new ArgumentNullException(nameof(handlerType));
|
||||
|
||||
//
|
||||
if (filters.Any(filterAttr => !filterAttr.AllowedTypes.Contains(validUpdType)))
|
||||
throw new InvalidOperationException();
|
||||
List<UpdateFilterAttributeBase> filters = handlerType.GetCustomAttributes<UpdateFilterAttributeBase>().ToList();
|
||||
|
||||
UpdateFilterAttributeBase? invalidFilter = filters.FirstOrDefault(f => !f.AllowedTypes.Contains(validUpdType));
|
||||
if (invalidFilter != null)
|
||||
{
|
||||
string allowedTypesStr = string.Join(", ", invalidFilter.AllowedTypes);
|
||||
throw new InvalidOperationException(
|
||||
$"Filter conflict on handler '{handlerType.Name}': The filter '{invalidFilter.GetType().Name}' " +
|
||||
$"does not support update type '{validUpdType}'. Allowed types: [{allowedTypesStr}].");
|
||||
}
|
||||
|
||||
UpdateFilterAttributeBase? lastFilterAttribute = null;
|
||||
foreach (UpdateFilterAttributeBase filterAttribute in filters)
|
||||
@@ -78,7 +114,6 @@ public static class HandlerInspector
|
||||
else
|
||||
{
|
||||
lastFilterAttribute = filterAttribute;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,8 +126,24 @@ public static class HandlerInspector
|
||||
/// <returns>A <see cref="DescriptorAspectsSet"/> containing the aspects configuration.</returns>
|
||||
public static DescriptorAspectsSet GetAspects(Type handlerType)
|
||||
{
|
||||
Type? typedPre = handlerType.GetCustomAttribute(typeof(BeforeExecutionAttribute<>))?.GetType().GetGenericArguments()[0];
|
||||
Type? typedPost = handlerType.GetCustomAttribute(typeof(AfterExecutionAttribute<>))?.GetType().GetGenericArguments()[0];
|
||||
if (handlerType == null)
|
||||
throw new ArgumentNullException(nameof(handlerType));
|
||||
|
||||
Type? typedPre = GetGenericArgumentFromOpenGenericAttribute(handlerType, typeof(BeforeExecutionAttribute<>));
|
||||
Type? typedPost = GetGenericArgumentFromOpenGenericAttribute(handlerType, typeof(AfterExecutionAttribute<>));
|
||||
|
||||
return new DescriptorAspectsSet(typedPre, typedPost);
|
||||
}
|
||||
|
||||
private static Type? GetGenericArgumentFromOpenGenericAttribute(Type handlerType, Type openGenericAttributeType)
|
||||
{
|
||||
Attribute? attribute = handlerType.GetCustomAttributes()
|
||||
.FirstOrDefault(attr =>
|
||||
{
|
||||
Type type = attr.GetType();
|
||||
return type.IsGenericType && type.GetGenericTypeDefinition() == openGenericAttributeType;
|
||||
});
|
||||
|
||||
return attribute?.GetType().GetGenericArguments().FirstOrDefault();
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdate
|
||||
.ExecutePre(this, container, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!preResult.InterruptRouter)
|
||||
if (!preResult.Success)
|
||||
return preResult;
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
@@ -69,7 +69,7 @@ public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdate
|
||||
{
|
||||
// Executing handler
|
||||
Result execResult = await ExecuteInternal(container, cancellationToken).ConfigureAwait(false);
|
||||
if (!execResult.InterruptRouter)
|
||||
if (!execResult.Success)
|
||||
return execResult;
|
||||
}
|
||||
catch (NotImplementedException)
|
||||
@@ -86,7 +86,7 @@ public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdate
|
||||
.ExecutePost(this, container, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!postResult.InterruptRouter)
|
||||
if (!postResult.Success)
|
||||
return postResult;
|
||||
}
|
||||
}
|
||||
@@ -166,7 +166,7 @@ public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdate
|
||||
/// <returns></returns>
|
||||
public virtual Task<Result> FiltersFallback(FiltersFallbackReport report, ITelegramBotClient client, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(Result.Ok());
|
||||
return Task.FromResult(Result.Next());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -97,11 +97,8 @@ public class UpdateRouter : IUpdateRouter
|
||||
Result? lastResult = null;
|
||||
foreach (DescribedHandlerDescriptor handlerInfo in GetHandlers(AwaitingProvider, botClient, update, cancellationToken))
|
||||
{
|
||||
if (lastResult?.NextType != null)
|
||||
{
|
||||
if (lastResult.NextType != handlerInfo.From.HandlerType)
|
||||
continue;
|
||||
}
|
||||
if (lastResult?.NextType is not null && lastResult?.NextType != handlerInfo.From.HandlerType)
|
||||
continue;
|
||||
|
||||
// Enqueuing found awiting handlers
|
||||
await HandlersPool.Enqueue(handlerInfo);
|
||||
@@ -111,7 +108,7 @@ public class UpdateRouter : IUpdateRouter
|
||||
if (lastResult == null)
|
||||
break; // Smth went horribly wrong, better to stop routing
|
||||
|
||||
if (lastResult.InterruptRouter)
|
||||
if (!lastResult.RouteNext)
|
||||
break;
|
||||
|
||||
TelegratorLogging.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id);
|
||||
@@ -127,11 +124,8 @@ public class UpdateRouter : IUpdateRouter
|
||||
// Queuing reagular handlers for execution
|
||||
foreach (DescribedHandlerDescriptor handlerInfo in GetHandlers(HandlersProvider, botClient, update, cancellationToken))
|
||||
{
|
||||
if (lastResult?.NextType != null)
|
||||
{
|
||||
if (lastResult.NextType != handlerInfo.From.HandlerType)
|
||||
continue;
|
||||
}
|
||||
if (lastResult?.NextType is not null && lastResult?.NextType != handlerInfo.From.HandlerType)
|
||||
continue;
|
||||
|
||||
// Enqueuing found handlers
|
||||
await HandlersPool.Enqueue(handlerInfo);
|
||||
@@ -141,7 +135,7 @@ public class UpdateRouter : IUpdateRouter
|
||||
if (lastResult == null)
|
||||
break; // Smth went horribly wrong, better to stop routing
|
||||
|
||||
if (lastResult.InterruptRouter)
|
||||
if (!lastResult.RouteNext)
|
||||
break;
|
||||
|
||||
TelegratorLogging.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id);
|
||||
@@ -244,12 +238,16 @@ public class UpdateRouter : IUpdateRouter
|
||||
FiltersFallbackReport report = new FiltersFallbackReport(descriptor, filterContext);
|
||||
Result filtersResult = descriptor.Filters.Validate(filterContext, descriptor.FormReport, ref report);
|
||||
|
||||
if (filtersResult.InterruptRouter)
|
||||
if (filtersResult.RouteNext)
|
||||
{
|
||||
Result fallbackResult = handlerInstance.FiltersFallback(report, client, cancellationToken).Result;
|
||||
breakRouting = !fallbackResult.RouteNext;
|
||||
return null;
|
||||
|
||||
Result fallbackResult = handlerInstance.FiltersFallback(report, client, cancellationToken).Result;
|
||||
breakRouting = fallbackResult.InterruptRouter;
|
||||
return null;
|
||||
}
|
||||
else if (!filtersResult.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new DescribedHandlerDescriptor(descriptor, this, AwaitingProvider, StateStorage, client, handlerInstance, filterContext, descriptor.DisplayString);
|
||||
|
||||
@@ -10,23 +10,29 @@ namespace Telegrator;
|
||||
/// </summary>
|
||||
public sealed class Result
|
||||
{
|
||||
private static readonly Result ok = new Result(true, null);
|
||||
private static readonly Result fault = new Result(true, null);
|
||||
private static readonly Result next = new Result(false, null);
|
||||
private static readonly Result ok = new Result(true, false, null);
|
||||
private static readonly Result fault = new Result(false, false, null);
|
||||
private static readonly Result next = new Result(true, true, null);
|
||||
|
||||
/// <summary>
|
||||
/// Tell router to stop describing
|
||||
/// </summary>
|
||||
public bool InterruptRouter { get; }
|
||||
public bool Success { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tell router to continue describing
|
||||
/// </summary>
|
||||
public bool RouteNext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Exact type that router should search
|
||||
/// </summary>
|
||||
public Type? NextType { get; }
|
||||
|
||||
internal Result(bool interruptRouter, Type? nextType)
|
||||
internal Result(bool success, bool routeNext, Type? nextType)
|
||||
{
|
||||
InterruptRouter = interruptRouter;
|
||||
Success = success;
|
||||
RouteNext = routeNext;
|
||||
NextType = nextType;
|
||||
}
|
||||
|
||||
@@ -73,5 +79,5 @@ public sealed class Result
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static Result Next<T>()
|
||||
=> new Result(false, typeof(T));
|
||||
=> new Result(true, true, typeof(T));
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Telegram.Bot" Version="22.9.6.1" />
|
||||
<PackageReference Include="Telegram.Bot" Version="22.9.5.3" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -2,28 +2,12 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Data.Common;
|
||||
using Telegram.Bot;
|
||||
|
||||
namespace Telegrator;
|
||||
namespace Telegrator.Tests;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
public static void WebApplicationBuilder_Example(string[] args)
|
||||
{
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions()
|
||||
{
|
||||
Args = args,
|
||||
ApplicationName = "WebApplication example",
|
||||
});
|
||||
|
||||
builder.AddTelegratorWeb(action: builder => builder.Handlers
|
||||
.CollectHandlersAssemblyWide());
|
||||
|
||||
builder.Build()
|
||||
.UseTelegratorWeb(dontMap: true)
|
||||
.RemapWebhook("https://amazing-butt-sex.cloudpub.ru/")
|
||||
.Run();
|
||||
}
|
||||
|
||||
public static void HostApplicationBuilder_Example(string[] args)
|
||||
{
|
||||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings()
|
||||
@@ -49,7 +33,8 @@ internal static class Program
|
||||
});
|
||||
|
||||
using DbConnection connection = new SqliteConnection(@"Data Source=wtgb.db");
|
||||
builder.Services.ConfigureWideTelegram(new Telegram.Bot.WTelegramBotClientOptions(token: "BOT_TOKEN", apiId: 123, apiHash: "API_HASH", dbConnection: connection));
|
||||
builder.Services.ConfigureWideTelegram(
|
||||
new WTelegramBotClientOptions(token: "BOT_TOKEN", apiId: 123, apiHash: "API_HASH", dbConnection: connection));
|
||||
|
||||
builder.AddWideTelegrator(action: builder => builder.Handlers
|
||||
.CollectHandlersAssemblyWide());
|
||||
@@ -58,4 +43,21 @@ internal static class Program
|
||||
.UseWideTelegrator()
|
||||
.Run();
|
||||
}
|
||||
|
||||
public static void WebApplicationBuilder_Example(string[] args)
|
||||
{
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions()
|
||||
{
|
||||
Args = args,
|
||||
ApplicationName = "WebApplication example",
|
||||
});
|
||||
|
||||
builder.AddTelegratorWeb(action: builder => builder.Handlers
|
||||
.CollectHandlersAssemblyWide());
|
||||
|
||||
builder.Build()
|
||||
.UseTelegratorWeb(dontMap: true)
|
||||
.RemapWebhook("https://amazing-butt-sex.cloudpub.ru/")
|
||||
.Run();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user