diff --git a/Examples/DependencyInjection/Program.cs b/Examples/DependencyInjection/Program.cs index f33bf53..1e32385 100644 --- a/Examples/DependencyInjection/Program.cs +++ b/Examples/DependencyInjection/Program.cs @@ -25,6 +25,7 @@ namespace DependencyInjection .NoCommands() .NoSerialization() .DefaultLanguage() + .UseSingleThread() .Build(); await bot.Start(); diff --git a/Examples/EFCoreBot/Program.cs b/Examples/EFCoreBot/Program.cs index 37f5587..e49a1c1 100644 --- a/Examples/EFCoreBot/Program.cs +++ b/Examples/EFCoreBot/Program.cs @@ -18,6 +18,7 @@ var bot = BotBaseBuilder.Create() .NoCommands() .NoSerialization() .DefaultLanguage() + .UseSingleThread() .Build(); await bot.Start(); diff --git a/Examples/InlineAndReplyCombination/Program.cs b/Examples/InlineAndReplyCombination/Program.cs index fab456d..7354ec7 100644 --- a/Examples/InlineAndReplyCombination/Program.cs +++ b/Examples/InlineAndReplyCombination/Program.cs @@ -21,6 +21,7 @@ namespace InlineAndReplyCombination .DefaultCommands() .UseJSON(Path.Combine(Directory.GetCurrentDirectory(), "states.json")) .UseEnglish() + .UseSingleThread() .Build(); diff --git a/README.md b/README.md index b1f7706..903a138 100644 --- a/README.md +++ b/README.md @@ -1074,6 +1074,21 @@ Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.IronSo --- +Project: [open source](TelegramBotBase.Extensions.Images/) + +### TelegramBotBase.Extensions.Images.IronSoftware + +Extends the base package with some additional image methods like SendPhoto (using Bitmap) + +Important: This extension uses the IronSoftware drawing library. + +[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/) +[![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware) + +[https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/) + +Project: [open source](TelegramBotBase.Extensions.Images.IronSoftware/) + ### TelegramBotBase.Extensions.Serializer.Database.MSSQL A session serializer for Microsoft SQL Server. @@ -1102,6 +1117,8 @@ Credits: [@Kataane](https://github.com/Kataane) --- +Project: [open source](TelegramBotBase.Extensions.Serializer.Database.MSSQL/) + ## Test Project There is a "TelegramBotBase.Test" project inside the repository which includes minimal examples for all controls available. diff --git a/TelegramBotBase.Test/Program.cs b/TelegramBotBase.Test/Program.cs index 9276cbd..a9236a1 100644 --- a/TelegramBotBase.Test/Program.cs +++ b/TelegramBotBase.Test/Program.cs @@ -32,6 +32,7 @@ internal class Program }) .NoSerialization() .UseEnglish() + .UseThreadPool() .Build(); diff --git a/TelegramBotBase.Test/TelegramBotBase.Example.csproj b/TelegramBotBase.Test/TelegramBotBase.Example.csproj index 3e534ad..3affa0a 100644 --- a/TelegramBotBase.Test/TelegramBotBase.Example.csproj +++ b/TelegramBotBase.Test/TelegramBotBase.Example.csproj @@ -8,7 +8,10 @@ - + + + + diff --git a/TelegramBotBase/Base/MessageClient.cs b/TelegramBotBase/Base/MessageClient.cs index 10a724e..5d82f8d 100644 --- a/TelegramBotBase/Base/MessageClient.cs +++ b/TelegramBotBase/Base/MessageClient.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Net; using System.Net.Http; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Telegram.Bot; @@ -10,6 +11,7 @@ using Telegram.Bot.Exceptions; using Telegram.Bot.Polling; using Telegram.Bot.Types; + namespace TelegramBotBase.Base; /// @@ -17,16 +19,18 @@ namespace TelegramBotBase.Base; /// public class MessageClient { + private EventHandlerList Events { get; } = new(); + private static readonly object EvOnMessageLoop = new(); private static readonly object EvOnReceiveError = new(); - private static object __evOnMessage = new(); + protected CancellationTokenSource _cancellationTokenSource; - private static object __evOnMessageEdit = new(); + public string ApiKey { get; } + + public ITelegramBotClient TelegramClient { get; set; } - private static object __evCallbackQuery = new(); - private CancellationTokenSource _cancellationTokenSource; /// /// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before @@ -41,16 +45,12 @@ public class MessageClient { ApiKey = apiKey; TelegramClient = new TelegramBotClient(apiKey); - - Prepare(); } public MessageClient(string apiKey, HttpClient proxy) { ApiKey = apiKey; TelegramClient = new TelegramBotClient(apiKey, proxy); - - Prepare(); } @@ -68,8 +68,6 @@ public class MessageClient ); TelegramClient = new TelegramBotClient(apiKey, httpClient); - - Prepare(); } /// @@ -89,8 +87,6 @@ public class MessageClient ); TelegramClient = new TelegramBotClient(apiKey, httpClient); - - Prepare(); } @@ -98,25 +94,10 @@ public class MessageClient { ApiKey = apiKey; TelegramClient = client; - - Prepare(); } - public string ApiKey { get; } - - public ITelegramBotClient TelegramClient { get; set; } - - private EventHandlerList Events { get; } = new(); - - - public void Prepare() - { - TelegramClient.Timeout = new TimeSpan(0, 0, 30); - } - - - public void StartReceiving() + public virtual void StartReceiving() { _cancellationTokenSource = new CancellationTokenSource(); @@ -124,27 +105,30 @@ public class MessageClient receiverOptions.ThrowPendingUpdates = ThrowPendingUpdates; - TelegramClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions, - _cancellationTokenSource.Token); + TelegramClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions, _cancellationTokenSource.Token); } - public void StopReceiving() + + public virtual void StopReceiving() { _cancellationTokenSource.Cancel(); } - public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + private async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { await OnMessageLoop(new UpdateResult(update, null)); } - public async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, + + private async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) { await OnReceiveError(new ErrorResult(exception)); } + + #region "BotCommands" /// /// This will return the current list of bot commands. /// @@ -176,6 +160,8 @@ public class MessageClient await TelegramClient.DeleteMyCommandsAsync(scope, languageCode); } + #endregion + #region "Events" @@ -185,6 +171,7 @@ public class MessageClient remove => Events.RemoveHandler(EvOnMessageLoop, value); } + public async Task OnMessageLoop(UpdateResult update) { var eventHandlers = (Events[EvOnMessageLoop] as Async.AsyncEventHandler)?.Invoke(this, update); @@ -202,6 +189,7 @@ public class MessageClient remove => Events.RemoveHandler(EvOnReceiveError, value); } + public async Task OnReceiveError(ErrorResult update) { var eventHandlers = (Events[EvOnReceiveError] as Async.AsyncEventHandler)?.Invoke(this, update); @@ -225,4 +213,5 @@ public class MessageClient } #endregion + } diff --git a/TelegramBotBase/Base/ThreadPoolMessageClient.cs b/TelegramBotBase/Base/ThreadPoolMessageClient.cs new file mode 100644 index 0000000..41fb131 --- /dev/null +++ b/TelegramBotBase/Base/ThreadPoolMessageClient.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Telegram.Bot; +using Telegram.Bot.Exceptions; +using Telegram.Bot.Polling; +using Telegram.Bot.Types; +using TelegramBotBase.Interfaces; + +namespace TelegramBotBase.Base; + +/// +/// Base class for message handling +/// +public class ThreadPoolMessageClient : MessageClient +{ + + /// + /// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before + // start polling. If set to true Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates + // should be set to not null, otherwise Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates + // will effectively be set to receive all Telegram.Bot.Types.Updates. + /// + + public int ThreadPool_WorkerThreads { get; set; } = 1; + + public int ThreadPool_IOThreads { get; set; } = 1; + + + public ThreadPoolMessageClient(string apiKey) : base(apiKey) + { + + } + + public ThreadPoolMessageClient(string apiKey, HttpClient proxy) : base(apiKey, proxy) + { + + } + + + public ThreadPoolMessageClient(string apiKey, Uri proxyUrl, NetworkCredential credential = null) : base(apiKey, proxyUrl, credential) + { + + } + + /// + /// Initializes the client with a proxy + /// + /// + /// i.e. 127.0.0.1 + /// i.e. 10000 + public ThreadPoolMessageClient(string apiKey, string proxyHost, int proxyPort) : base(apiKey, proxyHost, proxyPort) + { + + } + + + public ThreadPoolMessageClient(string apiKey, TelegramBotClient client) : base(apiKey, client) + { + + } + + + + public override void StartReceiving() + { + _cancellationTokenSource = new CancellationTokenSource(); + + var receiverOptions = new ReceiverOptions(); + + receiverOptions.ThrowPendingUpdates = ThrowPendingUpdates; + + ThreadPool.SetMaxThreads(ThreadPool_WorkerThreads, ThreadPool_IOThreads); + + TelegramClient.StartReceiving(HandleUpdateAsyncThreadPool, HandleErrorAsyncThreadPool, receiverOptions, _cancellationTokenSource.Token); + } + + public override void StopReceiving() + { + _cancellationTokenSource.Cancel(); + } + + + public Task HandleUpdateAsyncThreadPool(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + { + ThreadPool.QueueUserWorkItem(async a => + { + await OnMessageLoop(new UpdateResult(update, null)); + }); + + return Task.CompletedTask; + } + + public Task HandleErrorAsyncThreadPool(ITelegramBotClient botClient, Exception exception, + CancellationToken cancellationToken) + { + ThreadPool.QueueUserWorkItem(async a => + { + await OnReceiveError(new ErrorResult(exception)); + }); + + return Task.CompletedTask; + } + +} diff --git a/TelegramBotBase/Builder/BotBaseBuilder.cs b/TelegramBotBase/Builder/BotBaseBuilder.cs index 39bb317..64596b9 100644 --- a/TelegramBotBase/Builder/BotBaseBuilder.cs +++ b/TelegramBotBase/Builder/BotBaseBuilder.cs @@ -18,7 +18,7 @@ namespace TelegramBotBase.Builder; public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, IStartFormSelectionStage, IBuildingStage, INetworkingSelectionStage, IBotCommandsStage, ISessionSerializationStage, - ILanguageSelectionStage + ILanguageSelectionStage, IThreadingStage { private string _apiKey; @@ -32,6 +32,7 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, private BotBaseBuilder() { + } /// @@ -87,6 +88,8 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, DefaultLanguage(); + UseSingleThread(); + return this; } @@ -107,6 +110,8 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, DefaultLanguage(); + UseSingleThread(); + return this; } @@ -125,6 +130,8 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, DefaultLanguage(); + UseSingleThread(); + return this; } @@ -207,14 +214,14 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, #region "Step 4 (Network Settings)" - public IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false) + public IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false, int timeoutInSeconds = 60) { var url = new Uri(proxyAddress); _client = new MessageClient(_apiKey, url) { TelegramClient = { - Timeout = new TimeSpan(0, 1, 0) + Timeout = TimeSpan.FromSeconds(timeoutInSeconds) }, }; _client.ThrowPendingUpdates = throwPendingUpdates; @@ -222,13 +229,13 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, } - public IBotCommandsStage NoProxy(bool throwPendingUpdates = false) + public IBotCommandsStage NoProxy(bool throwPendingUpdates = false, int timeoutInSeconds = 60) { _client = new MessageClient(_apiKey) { TelegramClient = { - Timeout = new TimeSpan(0, 1, 0) + Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0) } }; _client.ThrowPendingUpdates = throwPendingUpdates; @@ -236,13 +243,13 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, } - public IBotCommandsStage WithBotClient(TelegramBotClient tgclient, bool throwPendingUpdates = false) + public IBotCommandsStage WithBotClient(TelegramBotClient tgclient, bool throwPendingUpdates = false, int timeoutInSeconds = 60) { _client = new MessageClient(_apiKey, tgclient) { TelegramClient = { - Timeout = new TimeSpan(0, 1, 0) + Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0) } }; _client.ThrowPendingUpdates = throwPendingUpdates; @@ -250,26 +257,26 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, } - public IBotCommandsStage WithHostAndPort(string proxyHost, int proxyPort, bool throwPendingUpdates = false) + public IBotCommandsStage WithHostAndPort(string proxyHost, int proxyPort, bool throwPendingUpdates = false, int timeoutInSeconds = 60) { _client = new MessageClient(_apiKey, proxyHost, proxyPort) { TelegramClient = { - Timeout = new TimeSpan(0, 1, 0) + Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0) } }; _client.ThrowPendingUpdates = throwPendingUpdates; return this; } - public IBotCommandsStage WithHttpClient(HttpClient tgclient, bool throwPendingUpdates = false) + public IBotCommandsStage WithHttpClient(HttpClient tgclient, bool throwPendingUpdates = false, int timeoutInSeconds = 60) { _client = new MessageClient(_apiKey, tgclient) { TelegramClient = { - Timeout = new TimeSpan(0, 1, 0) + Timeout = TimeSpan.FromSeconds(timeoutInSeconds)// new TimeSpan(0, 1, 0) } }; _client.ThrowPendingUpdates = throwPendingUpdates; @@ -432,5 +439,30 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, return this; } + #endregion + + + #region "Step 8 (Threading)" + + public IBuildingStage UseSingleThread() + { + return this; + } + + public IBuildingStage UseThreadPool(int workerThreads = 2, int ioThreads = 1) + { + var c = new ThreadPoolMessageClient(_apiKey, (TelegramBotClient)_client.TelegramClient); + + c.ThreadPool_WorkerThreads = workerThreads; + c.ThreadPool_IOThreads = ioThreads; + c.ThrowPendingUpdates = _client.ThrowPendingUpdates; + + _client = c; + + return this; + } + + #endregion + } diff --git a/TelegramBotBase/Builder/Interfaces/INetworkingSelectionStage.cs b/TelegramBotBase/Builder/Interfaces/INetworkingSelectionStage.cs index cd17e9d..8ab58d4 100644 --- a/TelegramBotBase/Builder/Interfaces/INetworkingSelectionStage.cs +++ b/TelegramBotBase/Builder/Interfaces/INetworkingSelectionStage.cs @@ -11,14 +11,14 @@ public interface INetworkingSelectionStage /// /// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling. /// - IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false); + IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false, int timeoutInSeconds = 60); /// /// Do not choose a proxy as network configuration. /// /// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling. /// - IBotCommandsStage NoProxy(bool throwPendingUpdates = false); + IBotCommandsStage NoProxy(bool throwPendingUpdates = false, int timeoutInSeconds = 60); /// @@ -27,7 +27,7 @@ public interface INetworkingSelectionStage /// /// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling. /// - IBotCommandsStage WithBotClient(TelegramBotClient client, bool throwPendingUpdates = false); + IBotCommandsStage WithBotClient(TelegramBotClient client, bool throwPendingUpdates = false, int timeoutInSeconds = 60); /// @@ -37,7 +37,7 @@ public interface INetworkingSelectionStage /// /// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling. /// - IBotCommandsStage WithHostAndPort(string proxyHost, int Port, bool throwPendingUpdates = false); + IBotCommandsStage WithHostAndPort(string proxyHost, int Port, bool throwPendingUpdates = false, int timeoutInSeconds = 60); /// /// Uses a custom http client. @@ -45,5 +45,5 @@ public interface INetworkingSelectionStage /// /// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling. /// - IBotCommandsStage WithHttpClient(HttpClient client, bool throwPendingUpdates = false); + IBotCommandsStage WithHttpClient(HttpClient client, bool throwPendingUpdates = false, int timeoutInSeconds = 60); } \ No newline at end of file diff --git a/TelegramBotBase/Builder/Interfaces/IThreadingStage.cs b/TelegramBotBase/Builder/Interfaces/IThreadingStage.cs new file mode 100644 index 0000000..f53c087 --- /dev/null +++ b/TelegramBotBase/Builder/Interfaces/IThreadingStage.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TelegramBotBase.Builder.Interfaces +{ + public interface IThreadingStage + { + /// + /// Uses one single thread for message loop. (Default) + /// + /// + public IBuildingStage UseSingleThread(); + + /// + /// Using the threadpool for managing requests. + /// + /// Number of concurrent working threads. + /// Number of concurrent I/O threads. + /// + public IBuildingStage UseThreadPool(int workerThreads = 2, int ioThreads = 1); + + } +}