From 7988650e4e52676d1d402982057a41a5dcb3ce1e Mon Sep 17 00:00:00 2001 From: Bn4 Date: Wed, 13 May 2020 18:15:15 +0700 Subject: [PATCH 01/39] boring version updates --- WebToTelegramCore/Startup.cs | 2 +- WebToTelegramCore/WebToTelegramCore.csproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 845cea0..a9a0979 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -24,7 +24,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); // singleton makes changes to non-db properties persistent services.AddDbContext(options => diff --git a/WebToTelegramCore/WebToTelegramCore.csproj b/WebToTelegramCore/WebToTelegramCore.csproj index 64c0dac..f446524 100644 --- a/WebToTelegramCore/WebToTelegramCore.csproj +++ b/WebToTelegramCore/WebToTelegramCore.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp3.0 1.2.0.0 bnfour Dotnet Telegram forwarder @@ -9,10 +9,10 @@ - + - - + + From b6db6b52d96abd306396cd1cf6c2775504a30339 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Thu, 14 May 2020 00:30:51 +0700 Subject: [PATCH 02/39] commands redone to eliminate need of aptly named crutch, but at what cost? Also updated year in description and license --- LICENSE | 2 +- WebToTelegramCore/BotCommands/AboutCommand.cs | 8 +++-- .../BotCommands/BotCommandBase.cs | 3 +- .../BotCommands/CancelCommand.cs | 5 +-- .../BotCommands/ConfirmCommand.cs | 5 +-- .../BotCommands/ConfirmationCommandBase.cs | 3 +- .../BotCommands/CreateCommand.cs | 27 ++++---------- .../BotCommands/DeleteCommand.cs | 5 +-- .../BotCommands/DirectiveCommand.cs | 5 +-- .../BotCommands/GuestOnlyCommandBase.cs | 14 ++++---- WebToTelegramCore/BotCommands/HelpCommand.cs | 5 +-- WebToTelegramCore/BotCommands/IBotCommand.cs | 7 ++-- .../BotCommands/RegenerateCommand.cs | 5 +-- WebToTelegramCore/BotCommands/StartCommand.cs | 5 +-- WebToTelegramCore/BotCommands/TokenCommand.cs | 4 +-- .../BotCommands/UserOnlyCommandBase.cs | 6 ++-- .../Services/TelegramApiService.cs | 36 ++++++------------- WebToTelegramCore/WebToTelegramCore.csproj | 3 +- 18 files changed, 68 insertions(+), 80 deletions(-) diff --git a/LICENSE b/LICENSE index ff6b6eb..e0a6f98 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 bnfour +Copyright (c) 2018, 2020 bnfour Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index e55cf42..adfacae 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -17,7 +17,7 @@ public class AboutCommand : BotCommandBase, IBotCommand private const string _template = "**Dotnet Telegram forwarder** v. {0}\n\n" + "[Open-source!](https://github.com/bnfour/dotnet-telegram-forwarder) " + "Powered by ASP.NET Core!\n" + - "Written by bnfour, August, October 2018.\n\nN<3"; + "Written by bnfour, August, October 2018; May 2020.\n\nN<3"; /// /// Command's text. @@ -33,13 +33,15 @@ public AboutCommand(LocalizationOptions locale) : base(locale) { } /// /// Method to process the command. /// + /// Telegram ID of the user who sent the command. + /// Unused here. /// Record associated with user who sent the command. /// Unused here. /// Text of message that should be returned to user. - public override string Process(Record record) + public override string Process(long userId, Record record) { var version = Assembly.GetExecutingAssembly().GetName().Version; - return base.Process(record) ?? String.Format(_template, version); + return base.Process(userId, record) ?? String.Format(_template, version); } } } diff --git a/WebToTelegramCore/BotCommands/BotCommandBase.cs b/WebToTelegramCore/BotCommands/BotCommandBase.cs index c99df70..a741d81 100644 --- a/WebToTelegramCore/BotCommands/BotCommandBase.cs +++ b/WebToTelegramCore/BotCommands/BotCommandBase.cs @@ -34,10 +34,11 @@ public BotCommandBase(LocalizationOptions locale) /// Method of abstract base class that filters out users with pending /// cancellations or deletions of token. /// + /// Telegram ID of user that sent the message. /// Record to process. /// Error message if there is an operation pending, /// or null otherwise. - public virtual string Process(Record record) + public virtual string Process(long userId, Record record) { return (record != null && record.State != RecordState.Normal) ? _error : null; diff --git a/WebToTelegramCore/BotCommands/CancelCommand.cs b/WebToTelegramCore/BotCommands/CancelCommand.cs index ac373ce..5c7d63a 100644 --- a/WebToTelegramCore/BotCommands/CancelCommand.cs +++ b/WebToTelegramCore/BotCommands/CancelCommand.cs @@ -37,12 +37,13 @@ public CancelCommand(LocalizationOptions locale) : base(locale) /// /// Method to process the command. Resets Record's State back to Normal. /// + /// Telegram user ID. Unused here. /// Record associated with user who sent the command. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(Record record) + public override string Process(long userId, Record record) { - string baseResult = base.Process(record); + string baseResult = base.Process(userId, record); if (baseResult != null) { return baseResult; diff --git a/WebToTelegramCore/BotCommands/ConfirmCommand.cs b/WebToTelegramCore/BotCommands/ConfirmCommand.cs index 3a87e73..bccbba2 100644 --- a/WebToTelegramCore/BotCommands/ConfirmCommand.cs +++ b/WebToTelegramCore/BotCommands/ConfirmCommand.cs @@ -57,11 +57,12 @@ public ConfirmCommand(LocalizationOptions locale, RecordContext context, /// /// Method to carry on confirmed destructive operations. /// + /// Telegram user ID, unused here. /// Record associated with user who sent the command. /// End-user readable result of the operation. - public override string Process(Record record) + public override string Process(long userId, Record record) { - string baseResult = base.Process(record); + string baseResult = base.Process(userId, record); if (baseResult != null) { return baseResult; diff --git a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs index f6299e7..3a6aafd 100644 --- a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs +++ b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs @@ -35,10 +35,11 @@ public ConfirmationCommandBase(LocalizationOptions locale) /// Method of abstract base class that filters out users without pending /// cancellations or deletions of token. /// + /// Unused Telegram user ID. /// Record to process. /// Error message if there is no operation pending, /// or null otherwise. - public virtual string Process(Record record) + public virtual string Process(long userId, Record record) { return (record == null || record.State == RecordState.Normal) ? _error : null; diff --git a/WebToTelegramCore/BotCommands/CreateCommand.cs b/WebToTelegramCore/BotCommands/CreateCommand.cs index 1bd104e..3e6188d 100644 --- a/WebToTelegramCore/BotCommands/CreateCommand.cs +++ b/WebToTelegramCore/BotCommands/CreateCommand.cs @@ -35,16 +35,6 @@ public class CreateCommand : GuestOnlyCommandBase, IBotCommand /// private readonly ITokenGeneratorService _generator; - // I'm bad at computer programming: - // the existing "architecture" always passes nulls as Records if user have - // no record in DB yet. That means it's impossible to create a user :( - // so here we go - // TODO: do something better - /// - /// ID of account that sent the command. - /// - public long? Crutch { get; set; } - /// /// Field to store whether registration is enabled. True is enabled. /// @@ -73,28 +63,25 @@ public CreateCommand(LocalizationOptions locale, RecordContext context, /// /// Record to process. /// Message with new token or error when there is one already. - public override string Process(Record record) + public override string Process(long userId, Record record) { - return base.Process(record) ?? InternalProcess(record); + return base.Process(userId, record) ?? InternalProcess(userId, record); } /// /// Actual method that does registration or denies it. /// - /// Record to process. _Must be null_. + /// Telegram user ID to create a new record. _The_ place in the app + /// it's used. + /// Record to process. Is null if working properly. /// Message with new token or message stating that registration /// is closed for good. - private string InternalProcess(Record record) + private string InternalProcess(long userId, Record record) { - // record being null is enforced by base calls. - if (!Crutch.HasValue) - { - throw new ApplicationException("Crutch is not set before calling"); - } if (_isRegistrationEnabled) { string token = _generator.Generate(); - Record r = new Record() { AccountNumber = Crutch.Value, Token = token }; + Record r = new Record() { AccountNumber = userId, Token = token }; _context.Add(r); _context.SaveChanges(); return String.Format(_message, token); diff --git a/WebToTelegramCore/BotCommands/DeleteCommand.cs b/WebToTelegramCore/BotCommands/DeleteCommand.cs index aecd6a3..e6aa6bf 100644 --- a/WebToTelegramCore/BotCommands/DeleteCommand.cs +++ b/WebToTelegramCore/BotCommands/DeleteCommand.cs @@ -48,11 +48,12 @@ public DeleteCommand(LocalizationOptions locale, bool registrationEnabled) /// /// Method to process the command. /// + /// Unused Telegram user ID. /// Record to process. /// Message with results of processing. - public override string Process(Record record) + public override string Process(long userId, Record record) { - return base.Process(record) ?? InternalProcess(record); + return base.Process(userId, record) ?? InternalProcess(record); } /// diff --git a/WebToTelegramCore/BotCommands/DirectiveCommand.cs b/WebToTelegramCore/BotCommands/DirectiveCommand.cs index 92d7296..1f3a0f8 100644 --- a/WebToTelegramCore/BotCommands/DirectiveCommand.cs +++ b/WebToTelegramCore/BotCommands/DirectiveCommand.cs @@ -30,13 +30,14 @@ public DirectiveCommand(LocalizationOptions locale) : base(locale) { } /// /// Method to process the command. /// + /// Telegram user ID, unused. /// Record associated with user who sent the command. /// Unused here. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(Record record) + public override string Process(long userId, Record record) { - return base.Process(record) ?? _message; + return base.Process(userId, record) ?? _message; } } } diff --git a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs index 93c5a9a..d9b80d8 100644 --- a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs @@ -26,22 +26,22 @@ public GuestOnlyCommandBase(LocalizationOptions locale) : base(locale) /// /// Method of abstract base class that adds filtering out users - /// with no associated token. - /// + /// with no associated token. + /// Telegram user ID. Only used in descendats of this class, + /// as any other command category has access to records. /// Record to process. - /// Error message if there is an operation pending or user has no token, + /// Error message if there is an operation pending or user has a token, /// or null otherwise. - public new virtual string Process(Record record) + public new virtual string Process(long userId, Record record) { - return base.Process(record) ?? InternalProcess(record); + return base.Process(userId, record) ?? InternalProcess(record); } /// /// Method that filters out users with tokens. /// /// Record to process. - /// Error message if user has a token, - /// or null otherwise. + /// Error message if user has a token, or null otherwise. private string InternalProcess(Record record) { return record != null ? _error : null; diff --git a/WebToTelegramCore/BotCommands/HelpCommand.cs b/WebToTelegramCore/BotCommands/HelpCommand.cs index 37e40a5..1e978f5 100644 --- a/WebToTelegramCore/BotCommands/HelpCommand.cs +++ b/WebToTelegramCore/BotCommands/HelpCommand.cs @@ -30,13 +30,14 @@ public HelpCommand(LocalizationOptions locale) : base(locale) /// /// Method to process the command. /// + /// Unused Telegram used ID. /// Record associated with user who sent the command. /// Unused here. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(Record record) + public override string Process(long userId, Record record) { - return base.Process(record) ?? _message; + return base.Process(userId, record) ?? _message; } } } diff --git a/WebToTelegramCore/BotCommands/IBotCommand.cs b/WebToTelegramCore/BotCommands/IBotCommand.cs index ea1d100..09eda7a 100644 --- a/WebToTelegramCore/BotCommands/IBotCommand.cs +++ b/WebToTelegramCore/BotCommands/IBotCommand.cs @@ -17,9 +17,12 @@ public interface IBotCommand /// /// Method to process received message. /// - /// Record associated with user who sent teh command, + /// Telegram user ID, present even when no record is provided. + /// Used mostly for (and any potential descendant) + /// so extra processing can be removed. + /// Record associated with user who sent the command, /// or null if user has no Record (have not received the token). /// Message to send back to user. Markdown is supported. - string Process(Record record); + string Process(long userId, Record record); } } diff --git a/WebToTelegramCore/BotCommands/RegenerateCommand.cs b/WebToTelegramCore/BotCommands/RegenerateCommand.cs index 1d7e4a9..e52881b 100644 --- a/WebToTelegramCore/BotCommands/RegenerateCommand.cs +++ b/WebToTelegramCore/BotCommands/RegenerateCommand.cs @@ -32,11 +32,12 @@ public RegenerateCommand(LocalizationOptions locale) : base(locale) /// /// Method to process the command. /// + /// Unused Telegram user ID. /// Record to process. /// Message with results of processing. - public override string Process(Record record) + public override string Process(long userId, Record record) { - return base.Process(record) ?? InternalProcess(record); + return base.Process(userId, record) ?? InternalProcess(record); } /// diff --git a/WebToTelegramCore/BotCommands/StartCommand.cs b/WebToTelegramCore/BotCommands/StartCommand.cs index 535dbd2..3d0f433 100644 --- a/WebToTelegramCore/BotCommands/StartCommand.cs +++ b/WebToTelegramCore/BotCommands/StartCommand.cs @@ -52,14 +52,15 @@ public StartCommand(LocalizationOptions locale, bool registrationEnabled) /// /// Method to process the command. /// + /// Unused Telegram user ID. /// Record associated with user who sent the command. /// Unused here. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(Record record) + public override string Process(long userId, Record record) { string appendix = _isRegistrationOpen ? _registrationHint : _noRegistration; - return base.Process(record) ?? _startMessage + "\n\n" + appendix; + return base.Process(userId, record) ?? _startMessage + "\n\n" + appendix; } } } diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index 75beec9..02b769e 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -72,9 +72,9 @@ public TokenCommand(LocalizationOptions locale, string apiEndpoint) /// Record associated with user who sent the command. /// Message with token and API usage, or error message if user /// has no token. - public override string Process(Record record) + public override string Process(long userId, Record record) { - return base.Process(record) ?? InternalProcess(record); + return base.Process(userId, record) ?? InternalProcess(record); } /// diff --git a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs index 0d54cf1..0b1f997 100644 --- a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs @@ -25,12 +25,14 @@ public UserOnlyCommandBase(LocalizationOptions locale) : base(locale) /// Method of abstract base class that adds filtering out users /// with no associated token. /// + /// Telegram user ID. Unused there, as record.AccountNumber is + /// the same value. /// Record to process. /// Error message if there is an operation pending or user has no token, /// or null otherwise. - public new virtual string Process(Record record) + public new virtual string Process(long userId, Record record) { - return base.Process(record) ?? InternalProcess(record); + return base.Process(userId, record) ?? InternalProcess(record); } /// diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index cb36f12..3a527e8 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -41,11 +41,6 @@ public class TelegramApiService : ITelegramApiService /// private readonly List _commands; - /// - /// /create handler, as it requires special treating since i'm bad at programming. - /// - private readonly CreateCommand _thatOneCommand; - /// /// Indicates whether usage of /create command is enabled. /// @@ -96,11 +91,9 @@ public TelegramApiService(IOptions options, new CancelCommand(locOptions), new HelpCommand(locOptions), new DirectiveCommand(locOptions), - new AboutCommand(locOptions) - + new AboutCommand(locOptions), + new CreateCommand(locOptions, _context, _generator, _isRegistrationOpen) }; - _thatOneCommand = new CreateCommand(locOptions, _context, _generator, - _isRegistrationOpen); } /// @@ -109,7 +102,8 @@ public TelegramApiService(IOptions options, /// Received update. public void HandleUpdate(Update update) { - // a few sanity checks + // a few sanity checks: + // only handles text messages, hopefully commands if (update.Message.Type != MessageType.Text) { return; @@ -117,7 +111,7 @@ public void HandleUpdate(Update update) long? userId = update?.Message?.From?.Id; string text = update?.Message?.Text; - + // and the update contains everything we need to process it if (userId == null || String.IsNullOrEmpty(text)) { return; @@ -126,23 +120,16 @@ public void HandleUpdate(Update update) Record record = _context.GetRecordByAccountId(userId.Value); IBotCommand handler = null; - if (text.StartsWith(_thatOneCommand.Command)) - { - handler = _thatOneCommand; - _thatOneCommand.Crutch = userId.Value; - } - else - { - _thatOneCommand.Crutch = null; - handler = _commands.SingleOrDefault(c => text.StartsWith(c.Command)); - } + string commandText = text.Split(' ').FirstOrDefault(); + // will crash if multiple command classes share same text, who cares + handler = _commands.SingleOrDefault(c => c.Command.Equals(commandText)); if (handler != null) { - _bot.SendPureMarkdown(userId.Value, handler.Process(record)); + _bot.SendPureMarkdown(userId.Value, handler.Process(userId.Value, record)); } else { - HandleUnknownText(userId.Value, text); + HandleUnknownText(userId.Value, commandText); } } @@ -173,8 +160,7 @@ private void HandleUnknownText(long accountId, string text) } else { - string reply = text.StartsWith("/") ? - _invalidCommandReply : _invalidReply; + string reply = text.StartsWith("/") ? _invalidCommandReply : _invalidReply; _bot.Send(accountId, reply); } } diff --git a/WebToTelegramCore/WebToTelegramCore.csproj b/WebToTelegramCore/WebToTelegramCore.csproj index f446524..cdbcda3 100644 --- a/WebToTelegramCore/WebToTelegramCore.csproj +++ b/WebToTelegramCore/WebToTelegramCore.csproj @@ -5,12 +5,11 @@ 1.2.0.0 bnfour Dotnet Telegram forwarder - © 2018 bnfour + © 2018, 2020 bnfour - From cc6bb4c97313369e4b8556c069a81513c1102eac Mon Sep 17 00:00:00 2001 From: Bn4 Date: Wed, 8 Dec 2021 00:11:46 +0700 Subject: [PATCH 03/39] made it actually work on .NET 6 instead of old most likely unsupported whatever version it was before no major changes ...yet PauseChamp --- WebToTelegramCore/BotCommands/AboutCommand.cs | 7 ++-- WebToTelegramCore/Program.cs | 37 ++++++++++++------- .../PublishProfiles/FolderProfile.pubxml | 24 ------------ .../Properties/launchSettings.json | 4 -- .../Services/TelegramBotService.cs | 15 +++++--- WebToTelegramCore/Startup.cs | 22 +++-------- WebToTelegramCore/WebToTelegramCore.csproj | 9 +++-- 7 files changed, 48 insertions(+), 70 deletions(-) delete mode 100644 WebToTelegramCore/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index e55cf42..4813da2 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -16,8 +16,8 @@ public class AboutCommand : BotCommandBase, IBotCommand /// private const string _template = "**Dotnet Telegram forwarder** v. {0}\n\n" + "[Open-source!](https://github.com/bnfour/dotnet-telegram-forwarder) " + - "Powered by ASP.NET Core!\n" + - "Written by bnfour, August, October 2018.\n\nN<3"; + "Powered by .NET 6.0!!!\n" + + "Written by bnfour, August, October 2018; December 2021."; /// /// Command's text. @@ -39,7 +39,8 @@ public AboutCommand(LocalizationOptions locale) : base(locale) { } public override string Process(Record record) { var version = Assembly.GetExecutingAssembly().GetName().Version; - return base.Process(record) ?? String.Format(_template, version); + var prettyVersion = $"{version.Major}.{version.Minor}"; + return base.Process(record) ?? String.Format(_template, prettyVersion); } } } diff --git a/WebToTelegramCore/Program.cs b/WebToTelegramCore/Program.cs index aef82ae..e0725c1 100644 --- a/WebToTelegramCore/Program.cs +++ b/WebToTelegramCore/Program.cs @@ -1,31 +1,42 @@ -using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using System.IO; namespace WebToTelegramCore { public class Program { - public static void Main(string[] args) - { - CreateWebHostBuilder(args).Build().Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) { // hardcoded default int port = 8082; - if (args.Length == 2 && args[0].Equals("--port") - && System.Int32.TryParse(args[1], out int nonDefPort)) + if (args.Length == 2 && args[0].Equals("--port") && int.TryParse(args[1], out int nonDefPort)) { port = nonDefPort; } - return WebHost.CreateDefaultBuilder(args) - .UseKestrel() - .UseUrls($"http://localhost:{port}") - .UseStartup(); + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + // quick fix to prevent using solution folder for configuration files + // instead of built binary folder, where these files are overridden with juicy secrets + ContentRootPath = System.AppContext.BaseDirectory + }); + + builder.WebHost.UseKestrel(kestrelOptions => + { + kestrelOptions.ListenLocalhost(port); + }); + + // carrying over legacy startup-based configuration (for now?) + // lifetime-dependent config (use detailed exception page in dev environment, basically) was dropped (for now?) + var startup = new Startup(builder.Configuration); + startup.ConfigureServices(builder.Services); + + var app = builder.Build(); + app.MapControllers(); + + app.Run(); } } } diff --git a/WebToTelegramCore/Properties/PublishProfiles/FolderProfile.pubxml b/WebToTelegramCore/Properties/PublishProfiles/FolderProfile.pubxml deleted file mode 100644 index df979ca..0000000 --- a/WebToTelegramCore/Properties/PublishProfiles/FolderProfile.pubxml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - FileSystem - FileSystem - Release - Any CPU - - True - False - netcoreapp2.1 - 2.1.3 - linux-x64 - 09c905a4-e83d-42f1-9f7c-299082194b74 - false - <_IsPortable>true - bin\Debug\netcoreapp2.1\publish\ - True - - \ No newline at end of file diff --git a/WebToTelegramCore/Properties/launchSettings.json b/WebToTelegramCore/Properties/launchSettings.json index dadd404..1a8f352 100644 --- a/WebToTelegramCore/Properties/launchSettings.json +++ b/WebToTelegramCore/Properties/launchSettings.json @@ -1,8 +1,4 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true - }, "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "WebToTelegramCore": { diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index 557a582..aaff85f 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Options; +using System.Threading.Tasks; using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; @@ -47,10 +48,12 @@ public TelegramBotService(IOptions options, _formatter = formatter; - var webhookUrl = options.Value.ApiEndpointUrl + "/" + options.Value.Token; - // this code is dumb and single-threaded. _Maybe_ later - _client.SetWebhookAsync(webhookUrl, - allowedUpdates: new[] { UpdateType.Message }); + // made unclear that "api" part is needed as well, shot myself in the leg 3 years after + var webhookUrl = options.Value.ApiEndpointUrl + "/api/" + options.Value.Token; + // don't know whether old version without firing an actual thread still worked, + // it was changed as i tried to debug a "connection" issue where wrong config file was loaded + Task.Run(() => _client.SetWebhookAsync(webhookUrl, + allowedUpdates: new[] { UpdateType.Message })).Wait(); } /// @@ -71,7 +74,7 @@ public void Send(long accountId, string message) // I think we have to promote account ID back to ID of chat with this bot var chatId = new ChatId(accountId); _client.SendTextMessageAsync(chatId, _formatter.TransformToHtml(message), - ParseMode.Html, true); + ParseMode.Html, disableWebPagePreview: true); } /// @@ -93,7 +96,7 @@ public void SendTheSticker(long accountId) public void SendPureMarkdown(long accountId, string message) { var chatId = new ChatId(accountId); - _client.SendTextMessageAsync(chatId, message, ParseMode.Markdown, true); + _client.SendTextMessageAsync(chatId, message, ParseMode.Markdown, disableWebPagePreview: true); } } } diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 845cea0..0523cce 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -1,7 +1,4 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -24,7 +21,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + services.AddMvc(); // singleton makes changes to non-db properties persistent services.AddDbContext(options => @@ -38,6 +35,10 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); + // quick crutch -- Telegram.Bot's update class relies on some Newtonsoft attributes, + // so to deserialize it correctly, we need to use this library as well + services.AddControllers().AddNewtonsoftJson(); + // Options pattern to the rescue? services.Configure(Configuration.GetSection("General")); services.Configure(Configuration.GetSection("Bandwidth")); @@ -48,16 +49,5 @@ public void ConfigureServices(IServiceCollection services) var preload = Configuration.GetSection("Bandwidth").GetValue("InitialCount"); Record.SetMaxValue(preload); } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseMvc(); - } } } diff --git a/WebToTelegramCore/WebToTelegramCore.csproj b/WebToTelegramCore/WebToTelegramCore.csproj index 64c0dac..1a13ef3 100644 --- a/WebToTelegramCore/WebToTelegramCore.csproj +++ b/WebToTelegramCore/WebToTelegramCore.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + net6.0 1.2.0.0 bnfour Dotnet Telegram forwarder @@ -9,10 +9,11 @@ - + - - + + + From f8b03c9c171027de9d27ee3b4e2ec95320c47de2 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Wed, 8 Dec 2021 00:20:21 +0700 Subject: [PATCH 04/39] removed obsolete package after resolving 1.5 year overdue conflict last commit --- WebToTelegramCore/WebToTelegramCore.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/WebToTelegramCore/WebToTelegramCore.csproj b/WebToTelegramCore/WebToTelegramCore.csproj index 3b5caf7..7bd2d3b 100644 --- a/WebToTelegramCore/WebToTelegramCore.csproj +++ b/WebToTelegramCore/WebToTelegramCore.csproj @@ -10,7 +10,6 @@ - From cd478e678533361864f59d48c3418d74e0ea66fd Mon Sep 17 00:00:00 2001 From: Bn4 Date: Thu, 9 Dec 2021 00:43:59 +0700 Subject: [PATCH 05/39] fix the readme a lot --- readme.md | 90 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/readme.md b/readme.md index a7dd1ec..491a145 100644 --- a/readme.md +++ b/readme.md @@ -1,73 +1,83 @@ # Dotnet Telegram forwarder -(temporary name that stuck since I can't think of a better one) -An ASP.NET Core app providing HTTP API for notifications via Telegram bot. - -## What's this? -My attempt to grasp the basics of both ASP.NET and EF by writing moderately useful app. -Telegram bot is used to provide auth tokens to users and to actually notify them when something makes a request to the provided web API. -This can be used to deliver all kinds of notifications via Telegram. +("temporary" generic name from the very start of development that stuck forever) +An app providing HTTP API to deliver arbitrary text notifications via Telegram bot from anywhere where making HTTP POST requests is available. ## Status -Pretty much works on my server (sorry, registration's closed). Your mileage may vary. -I still have to write some actual clients though. +Operational. First version has been serving me flawlessly (as I can tell) since mid 2018, and a long overdue refactoring/framework update (spoiler: one of the major updates two planned) is currently underway. ## Description -Let's try [readme driven development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html) this time. So this app consists of two parts: Telegram bot and a web API. +Let's try [readme driven development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html) this time. So this app consists of two equally important parts: Telegram bot and a web API. ### Web API -Has only one method to send the notification. Its endpoint listens for requests with JSON body containing auth token and message to deliver. (Messages should support markdown.) -Request should look like that: +Has only one method to send the notification. Its endpoint listens for POST requests with JSON body. Actual endpoint URL will be provided via the bot itself when a new token is created. +#### Request +Request's body has this structure: ``` { - "token": "basically `[0-9a-zA-Z+=]{16}`>", - "message": "text to be delivered via bot" + "token": string, + "type": (optional) string, + "message": string, + "silent": (optional) boolean } ``` -Messages use the same format Telegram does: major differences are `**bold**` for **bold**, `__italic__` for *italic*. Also, `<` and `>` should be escaped as `<` and `>` for notifications to work. Complex formatting may break down the makeshift "parser", test notification to get through. +* Token is this service's user identifier, randomly generated per Telegram user, with abilities to withdraw it or replace with a new one anytime. (Implement a cooldown on consequent resets?) It's a 16 characters long string that may contain alphanumerics, and plus and equals signs (So `[0-9a-zA-Z+=]{16}`). +* Type is used to select between two supported parse modes: `"plaintext"` for plain text, and `"markdown"` for MarkdownV2 as described in Telegram docs [here](https://core.telegram.org/bots/api#markdownv2-style). If value is not supplied, defaults to `"plaintext"`. These two are separated, because Telegram flavoured Markdown requires escaping for a fairly common plaintext punctuation marks, and Telegram backend (from my experience three years ago, actualize) tends to silently drop malformed Markdown. +* Message is the text of the message to be sent via the bot. Maximum length is 4096, and preferred encoding is UTF-8. +* Silent is boolean to indicate whether them message from the bot in Telegram will come with a notification. Behaves what you'd expect. If not supplied, defaults to `false`. Please note that the end user is able to mute the bot, effectively rendering this option useless. -Tokens are tied to Telegram IDs internally, also there is limitations on how often messages can be sent: every token has up to 20 instant deliveries, -with one regenerating every minute after last successful message delivery. -If request was correctly formed, API will respond with another JSON object: -``` -{ - "ok": boolean, (if true message is accepted. If false, some kind of error happened), - "code": int, (represents error codes in machine-friendly format), - "details": string (human-friendly error description) -} -``` -Possible error codes: -* 0 -- No error, message sent. Used when "ok" is true; -* 1 -- No such token. Token is in valid format but is not found in the database; -* 2 -- Bandwidth exceeded. Too many messages in a given amount of time. Client should wait at least a minute (by default) before retrying. - -If request isn't in correct format, blank 400 Bad Request is thrown instead. +#### Response +API returns an empty HTTP response with any of the following status codes: +* `200 OK` if everything is indeed OK and message should be expeted to be delivered via the bot +No further actions from the client required +* `400 Bad Request` if the user request is malformed and cannot be processed +Client should check that the request is well-formed and meet the specifications, and retry with the fixed request +* `404 Not Found` if supplied token is not present in the database +Client should check that the token they provided is valid and has not been removed via commands, and retry with the correct one +* `429 Too Many Requests` if current limit of messages sent is exhausted +Client should retry later, after waiting at least one minute (on default throughput config) +* `500 Internal Server Error` in case anything goes wrong +Client can try to retry later, but ¯\\\_(ツ)\_/¯ +#### Rate limitation +The API has a rate limitation, preventing large(ish) amount of notifications in a short amount of time. By default (can be adjusted via config files), every user has 20 ...message points, I guess? Every notification sent removes 1 message point, and requests will be refused with `429 Too Many Requests` status code when points are depleted. A single point is regenerated every minute after last message was sent. +For instance, if API is used to send 40 notifications in quick succession, only 20 first messages will be sent to the user. If client waits 5 minutes after API starts responding with 429's, they will be able to send 5 more messages instantenously before hitting the limit again. After 20 minutes of idle time since the last successfully sent message, the API will behave as usual. ### Telegram bot The bot is used both to deliver messages and to obtain token for requests for a given account. It has some commands: * `/start` -- obligatory one, contains short description; -* `/help` -- also obligatory, displays output similar to this text; +* `/help` -- also obligatory, displays output similar to this section; * `/about` -- displays basic info about the bot, like link back to this repo; -* `/token` -- reminds user's token if there is one, also API usage hints; +* `/token` -- reminds user's token if there is one, also API usage hints and endpoint location; * `/create` -- generates new token for user if none present; * `/regenerate` -- once confirmed (see `/confirm` and `/cancel`) replaces user's token with a new one; * `/delete` -- once confirmed removes user's token; * `/confirm` -- confirms regeneration or deletion; * `/cancel` -- cancels regeneration or deletion. -When there is destructive (either regeneration or deletion) operation pending after initial command, only `/cancel` or `/confirm` commands are -accepted. +The ability for anyone to create a token for themselves is toggleable via config entry. You can always run direct queries against bot's DB for quick editing. Note that messages will not be delivered unless user actually engaged in a conversation with the bot. + +When there is a destructive (either regeneration or deletion) operation pending, only `/cancel` or `/confirm` commands are accepted. ## Configuration and deployment +You'll need .NET 6 runtime. Hosting on GNU/Linux in a proper data center is highly encouraged. By default, listens on port 8082. This can be changed with `--port ` command-line argument. Rest of the settings are inside `appsettings.json`: -* "Strings" section contains all the customizable strings just in case localization is ever needed. * "General" section contains Telegram's bot token, API endpoint URL as seen from outside world (certainly not localhost:8082 as Kestrel would told you it listens to) and a boolean that controls whether new users can create tokens. * "Bandwidth" section controls bot's throughput: maximum amount of messages to be delivered at once and amount of seconds to regenerate one message delivery is set here. -To deploy this bot, you'll need something that will append SSL as required by Telegram. As always, I recommend `nginx` as a reverse proxy. Another quirk is that launching via `./WebToTelegramCore` -didn't worked with my setup: server had ASP.NET Core 2.1.3 runtime, but app was expecting 2.1.2. `dotnet WebToTelegramCore.dll` worked though. +// TODO write about localization string moved somewhere else + +To deploy this bot, you'll need something that will append SSL as required by Telegram. As always with Telegram bots, I recommend `nginx` as a reverse proxy. You'll need to set up HTTPS as well. + +### Debug +Short section outlining ngrok usage? -## Possible TODO -Make a companion website that also allows token manipulation just like the bot? \ No newline at end of file +## Version history +* **v 1.0**, 2018-08-29 +Initial release. More an excercise in ASP.NET Core than an app I needed at the moment. Actually helped me to score a software engineering job and turned out to be moderately useful tool. +* **v 1.2**, 2018-10-05 +Greatly increased the reliability of Markdown parsing in one of the most **not** straightforward ways you can imagine -- by converting the Markdown to HTML with a few custom convertion quirks. +* **no version number**, 2020-05-14 +Shelved attempt to improve the codebase. Consists of one architecture change and is fully included in the next release. +* TODO the current iteration, version number 1.5? 1.9? Certainly not 2.x _yet_. \ No newline at end of file From 954da12bbe3f8e1ad4575b16f2085d3bd2912239 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 12 Dec 2021 13:07:03 +0700 Subject: [PATCH 06/39] dual-purpose controller split in two, api methods converted to async, mostly final error handling in Telegram API controller --- .../Controllers/TelegramApiController.cs | 58 ++++++++++++++ .../WebAndTelegramApisController.cs | 78 ------------------- .../Controllers/WebApiController.cs | 39 ++++++++++ 3 files changed, 97 insertions(+), 78 deletions(-) create mode 100644 WebToTelegramCore/Controllers/TelegramApiController.cs delete mode 100644 WebToTelegramCore/Controllers/WebAndTelegramApisController.cs create mode 100644 WebToTelegramCore/Controllers/WebApiController.cs diff --git a/WebToTelegramCore/Controllers/TelegramApiController.cs b/WebToTelegramCore/Controllers/TelegramApiController.cs new file mode 100644 index 0000000..6352d1a --- /dev/null +++ b/WebToTelegramCore/Controllers/TelegramApiController.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using System.Net; +using System.Threading.Tasks; +using Telegram.Bot.Types; +using WebToTelegramCore.Services; + +namespace WebToTelegramCore.Controllers +{ + /// + /// Controller that handles both web API and telegram API calls, + /// since they're both POST with JSON bodies. + /// + public class TelegramApiController : Controller + { + /// + /// Field to store injected Telegram API service. + /// + private ITelegramApiService _tgApi; + + /// + /// Constructor with dependency injection. + /// + /// Web API service instance to use. + /// Telegram API service instance to use. + public TelegramApiController(ITelegramApiService tgApi) + { + _tgApi = tgApi; + } + + // POST /api/{bot token} + /// + /// Handles webhook calls. + /// + /// Token which is used as part of endpoint url + /// to verify request's origin. + /// Update to handle. + /// 404 Not Found on wrong tokens, 200 OK otherwise, + /// unless there is an internal server error. + [HttpPost, Route("api/{token}")] + public async Task HandleTelegramApi(string token, [FromBody] Update update) + { + if (!_tgApi.IsToken(token)) + { + return NotFound(); + } + try + { + _tgApi.HandleUpdate(update); + return Ok(); + } + catch + { + return StatusCode((int)HttpStatusCode.InternalServerError); + } + + } + } +} diff --git a/WebToTelegramCore/Controllers/WebAndTelegramApisController.cs b/WebToTelegramCore/Controllers/WebAndTelegramApisController.cs deleted file mode 100644 index 25675b0..0000000 --- a/WebToTelegramCore/Controllers/WebAndTelegramApisController.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Telegram.Bot.Types; - -using WebToTelegramCore.Models; -using WebToTelegramCore.Services; - -namespace WebToTelegramCore.Controllers -{ - /// - /// Controller that handles both web API and telegram API calls, - /// since they're both POST with JSON bodies. - /// - public class WebAndTelegramApisController : Controller - { - /// - /// Field to store injected web API service. - /// - private IOwnApiService _ownApi; - - /// - /// Field to store injected Telegram API service. - /// - private ITelegramApiService _tgApi; - - /// - /// Constructor with dependency injection. - /// - /// Web API service instance to use. - /// Telegram API service instance to use. - public WebAndTelegramApisController(IOwnApiService ownApi, - ITelegramApiService tgApi) - { - _ownApi = ownApi; - _tgApi = tgApi; - } - - // POST /api - /// - /// Handles web API calls. - /// - /// Request object in POST request body. - /// 400 Bad request on malformed Requests, - /// 200 OK with corresponding Response otherwise. - [HttpPost, Route("api")] - public ActionResult HandleWebApi([FromBody] Request request) - { - // silently deny malformed requests - if (!ModelState.IsValid) - { - return BadRequest(); - } - return _ownApi.HandleRequest(request); - } - - // POST /api/{bot token} - /// - /// Handles webhook calls. - /// - /// Token which is used as part of endpoint url - /// to verify request's origin. - /// Update to handle. - /// 400 Bad Request on wrong tokens, 200 OK otherwise. - [HttpPost, Route("api/{token}")] - public ActionResult HandleTelegramApi(string token, [FromBody] Update update) - { - if (!_tgApi.IsToken(token)) - { - return BadRequest(); - } - _tgApi.HandleUpdate(update); - return Ok(); - } - } -} diff --git a/WebToTelegramCore/Controllers/WebApiController.cs b/WebToTelegramCore/Controllers/WebApiController.cs new file mode 100644 index 0000000..33f35e5 --- /dev/null +++ b/WebToTelegramCore/Controllers/WebApiController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using WebToTelegramCore.Models; +using WebToTelegramCore.Services; + +namespace WebToTelegramCore.Controllers +{ + public class WebApiController : Controller + { + /// + /// Field to store injected web API service. + /// + private readonly IOwnApiService _ownApi; + + public WebApiController(IOwnApiService ownApi) + { + _ownApi = ownApi; + } + + // POST /api + /// + /// Handles web API calls. + /// + /// Request object in POST request body. + /// 400 Bad request on malformed Requests, + /// 200 OK with corresponding Response otherwise. + [HttpPost, Route("api")] + // TODO drop response from return type + public async Task> HandleWebApi([FromBody] Request request) + { + // silently deny malformed requests + if (!ModelState.IsValid) + { + return BadRequest(); + } + return _ownApi.HandleRequest(request); + } + } +} From 5a98d87906135af85e206616d5832c0811122406 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 12 Dec 2021 13:36:28 +0700 Subject: [PATCH 07/39] web api now returns non-200 status codes for any error occured, Response class removed as unneeded --- .../Controllers/WebApiController.cs | 30 ++++++++++--- WebToTelegramCore/Data/ResponseState.cs | 25 ----------- .../Exceptions/BandwidthExceededException.cs | 6 +++ .../Exceptions/TokenNotFoundException.cs | 6 +++ WebToTelegramCore/Models/Response.cs | 38 ---------------- WebToTelegramCore/Services/IOwnApiService.cs | 4 +- WebToTelegramCore/Services/OwnApiService.cs | 43 ++++--------------- 7 files changed, 46 insertions(+), 106 deletions(-) delete mode 100644 WebToTelegramCore/Data/ResponseState.cs create mode 100644 WebToTelegramCore/Exceptions/BandwidthExceededException.cs create mode 100644 WebToTelegramCore/Exceptions/TokenNotFoundException.cs delete mode 100644 WebToTelegramCore/Models/Response.cs diff --git a/WebToTelegramCore/Controllers/WebApiController.cs b/WebToTelegramCore/Controllers/WebApiController.cs index 33f35e5..51b6197 100644 --- a/WebToTelegramCore/Controllers/WebApiController.cs +++ b/WebToTelegramCore/Controllers/WebApiController.cs @@ -1,5 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using System; +using System.Net; using System.Threading.Tasks; +using WebToTelegramCore.Exceptions; using WebToTelegramCore.Models; using WebToTelegramCore.Services; @@ -22,18 +25,33 @@ public WebApiController(IOwnApiService ownApi) /// Handles web API calls. /// /// Request object in POST request body. - /// 400 Bad request on malformed Requests, - /// 200 OK with corresponding Response otherwise. + /// HTTP status code result indicating whether the request was handled + /// successfully, or one of the error codes. [HttpPost, Route("api")] - // TODO drop response from return type - public async Task> HandleWebApi([FromBody] Request request) + public async Task HandleWebApi([FromBody] Request request) { - // silently deny malformed requests + // deny malformed requests if (!ModelState.IsValid) { return BadRequest(); } - return _ownApi.HandleRequest(request); + try + { + _ownApi.HandleRequest(request); + return Ok(); + } + catch (TokenNotFoundException) + { + return NotFound(); + } + catch (BandwidthExceededException) + { + return StatusCode((int)HttpStatusCode.TooManyRequests); + } + catch (Exception) + { + return StatusCode((int)HttpStatusCode.InternalServerError); + } } } } diff --git a/WebToTelegramCore/Data/ResponseState.cs b/WebToTelegramCore/Data/ResponseState.cs deleted file mode 100644 index 3739576..0000000 --- a/WebToTelegramCore/Data/ResponseState.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace WebToTelegramCore.Data -{ - /// - /// Represents various web API calls outcomes. - /// - public enum ResponseState - { - /// - /// Request accepted. No further action by client needed. - /// - OkSent, - /// - /// Token not found. Client should not retry. - /// - NoSuchToken, - /// - /// Too many messages at a time. Client should retry later. - /// - BandwidthExceeded, - /// - /// An exception occured. Client may or may not retry later. - /// - SomethingBadHappened - } -} diff --git a/WebToTelegramCore/Exceptions/BandwidthExceededException.cs b/WebToTelegramCore/Exceptions/BandwidthExceededException.cs new file mode 100644 index 0000000..6e70db0 --- /dev/null +++ b/WebToTelegramCore/Exceptions/BandwidthExceededException.cs @@ -0,0 +1,6 @@ +using System; + +namespace WebToTelegramCore.Exceptions +{ + public class BandwidthExceededException : Exception { } +} diff --git a/WebToTelegramCore/Exceptions/TokenNotFoundException.cs b/WebToTelegramCore/Exceptions/TokenNotFoundException.cs new file mode 100644 index 0000000..401bc02 --- /dev/null +++ b/WebToTelegramCore/Exceptions/TokenNotFoundException.cs @@ -0,0 +1,6 @@ +using System; + +namespace WebToTelegramCore.Exceptions +{ + public class TokenNotFoundException : Exception { } +} diff --git a/WebToTelegramCore/Models/Response.cs b/WebToTelegramCore/Models/Response.cs deleted file mode 100644 index 88249a6..0000000 --- a/WebToTelegramCore/Models/Response.cs +++ /dev/null @@ -1,38 +0,0 @@ -using ResponseState = WebToTelegramCore.Data.ResponseState; - -namespace WebToTelegramCore.Models -{ - /// - /// Represents web API response to user. - /// Also in JSON, just like the initial request. - /// - public class Response - { - /// - /// Quick indicator of whether request was accepted. - /// - public bool Ok { get; private set; } - - /// - /// Machine-readable error code. - /// - public int Code { get; private set; } - - /// - /// Human-readable description of error. - /// - public string Details { get; private set; } - - /// - /// Class constructor. - /// - /// ResponseState field values are based on. - /// Human-readable message corresponding to state. - public Response(ResponseState state, string details) - { - Ok = state == ResponseState.OkSent; - Code = (int)state; - Details = details; - } - } -} diff --git a/WebToTelegramCore/Services/IOwnApiService.cs b/WebToTelegramCore/Services/IOwnApiService.cs index d08acd7..adec51a 100644 --- a/WebToTelegramCore/Services/IOwnApiService.cs +++ b/WebToTelegramCore/Services/IOwnApiService.cs @@ -11,7 +11,7 @@ public interface IOwnApiService /// Method to handle incoming request from the web API. /// /// Request to handle. - /// Response to the request, ready to be returned to client. - Response HandleRequest(Request request); + // TODO add async everywhere and try not to go mad in the process + void HandleRequest(Request request); } } diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index 3398e9d..84e7e3c 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -1,8 +1,6 @@ using Microsoft.Extensions.Options; using System; -using System.Collections.Generic; - -using WebToTelegramCore.Data; +using WebToTelegramCore.Exceptions; using WebToTelegramCore.Models; using WebToTelegramCore.Options; @@ -28,64 +26,40 @@ public class OwnApiService : IOwnApiService /// private readonly ITelegramBotService _bot; - /// - /// Holds string representations of various response results. - /// - private readonly Dictionary _details; - /// /// Constuctor that injects dependencies. /// /// Database context to use. /// Bot service to use. /// Bandwidth options. - /// Localization options. - public OwnApiService(RecordContext context, ITelegramBotService bot, - IOptions options, IOptions locale) + public OwnApiService(RecordContext context, ITelegramBotService bot, IOptions options) { _context = context; _bot = bot; _secondsPerRegen = options.Value.SecondsPerRegeneration; - - LocalizationOptions locOptions = locale.Value; - _details = new Dictionary() - { - [ResponseState.OkSent] = locOptions.RequestOk, - [ResponseState.BandwidthExceeded] = locOptions.RequestBandwidthExceeded, - [ResponseState.NoSuchToken] = locOptions.RequestNoToken, - [ResponseState.SomethingBadHappened] = locOptions.RequestWhat - }; } /// - /// Public method to handle incoming requests. Call underlying internal method - /// and wraps its output into a Response. + /// Public method to handle incoming requests. Call underlying internal method. /// /// Request to handle. - /// Response to the request, ready to be returned to client. - public Response HandleRequest(Request request) + public void HandleRequest(Request request) { - ResponseState result = HandleRequestInternally(request); - return new Response(result, _details[result]); + HandleRequestInternally(request); } /// /// Internal method to handle requests. /// /// Request to handle. - /// ResponseState indicatig result of the request processing. - private ResponseState HandleRequestInternally(Request request) + private void HandleRequestInternally(Request request) { - // wrapping all these into a big try clause - // returning ResponseState.SomethingBadHappened in catch is silly - // but how else API can be made "robust"? - // TODO: think about role of ResponseState.SomethingBadHappened var record = _context.GetRecordByToken(request.Token); if (record == null) { - return ResponseState.NoSuchToken; + throw new TokenNotFoundException(); } UpdateRecordCounter(record); if (record.UsageCounter > 0) @@ -93,11 +67,10 @@ private ResponseState HandleRequestInternally(Request request) record.LastSuccessTimestamp = DateTime.Now; record.UsageCounter--; _bot.Send(record.AccountNumber, request.Message); - return ResponseState.OkSent; } else { - return ResponseState.BandwidthExceeded; + throw new BandwidthExceededException(); } } From ab8a2f6ac15e8110aef08bfa3de24ea8ba6ad9ba Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 12 Dec 2021 13:57:32 +0700 Subject: [PATCH 08/39] interfaces moved to their own folder/namespace, _a lot_ of references to them from elsewhere updated --- WebToTelegramCore/BotCommands/AboutCommand.cs | 2 +- WebToTelegramCore/BotCommands/BotCommandBase.cs | 1 + WebToTelegramCore/BotCommands/CancelCommand.cs | 1 + WebToTelegramCore/BotCommands/ConfirmCommand.cs | 2 +- WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs | 1 + WebToTelegramCore/BotCommands/CreateCommand.cs | 2 +- WebToTelegramCore/BotCommands/DeleteCommand.cs | 1 + WebToTelegramCore/BotCommands/DirectiveCommand.cs | 3 ++- WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs | 3 ++- WebToTelegramCore/BotCommands/HelpCommand.cs | 3 ++- WebToTelegramCore/BotCommands/RegenerateCommand.cs | 1 + WebToTelegramCore/BotCommands/StartCommand.cs | 3 ++- WebToTelegramCore/BotCommands/TokenCommand.cs | 1 + WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs | 3 ++- WebToTelegramCore/Controllers/TelegramApiController.cs | 2 +- WebToTelegramCore/Controllers/WebApiController.cs | 2 +- WebToTelegramCore/{BotCommands => Interfaces}/IBotCommand.cs | 2 +- .../{Services => Interfaces}/IFormatterService.cs | 2 +- WebToTelegramCore/{Services => Interfaces}/IOwnApiService.cs | 2 +- .../{Services => Interfaces}/ITelegramApiService.cs | 2 +- .../{Services => Interfaces}/ITelegramBotService.cs | 2 +- .../{Services => Interfaces}/ITokenGeneratorService.cs | 2 +- WebToTelegramCore/Services/FormatterService.cs | 1 + WebToTelegramCore/Services/OwnApiService.cs | 1 + WebToTelegramCore/Services/TelegramApiService.cs | 1 + WebToTelegramCore/Services/TelegramBotService.cs | 2 +- WebToTelegramCore/Services/TokenGeneratorService.cs | 1 + WebToTelegramCore/Startup.cs | 4 ++-- 28 files changed, 34 insertions(+), 19 deletions(-) rename WebToTelegramCore/{BotCommands => Interfaces}/IBotCommand.cs (96%) rename WebToTelegramCore/{Services => Interfaces}/IFormatterService.cs (93%) rename WebToTelegramCore/{Services => Interfaces}/IOwnApiService.cs (92%) rename WebToTelegramCore/{Services => Interfaces}/ITelegramApiService.cs (95%) rename WebToTelegramCore/{Services => Interfaces}/ITelegramBotService.cs (96%) rename WebToTelegramCore/{Services => Interfaces}/ITokenGeneratorService.cs (92%) diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index 2019fe4..f92ca38 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -1,6 +1,6 @@ using System; using System.Reflection; - +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/BotCommands/BotCommandBase.cs b/WebToTelegramCore/BotCommands/BotCommandBase.cs index a741d81..8a97da4 100644 --- a/WebToTelegramCore/BotCommands/BotCommandBase.cs +++ b/WebToTelegramCore/BotCommands/BotCommandBase.cs @@ -1,4 +1,5 @@ using WebToTelegramCore.Data; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/BotCommands/CancelCommand.cs b/WebToTelegramCore/BotCommands/CancelCommand.cs index 5c7d63a..10ac44a 100644 --- a/WebToTelegramCore/BotCommands/CancelCommand.cs +++ b/WebToTelegramCore/BotCommands/CancelCommand.cs @@ -1,4 +1,5 @@ using WebToTelegramCore.Data; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/BotCommands/ConfirmCommand.cs b/WebToTelegramCore/BotCommands/ConfirmCommand.cs index bccbba2..690ec07 100644 --- a/WebToTelegramCore/BotCommands/ConfirmCommand.cs +++ b/WebToTelegramCore/BotCommands/ConfirmCommand.cs @@ -1,8 +1,8 @@ using System; using WebToTelegramCore.Data; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; -using WebToTelegramCore.Services; namespace WebToTelegramCore.BotCommands { diff --git a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs index 3a6aafd..4601ac9 100644 --- a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs +++ b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs @@ -1,4 +1,5 @@ using WebToTelegramCore.Data; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/BotCommands/CreateCommand.cs b/WebToTelegramCore/BotCommands/CreateCommand.cs index 3e6188d..8302aea 100644 --- a/WebToTelegramCore/BotCommands/CreateCommand.cs +++ b/WebToTelegramCore/BotCommands/CreateCommand.cs @@ -1,7 +1,7 @@ using System; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; -using WebToTelegramCore.Services; namespace WebToTelegramCore.BotCommands { diff --git a/WebToTelegramCore/BotCommands/DeleteCommand.cs b/WebToTelegramCore/BotCommands/DeleteCommand.cs index e6aa6bf..25efe08 100644 --- a/WebToTelegramCore/BotCommands/DeleteCommand.cs +++ b/WebToTelegramCore/BotCommands/DeleteCommand.cs @@ -1,4 +1,5 @@ using WebToTelegramCore.Data; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/BotCommands/DirectiveCommand.cs b/WebToTelegramCore/BotCommands/DirectiveCommand.cs index 1f3a0f8..337dc5f 100644 --- a/WebToTelegramCore/BotCommands/DirectiveCommand.cs +++ b/WebToTelegramCore/BotCommands/DirectiveCommand.cs @@ -1,4 +1,5 @@ -using WebToTelegramCore.Models; +using WebToTelegramCore.Interfaces; +using WebToTelegramCore.Models; using WebToTelegramCore.Options; namespace WebToTelegramCore.BotCommands diff --git a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs index d9b80d8..c8daa31 100644 --- a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs @@ -1,4 +1,5 @@ -using WebToTelegramCore.Models; +using WebToTelegramCore.Interfaces; +using WebToTelegramCore.Models; using WebToTelegramCore.Options; namespace WebToTelegramCore.BotCommands diff --git a/WebToTelegramCore/BotCommands/HelpCommand.cs b/WebToTelegramCore/BotCommands/HelpCommand.cs index 1e978f5..780194b 100644 --- a/WebToTelegramCore/BotCommands/HelpCommand.cs +++ b/WebToTelegramCore/BotCommands/HelpCommand.cs @@ -1,4 +1,5 @@ -using WebToTelegramCore.Models; +using WebToTelegramCore.Interfaces; +using WebToTelegramCore.Models; using WebToTelegramCore.Options; namespace WebToTelegramCore.BotCommands diff --git a/WebToTelegramCore/BotCommands/RegenerateCommand.cs b/WebToTelegramCore/BotCommands/RegenerateCommand.cs index e52881b..8268c9b 100644 --- a/WebToTelegramCore/BotCommands/RegenerateCommand.cs +++ b/WebToTelegramCore/BotCommands/RegenerateCommand.cs @@ -1,4 +1,5 @@ using WebToTelegramCore.Data; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/BotCommands/StartCommand.cs b/WebToTelegramCore/BotCommands/StartCommand.cs index 3d0f433..ae99419 100644 --- a/WebToTelegramCore/BotCommands/StartCommand.cs +++ b/WebToTelegramCore/BotCommands/StartCommand.cs @@ -1,4 +1,5 @@ -using WebToTelegramCore.Models; +using WebToTelegramCore.Interfaces; +using WebToTelegramCore.Models; using WebToTelegramCore.Options; namespace WebToTelegramCore.BotCommands diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index 02b769e..0daaea5 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs index 0b1f997..c688023 100644 --- a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs @@ -1,4 +1,5 @@ -using WebToTelegramCore.Models; +using WebToTelegramCore.Interfaces; +using WebToTelegramCore.Models; using WebToTelegramCore.Options; namespace WebToTelegramCore.BotCommands diff --git a/WebToTelegramCore/Controllers/TelegramApiController.cs b/WebToTelegramCore/Controllers/TelegramApiController.cs index 6352d1a..962dcae 100644 --- a/WebToTelegramCore/Controllers/TelegramApiController.cs +++ b/WebToTelegramCore/Controllers/TelegramApiController.cs @@ -2,7 +2,7 @@ using System.Net; using System.Threading.Tasks; using Telegram.Bot.Types; -using WebToTelegramCore.Services; +using WebToTelegramCore.Interfaces; namespace WebToTelegramCore.Controllers { diff --git a/WebToTelegramCore/Controllers/WebApiController.cs b/WebToTelegramCore/Controllers/WebApiController.cs index 51b6197..ea2d424 100644 --- a/WebToTelegramCore/Controllers/WebApiController.cs +++ b/WebToTelegramCore/Controllers/WebApiController.cs @@ -3,8 +3,8 @@ using System.Net; using System.Threading.Tasks; using WebToTelegramCore.Exceptions; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Services; namespace WebToTelegramCore.Controllers { diff --git a/WebToTelegramCore/BotCommands/IBotCommand.cs b/WebToTelegramCore/Interfaces/IBotCommand.cs similarity index 96% rename from WebToTelegramCore/BotCommands/IBotCommand.cs rename to WebToTelegramCore/Interfaces/IBotCommand.cs index 09eda7a..bef8929 100644 --- a/WebToTelegramCore/BotCommands/IBotCommand.cs +++ b/WebToTelegramCore/Interfaces/IBotCommand.cs @@ -1,6 +1,6 @@ using WebToTelegramCore.Models; -namespace WebToTelegramCore.BotCommands +namespace WebToTelegramCore.Interfaces { /// /// Interface to implement various bot commands without arguments. diff --git a/WebToTelegramCore/Services/IFormatterService.cs b/WebToTelegramCore/Interfaces/IFormatterService.cs similarity index 93% rename from WebToTelegramCore/Services/IFormatterService.cs rename to WebToTelegramCore/Interfaces/IFormatterService.cs index cc2e5da..077e0bf 100644 --- a/WebToTelegramCore/Services/IFormatterService.cs +++ b/WebToTelegramCore/Interfaces/IFormatterService.cs @@ -1,4 +1,4 @@ -namespace WebToTelegramCore.Services +namespace WebToTelegramCore.Interfaces { /// /// Interface that exposes method to convert Markdown flavor that used in diff --git a/WebToTelegramCore/Services/IOwnApiService.cs b/WebToTelegramCore/Interfaces/IOwnApiService.cs similarity index 92% rename from WebToTelegramCore/Services/IOwnApiService.cs rename to WebToTelegramCore/Interfaces/IOwnApiService.cs index adec51a..7b52883 100644 --- a/WebToTelegramCore/Services/IOwnApiService.cs +++ b/WebToTelegramCore/Interfaces/IOwnApiService.cs @@ -1,6 +1,6 @@ using WebToTelegramCore.Models; -namespace WebToTelegramCore.Services +namespace WebToTelegramCore.Interfaces { /// /// Interface to implement non-Telegram web API handling. diff --git a/WebToTelegramCore/Services/ITelegramApiService.cs b/WebToTelegramCore/Interfaces/ITelegramApiService.cs similarity index 95% rename from WebToTelegramCore/Services/ITelegramApiService.cs rename to WebToTelegramCore/Interfaces/ITelegramApiService.cs index 6467854..f478afa 100644 --- a/WebToTelegramCore/Services/ITelegramApiService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramApiService.cs @@ -1,6 +1,6 @@ using Update = Telegram.Bot.Types.Update; -namespace WebToTelegramCore.Services +namespace WebToTelegramCore.Interfaces { /// /// Interface to implement Telegram webhook handling. diff --git a/WebToTelegramCore/Services/ITelegramBotService.cs b/WebToTelegramCore/Interfaces/ITelegramBotService.cs similarity index 96% rename from WebToTelegramCore/Services/ITelegramBotService.cs rename to WebToTelegramCore/Interfaces/ITelegramBotService.cs index 279327b..49edd57 100644 --- a/WebToTelegramCore/Services/ITelegramBotService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramBotService.cs @@ -1,4 +1,4 @@ -namespace WebToTelegramCore.Services +namespace WebToTelegramCore.Interfaces { /// /// Interface that exposes greatly simplified version of Telegram bot API. diff --git a/WebToTelegramCore/Services/ITokenGeneratorService.cs b/WebToTelegramCore/Interfaces/ITokenGeneratorService.cs similarity index 92% rename from WebToTelegramCore/Services/ITokenGeneratorService.cs rename to WebToTelegramCore/Interfaces/ITokenGeneratorService.cs index 25294dd..3fabe6e 100644 --- a/WebToTelegramCore/Services/ITokenGeneratorService.cs +++ b/WebToTelegramCore/Interfaces/ITokenGeneratorService.cs @@ -1,4 +1,4 @@ -namespace WebToTelegramCore.Services +namespace WebToTelegramCore.Interfaces { /// /// Interface to services that generate auth tokens. diff --git a/WebToTelegramCore/Services/FormatterService.cs b/WebToTelegramCore/Services/FormatterService.cs index b159676..356b76b 100644 --- a/WebToTelegramCore/Services/FormatterService.cs +++ b/WebToTelegramCore/Services/FormatterService.cs @@ -1,6 +1,7 @@ using System.IO; using System.Linq; using WebToTelegramCore.FormatterHelpers; +using WebToTelegramCore.Interfaces; namespace WebToTelegramCore.Services { diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index 84e7e3c..343d2f7 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Options; using System; using WebToTelegramCore.Exceptions; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index 3a527e8..0f33aba 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -6,6 +6,7 @@ using Telegram.Bot.Types.Enums; using WebToTelegramCore.BotCommands; +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index aaff85f..18edb8c 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -4,7 +4,7 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.InputFiles; - +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Options; namespace WebToTelegramCore.Services diff --git a/WebToTelegramCore/Services/TokenGeneratorService.cs b/WebToTelegramCore/Services/TokenGeneratorService.cs index b56c388..32791d3 100644 --- a/WebToTelegramCore/Services/TokenGeneratorService.cs +++ b/WebToTelegramCore/Services/TokenGeneratorService.cs @@ -1,6 +1,7 @@ using System; using System.Security.Cryptography; using System.Text; +using WebToTelegramCore.Interfaces; namespace WebToTelegramCore.Services { diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 0523cce..9e03ac0 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - +using WebToTelegramCore.Interfaces; using WebToTelegramCore.Options; using WebToTelegramCore.Services; @@ -35,7 +35,7 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); - // quick crutch -- Telegram.Bot's update class relies on some Newtonsoft attributes, + // Telegram.Bot's Update class relies on some Newtonsoft attributes, // so to deserialize it correctly, we need to use this library as well services.AddControllers().AddNewtonsoftJson(); From bd405872b05bb2d24f78bdd92e1c4ee86f68d843 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 12 Dec 2021 14:25:26 +0700 Subject: [PATCH 09/39] all relevant interfaces switched to be async, implementation updated to support multithreading (looooong overdue, i know) --- .../Controllers/TelegramApiController.cs | 2 +- .../Controllers/WebApiController.cs | 2 +- WebToTelegramCore/Interfaces/IOwnApiService.cs | 6 +++--- .../Interfaces/ITelegramApiService.cs | 5 +++-- .../Interfaces/ITelegramBotService.cs | 10 ++++++---- WebToTelegramCore/Services/OwnApiService.cs | 9 +++++---- WebToTelegramCore/Services/TelegramApiService.cs | 13 +++++++------ WebToTelegramCore/Services/TelegramBotService.cs | 16 +++++++++------- 8 files changed, 35 insertions(+), 28 deletions(-) diff --git a/WebToTelegramCore/Controllers/TelegramApiController.cs b/WebToTelegramCore/Controllers/TelegramApiController.cs index 962dcae..67235bb 100644 --- a/WebToTelegramCore/Controllers/TelegramApiController.cs +++ b/WebToTelegramCore/Controllers/TelegramApiController.cs @@ -45,7 +45,7 @@ public async Task HandleTelegramApi(string token, [FromBody] Updat } try { - _tgApi.HandleUpdate(update); + await _tgApi.HandleUpdate(update); return Ok(); } catch diff --git a/WebToTelegramCore/Controllers/WebApiController.cs b/WebToTelegramCore/Controllers/WebApiController.cs index ea2d424..6d273e2 100644 --- a/WebToTelegramCore/Controllers/WebApiController.cs +++ b/WebToTelegramCore/Controllers/WebApiController.cs @@ -37,7 +37,7 @@ public async Task HandleWebApi([FromBody] Request request) } try { - _ownApi.HandleRequest(request); + await _ownApi.HandleRequest(request); return Ok(); } catch (TokenNotFoundException) diff --git a/WebToTelegramCore/Interfaces/IOwnApiService.cs b/WebToTelegramCore/Interfaces/IOwnApiService.cs index 7b52883..f8d5150 100644 --- a/WebToTelegramCore/Interfaces/IOwnApiService.cs +++ b/WebToTelegramCore/Interfaces/IOwnApiService.cs @@ -1,4 +1,5 @@ -using WebToTelegramCore.Models; +using System.Threading.Tasks; +using WebToTelegramCore.Models; namespace WebToTelegramCore.Interfaces { @@ -11,7 +12,6 @@ public interface IOwnApiService /// Method to handle incoming request from the web API. /// /// Request to handle. - // TODO add async everywhere and try not to go mad in the process - void HandleRequest(Request request); + Task HandleRequest(Request request); } } diff --git a/WebToTelegramCore/Interfaces/ITelegramApiService.cs b/WebToTelegramCore/Interfaces/ITelegramApiService.cs index f478afa..a7a0aea 100644 --- a/WebToTelegramCore/Interfaces/ITelegramApiService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramApiService.cs @@ -1,4 +1,5 @@ -using Update = Telegram.Bot.Types.Update; +using System.Threading.Tasks; +using Update = Telegram.Bot.Types.Update; namespace WebToTelegramCore.Interfaces { @@ -19,6 +20,6 @@ public interface ITelegramApiService /// Method to handle incoming updates from the webhook. /// /// Received update. - void HandleUpdate(Update update); + Task HandleUpdate(Update update); } } diff --git a/WebToTelegramCore/Interfaces/ITelegramBotService.cs b/WebToTelegramCore/Interfaces/ITelegramBotService.cs index 49edd57..9825848 100644 --- a/WebToTelegramCore/Interfaces/ITelegramBotService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramBotService.cs @@ -1,4 +1,6 @@ -namespace WebToTelegramCore.Interfaces +using System.Threading.Tasks; + +namespace WebToTelegramCore.Interfaces { /// /// Interface that exposes greatly simplified version of Telegram bot API. @@ -11,14 +13,14 @@ public interface ITelegramBotService /// /// ID of account to send message to. /// Text of the message. - void Send(long accountId, string message); + Task Send(long accountId, string message); /// /// Sends a predefined sticker. Used as an easter egg with a 5% chance /// of message on unknown user input to be the sticker instead of text. /// /// ID of account to send sticker to. - void SendTheSticker(long accountId); + Task SendTheSticker(long accountId); /// /// Sends message in CommonMark as Markdown. Used only internally as a crutch @@ -26,6 +28,6 @@ public interface ITelegramBotService /// /// ID of account to send message to. /// Text of the message. - void SendPureMarkdown(long accountId, string message); + Task SendPureMarkdown(long accountId, string message); } } diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index 343d2f7..16834eb 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Options; using System; +using System.Threading.Tasks; using WebToTelegramCore.Exceptions; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; @@ -46,16 +47,16 @@ public OwnApiService(RecordContext context, ITelegramBotService bot, IOptions /// Request to handle. - public void HandleRequest(Request request) + public async Task HandleRequest(Request request) { - HandleRequestInternally(request); + await HandleRequestInternally(request); } /// /// Internal method to handle requests. /// /// Request to handle. - private void HandleRequestInternally(Request request) + private async Task HandleRequestInternally(Request request) { var record = _context.GetRecordByToken(request.Token); if (record == null) @@ -67,7 +68,7 @@ private void HandleRequestInternally(Request request) { record.LastSuccessTimestamp = DateTime.Now; record.UsageCounter--; - _bot.Send(record.AccountNumber, request.Message); + await _bot.Send(record.AccountNumber, request.Message); } else { diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index 0f33aba..52f9b14 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; @@ -101,7 +102,7 @@ public TelegramApiService(IOptions options, /// Method to handle incoming updates from the webhook. /// /// Received update. - public void HandleUpdate(Update update) + public async Task HandleUpdate(Update update) { // a few sanity checks: // only handles text messages, hopefully commands @@ -126,11 +127,11 @@ public void HandleUpdate(Update update) handler = _commands.SingleOrDefault(c => c.Command.Equals(commandText)); if (handler != null) { - _bot.SendPureMarkdown(userId.Value, handler.Process(userId.Value, record)); + await _bot.SendPureMarkdown(userId.Value, handler.Process(userId.Value, record)); } else { - HandleUnknownText(userId.Value, commandText); + await HandleUnknownText(userId.Value, commandText); } } @@ -152,17 +153,17 @@ public bool IsToken(string calledToken) /// User to reply to. /// Received message that was not processed /// by actual commands. - private void HandleUnknownText(long accountId, string text) + private async Task HandleUnknownText(long accountId, string text) { // suddenly, cat! if (new Random().Next(0, 19) == 0) { - _bot.SendTheSticker(accountId); + await _bot.SendTheSticker(accountId); } else { string reply = text.StartsWith("/") ? _invalidCommandReply : _invalidReply; - _bot.Send(accountId, reply); + await _bot.Send(accountId, reply); } } } diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index 18edb8c..fe39f94 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -61,7 +61,9 @@ public TelegramBotService(IOptions options, /// ~TelegramBotService() { - _client.DeleteWebhookAsync(); + // this probably never worked anyway + // TODO think of a better way to acquire/release webhooks + Task.Run(() => _client.DeleteWebhookAsync()).Wait(); } /// @@ -69,11 +71,11 @@ public TelegramBotService(IOptions options, /// /// ID of the account to send to. /// Markdown-formatted message. - public void Send(long accountId, string message) + public async Task Send(long accountId, string message) { // I think we have to promote account ID back to ID of chat with this bot var chatId = new ChatId(accountId); - _client.SendTextMessageAsync(chatId, _formatter.TransformToHtml(message), + await _client.SendTextMessageAsync(chatId, _formatter.TransformToHtml(message), ParseMode.Html, disableWebPagePreview: true); } @@ -81,10 +83,10 @@ public void Send(long accountId, string message) /// Method to send predefined sticker on behalf of the bot. /// /// ID of the account to send to. - public void SendTheSticker(long accountId) + public async Task SendTheSticker(long accountId) { var chatId = new ChatId(accountId); - _client.SendStickerAsync(chatId, _sticker); + await _client.SendStickerAsync(chatId, _sticker); } /// @@ -93,10 +95,10 @@ public void SendTheSticker(long accountId) /// /// ID of account to send message to. /// Text of the message. - public void SendPureMarkdown(long accountId, string message) + public async Task SendPureMarkdown(long accountId, string message) { var chatId = new ChatId(accountId); - _client.SendTextMessageAsync(chatId, message, ParseMode.Markdown, disableWebPagePreview: true); + await _client.SendTextMessageAsync(chatId, message, ParseMode.Markdown, disableWebPagePreview: true); } } } From 6fdbaff326c8a919330343a6215ccc8787d8c146 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 12 Dec 2021 15:16:35 +0700 Subject: [PATCH 10/39] fixed using the example database file from project root when debugging locally instead of the file copied to the output folder --- WebToTelegramCore/Program.cs | 9 +++++++-- WebToTelegramCore/appsettings.json | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/WebToTelegramCore/Program.cs b/WebToTelegramCore/Program.cs index e0725c1..b9b75ab 100644 --- a/WebToTelegramCore/Program.cs +++ b/WebToTelegramCore/Program.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using System.IO; +using System; namespace WebToTelegramCore { @@ -20,8 +20,13 @@ public static void Main(string[] args) { // quick fix to prevent using solution folder for configuration files // instead of built binary folder, where these files are overridden with juicy secrets - ContentRootPath = System.AppContext.BaseDirectory + ContentRootPath = AppContext.BaseDirectory }); + // this is used to force the code to use database from the current (e.g output for debug) directory + // instead of using the file in the project root every launch + // please not that this value ends with backslash, so in the connection string, + // file name goes straight after |DataDirectory|, no slashes of any kind + AppDomain.CurrentDomain.SetData("DataDirectory", AppContext.BaseDirectory); builder.WebHost.UseKestrel(kestrelOptions => { diff --git a/WebToTelegramCore/appsettings.json b/WebToTelegramCore/appsettings.json index 30a4cd6..4601d96 100644 --- a/WebToTelegramCore/appsettings.json +++ b/WebToTelegramCore/appsettings.json @@ -6,7 +6,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Data Source=database.sqlite" + "DefaultConnection": "Data Source=|DataDirectory|database.sqlite" }, "General": { "Token": "it's a secret", From b462d702bd13438e093f5915995506c2b87f8a10 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 12 Dec 2021 15:23:31 +0700 Subject: [PATCH 11/39] added an optional Silent switch to request model, current;y ignored --- WebToTelegramCore/Models/Request.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WebToTelegramCore/Models/Request.cs b/WebToTelegramCore/Models/Request.cs index 95c9e1a..e6a8d32 100644 --- a/WebToTelegramCore/Models/Request.cs +++ b/WebToTelegramCore/Models/Request.cs @@ -19,5 +19,7 @@ public class Request /// [Required, StringLength(4096)] public string Message { get; set; } + + public bool Silent { get; set; } = false; } } From 4b261410658195beb7beff2e464e894086f234f4 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 12 Dec 2021 15:44:59 +0700 Subject: [PATCH 12/39] integrated silent parameter to the code so it does something now --- WebToTelegramCore/Interfaces/ITelegramBotService.cs | 2 +- WebToTelegramCore/Services/OwnApiService.cs | 2 +- WebToTelegramCore/Services/TelegramApiService.cs | 5 ++++- WebToTelegramCore/Services/TelegramBotService.cs | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/WebToTelegramCore/Interfaces/ITelegramBotService.cs b/WebToTelegramCore/Interfaces/ITelegramBotService.cs index 9825848..a6f17e5 100644 --- a/WebToTelegramCore/Interfaces/ITelegramBotService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramBotService.cs @@ -13,7 +13,7 @@ public interface ITelegramBotService /// /// ID of account to send message to. /// Text of the message. - Task Send(long accountId, string message); + Task Send(long accountId, string message, bool silent); /// /// Sends a predefined sticker. Used as an easter egg with a 5% chance diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index 16834eb..4c45db5 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -68,7 +68,7 @@ private async Task HandleRequestInternally(Request request) { record.LastSuccessTimestamp = DateTime.Now; record.UsageCounter--; - await _bot.Send(record.AccountNumber, request.Message); + await _bot.Send(record.AccountNumber, request.Message, request.Silent); } else { diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index 52f9b14..ec72e60 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -163,7 +163,10 @@ private async Task HandleUnknownText(long accountId, string text) else { string reply = text.StartsWith("/") ? _invalidCommandReply : _invalidReply; - await _bot.Send(accountId, reply); + // this is sent immediately after user input, so is not silenced + // TODO consider either fully separating or fully merging sending messages from the app logic and api input + // (all other command-related text is currently sent via SendPureMarkdown) + await _bot.Send(accountId, reply, false); } } } diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index fe39f94..da8e2cb 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -71,12 +71,12 @@ public TelegramBotService(IOptions options, /// /// ID of the account to send to. /// Markdown-formatted message. - public async Task Send(long accountId, string message) + public async Task Send(long accountId, string message, bool silent) { // I think we have to promote account ID back to ID of chat with this bot var chatId = new ChatId(accountId); await _client.SendTextMessageAsync(chatId, _formatter.TransformToHtml(message), - ParseMode.Html, disableWebPagePreview: true); + ParseMode.Html, disableWebPagePreview: true, disableNotification: silent); } /// From 95c129d4db7668c9385cc30efbdddfbe353c1f60 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Tue, 14 Dec 2021 20:36:34 +0700 Subject: [PATCH 13/39] commented some code --- .../Exceptions/BandwidthExceededException.cs | 5 +++++ WebToTelegramCore/Exceptions/TokenNotFoundException.cs | 5 +++++ WebToTelegramCore/Interfaces/ITelegramBotService.cs | 1 + WebToTelegramCore/Models/Request.cs | 6 ++++++ WebToTelegramCore/Program.cs | 8 ++++++-- WebToTelegramCore/Services/TelegramBotService.cs | 1 + 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/WebToTelegramCore/Exceptions/BandwidthExceededException.cs b/WebToTelegramCore/Exceptions/BandwidthExceededException.cs index 6e70db0..521a79d 100644 --- a/WebToTelegramCore/Exceptions/BandwidthExceededException.cs +++ b/WebToTelegramCore/Exceptions/BandwidthExceededException.cs @@ -2,5 +2,10 @@ namespace WebToTelegramCore.Exceptions { + /// + /// Exception that is thrown by + /// when used have exceeded their allowed bandwidth. + /// Handled in the + /// public class BandwidthExceededException : Exception { } } diff --git a/WebToTelegramCore/Exceptions/TokenNotFoundException.cs b/WebToTelegramCore/Exceptions/TokenNotFoundException.cs index 401bc02..1372c67 100644 --- a/WebToTelegramCore/Exceptions/TokenNotFoundException.cs +++ b/WebToTelegramCore/Exceptions/TokenNotFoundException.cs @@ -2,5 +2,10 @@ namespace WebToTelegramCore.Exceptions { + /// + /// Exception that is thrown by + /// when user-supplied token does not match any registered in the database. + /// Handled in the + /// public class TokenNotFoundException : Exception { } } diff --git a/WebToTelegramCore/Interfaces/ITelegramBotService.cs b/WebToTelegramCore/Interfaces/ITelegramBotService.cs index a6f17e5..778558e 100644 --- a/WebToTelegramCore/Interfaces/ITelegramBotService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramBotService.cs @@ -13,6 +13,7 @@ public interface ITelegramBotService /// /// ID of account to send message to. /// Text of the message. + /// Flag to set whether to suppress the notification. Task Send(long accountId, string message, bool silent); /// diff --git a/WebToTelegramCore/Models/Request.cs b/WebToTelegramCore/Models/Request.cs index e6a8d32..7e41674 100644 --- a/WebToTelegramCore/Models/Request.cs +++ b/WebToTelegramCore/Models/Request.cs @@ -20,6 +20,12 @@ public class Request [Required, StringLength(4096)] public string Message { get; set; } + /// + /// Optional parameter to suppress the notification for the + /// message from the bot. If the to true, message will be silent. + /// Note that it's possible for the end used to mute the bot so + /// this setting will have no effect. + /// public bool Silent { get; set; } = false; } } diff --git a/WebToTelegramCore/Program.cs b/WebToTelegramCore/Program.cs index b9b75ab..c03d568 100644 --- a/WebToTelegramCore/Program.cs +++ b/WebToTelegramCore/Program.cs @@ -8,7 +8,7 @@ public class Program { public static void Main(string[] args) { - // hardcoded default + // hardcoded default, because 8080 and 8081 were already taken on my machine int port = 8082; if (args.Length == 2 && args[0].Equals("--port") && int.TryParse(args[1], out int nonDefPort)) @@ -22,14 +22,18 @@ public static void Main(string[] args) // instead of built binary folder, where these files are overridden with juicy secrets ContentRootPath = AppContext.BaseDirectory }); + // this is used to force the code to use database from the current (e.g output for debug) directory // instead of using the file in the project root every launch - // please not that this value ends with backslash, so in the connection string, + + // please note that this value ends with backslash, so in the connection string, // file name goes straight after |DataDirectory|, no slashes of any kind AppDomain.CurrentDomain.SetData("DataDirectory", AppContext.BaseDirectory); builder.WebHost.UseKestrel(kestrelOptions => { + // this won't work without a SSL proxy over it anyway, + // so listening to localhost only kestrelOptions.ListenLocalhost(port); }); diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index da8e2cb..021aa00 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -71,6 +71,7 @@ public TelegramBotService(IOptions options, /// /// ID of the account to send to. /// Markdown-formatted message. + /// Flag to set whether to suppress the notification. public async Task Send(long accountId, string message, bool silent) { // I think we have to promote account ID back to ID of chat with this bot From 5a2dcc174eec0efb7ab77910dfa9ae86ca31f540 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Tue, 14 Dec 2021 20:55:00 +0700 Subject: [PATCH 14/39] completely removed the infamous markdown-to-html pipeline --- .../FormatterHelpers/MarkdownTransformer.cs | 63 --------------- .../NonNestedHtmlLinkInlineRenderer.cs | 80 ------------------- .../TweakedCodeBlockRenderer.cs | 40 ---------- .../Interfaces/IFormatterService.cs | 17 ---- .../Services/FormatterService.cs | 62 -------------- .../Services/TelegramBotService.cs | 15 +--- WebToTelegramCore/Startup.cs | 1 - WebToTelegramCore/WebToTelegramCore.csproj | 1 - 8 files changed, 4 insertions(+), 275 deletions(-) delete mode 100644 WebToTelegramCore/FormatterHelpers/MarkdownTransformer.cs delete mode 100644 WebToTelegramCore/FormatterHelpers/NonNestedHtmlLinkInlineRenderer.cs delete mode 100644 WebToTelegramCore/FormatterHelpers/TweakedCodeBlockRenderer.cs delete mode 100644 WebToTelegramCore/Interfaces/IFormatterService.cs delete mode 100644 WebToTelegramCore/Services/FormatterService.cs diff --git a/WebToTelegramCore/FormatterHelpers/MarkdownTransformer.cs b/WebToTelegramCore/FormatterHelpers/MarkdownTransformer.cs deleted file mode 100644 index a7ac799..0000000 --- a/WebToTelegramCore/FormatterHelpers/MarkdownTransformer.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace WebToTelegramCore.FormatterHelpers -{ - /// - /// Class that converts Telegram's flavor of Markdown to CommonMark - /// understood by Markdig. - /// - public class MarkdownTransformer - { - /// - /// List of sequences to escape. Contains single asterisk and underscore, - /// as these are not formatting and should be left as-is. - /// - private static readonly List _toEscape = new List() - { - // * in regexes should be escaped - @"\*", - "_" - }; - - /// - /// List of regexes that will escape symbols from _toEscape. - /// - private readonly List _regexes = new List(); - - /// - /// Constructor that sets up used regexes. - /// - public MarkdownTransformer() - { - foreach (var p in _toEscape) - { - var r = new Regex($@"([^{p}])({p})([^{p}])"); - _regexes.Add(r); - } - } - - /// - /// Method to convert Telegram's markdown to CommonMark. - /// - /// String to convert. - /// String with converted formatting. - public string ToCommonMark(string TgMarkdown) - { - string result = TgMarkdown; - // escape single "*" and "_" - foreach (var regex in _regexes) - { - result = regex.Replace(result, @"$1\$2$3"); - } - - result = result - // fix italics - .Replace("__", "*") - // ensure triple backticks are on their own line to prevent misparsing - .Replace("```", "\n```\n"); - - return result; - } - } -} diff --git a/WebToTelegramCore/FormatterHelpers/NonNestedHtmlLinkInlineRenderer.cs b/WebToTelegramCore/FormatterHelpers/NonNestedHtmlLinkInlineRenderer.cs deleted file mode 100644 index 8e5bfef..0000000 --- a/WebToTelegramCore/FormatterHelpers/NonNestedHtmlLinkInlineRenderer.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Markdig.Renderers; -using Markdig.Syntax.Inlines; - -namespace WebToTelegramCore.FormatterHelpers -{ - /// - /// HTML renderer for Markdig's LinkInline that does not put any formatting - /// inside the link tag. - /// - public class NonNestedHtmlLinkInlineRenderer - : Markdig.Renderers.Html.Inlines.LinkInlineRenderer - { - /// - /// Method to render inline link. - /// - /// Renderer to use. - /// Link to render. - protected override void Write(HtmlRenderer renderer, LinkInline link) - { - if (renderer.EnableHtmlForInline) - { - renderer.Write(link.IsImage ? "\"");"); - } - } - else - { - if (renderer.EnableHtmlForInline) - { - if (AutoRelNoFollow) - { - renderer.Write(" rel=\"nofollow\""); - } - renderer.Write(">"); - } - // this block of code was the sole reason of re-implemening this method - // Telegram's API docs forbid nested HTML tags. Better safe than sorry. - var wasEnableHtmlForInline = renderer.EnableHtmlForInline; - renderer.EnableHtmlForInline = false; - renderer.WriteChildren(link); - renderer.EnableHtmlForInline = wasEnableHtmlForInline; - if (renderer.EnableHtmlForInline) - { - renderer.Write(""); - } - } - } - } -} diff --git a/WebToTelegramCore/FormatterHelpers/TweakedCodeBlockRenderer.cs b/WebToTelegramCore/FormatterHelpers/TweakedCodeBlockRenderer.cs deleted file mode 100644 index 8dccb3a..0000000 --- a/WebToTelegramCore/FormatterHelpers/TweakedCodeBlockRenderer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Markdig.Renderers; -using Markdig.Syntax; - -namespace WebToTelegramCore.FormatterHelpers -{ - /// - /// Class that renders multiline code blocks inside <pre> tags only - /// as requested by Telegram's API docs, - /// opposed to default <pre> and <code>. - /// - public class TweakedCodeBlockRenderer : Markdig.Renderers.Html.CodeBlockRenderer - { - /// - /// Method to render the code block. - /// - /// Renderer to use. - /// Code block to render. - protected override void Write(HtmlRenderer renderer, CodeBlock obj) - { - renderer.EnsureLine(); - // all code related to
was wiped out as it's unused here anyway - if (renderer.EnableHtmlForBlock) - { - renderer.Write(""); - } - - renderer.WriteLeafRawLines(obj, true, true); - - if (renderer.EnableHtmlForBlock) - { - renderer.WriteLine(""); - } - - renderer.EnsureLine(); - - } - } -} diff --git a/WebToTelegramCore/Interfaces/IFormatterService.cs b/WebToTelegramCore/Interfaces/IFormatterService.cs deleted file mode 100644 index 077e0bf..0000000 --- a/WebToTelegramCore/Interfaces/IFormatterService.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace WebToTelegramCore.Interfaces -{ - /// - /// Interface that exposes method to convert Markdown flavor that used in - /// Telegram clients to HTML. - /// - public interface IFormatterService - { - /// - /// Transforms Markdown to HTML. Expects Markdown like Telegram clients do: - /// **bold** and __italic__ are main differences from the CommonMark. - /// - /// Markdown-formatted text. - /// Text with same format but in HTML supported by Telegram's API. - string TransformToHtml(string TgFlavoredMarkdown); - } -} diff --git a/WebToTelegramCore/Services/FormatterService.cs b/WebToTelegramCore/Services/FormatterService.cs deleted file mode 100644 index 356b76b..0000000 --- a/WebToTelegramCore/Services/FormatterService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.IO; -using System.Linq; -using WebToTelegramCore.FormatterHelpers; -using WebToTelegramCore.Interfaces; - -namespace WebToTelegramCore.Services -{ - /// - /// Class that converts Markdown to HTML. - /// - public class FormatterService : IFormatterService - { - /// - /// Instance of MarkdownTransformer to use. - /// - private readonly MarkdownTransformer _transformer = new MarkdownTransformer(); - - /// - /// Transforms Markdown to HTML. Expects Markdown like Telegram clients do: - /// **bold** and __italic__ are main differences from the CommonMark. - /// - /// Markdown-formatted text. - /// Text with same format but in HTML supported by Telegram's API. - public string TransformToHtml(string TgFlavoredMarkdown) - { - // TODO think how to reuse these - TextWriter writer = new StringWriter(); - var renderer = new Markdig.Renderers.HtmlRenderer(writer); - - renderer = new Markdig.Renderers.HtmlRenderer(writer) - { - ImplicitParagraph = true - }; - - // replacing default renderes with our "fixed" versions - var toRemove = renderer.ObjectRenderers.Where(x => - x.GetType() == typeof(Markdig.Renderers.Html.CodeBlockRenderer) || - x.GetType() == typeof(Markdig.Renderers.Html.Inlines.LinkInlineRenderer)) - .ToList(); - foreach (var r in toRemove) - { - renderer.ObjectRenderers.Remove(r); - } - renderer.ObjectRenderers.Add(new NonNestedHtmlLinkInlineRenderer()); - renderer.ObjectRenderers.Add(new TweakedCodeBlockRenderer()); - - // setting up rendering pipeline - var pipeline = new Markdig.MarkdownPipelineBuilder().Build(); - pipeline.Setup(renderer); - - // actually rendering the HTML - var cm = _transformer.ToCommonMark(TgFlavoredMarkdown); - var doc = Markdig.Parsers.MarkdownParser.Parse(cm); - renderer.Render(doc); - writer.Flush(); - var formatted = writer.ToString(); - formatted = formatted - .Replace("
", " "); - return formatted; - } - } -} diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index 021aa00..3f826e7 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -31,23 +31,15 @@ public class TelegramBotService : ITelegramBotService /// private readonly InputOnlineFile _sticker = new InputOnlineFile(_theStickerID); - /// - /// Field to store used instance of formatter. - /// - private readonly IFormatterService _formatter; - /// /// Constructor that also sets up the webhook. /// /// Options to use. /// Formatter to use. - public TelegramBotService(IOptions options, - IFormatterService formatter) + public TelegramBotService(IOptions options) { _client = new TelegramBotClient(options.Value.Token); - _formatter = formatter; - // made unclear that "api" part is needed as well, shot myself in the leg 3 years after var webhookUrl = options.Value.ApiEndpointUrl + "/api/" + options.Value.Token; // don't know whether old version without firing an actual thread still worked, @@ -76,8 +68,9 @@ public async Task Send(long accountId, string message, bool silent) { // I think we have to promote account ID back to ID of chat with this bot var chatId = new ChatId(accountId); - await _client.SendTextMessageAsync(chatId, _formatter.TransformToHtml(message), - ParseMode.Html, disableWebPagePreview: true, disableNotification: silent); + // TODO + await _client.SendTextMessageAsync(chatId, "under construction ...sorry", + null, disableWebPagePreview: true, disableNotification: silent); } /// diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 9e03ac0..07d260b 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -30,7 +30,6 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddScoped(); services.AddScoped(); diff --git a/WebToTelegramCore/WebToTelegramCore.csproj b/WebToTelegramCore/WebToTelegramCore.csproj index 7bd2d3b..a65fa16 100644 --- a/WebToTelegramCore/WebToTelegramCore.csproj +++ b/WebToTelegramCore/WebToTelegramCore.csproj @@ -9,7 +9,6 @@ - From f747b70d91503c285d5b50b13d84e0cb61a03f67 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Tue, 14 Dec 2021 21:10:53 +0700 Subject: [PATCH 15/39] added a message parsing switch to the Request model which does nothing for now --- WebToTelegramCore/Data/MessageParsingType.cs | 20 ++++++++++++++++++++ WebToTelegramCore/Models/Request.cs | 12 +++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 WebToTelegramCore/Data/MessageParsingType.cs diff --git a/WebToTelegramCore/Data/MessageParsingType.cs b/WebToTelegramCore/Data/MessageParsingType.cs new file mode 100644 index 0000000..741c894 --- /dev/null +++ b/WebToTelegramCore/Data/MessageParsingType.cs @@ -0,0 +1,20 @@ +namespace WebToTelegramCore.Data +{ + /// + /// Represents available parsing modes for the user message + /// to be sent via Telegram's API. + /// + public enum MessageParsingType + { + /// + /// Text as is, no formatting. + /// + Plaintext, + /// + /// Text with some formatting to be displayed. + /// Please note that this is Telegram's own "MarkdownV2" flavour, + /// see https://core.telegram.org/bots/api#markdownv2-style for reference. + /// + Markdown + } +} diff --git a/WebToTelegramCore/Models/Request.cs b/WebToTelegramCore/Models/Request.cs index 7e41674..922702e 100644 --- a/WebToTelegramCore/Models/Request.cs +++ b/WebToTelegramCore/Models/Request.cs @@ -1,4 +1,7 @@ -using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.ComponentModel.DataAnnotations; +using WebToTelegramCore.Data; namespace WebToTelegramCore.Models { @@ -27,5 +30,12 @@ public class Request /// this setting will have no effect. /// public bool Silent { get; set; } = false; + + /// + /// Optional parameter to set parsing mode of the message. + /// Defaults to plaintext if not specified. + /// + [JsonConverter(typeof(StringEnumConverter))] + public MessageParsingType Type { get; set; } = MessageParsingType.Plaintext; } } From f49c5d55e61fe73b0b8af0b25951d7a110ea40a9 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Tue, 14 Dec 2021 21:50:59 +0700 Subject: [PATCH 16/39] actually implemented sending messages in one of two parsing modes --- .../Interfaces/ITelegramBotService.cs | 4 +++- WebToTelegramCore/Services/OwnApiService.cs | 2 +- .../Services/TelegramApiService.cs | 2 +- .../Services/TelegramBotService.cs | 24 +++++++++++++++---- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/WebToTelegramCore/Interfaces/ITelegramBotService.cs b/WebToTelegramCore/Interfaces/ITelegramBotService.cs index 778558e..aad401c 100644 --- a/WebToTelegramCore/Interfaces/ITelegramBotService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramBotService.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using WebToTelegramCore.Data; namespace WebToTelegramCore.Interfaces { @@ -14,7 +15,8 @@ public interface ITelegramBotService /// ID of account to send message to. /// Text of the message. /// Flag to set whether to suppress the notification. - Task Send(long accountId, string message, bool silent); + /// Formatting type used in the message. + Task Send(long accountId, string message, bool silent, MessageParsingType parsingType); /// /// Sends a predefined sticker. Used as an easter egg with a 5% chance diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index 4c45db5..193d487 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -68,7 +68,7 @@ private async Task HandleRequestInternally(Request request) { record.LastSuccessTimestamp = DateTime.Now; record.UsageCounter--; - await _bot.Send(record.AccountNumber, request.Message, request.Silent); + await _bot.Send(record.AccountNumber, request.Message, request.Silent, request.Type); } else { diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index ec72e60..7acef2e 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -166,7 +166,7 @@ private async Task HandleUnknownText(long accountId, string text) // this is sent immediately after user input, so is not silenced // TODO consider either fully separating or fully merging sending messages from the app logic and api input // (all other command-related text is currently sent via SendPureMarkdown) - await _bot.Send(accountId, reply, false); + await _bot.Send(accountId, reply, false, Data.MessageParsingType.Plaintext); } } } diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index 3f826e7..1e8ec7e 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -1,9 +1,11 @@ using Microsoft.Extensions.Options; +using System; using System.Threading.Tasks; using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.InputFiles; +using WebToTelegramCore.Data; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Options; @@ -64,13 +66,15 @@ public TelegramBotService(IOptions options) /// ID of the account to send to. /// Markdown-formatted message. /// Flag to set whether to suppress the notification. - public async Task Send(long accountId, string message, bool silent) + /// Formatting type used in the message. + public async Task Send(long accountId, string message, bool silent, MessageParsingType parsingType) { // I think we have to promote account ID back to ID of chat with this bot var chatId = new ChatId(accountId); - // TODO - await _client.SendTextMessageAsync(chatId, "under construction ...sorry", - null, disableWebPagePreview: true, disableNotification: silent); + + await _client.SendTextMessageAsync(chatId, message, + ResolveRequestParseMode(parsingType), disableWebPagePreview: true, + disableNotification: silent); } /// @@ -94,5 +98,17 @@ public async Task SendPureMarkdown(long accountId, string message) var chatId = new ChatId(accountId); await _client.SendTextMessageAsync(chatId, message, ParseMode.Markdown, disableWebPagePreview: true); } + + private ParseMode? ResolveRequestParseMode(MessageParsingType fromRequest) + { + return fromRequest switch + { + MessageParsingType.Plaintext => null, + MessageParsingType.Markdown => ParseMode.MarkdownV2, + // should never happen, but for sake of completeness, + // fall back to plaintext + _ => null + }; + } } } From 7d52a27709a97845cf3a1692bc43944b372eb904 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Tue, 14 Dec 2021 22:17:47 +0700 Subject: [PATCH 17/39] messages on behalf of the bot changed to also use the markdownV2 via general (now one and only) method, strings change: now unused statuses removed, rest converted to markdownV2 --- WebToTelegramCore/BotCommands/AboutCommand.cs | 13 +++--- .../Controllers/TelegramApiController.cs | 1 - .../Interfaces/ITelegramBotService.cs | 16 ++----- .../Options/LocalizationOptions.cs | 21 --------- .../Services/TelegramApiService.cs | 4 +- .../Services/TelegramBotService.cs | 14 +----- WebToTelegramCore/appsettings.json | 46 +++++++++---------- 7 files changed, 36 insertions(+), 79 deletions(-) diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index f92ca38..2d1c6cf 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -13,11 +13,12 @@ public class AboutCommand : BotCommandBase, IBotCommand { /// /// Template to message, {0} is assembly version. + /// Written in Telegram's MarkdownV2 with a lot of escaping. /// - private const string _template = "**Dotnet Telegram forwarder** v. {0}\n\n" + - "[Open-source!](https://github.com/bnfour/dotnet-telegram-forwarder) " + - "Powered by .NET 6.0!!!\n" + - "Written by bnfour, August, October 2018; May 2020; December 2021."; + private const string _template = "**Dotnet Telegram forwarder** v\\. {0}\n\n" + + "[Open\\-source\\!](https://github.com/bnfour/dotnet-telegram-forwarder) " + + "Powered by \\.NET 6\\.0\\!\\!\\!\n" + + "Written by bnfour, August, October 2018; May 2020; December 2021\\."; /// /// Command's text. @@ -37,11 +38,11 @@ public AboutCommand(LocalizationOptions locale) : base(locale) { } /// Unused here. /// Record associated with user who sent the command. /// Unused here. - /// Text of message that should be returned to user. + /// Text of message that should be returned to user, with '.' escaped for MarkdownV2 public override string Process(long userId, Record record) { var version = Assembly.GetExecutingAssembly().GetName().Version; - var prettyVersion = $"{version.Major}.{version.Minor}"; + var prettyVersion = $"{version.Major}\\.{version.Minor}"; return base.Process(userId, record) ?? String.Format(_template, prettyVersion); } } diff --git a/WebToTelegramCore/Controllers/TelegramApiController.cs b/WebToTelegramCore/Controllers/TelegramApiController.cs index 67235bb..0d61d5f 100644 --- a/WebToTelegramCore/Controllers/TelegramApiController.cs +++ b/WebToTelegramCore/Controllers/TelegramApiController.cs @@ -52,7 +52,6 @@ public async Task HandleTelegramApi(string token, [FromBody] Updat { return StatusCode((int)HttpStatusCode.InternalServerError); } - } } } diff --git a/WebToTelegramCore/Interfaces/ITelegramBotService.cs b/WebToTelegramCore/Interfaces/ITelegramBotService.cs index aad401c..9329ab1 100644 --- a/WebToTelegramCore/Interfaces/ITelegramBotService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramBotService.cs @@ -14,9 +14,11 @@ public interface ITelegramBotService /// /// ID of account to send message to. /// Text of the message. - /// Flag to set whether to suppress the notification. - /// Formatting type used in the message. - Task Send(long accountId, string message, bool silent, MessageParsingType parsingType); + /// Flag to set whether to suppress the notification. + /// Like the API defaults to false for bot responses, which are sent immediately after user's message. + /// Formatting type used in the message. + /// Unlike the API, defaults for Markdown for bot responses, which do use some formatting. + Task Send(long accountId, string message, bool silent = false, MessageParsingType parsingType = MessageParsingType.Markdown); /// /// Sends a predefined sticker. Used as an easter egg with a 5% chance @@ -24,13 +26,5 @@ public interface ITelegramBotService /// /// ID of account to send sticker to. Task SendTheSticker(long accountId); - - /// - /// Sends message in CommonMark as Markdown. Used only internally as a crutch - /// to display properly formatteded pre-defined messages. HTML breaks them :( - /// - /// ID of account to send message to. - /// Text of the message. - Task SendPureMarkdown(long accountId, string message); } } diff --git a/WebToTelegramCore/Options/LocalizationOptions.cs b/WebToTelegramCore/Options/LocalizationOptions.cs index 8001d1d..0d8f42d 100644 --- a/WebToTelegramCore/Options/LocalizationOptions.cs +++ b/WebToTelegramCore/Options/LocalizationOptions.cs @@ -91,27 +91,6 @@ public class LocalizationOptions /// public string RegenerationPending { get; set; } - /// - /// Human-readable representation of ResponseState.BandwidthExceeded enum member. - /// - public string RequestBandwidthExceeded { get; set; } - - /// - /// Human-readable representation of ResponseState.NoSuchToken enum member. - /// - public string RequestNoToken { get; set; } - - /// - /// Human-readable representation of ResponseState.OkSent enum member. - /// - public string RequestOk { get; set; } - - /// - /// Human-readable representation of ResponseState.SomethingBadHappened - /// enum member. - /// - public string RequestWhat { get; set; } - /// /// Message to warn new users that registration is closed. /// diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index 7acef2e..f7c31a2 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -127,7 +127,7 @@ public async Task HandleUpdate(Update update) handler = _commands.SingleOrDefault(c => c.Command.Equals(commandText)); if (handler != null) { - await _bot.SendPureMarkdown(userId.Value, handler.Process(userId.Value, record)); + await _bot.Send(userId.Value, handler.Process(userId.Value, record)); } else { @@ -166,7 +166,7 @@ private async Task HandleUnknownText(long accountId, string text) // this is sent immediately after user input, so is not silenced // TODO consider either fully separating or fully merging sending messages from the app logic and api input // (all other command-related text is currently sent via SendPureMarkdown) - await _bot.Send(accountId, reply, false, Data.MessageParsingType.Plaintext); + await _bot.Send(accountId, reply); } } } diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index 1e8ec7e..42e9475 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -67,7 +67,7 @@ public TelegramBotService(IOptions options) /// Markdown-formatted message. /// Flag to set whether to suppress the notification. /// Formatting type used in the message. - public async Task Send(long accountId, string message, bool silent, MessageParsingType parsingType) + public async Task Send(long accountId, string message, bool silent = false, MessageParsingType parsingType = MessageParsingType.Markdown) { // I think we have to promote account ID back to ID of chat with this bot var chatId = new ChatId(accountId); @@ -87,18 +87,6 @@ public async Task SendTheSticker(long accountId) await _client.SendStickerAsync(chatId, _sticker); } - /// - /// Sends message in CommonMark as Markdown. Used only internally as a crutch - /// to display properly formatteded pre-defined messages. HTML breaks them :( - /// - /// ID of account to send message to. - /// Text of the message. - public async Task SendPureMarkdown(long accountId, string message) - { - var chatId = new ChatId(accountId); - await _client.SendTextMessageAsync(chatId, message, ParseMode.Markdown, disableWebPagePreview: true); - } - private ParseMode? ResolveRequestParseMode(MessageParsingType fromRequest) { return fromRequest switch diff --git a/WebToTelegramCore/appsettings.json b/WebToTelegramCore/appsettings.json index 4601d96..bfa5657 100644 --- a/WebToTelegramCore/appsettings.json +++ b/WebToTelegramCore/appsettings.json @@ -18,30 +18,26 @@ "SecondsPerRegeneration": 60 }, "Strings": { - "CancelDeletion": "Token deletion cancelled.", - "CancelRegeneration": "Token regeneration cancelled.", - "ConfirmDeletion": "Token deleted. Thank you for giving us an oppurtunity to serve you.", - "ConfirmRegeneration": "Your new token is\n\n`{0}`\n\nDon't forget to update your clients' settings.", - "CreateGoAway": "This instance of bot is not accepting new users for now.", - "CreateSuccess": "Success! Your token is:\n\n`{0}`\n\nPlease consult /token for usage.", - "DeletionNoTurningBack": "This bot has registration turned *off*. You won't be able to create new token. Please be certain.", - "DeletionPending": "*Token deletion pending!*\n\nPlease either /confirm or /cancel it. It cannot be undone.\nIf you need to change your token, please consider to /regenerate it instead of deleting and re-creating it.", - "ErrorConfirmationPending": "You have an operation pending cancellation. Please /confirm or /cancel it before using other commands.", - "ErrorDave": "I'm afraid I can't let you do that.", - "ErrorMustBeGuest": "In order to use this command, you must have no token associated with your account. You can /delete your existing one, but why?", - "ErrorMustBeUser": "In order to use this command, you must have a token associated with your account. Try running /create first.", - "ErrorNoConfirmationPending": "This command is only useful when you're trying to /delete or /regenerate your token.", - "ErrorWhat": "Unfortunately, I'm not sure how to interpret this.", - "Help": "This bot provides web API to route messages from API's endpoint to you in Telegram via bot. It can be used to notify you in Telegram from any Internet-enabled device you want (provided you know how to make POST requests from it).\nTo start, /create your token (if this particular instance of bot has registration of new users open). You can /delete it anytime. To change your token for whatever reason, please /regenerate it and not delete and re-create.\nOnce you have a token, see /token for additional usage help.\nOther commands supported by bot include:\n- /confirm and /cancel are used to prevent accidental deletions and regenerations;\n- /help displays this message;\n- /about displays general info about this bot.\n\nThere's also an easter egg command and a rare response to unknown commands: one way to satisfy your curiosity is to check out bot's source code.\n\n--bnfour", - "RegenerationPending": "*Token regenration pending!*\n\nPlease either /confirm or /cancel it. It cannot be undone. Please be certain.", - "RequestBandwidthExceeded": "Too many messages. Try again later.", - "RequestNoToken": "Invalid token.", - "RequestOk": "Request accepted.", - "RequestWhat": "[UNUSED] Something bad happened", - "StartGoAway": "Sorry, this instance of bot is not accepting new users for now.", - "StartMessage": "Hello!\n\nThis bot provides a standalone web API to relay messages from whatever you'll use it from to Telegram as messages from the bot. It might come in handy to unify your notifications in one place.\n\n*Please note*: this requires some external tools. If you consider phrases like \"Send a POST request to the endpoint with JSON body with two string fields\" a magic gibberish you don't understand, this bot probably isn't much of use to you.", - "StartRegistrationHint": "If that does not stop you, feel free to /create your very own token.", - "TokenErrorsDescription": "If you send a malformed request, the API will return `400 Bad Request`. If request is properly formed, `200 OK` is returned, along with a response in JSON. The response contains three fields:\n- *Ok: boolean.* If `true`, your message is received and will be sent via bot shortly. If `false`, something went wrong, see the next field.\n- *Code: int.* Represents various error codes:\n-- 0: No error. Sent when *Ok* is true;\n-- 1: No such token found in database. Your client should not retry same request but should ask user to double-check their settings;\n-- 2: Bandwidth exceeded. Bot's throughput is limited. You shouldn't see this unless you send *a lot* of messages, but when you do, please wait a few minutes before retrying your request.\n- *Details: string.* Human-readable description of *Code* field, similar to the text above.\n\n There's also always a possibility of `500 Internal Server Error`.", - "TokenTemplate": "Your token is\n\n`{0}`\n\n*Usage*:\nSend a POST request to {1} with JSON body. Body must contain two string fields: \"token\" with your token, and \"message\" with text to send via bot.\n\n*Example*:\n```\n{{\n \"token\": \"{0}\",\n \"message\": \"{2}\"\n}}\n```" + "CancelDeletion": "Token deletion cancelled\\.", + "CancelRegeneration": "Token regeneration cancelled\\.", + "ConfirmDeletion": "Token deleted\\. Thank you for giving us an oppurtunity to serve you\\.", + "ConfirmRegeneration": "Your new token is\n\n`{0}`\n\nDon't forget to update your clients' settings\\.", + "CreateGoAway": "This instance of bot is not accepting new users for now\\.", + "CreateSuccess": "Success\\! Your token is:\n\n`{0}`\n\nPlease consult /token for usage\\.", + "DeletionNoTurningBack": "This bot has registration turned *off*\\. You won't be able to create new token\\. Please be certain\\.", + "DeletionPending": "*Token deletion pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\.\nIf you need to change your token, please consider to /regenerate it instead of deleting and re\\-creating it\\.", + "ErrorConfirmationPending": "You have an operation pending cancellation\\. Please /confirm or /cancel it before using other commands\\.", + "ErrorDave": "I'm afraid I can't let you do that\\.", + "ErrorMustBeGuest": "In order to use this command, you must have no token associated with your account\\. You can /delete your existing one, but why?", + "ErrorMustBeUser": "In order to use this command, you must have a token associated with your account\\. Try running /create first\\.", + "ErrorNoConfirmationPending": "This command is only useful when you're trying to /delete or /regenerate your token\\.", + "ErrorWhat": "Unfortunately, I'm not sure how to interpret this\\.", + "Help": "This bot provides web API to route messages from API's endpoint to you in Telegram via bot\\. It can be used to notify you in Telegram from any Internet\\-enabled device you want \\(provided you know how to make POST requests from it\\)\\.\nTo start, /create your token \\(if this particular instance of bot has registration of new users open\\)\\. You can /delete it anytime\\. To change your token for whatever reason, please /regenerate it and not delete and re\\-create\\.\nOnce you have a token, see /token for additional usage help\\.\nOther commands supported by bot include:\n\\- /confirm and /cancel are used to prevent accidental deletions and regenerations;\n\\- /help displays this message;\n\\- /about displays general info about this bot\\.\n\nThere's also an easter egg command and a rare response to unknown commands: one way to satisfy your curiosity is to check out bot's source code\\.\n\n\\-\\-bnfour", + "RegenerationPending": "*Token regenration pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\. Please be certain\\.", + "StartGoAway": "Sorry, this instance of bot is not accepting new users for now\\.", + "StartMessage": "Hello\\!\n\nThis bot provides a standalone web API to relay messages from whatever you'll use it from to Telegram as messages from the bot\\. It might come in handy to unify your notifications in one place\\.\n\n*Please note*: this requires some external tools\\. If you consider phrases like \"Send a POST request to the endpoint with JSON body with two string fields\" a magic gibberish you don't understand, this bot probably isn't much of use to you\\.", + "StartRegistrationHint": "If that does not stop you, feel free to /create your very own token\\.", + "TokenErrorsDescription": "TODO updated description", + "TokenTemplate": "TODO also updated\\." } } From e30fd3feb80edab30a8a71bc78024f16e0f9024e Mon Sep 17 00:00:00 2001 From: Bn4 Date: Tue, 14 Dec 2021 22:50:22 +0700 Subject: [PATCH 18/39] display errors from malformed markdown _from user_ as HTTP 400, not 500 --- WebToTelegramCore/Controllers/WebApiController.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WebToTelegramCore/Controllers/WebApiController.cs b/WebToTelegramCore/Controllers/WebApiController.cs index 6d273e2..501581d 100644 --- a/WebToTelegramCore/Controllers/WebApiController.cs +++ b/WebToTelegramCore/Controllers/WebApiController.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using System; using System.Net; using System.Threading.Tasks; using WebToTelegramCore.Exceptions; @@ -48,7 +47,12 @@ public async Task HandleWebApi([FromBody] Request request) { return StatusCode((int)HttpStatusCode.TooManyRequests); } - catch (Exception) + // if the formatting is wrong, we may catch this + catch (Telegram.Bot.Exceptions.ApiRequestException) + { + return BadRequest(); + } + catch { return StatusCode((int)HttpStatusCode.InternalServerError); } From 29358963583de48239ddb812f8e3a11d218d1f21 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Wed, 15 Dec 2021 21:51:25 +0700 Subject: [PATCH 19/39] minor comment fixed that waited a day to be committed T_T --- WebToTelegramCore/Controllers/WebApiController.cs | 1 + WebToTelegramCore/Services/TelegramApiService.cs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/WebToTelegramCore/Controllers/WebApiController.cs b/WebToTelegramCore/Controllers/WebApiController.cs index 501581d..b77e627 100644 --- a/WebToTelegramCore/Controllers/WebApiController.cs +++ b/WebToTelegramCore/Controllers/WebApiController.cs @@ -48,6 +48,7 @@ public async Task HandleWebApi([FromBody] Request request) return StatusCode((int)HttpStatusCode.TooManyRequests); } // if the formatting is wrong, we may catch this + // TODO maybe narrow down to the specific message, if there are more cases when this is thrown catch (Telegram.Bot.Exceptions.ApiRequestException) { return BadRequest(); diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index f7c31a2..f1e96a9 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -163,9 +163,6 @@ private async Task HandleUnknownText(long accountId, string text) else { string reply = text.StartsWith("/") ? _invalidCommandReply : _invalidReply; - // this is sent immediately after user input, so is not silenced - // TODO consider either fully separating or fully merging sending messages from the app logic and api input - // (all other command-related text is currently sent via SendPureMarkdown) await _bot.Send(accountId, reply); } } From 82e3636983dcbe381c66cc3db006ee0a347425a5 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Fri, 17 Dec 2021 22:22:18 +0700 Subject: [PATCH 20/39] way better external webhook management: now set and removed at app's startup and closed events instead of service's constructor and destructor (i'm almost certain it didn't work anyway) --- .../Interfaces/ITelegramBotService.cs | 14 ++++++++ WebToTelegramCore/Program.cs | 7 ++++ .../Services/TelegramBotService.cs | 33 +++++++++++-------- WebToTelegramCore/Startup.cs | 2 -- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/WebToTelegramCore/Interfaces/ITelegramBotService.cs b/WebToTelegramCore/Interfaces/ITelegramBotService.cs index 9329ab1..8fb0c02 100644 --- a/WebToTelegramCore/Interfaces/ITelegramBotService.cs +++ b/WebToTelegramCore/Interfaces/ITelegramBotService.cs @@ -26,5 +26,19 @@ public interface ITelegramBotService /// /// ID of account to send sticker to. Task SendTheSticker(long accountId); + + /// + /// Method to manage external webhook for the Telegram API. + /// Part one: called to set the webhook on application start. + /// + Task SetExternalWebhook(); + + /// + /// Method to manage external webhook for the Telegram API. + /// Part two: called to remove the webhook gracefully on application exit. + /// Not that it makes much sense in an app designed to be run 24/7, but is + /// nice to have nonetheless. + /// + Task ClearExternalWebhook(); } } diff --git a/WebToTelegramCore/Program.cs b/WebToTelegramCore/Program.cs index c03d568..98a9de0 100644 --- a/WebToTelegramCore/Program.cs +++ b/WebToTelegramCore/Program.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using System; +using WebToTelegramCore.Interfaces; namespace WebToTelegramCore { @@ -45,6 +46,12 @@ public static void Main(string[] args) var app = builder.Build(); app.MapControllers(); + app.Lifetime.ApplicationStarted.Register(async () => + await (app.Services.GetService(typeof(ITelegramBotService)) as ITelegramBotService).SetExternalWebhook()); + + app.Lifetime.ApplicationStopped.Register(async () => + await (app.Services.GetService(typeof(ITelegramBotService)) as ITelegramBotService).ClearExternalWebhook()); + app.Run(); } } diff --git a/WebToTelegramCore/Services/TelegramBotService.cs b/WebToTelegramCore/Services/TelegramBotService.cs index 42e9475..893362a 100644 --- a/WebToTelegramCore/Services/TelegramBotService.cs +++ b/WebToTelegramCore/Services/TelegramBotService.cs @@ -34,30 +34,37 @@ public class TelegramBotService : ITelegramBotService private readonly InputOnlineFile _sticker = new InputOnlineFile(_theStickerID); /// - /// Constructor that also sets up the webhook. + /// Field to store our webhook URL to be advertised to Telegram's API. + /// + private readonly string _webhookUrl; + + /// + /// Constructor that get the options required for this service to operate. /// /// Options to use. - /// Formatter to use. public TelegramBotService(IOptions options) { _client = new TelegramBotClient(options.Value.Token); - // made unclear that "api" part is needed as well, shot myself in the leg 3 years after - var webhookUrl = options.Value.ApiEndpointUrl + "/api/" + options.Value.Token; - // don't know whether old version without firing an actual thread still worked, - // it was changed as i tried to debug a "connection" issue where wrong config file was loaded - Task.Run(() => _client.SetWebhookAsync(webhookUrl, - allowedUpdates: new[] { UpdateType.Message })).Wait(); + _webhookUrl = options.Value.ApiEndpointUrl + "/api/" + options.Value.Token; + } + + /// + /// Method to manage external webhook for the Telegram API. + /// Part one: called to set the webhook on application start. + /// + public async Task SetExternalWebhook() + { + await _client.SetWebhookAsync(_webhookUrl, allowedUpdates: new[] { UpdateType.Message }); } /// - /// Destructor that removes the webhook. + /// Method to manage external webhook for the Telegram API. + /// Part two: called to remove the webhook gracefully on application exit. /// - ~TelegramBotService() + public async Task ClearExternalWebhook() { - // this probably never worked anyway - // TODO think of a better way to acquire/release webhooks - Task.Run(() => _client.DeleteWebhookAsync()).Wait(); + await _client.DeleteWebhookAsync(); } /// diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 07d260b..9fb0b26 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -21,8 +21,6 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); - // singleton makes changes to non-db properties persistent services.AddDbContext(options => options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")), From b806c87eb04cdbec2c34dbfe46e84f590fd26a09 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Fri, 17 Dec 2021 22:37:31 +0700 Subject: [PATCH 21/39] converted token generator service to transient one instead of a singleton, way shorted secure random lifetime --- .../Services/TokenGeneratorService.cs | 14 ++++++-------- WebToTelegramCore/Startup.cs | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/WebToTelegramCore/Services/TokenGeneratorService.cs b/WebToTelegramCore/Services/TokenGeneratorService.cs index 32791d3..62498b0 100644 --- a/WebToTelegramCore/Services/TokenGeneratorService.cs +++ b/WebToTelegramCore/Services/TokenGeneratorService.cs @@ -22,19 +22,13 @@ internal class TokenGeneratorService : ITokenGeneratorService private static readonly char[] _alphabet = ("0123456789" + "+=" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz").ToCharArray(); - /// - /// Strong random instance used to generate random tokens. - /// - private readonly RandomNumberGenerator _random; - /// /// Class constructor. /// public TokenGeneratorService() { - // let's pretend we're serious business for a moment - _random = RandomNumberGenerator.Create(); // sanity check for random evenness + // TODO consider it to be a warning instead of an exception if (256 % _alphabet.Length != 0) { throw new ApplicationException("Selected alphabet does not map evenly " + @@ -49,7 +43,11 @@ public TokenGeneratorService() public string Generate() { var randomBytes = new byte[_tokenLength]; - _random.GetBytes(randomBytes); + // let's pretend we're serious business for a moment + using (var random = RandomNumberGenerator.Create()) + { + random.GetBytes(randomBytes); + } var sb = new StringBuilder(); foreach (var b in randomBytes) diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 9fb0b26..94c2ae3 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -26,12 +26,13 @@ public void ConfigureServices(IServiceCollection services) options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Singleton, ServiceLifetime.Singleton); - services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); + services.AddTransient(); + // Telegram.Bot's Update class relies on some Newtonsoft attributes, // so to deserialize it correctly, we need to use this library as well services.AddControllers().AddNewtonsoftJson(); From cf6e78702830a966043e89518c232b2108130d5c Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 18 Dec 2021 00:27:43 +0700 Subject: [PATCH 22/39] made context async --- WebToTelegramCore/RecordContext.cs | 28 ++++--------------- WebToTelegramCore/Services/OwnApiService.cs | 2 +- .../Services/TelegramApiService.cs | 2 +- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/WebToTelegramCore/RecordContext.cs b/WebToTelegramCore/RecordContext.cs index aaa37e1..e4ba5dc 100644 --- a/WebToTelegramCore/RecordContext.cs +++ b/WebToTelegramCore/RecordContext.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using System; using System.Linq; - +using System.Threading.Tasks; using WebToTelegramCore.Models; namespace WebToTelegramCore @@ -44,18 +44,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) /// /// Token to search for in the DB. /// Associated Record or null if none found. - public Record GetRecordByToken(string token) + public async Task GetRecordByToken(string token) { - Record ret; - try - { - ret = Records.Single(r => r.Token.Equals(token)); - } - catch (InvalidOperationException) - { - return null; - } - return ret; + return await Records.SingleOrDefaultAsync(r => r.Token.Equals(token)); } /// @@ -63,18 +54,9 @@ public Record GetRecordByToken(string token) /// /// ID to search for. /// Associated Record or null if none present. - public Record GetRecordByAccountId(long accountId) + public async Task GetRecordByAccountId(long accountId) { - Record ret; - try - { - ret = Records.Single(r => r.AccountNumber == accountId); - } - catch (InvalidOperationException) - { - return null; - } - return ret; + return await Records.SingleOrDefaultAsync(r => r.AccountNumber == accountId); } } } diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index 193d487..7fcbf5f 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -58,7 +58,7 @@ public async Task HandleRequest(Request request) /// Request to handle. private async Task HandleRequestInternally(Request request) { - var record = _context.GetRecordByToken(request.Token); + var record = await _context.GetRecordByToken(request.Token); if (record == null) { throw new TokenNotFoundException(); diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index f1e96a9..f943ed3 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -119,7 +119,7 @@ public async Task HandleUpdate(Update update) return; } // null check was done above, it's safe to use userId.Value directly - Record record = _context.GetRecordByAccountId(userId.Value); + Record record = await _context.GetRecordByAccountId(userId.Value); IBotCommand handler = null; string commandText = text.Split(' ').FirstOrDefault(); From eda10cc40565b80f31cc61924a987f254e93910d Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 18 Dec 2021 01:57:28 +0700 Subject: [PATCH 23/39] fixed an absolute abomination of injecting bandwidth options by moving related code to helper service instead of [long series of bleeps] i did in Record class 3 years ago --- .../BotCommands/ConfirmCommand.cs | 15 ++-- .../BotCommands/CreateCommand.cs | 12 ++- .../Interfaces/IRecordService.cs | 28 +++++++ WebToTelegramCore/Models/Record.cs | 69 +--------------- WebToTelegramCore/Services/OwnApiService.cs | 48 +++--------- WebToTelegramCore/Services/RecordService.cs | 78 +++++++++++++++++++ .../Services/TelegramApiService.cs | 14 +++- WebToTelegramCore/Startup.cs | 6 +- 8 files changed, 148 insertions(+), 122 deletions(-) create mode 100644 WebToTelegramCore/Interfaces/IRecordService.cs create mode 100644 WebToTelegramCore/Services/RecordService.cs diff --git a/WebToTelegramCore/BotCommands/ConfirmCommand.cs b/WebToTelegramCore/BotCommands/ConfirmCommand.cs index 690ec07..a11d070 100644 --- a/WebToTelegramCore/BotCommands/ConfirmCommand.cs +++ b/WebToTelegramCore/BotCommands/ConfirmCommand.cs @@ -38,17 +38,24 @@ public class ConfirmCommand : ConfirmationCommandBase, IBotCommand /// private readonly ITokenGeneratorService _tokenGenerator; + /// + /// Record manipulation service helper reference. + /// + private readonly IRecordService _recordService; + /// /// Constructor. /// /// Locale options to use. /// Database context to use. /// Token generator to use. + /// Record helper to use. public ConfirmCommand(LocalizationOptions locale, RecordContext context, - ITokenGeneratorService generator) : base(locale) + ITokenGeneratorService generator, IRecordService recordService) : base(locale) { _context = context; _tokenGenerator = generator; + _recordService = recordService; _deletion = locale.ConfirmDeletion; _regenration = locale.ConfirmRegeneration; @@ -87,11 +94,7 @@ private string Regenerate(Record record) { string newToken = _tokenGenerator.Generate(); // so apparently, primary key cannot be changed - Record newRecord = new Record() - { - AccountNumber = record.AccountNumber, - Token = newToken - }; + var newRecord = _recordService.Create(newToken, record.AccountNumber); _context.Remove(record); _context.Add(newRecord); _context.SaveChanges(); diff --git a/WebToTelegramCore/BotCommands/CreateCommand.cs b/WebToTelegramCore/BotCommands/CreateCommand.cs index 8302aea..f0a6139 100644 --- a/WebToTelegramCore/BotCommands/CreateCommand.cs +++ b/WebToTelegramCore/BotCommands/CreateCommand.cs @@ -35,6 +35,11 @@ public class CreateCommand : GuestOnlyCommandBase, IBotCommand /// private readonly ITokenGeneratorService _generator; + /// + /// Record manipulation service helper reference. + /// + private readonly IRecordService _recordService; + /// /// Field to store whether registration is enabled. True is enabled. /// @@ -46,12 +51,15 @@ public class CreateCommand : GuestOnlyCommandBase, IBotCommand /// Locale options to use. /// Database context to use. /// Token generator service to use. + /// Record helper service to use. /// State of registration. public CreateCommand(LocalizationOptions locale, RecordContext context, - ITokenGeneratorService generator, bool isRegistrationEnabled) : base(locale) + ITokenGeneratorService generator, IRecordService recordService, bool isRegistrationEnabled) : base(locale) { _context = context; _generator = generator; + _recordService = recordService; + _isRegistrationEnabled = isRegistrationEnabled; _message = locale.CreateSuccess; @@ -81,7 +89,7 @@ private string InternalProcess(long userId, Record record) if (_isRegistrationEnabled) { string token = _generator.Generate(); - Record r = new Record() { AccountNumber = userId, Token = token }; + var r = _recordService.Create(token, userId); _context.Add(r); _context.SaveChanges(); return String.Format(_message, token); diff --git a/WebToTelegramCore/Interfaces/IRecordService.cs b/WebToTelegramCore/Interfaces/IRecordService.cs new file mode 100644 index 0000000..a2275d5 --- /dev/null +++ b/WebToTelegramCore/Interfaces/IRecordService.cs @@ -0,0 +1,28 @@ +using WebToTelegramCore.Models; + +namespace WebToTelegramCore.Interfaces +{ + /// + /// Interface that makes managing records look easy for its users. + /// + public interface IRecordService + { + /// + /// Creates a new Record, setting common default values + /// for all instances. + /// + /// Token to create Record with. + /// Account id to create token with. + /// Record with all properties populated. + Record Create(string token, long accountId); + + /// + /// Checks if Record holds enough charges to be able to send a message + /// immediately ( > 0). If so, returns true, + /// and updates Record's state to indicate the message was sent just now. + /// + /// Record to check and possibly update. + /// True if message can and should be sent, false otherwise. + bool CheckIfCanSend(Record record); + } +} diff --git a/WebToTelegramCore/Models/Record.cs b/WebToTelegramCore/Models/Record.cs index b850fb3..f1d6cee 100644 --- a/WebToTelegramCore/Models/Record.cs +++ b/WebToTelegramCore/Models/Record.cs @@ -9,16 +9,6 @@ namespace WebToTelegramCore.Models /// public class Record { - /// - /// Maximum possible amount of messages available immidiately. - /// - private static int _counterMax; - - /// - /// Boolean indicating whether max value from config was set. - /// - private static bool _maxSet = false; - /// /// Auth token associated with this record. Primary key in the DB. /// @@ -30,32 +20,9 @@ public class Record public long AccountNumber { get; set; } /// - /// Backing field of UsageCounter property. + /// Holds amount of messages available immidiately. /// - private int _counter; - - /// - /// Holds amount of messages available immidiately, prevents over- and underflows. - /// - public int UsageCounter - { - get => _counter; - set - { - if (value <= 0) - { - _counter = 0; - } - else if (value >= _counterMax) - { - _counter = _counterMax; - } - else - { - _counter = value; - } - } - } + public int UsageCounter { get; set; } /// /// Timestamp of last successful request. Used to calculate how much to add @@ -68,37 +35,5 @@ public int UsageCounter /// on destructive command. /// public RecordState State { get; set; } - - /// - /// Constructor that sets up default values for properties not stored in the DB. - /// - public Record() - { - if (!_maxSet) - { - throw new ApplicationException("No default maximum count value was set."); - } - _counter = _counterMax; - LastSuccessTimestamp = DateTime.Now; - State = RecordState.Normal; - } - - /// - /// Sets maximum value once. Throws exception when value is already set. - /// - /// Value to set. - public static void SetMaxValue(int value) - { - if (!_maxSet) - { - _counterMax = value; - _maxSet = true; - } - else - { - throw new ApplicationException("Record's maximum count value " + - "was already set."); - } - } } } diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index 7fcbf5f..a923392 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -1,10 +1,7 @@ -using Microsoft.Extensions.Options; -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using WebToTelegramCore.Exceptions; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; namespace WebToTelegramCore.Services { @@ -13,11 +10,6 @@ namespace WebToTelegramCore.Services /// public class OwnApiService : IOwnApiService { - /// - /// Amount of seconds lince last successful API call to regenerate counter. - /// - private readonly int _secondsPerRegen; - /// /// Field to store app's database context. /// @@ -28,19 +20,22 @@ public class OwnApiService : IOwnApiService /// private readonly ITelegramBotService _bot; + /// + /// Field to store Record management service. + /// + private readonly IRecordService _recordService; + /// /// Constuctor that injects dependencies. /// /// Database context to use. /// Bot service to use. /// Bandwidth options. - public OwnApiService(RecordContext context, ITelegramBotService bot, IOptions options) + public OwnApiService(RecordContext context, ITelegramBotService bot, IRecordService recordService) { _context = context; _bot = bot; - - _secondsPerRegen = options.Value.SecondsPerRegeneration; - + _recordService = recordService; } /// @@ -48,26 +43,14 @@ public OwnApiService(RecordContext context, ITelegramBotService bot, IOptions /// Request to handle. public async Task HandleRequest(Request request) - { - await HandleRequestInternally(request); - } - - /// - /// Internal method to handle requests. - /// - /// Request to handle. - private async Task HandleRequestInternally(Request request) { var record = await _context.GetRecordByToken(request.Token); if (record == null) { throw new TokenNotFoundException(); } - UpdateRecordCounter(record); - if (record.UsageCounter > 0) + if (_recordService.CheckIfCanSend(record)) { - record.LastSuccessTimestamp = DateTime.Now; - record.UsageCounter--; await _bot.Send(record.AccountNumber, request.Message, request.Silent, request.Type); } else @@ -75,18 +58,5 @@ private async Task HandleRequestInternally(Request request) throw new BandwidthExceededException(); } } - - /// - /// Updates Record's usage counter based on time passed since last successful - /// message delivery. - /// - /// Record to update. - private void UpdateRecordCounter(Record record) - { - TimeSpan sinceLastSuccess = DateTime.Now - record.LastSuccessTimestamp; - int toAdd = (int)(sinceLastSuccess.TotalSeconds / _secondsPerRegen); - // overflows are handled in the property's setter - record.UsageCounter += toAdd; - } } } diff --git a/WebToTelegramCore/Services/RecordService.cs b/WebToTelegramCore/Services/RecordService.cs new file mode 100644 index 0000000..8eb8ffe --- /dev/null +++ b/WebToTelegramCore/Services/RecordService.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Options; +using System; +using WebToTelegramCore.Data; +using WebToTelegramCore.Interfaces; +using WebToTelegramCore.Models; +using WebToTelegramCore.Options; + +namespace WebToTelegramCore.Services +{ + /// + /// Class that makes managing Records look easy for its users. + /// hide_the_pain_harold.jpg + /// + public class RecordService : IRecordService + { + /// + /// Maximum possible amount of messages available immidiately. + /// + private readonly int _counterMax; + + /// + /// Amount of seconds lince last successful API call to regenerate counter. + /// + private readonly TimeSpan _timeToRegen; + + /// + /// Constructor that gets message bandwiths settings for later use. + /// + /// Options to use. + public RecordService(IOptions options) + { + _counterMax = options.Value.InitialCount; + _timeToRegen = TimeSpan.FromSeconds(options.Value.SecondsPerRegeneration); + } + + /// + /// Creates a new Record, setting common default values + /// for all instances. + /// + /// Token to create Record with. + /// Account id to create token with. + /// Record with all properties populated. + public bool CheckIfCanSend(Record record) + { + var sinceLastSuccess = DateTime.UtcNow - record.LastSuccessTimestamp; + var toAdd = (int)(sinceLastSuccess / _timeToRegen); + + record.UsageCounter = Math.Min(_counterMax, record.UsageCounter + toAdd); + + if (record.UsageCounter > 0) + { + record.LastSuccessTimestamp = DateTime.Now; + record.UsageCounter--; + return true; + } + return false; + } + + /// + /// Checks if Record holds enough charges to be able to send a message + /// immediately ( > 0). If so, returns true, + /// and updates Record's state to indicate the message was sent just now. + /// + /// Record to check and possibly update. + /// True if message can and should be sent, false otherwise. + public Record Create(string token, long accountId) + { + return new Record + { + AccountNumber = accountId, + LastSuccessTimestamp = DateTime.UtcNow, + State = RecordState.Normal, + Token = token, + UsageCounter = _counterMax + }; + } + } +} diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index f943ed3..0785952 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -38,6 +38,11 @@ public class TelegramApiService : ITelegramApiService /// private readonly ITokenGeneratorService _generator; + /// + /// Record manipulation service helper reference. + /// + private readonly IRecordService _recordService; + /// /// List of commands available to the bot. /// @@ -67,14 +72,17 @@ public class TelegramApiService : ITelegramApiService /// Database context to use. /// Bot service instance to use. /// Token generator service to use. + /// Record helper service to use. public TelegramApiService(IOptions options, IOptions locale, RecordContext context, - ITelegramBotService bot, ITokenGeneratorService generator) + ITelegramBotService bot, ITokenGeneratorService generator, + IRecordService recordService) { _token = options.Value.Token; _context = context; _bot = bot; _generator = generator; + _recordService = recordService; _isRegistrationOpen = options.Value.RegistrationEnabled; @@ -89,12 +97,12 @@ public TelegramApiService(IOptions options, new TokenCommand(locOptions, options.Value.ApiEndpointUrl), new RegenerateCommand(locOptions), new DeleteCommand(locOptions, _isRegistrationOpen), - new ConfirmCommand(locOptions, _context, _generator), + new ConfirmCommand(locOptions, _context, _generator, _recordService), new CancelCommand(locOptions), new HelpCommand(locOptions), new DirectiveCommand(locOptions), new AboutCommand(locOptions), - new CreateCommand(locOptions, _context, _generator, _isRegistrationOpen) + new CreateCommand(locOptions, _context, _generator, _recordService, _isRegistrationOpen) }; } diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 94c2ae3..1a53881 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -30,6 +30,7 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddTransient(); @@ -41,11 +42,6 @@ public void ConfigureServices(IServiceCollection services) services.Configure(Configuration.GetSection("General")); services.Configure(Configuration.GetSection("Bandwidth")); services.Configure(Configuration.GetSection("Strings")); - // loading this explicitly as there's no straightforward way to pass options - // to models; I can be wrong though - // TODO: see if there's a better way - var preload = Configuration.GetSection("Bandwidth").GetValue("InitialCount"); - Record.SetMaxValue(preload); } } } From a54f9ae2e464723ccbe79541ef084ac807f1350f Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 18 Dec 2021 02:11:19 +0700 Subject: [PATCH 24/39] fixed comments for RecordService methods being out of place --- WebToTelegramCore/Services/RecordService.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/WebToTelegramCore/Services/RecordService.cs b/WebToTelegramCore/Services/RecordService.cs index 8eb8ffe..5fa5682 100644 --- a/WebToTelegramCore/Services/RecordService.cs +++ b/WebToTelegramCore/Services/RecordService.cs @@ -34,12 +34,12 @@ public RecordService(IOptions options) } /// - /// Creates a new Record, setting common default values - /// for all instances. + /// Checks if Record holds enough charges to be able to send a message + /// immediately ( > 0). If so, returns true, + /// and updates Record's state to indicate the message was sent just now. /// - /// Token to create Record with. - /// Account id to create token with. - /// Record with all properties populated. + /// Record to check and possibly update. + /// True if message can and should be sent, false otherwise. public bool CheckIfCanSend(Record record) { var sinceLastSuccess = DateTime.UtcNow - record.LastSuccessTimestamp; @@ -57,12 +57,12 @@ public bool CheckIfCanSend(Record record) } /// - /// Checks if Record holds enough charges to be able to send a message - /// immediately ( > 0). If so, returns true, - /// and updates Record's state to indicate the message was sent just now. + /// Creates a new Record, setting common default values + /// for all instances. /// - /// Record to check and possibly update. - /// True if message can and should be sent, false otherwise. + /// Token to create Record with. + /// Account id to create token with. + /// Record with all properties populated. public Record Create(string token, long accountId) { return new Record From 5eb5e371b9247caddab68187db27be6c3fdc7a22 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 18 Dec 2021 02:23:52 +0700 Subject: [PATCH 25/39] minor improvements --- WebToTelegramCore/Controllers/WebApiController.cs | 5 ++--- WebToTelegramCore/Services/OwnApiService.cs | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/WebToTelegramCore/Controllers/WebApiController.cs b/WebToTelegramCore/Controllers/WebApiController.cs index b77e627..d5a51eb 100644 --- a/WebToTelegramCore/Controllers/WebApiController.cs +++ b/WebToTelegramCore/Controllers/WebApiController.cs @@ -47,9 +47,8 @@ public async Task HandleWebApi([FromBody] Request request) { return StatusCode((int)HttpStatusCode.TooManyRequests); } - // if the formatting is wrong, we may catch this - // TODO maybe narrow down to the specific message, if there are more cases when this is thrown - catch (Telegram.Bot.Exceptions.ApiRequestException) + // if the formatting is malformed, relay Telegram's "bad request" to the user + catch (Telegram.Bot.Exceptions.ApiRequestException ex) when (ex.Message.StartsWith("Bad Request")) { return BadRequest(); } diff --git a/WebToTelegramCore/Services/OwnApiService.cs b/WebToTelegramCore/Services/OwnApiService.cs index a923392..f8c128a 100644 --- a/WebToTelegramCore/Services/OwnApiService.cs +++ b/WebToTelegramCore/Services/OwnApiService.cs @@ -44,11 +44,7 @@ public OwnApiService(RecordContext context, ITelegramBotService bot, IRecordServ /// Request to handle. public async Task HandleRequest(Request request) { - var record = await _context.GetRecordByToken(request.Token); - if (record == null) - { - throw new TokenNotFoundException(); - } + var record = await _context.GetRecordByToken(request.Token) ?? throw new TokenNotFoundException(); if (_recordService.CheckIfCanSend(record)) { await _bot.Send(record.AccountNumber, request.Message, request.Silent, request.Type); From 870bceb895b0ef2245b85eedde387d6e7d89b4c3 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 18 Dec 2021 02:47:44 +0700 Subject: [PATCH 26/39] fixed(?) accountId passed twice unless the user is creating a token from scratch only one command works with no token, so why pass duplicate data to other 9 commands? --- WebToTelegramCore/BotCommands/AboutCommand.cs | 6 ++---- WebToTelegramCore/BotCommands/BotCommandBase.cs | 5 ++--- WebToTelegramCore/BotCommands/CancelCommand.cs | 5 ++--- WebToTelegramCore/BotCommands/ConfirmCommand.cs | 7 +++---- .../BotCommands/ConfirmationCommandBase.cs | 5 ++--- WebToTelegramCore/BotCommands/CreateCommand.cs | 10 ++++------ WebToTelegramCore/BotCommands/DeleteCommand.cs | 5 ++--- WebToTelegramCore/BotCommands/DirectiveCommand.cs | 5 ++--- WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs | 8 +++----- WebToTelegramCore/BotCommands/HelpCommand.cs | 5 ++--- WebToTelegramCore/BotCommands/RegenerateCommand.cs | 5 ++--- WebToTelegramCore/BotCommands/StartCommand.cs | 5 ++--- WebToTelegramCore/BotCommands/TokenCommand.cs | 4 ++-- WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs | 8 +++----- WebToTelegramCore/Interfaces/IBotCommand.cs | 7 ++----- WebToTelegramCore/Services/TelegramApiService.cs | 8 +++++--- readme.md | 2 +- 17 files changed, 41 insertions(+), 59 deletions(-) diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index 2d1c6cf..2fb2c10 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -34,16 +34,14 @@ public AboutCommand(LocalizationOptions locale) : base(locale) { } /// /// Method to process the command. /// - /// Telegram ID of the user who sent the command. - /// Unused here. /// Record associated with user who sent the command. /// Unused here. /// Text of message that should be returned to user, with '.' escaped for MarkdownV2 - public override string Process(long userId, Record record) + public override string Process(Record record) { var version = Assembly.GetExecutingAssembly().GetName().Version; var prettyVersion = $"{version.Major}\\.{version.Minor}"; - return base.Process(userId, record) ?? String.Format(_template, prettyVersion); + return base.Process(record) ?? String.Format(_template, prettyVersion); } } } diff --git a/WebToTelegramCore/BotCommands/BotCommandBase.cs b/WebToTelegramCore/BotCommands/BotCommandBase.cs index 8a97da4..8a4d62d 100644 --- a/WebToTelegramCore/BotCommands/BotCommandBase.cs +++ b/WebToTelegramCore/BotCommands/BotCommandBase.cs @@ -35,13 +35,12 @@ public BotCommandBase(LocalizationOptions locale) /// Method of abstract base class that filters out users with pending /// cancellations or deletions of token. /// - /// Telegram ID of user that sent the message. /// Record to process. /// Error message if there is an operation pending, /// or null otherwise. - public virtual string Process(long userId, Record record) + public virtual string Process(Record record) { - return (record != null && record.State != RecordState.Normal) ? + return (!string.IsNullOrEmpty(record.Token) && record.State != RecordState.Normal) ? _error : null; } } diff --git a/WebToTelegramCore/BotCommands/CancelCommand.cs b/WebToTelegramCore/BotCommands/CancelCommand.cs index 10ac44a..abbf5e1 100644 --- a/WebToTelegramCore/BotCommands/CancelCommand.cs +++ b/WebToTelegramCore/BotCommands/CancelCommand.cs @@ -38,13 +38,12 @@ public CancelCommand(LocalizationOptions locale) : base(locale) /// /// Method to process the command. Resets Record's State back to Normal. /// - /// Telegram user ID. Unused here. /// Record associated with user who sent the command. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(long userId, Record record) + public override string Process(Record record) { - string baseResult = base.Process(userId, record); + string baseResult = base.Process(record); if (baseResult != null) { return baseResult; diff --git a/WebToTelegramCore/BotCommands/ConfirmCommand.cs b/WebToTelegramCore/BotCommands/ConfirmCommand.cs index a11d070..e43a058 100644 --- a/WebToTelegramCore/BotCommands/ConfirmCommand.cs +++ b/WebToTelegramCore/BotCommands/ConfirmCommand.cs @@ -64,12 +64,11 @@ public ConfirmCommand(LocalizationOptions locale, RecordContext context, /// /// Method to carry on confirmed destructive operations. /// - /// Telegram user ID, unused here. /// Record associated with user who sent the command. /// End-user readable result of the operation. - public override string Process(long userId, Record record) + public override string Process(Record record) { - string baseResult = base.Process(userId, record); + string baseResult = base.Process(record); if (baseResult != null) { return baseResult; @@ -108,7 +107,7 @@ private string Regenerate(Record record) /// Message about performed operation. private string Delete(Record record) { - _context.Records.Remove(record); + _context.Remove(record); _context.SaveChanges(); return _deletion; } diff --git a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs index 4601ac9..428a27d 100644 --- a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs +++ b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs @@ -36,13 +36,12 @@ public ConfirmationCommandBase(LocalizationOptions locale) /// Method of abstract base class that filters out users without pending /// cancellations or deletions of token. /// - /// Unused Telegram user ID. /// Record to process. /// Error message if there is no operation pending, /// or null otherwise. - public virtual string Process(long userId, Record record) + public virtual string Process(Record record) { - return (record == null || record.State == RecordState.Normal) ? + return (string.IsNullOrEmpty(record.Token) || record.State == RecordState.Normal) ? _error : null; } } diff --git a/WebToTelegramCore/BotCommands/CreateCommand.cs b/WebToTelegramCore/BotCommands/CreateCommand.cs index f0a6139..857385e 100644 --- a/WebToTelegramCore/BotCommands/CreateCommand.cs +++ b/WebToTelegramCore/BotCommands/CreateCommand.cs @@ -71,25 +71,23 @@ public CreateCommand(LocalizationOptions locale, RecordContext context, /// /// Record to process. /// Message with new token or error when there is one already. - public override string Process(long userId, Record record) + public override string Process(Record record) { - return base.Process(userId, record) ?? InternalProcess(userId, record); + return base.Process(record) ?? InternalProcess(record); } /// /// Actual method that does registration or denies it. /// - /// Telegram user ID to create a new record. _The_ place in the app - /// it's used. /// Record to process. Is null if working properly. /// Message with new token or message stating that registration /// is closed for good. - private string InternalProcess(long userId, Record record) + private string InternalProcess(Record record) { if (_isRegistrationEnabled) { string token = _generator.Generate(); - var r = _recordService.Create(token, userId); + var r = _recordService.Create(token, record.AccountNumber); _context.Add(r); _context.SaveChanges(); return String.Format(_message, token); diff --git a/WebToTelegramCore/BotCommands/DeleteCommand.cs b/WebToTelegramCore/BotCommands/DeleteCommand.cs index 25efe08..22d01c7 100644 --- a/WebToTelegramCore/BotCommands/DeleteCommand.cs +++ b/WebToTelegramCore/BotCommands/DeleteCommand.cs @@ -49,12 +49,11 @@ public DeleteCommand(LocalizationOptions locale, bool registrationEnabled) /// /// Method to process the command. /// - /// Unused Telegram user ID. /// Record to process. /// Message with results of processing. - public override string Process(long userId, Record record) + public override string Process(Record record) { - return base.Process(userId, record) ?? InternalProcess(record); + return base.Process(record) ?? InternalProcess(record); } /// diff --git a/WebToTelegramCore/BotCommands/DirectiveCommand.cs b/WebToTelegramCore/BotCommands/DirectiveCommand.cs index 337dc5f..29abc81 100644 --- a/WebToTelegramCore/BotCommands/DirectiveCommand.cs +++ b/WebToTelegramCore/BotCommands/DirectiveCommand.cs @@ -31,14 +31,13 @@ public DirectiveCommand(LocalizationOptions locale) : base(locale) { } /// /// Method to process the command. /// - /// Telegram user ID, unused. /// Record associated with user who sent the command. /// Unused here. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(long userId, Record record) + public override string Process(Record record) { - return base.Process(userId, record) ?? _message; + return base.Process(record) ?? _message; } } } diff --git a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs index c8daa31..b71deaf 100644 --- a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs @@ -28,14 +28,12 @@ public GuestOnlyCommandBase(LocalizationOptions locale) : base(locale) /// /// Method of abstract base class that adds filtering out users /// with no associated token. - /// Telegram user ID. Only used in descendats of this class, - /// as any other command category has access to records. /// Record to process. /// Error message if there is an operation pending or user has a token, /// or null otherwise. - public new virtual string Process(long userId, Record record) + public new virtual string Process(Record record) { - return base.Process(userId, record) ?? InternalProcess(record); + return base.Process(record) ?? InternalProcess(record); } /// @@ -45,7 +43,7 @@ public GuestOnlyCommandBase(LocalizationOptions locale) : base(locale) /// Error message if user has a token, or null otherwise. private string InternalProcess(Record record) { - return record != null ? _error : null; + return string.IsNullOrEmpty(record.Token) ? null : _error; } } } diff --git a/WebToTelegramCore/BotCommands/HelpCommand.cs b/WebToTelegramCore/BotCommands/HelpCommand.cs index 780194b..0ce6395 100644 --- a/WebToTelegramCore/BotCommands/HelpCommand.cs +++ b/WebToTelegramCore/BotCommands/HelpCommand.cs @@ -31,14 +31,13 @@ public HelpCommand(LocalizationOptions locale) : base(locale) /// /// Method to process the command. /// - /// Unused Telegram used ID. /// Record associated with user who sent the command. /// Unused here. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(long userId, Record record) + public override string Process(Record record) { - return base.Process(userId, record) ?? _message; + return base.Process(record) ?? _message; } } } diff --git a/WebToTelegramCore/BotCommands/RegenerateCommand.cs b/WebToTelegramCore/BotCommands/RegenerateCommand.cs index 8268c9b..deb4c44 100644 --- a/WebToTelegramCore/BotCommands/RegenerateCommand.cs +++ b/WebToTelegramCore/BotCommands/RegenerateCommand.cs @@ -33,12 +33,11 @@ public RegenerateCommand(LocalizationOptions locale) : base(locale) /// /// Method to process the command. /// - /// Unused Telegram user ID. /// Record to process. /// Message with results of processing. - public override string Process(long userId, Record record) + public override string Process(Record record) { - return base.Process(userId, record) ?? InternalProcess(record); + return base.Process(record) ?? InternalProcess(record); } /// diff --git a/WebToTelegramCore/BotCommands/StartCommand.cs b/WebToTelegramCore/BotCommands/StartCommand.cs index ae99419..3c72403 100644 --- a/WebToTelegramCore/BotCommands/StartCommand.cs +++ b/WebToTelegramCore/BotCommands/StartCommand.cs @@ -53,15 +53,14 @@ public StartCommand(LocalizationOptions locale, bool registrationEnabled) /// /// Method to process the command. /// - /// Unused Telegram user ID. /// Record associated with user who sent the command. /// Unused here. /// Predefined text if all checks from parent classes passed, /// corresponding error message otherwise. - public override string Process(long userId, Record record) + public override string Process(Record record) { string appendix = _isRegistrationOpen ? _registrationHint : _noRegistration; - return base.Process(userId, record) ?? _startMessage + "\n\n" + appendix; + return base.Process(record) ?? _startMessage + "\n\n" + appendix; } } } diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index 0daaea5..0e727f6 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -73,9 +73,9 @@ public TokenCommand(LocalizationOptions locale, string apiEndpoint) /// Record associated with user who sent the command. /// Message with token and API usage, or error message if user /// has no token. - public override string Process(long userId, Record record) + public override string Process(Record record) { - return base.Process(userId, record) ?? InternalProcess(record); + return base.Process(record) ?? InternalProcess(record); } /// diff --git a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs index c688023..29769c0 100644 --- a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs @@ -26,14 +26,12 @@ public UserOnlyCommandBase(LocalizationOptions locale) : base(locale) /// Method of abstract base class that adds filtering out users /// with no associated token. /// - /// Telegram user ID. Unused there, as record.AccountNumber is - /// the same value. /// Record to process. /// Error message if there is an operation pending or user has no token, /// or null otherwise. - public new virtual string Process(long userId, Record record) + public new virtual string Process(Record record) { - return base.Process(userId, record) ?? InternalProcess(record); + return base.Process(record) ?? InternalProcess(record); } /// @@ -44,7 +42,7 @@ public UserOnlyCommandBase(LocalizationOptions locale) : base(locale) /// or null otherwise. private string InternalProcess(Record record) { - return record == null ? _error : null; + return string.IsNullOrEmpty(record.Token) ? _error : null; } } } diff --git a/WebToTelegramCore/Interfaces/IBotCommand.cs b/WebToTelegramCore/Interfaces/IBotCommand.cs index bef8929..a9ed703 100644 --- a/WebToTelegramCore/Interfaces/IBotCommand.cs +++ b/WebToTelegramCore/Interfaces/IBotCommand.cs @@ -17,12 +17,9 @@ public interface IBotCommand /// /// Method to process received message. /// - /// Telegram user ID, present even when no record is provided. - /// Used mostly for (and any potential descendant) - /// so extra processing can be removed. /// Record associated with user who sent the command, - /// or null if user has no Record (have not received the token). + /// or a mock Record with everything but account id set to default values. /// Message to send back to user. Markdown is supported. - string Process(long userId, Record record); + string Process(Record record); } } diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index 0785952..fc7fc0d 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -126,8 +126,10 @@ public async Task HandleUpdate(Update update) { return; } - // null check was done above, it's safe to use userId.Value directly - Record record = await _context.GetRecordByAccountId(userId.Value); + // if user has no record associated, make him a mock one with just an account number, + // so we know who they are in case we're going to create them a proper one + Record record = await _context.GetRecordByAccountId(userId.Value) + ?? new Record { AccountNumber = userId.Value, Token = null }; IBotCommand handler = null; string commandText = text.Split(' ').FirstOrDefault(); @@ -135,7 +137,7 @@ public async Task HandleUpdate(Update update) handler = _commands.SingleOrDefault(c => c.Command.Equals(commandText)); if (handler != null) { - await _bot.Send(userId.Value, handler.Process(userId.Value, record)); + await _bot.Send(userId.Value, handler.Process(record)); } else { diff --git a/readme.md b/readme.md index 491a145..7b89c9c 100644 --- a/readme.md +++ b/readme.md @@ -79,5 +79,5 @@ Initial release. More an excercise in ASP.NET Core than an app I needed at the m * **v 1.2**, 2018-10-05 Greatly increased the reliability of Markdown parsing in one of the most **not** straightforward ways you can imagine -- by converting the Markdown to HTML with a few custom convertion quirks. * **no version number**, 2020-05-14 -Shelved attempt to improve the codebase. Consists of one architecture change and is fully included in the next release. +Shelved attempt to improve the codebase. Consists of one architecture change and is fully ~~included~~ rewritten in the next release. * TODO the current iteration, version number 1.5? 1.9? Certainly not 2.x _yet_. \ No newline at end of file From 9b355ee9e894f9a26343504e5c2a24e5f2c24584 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 18 Dec 2021 03:22:22 +0700 Subject: [PATCH 27/39] minor fix --- WebToTelegramCore/Startup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 1a53881..1283934 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -5,8 +5,6 @@ using WebToTelegramCore.Options; using WebToTelegramCore.Services; -using Record = WebToTelegramCore.Models.Record; - namespace WebToTelegramCore { public class Startup From a714251ec26e925d74ff96e79799a8290e676e00 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Thu, 6 Jan 2022 18:05:21 +0700 Subject: [PATCH 28/39] minor fixes, mostly for text --- WebToTelegramCore/BotCommands/AboutCommand.cs | 7 +- WebToTelegramCore/BotCommands/TokenCommand.cs | 7 +- .../Services/TelegramApiService.cs | 4 +- WebToTelegramCore/appsettings.json | 80 +++++++++---------- readme.md | 9 +-- 5 files changed, 51 insertions(+), 56 deletions(-) diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index 2fb2c10..1930174 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -15,10 +15,9 @@ public class AboutCommand : BotCommandBase, IBotCommand /// Template to message, {0} is assembly version. /// Written in Telegram's MarkdownV2 with a lot of escaping. /// - private const string _template = "**Dotnet Telegram forwarder** v\\. {0}\n\n" + - "[Open\\-source\\!](https://github.com/bnfour/dotnet-telegram-forwarder) " + - "Powered by \\.NET 6\\.0\\!\\!\\!\n" + - "Written by bnfour, August, October 2018; May 2020; December 2021\\."; + private const string _template = "**Dotnet Telegram forwarder** v {0}\n\n" + + "[Open\\-source\\!](https://github.com/bnfour/dotnet-telegram-forwarder)\n" + + "by bnfour, 2018, 2020\\-2022\\."; /// /// Command's text. diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index 0e727f6..66e0535 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -24,7 +24,7 @@ public class TokenCommand : UserOnlyCommandBase, IBotCommand "Hello world!", "Timeline lost", "send help", - "`inhale`", + "inhale", "KNCA KYKY", "Hey Red", "Powered by .NET!", @@ -34,7 +34,8 @@ public class TokenCommand : UserOnlyCommandBase, IBotCommand "Is it banana time yet?", "Try again later", "More than two and less than four", - "Of course I still love you" + "Of course I still love you", + "それは何?" }; /// @@ -86,7 +87,7 @@ public override string Process(Record record) private string InternalProcess(Record record) { string text = _examples[new Random().Next(0, _examples.Count)]; - return String.Format(_templateOne, record.Token, _apiEndpoint, text) + return String.Format(_templateOne, record.Token, _apiEndpoint + "/api", text) + "\n\n" + _errors; } } diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index fc7fc0d..841e879 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -122,14 +122,14 @@ public async Task HandleUpdate(Update update) long? userId = update?.Message?.From?.Id; string text = update?.Message?.Text; // and the update contains everything we need to process it - if (userId == null || String.IsNullOrEmpty(text)) + if (userId == null || string.IsNullOrEmpty(text)) { return; } // if user has no record associated, make him a mock one with just an account number, // so we know who they are in case we're going to create them a proper one Record record = await _context.GetRecordByAccountId(userId.Value) - ?? new Record { AccountNumber = userId.Value, Token = null }; + ?? _recordService.Create(null, userId.Value); IBotCommand handler = null; string commandText = text.Split(' ').FirstOrDefault(); diff --git a/WebToTelegramCore/appsettings.json b/WebToTelegramCore/appsettings.json index bfa5657..0844e25 100644 --- a/WebToTelegramCore/appsettings.json +++ b/WebToTelegramCore/appsettings.json @@ -1,43 +1,43 @@ { - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnection": "Data Source=|DataDirectory|database.sqlite" - }, - "General": { - "Token": "it's a secret", - "ApiEndpointUrl": "also a secret for now", - "RegistrationEnabled": true - }, - "Bandwidth": { - "InitialCount": 20, - "SecondsPerRegeneration": 60 - }, - "Strings": { - "CancelDeletion": "Token deletion cancelled\\.", - "CancelRegeneration": "Token regeneration cancelled\\.", - "ConfirmDeletion": "Token deleted\\. Thank you for giving us an oppurtunity to serve you\\.", - "ConfirmRegeneration": "Your new token is\n\n`{0}`\n\nDon't forget to update your clients' settings\\.", - "CreateGoAway": "This instance of bot is not accepting new users for now\\.", - "CreateSuccess": "Success\\! Your token is:\n\n`{0}`\n\nPlease consult /token for usage\\.", - "DeletionNoTurningBack": "This bot has registration turned *off*\\. You won't be able to create new token\\. Please be certain\\.", - "DeletionPending": "*Token deletion pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\.\nIf you need to change your token, please consider to /regenerate it instead of deleting and re\\-creating it\\.", - "ErrorConfirmationPending": "You have an operation pending cancellation\\. Please /confirm or /cancel it before using other commands\\.", - "ErrorDave": "I'm afraid I can't let you do that\\.", - "ErrorMustBeGuest": "In order to use this command, you must have no token associated with your account\\. You can /delete your existing one, but why?", - "ErrorMustBeUser": "In order to use this command, you must have a token associated with your account\\. Try running /create first\\.", - "ErrorNoConfirmationPending": "This command is only useful when you're trying to /delete or /regenerate your token\\.", - "ErrorWhat": "Unfortunately, I'm not sure how to interpret this\\.", - "Help": "This bot provides web API to route messages from API's endpoint to you in Telegram via bot\\. It can be used to notify you in Telegram from any Internet\\-enabled device you want \\(provided you know how to make POST requests from it\\)\\.\nTo start, /create your token \\(if this particular instance of bot has registration of new users open\\)\\. You can /delete it anytime\\. To change your token for whatever reason, please /regenerate it and not delete and re\\-create\\.\nOnce you have a token, see /token for additional usage help\\.\nOther commands supported by bot include:\n\\- /confirm and /cancel are used to prevent accidental deletions and regenerations;\n\\- /help displays this message;\n\\- /about displays general info about this bot\\.\n\nThere's also an easter egg command and a rare response to unknown commands: one way to satisfy your curiosity is to check out bot's source code\\.\n\n\\-\\-bnfour", - "RegenerationPending": "*Token regenration pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\. Please be certain\\.", - "StartGoAway": "Sorry, this instance of bot is not accepting new users for now\\.", - "StartMessage": "Hello\\!\n\nThis bot provides a standalone web API to relay messages from whatever you'll use it from to Telegram as messages from the bot\\. It might come in handy to unify your notifications in one place\\.\n\n*Please note*: this requires some external tools\\. If you consider phrases like \"Send a POST request to the endpoint with JSON body with two string fields\" a magic gibberish you don't understand, this bot probably isn't much of use to you\\.", - "StartRegistrationHint": "If that does not stop you, feel free to /create your very own token\\.", - "TokenErrorsDescription": "TODO updated description", - "TokenTemplate": "TODO also updated\\." + "Logging": { + "LogLevel": { + "Default": "Warning" } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Data Source=|DataDirectory|database.sqlite" + }, + "General": { + "Token": "it's a secret", + "ApiEndpointUrl": "also a secret for now", + "RegistrationEnabled": true + }, + "Bandwidth": { + "InitialCount": 20, + "SecondsPerRegeneration": 60 + }, + "Strings": { + "CancelDeletion": "Token deletion cancelled\\.", + "CancelRegeneration": "Token regeneration cancelled\\.", + "ConfirmDeletion": "Token deleted\\. Thank you for giving us an oppurtunity to serve you\\.", + "ConfirmRegeneration": "Your new token is\n\n`{0}`\n\nDon't forget to update your clients' settings\\.", + "CreateGoAway": "This instance of bot is not accepting new users for now\\.", + "CreateSuccess": "Success\\! Your token is:\n\n`{0}`\n\nPlease consult /token for usage\\.", + "DeletionNoTurningBack": "This bot has registration turned *off*\\. You won't be able to create new token\\. Please be certain\\.", + "DeletionPending": "*Token deletion pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\.\nIf you need to change your token, please consider to /regenerate it instead of deleting and re\\-creating it\\.", + "ErrorConfirmationPending": "You have an operation pending cancellation\\. Please /confirm or /cancel it before using other commands\\.", + "ErrorDave": "I'm afraid I can't let you do that\\.", + "ErrorMustBeGuest": "In order to use this command, you must have no token associated with your account\\. You can /delete your existing one, but why?", + "ErrorMustBeUser": "In order to use this command, you must have a token associated with your account\\. Try running /create first\\.", + "ErrorNoConfirmationPending": "This command is only useful when you're trying to /delete or /regenerate your token\\.", + "ErrorWhat": "Unfortunately, I'm not sure how to interpret this\\.", + "Help": "This bot provides web API to route messages from API's endpoint to you in Telegram via bot\\. It can be used to notify you in Telegram from any Internet\\-enabled device you want \\(provided you know how to make POST requests from it\\)\\.\nTo start, /create your token \\(if this particular instance of bot has registration of new users open\\)\\. You can /delete it anytime\\. To change your token for whatever reason, please /regenerate it and not delete and re\\-create\\.\nOnce you have a token, see /token for additional usage help\\.\nOther commands supported by bot include:\n\\- /confirm and /cancel are used to prevent accidental deletions and regenerations;\n\\- /help displays this message;\n\\- /about displays general info about this bot\\.\n\nThere's also an easter egg command and a rare response to unknown commands: one way to satisfy your curiosity is to check out bot's source code\\.\n\n\\-\\-bnfour", + "RegenerationPending": "*Token regenration pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\. Please be certain\\.", + "StartGoAway": "Sorry, this instance of bot is not accepting new users for now\\.", + "StartMessage": "Hello\\!\n\nThis bot provides a standalone web API to relay messages from anything that can send web requests to Telegram as messages from the bot\\.\n\n*Please note*: this requires some external tools and knowledge\\. If you consider phrases like \"Send a POST request with JSON body\" a magic gibberish you don't understand, this bot probably isn't much of use to you\\.", + "StartRegistrationHint": "If that does not stop you, feel free to /create your very own token\\.", + "TokenErrorsDescription": "If everything is okay, the API will return a blank 200 OK response. If something is not okay, a different status code will be returned\\. Consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) to see descriptions of possible error status codes\\.", + "TokenTemplate": "Your token is\n\n`{0}`\n\n*Usage:* To deliver a message, send a POST request to {1} with JSON body\\. The payload must contain two parameters: your token and your message\\. There are also two optional parameters: parsing type and disabling the notification, please consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) for details\\. Example of a bare minimum payload:\n\n```\n{\n \"token\": \"{0}\",\n \"message\": \"{2}\"\n}```" + } } diff --git a/readme.md b/readme.md index 7b89c9c..f45d0cd 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@ Request's body has this structure: } ``` * Token is this service's user identifier, randomly generated per Telegram user, with abilities to withdraw it or replace with a new one anytime. (Implement a cooldown on consequent resets?) It's a 16 characters long string that may contain alphanumerics, and plus and equals signs (So `[0-9a-zA-Z+=]{16}`). -* Type is used to select between two supported parse modes: `"plaintext"` for plain text, and `"markdown"` for MarkdownV2 as described in Telegram docs [here](https://core.telegram.org/bots/api#markdownv2-style). If value is not supplied, defaults to `"plaintext"`. These two are separated, because Telegram flavoured Markdown requires escaping for a fairly common plaintext punctuation marks, and Telegram backend (from my experience three years ago, actualize) tends to silently drop malformed Markdown. +* Type is used to select between two supported parse modes: `"plaintext"` for plain text, and `"markdown"` for MarkdownV2 as described in Telegram docs [here](https://core.telegram.org/bots/api#markdownv2-style). If value is not supplied, defaults to `"plaintext"`. These two are separated, because Telegram flavoured Markdown requires escaping for a fairly common plaintext punctuation marks. * Message is the text of the message to be sent via the bot. Maximum length is 4096, and preferred encoding is UTF-8. * Silent is boolean to indicate whether them message from the bot in Telegram will come with a notification. Behaves what you'd expect. If not supplied, defaults to `false`. Please note that the end user is able to mute the bot, effectively rendering this option useless. @@ -63,16 +63,11 @@ When there is a destructive (either regeneration or deletion) operation pending, You'll need .NET 6 runtime. Hosting on GNU/Linux in a proper data center is highly encouraged. By default, listens on port 8082. This can be changed with `--port ` command-line argument. Rest of the settings are inside `appsettings.json`: -* "General" section contains Telegram's bot token, API endpoint URL as seen from outside world (certainly not localhost:8082 as Kestrel would told you it listens to) and a boolean that controls whether new users can create tokens. +* "General" section contains Telegram's bot token, API endpoint URL as seen from outside world (certainly not localhost:8082 as Kestrel would told you it listens to) and a boolean that controls whether new users can create tokens. Please note that `/api` will be appended to this address. So, for example, if you set `https://foo.example.com/bar` here, actual endpoint to be used with the app (both for Telegram API webhooks and for user messages) will be `https://foo.example.com/bar/api` * "Bandwidth" section controls bot's throughput: maximum amount of messages to be delivered at once and amount of seconds to regenerate one message delivery is set here. -// TODO write about localization string moved somewhere else - To deploy this bot, you'll need something that will append SSL as required by Telegram. As always with Telegram bots, I recommend `nginx` as a reverse proxy. You'll need to set up HTTPS as well. -### Debug -Short section outlining ngrok usage? - ## Version history * **v 1.0**, 2018-08-29 Initial release. More an excercise in ASP.NET Core than an app I needed at the moment. Actually helped me to score a software engineering job and turned out to be moderately useful tool. From 8455063a51898c5b791491d3082066afdcd3d8a9 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Tue, 1 Nov 2022 09:03:44 +0700 Subject: [PATCH 29/39] nuget updates, added PreserveNewest for appsettings to persist local overrides is bin/Debug across build for secrets, gitignore updated to include .vscode folder behind the scenes: added vscode build/launch configurations --- .gitignore | 3 ++- WebToTelegramCore/WebToTelegramCore.csproj | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index e469fdf..10b758b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vs +.vscode */bin */obj -*.user \ No newline at end of file +*.user diff --git a/WebToTelegramCore/WebToTelegramCore.csproj b/WebToTelegramCore/WebToTelegramCore.csproj index a65fa16..a450ec7 100644 --- a/WebToTelegramCore/WebToTelegramCore.csproj +++ b/WebToTelegramCore/WebToTelegramCore.csproj @@ -9,15 +9,18 @@ - - - + + + PreserveNewest + + PreserveNewest + From 9a81e45e1eea12bcbcc5ce5ab6bfeef5a95aa28c Mon Sep 17 00:00:00 2001 From: Bn4 Date: Fri, 28 Apr 2023 18:46:57 +0700 Subject: [PATCH 30/39] some updates one more time - runtime bumped to 7.0 - version bumped to 2.0 as Markdown changes are breaking - some cleanup action is coming, hopefully this time i finish the update (third time's the charm?) --- 3rd-party-licenses.txt | 28 ------------------- WebToTelegramCore/BotCommands/AboutCommand.cs | 2 +- WebToTelegramCore/Program.cs | 1 - WebToTelegramCore/WebToTelegramCore.csproj | 10 +++---- readme.md | 5 ++-- 5 files changed, 9 insertions(+), 37 deletions(-) diff --git a/3rd-party-licenses.txt b/3rd-party-licenses.txt index 415f1cf..1c82441 100644 --- a/3rd-party-licenses.txt +++ b/3rd-party-licenses.txt @@ -43,31 +43,3 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - - -Markdig -- https://github.com/lunet-io/markdig -============================================== - -Copyright (c) 2018, Alexandre Mutel -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification -, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index 1930174..1525455 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -17,7 +17,7 @@ public class AboutCommand : BotCommandBase, IBotCommand /// private const string _template = "**Dotnet Telegram forwarder** v {0}\n\n" + "[Open\\-source\\!](https://github.com/bnfour/dotnet-telegram-forwarder)\n" + - "by bnfour, 2018, 2020\\-2022\\."; + "by bnfour, 2018, 2020\\-2023\\."; /// /// Command's text. diff --git a/WebToTelegramCore/Program.cs b/WebToTelegramCore/Program.cs index 98a9de0..01b4068 100644 --- a/WebToTelegramCore/Program.cs +++ b/WebToTelegramCore/Program.cs @@ -39,7 +39,6 @@ public static void Main(string[] args) }); // carrying over legacy startup-based configuration (for now?) - // lifetime-dependent config (use detailed exception page in dev environment, basically) was dropped (for now?) var startup = new Startup(builder.Configuration); startup.ConfigureServices(builder.Services); diff --git a/WebToTelegramCore/WebToTelegramCore.csproj b/WebToTelegramCore/WebToTelegramCore.csproj index a450ec7..c07ebd4 100644 --- a/WebToTelegramCore/WebToTelegramCore.csproj +++ b/WebToTelegramCore/WebToTelegramCore.csproj @@ -1,16 +1,16 @@  - net6.0 - 1.2.0.0 + net7.0 + 2.0.0.0 bnfour Dotnet Telegram forwarder - © 2018, 2020–2021 bnfour + © 2018, 2020–2023, bnfour - - + + diff --git a/readme.md b/readme.md index f45d0cd..555bbf6 100644 --- a/readme.md +++ b/readme.md @@ -60,7 +60,7 @@ The ability for anyone to create a token for themselves is toggleable via config When there is a destructive (either regeneration or deletion) operation pending, only `/cancel` or `/confirm` commands are accepted. ## Configuration and deployment -You'll need .NET 6 runtime. Hosting on GNU/Linux in a proper data center is highly encouraged. +You'll need .NET 7 runtime. Hosting on GNU/Linux in a proper data center is highly encouraged. By default, listens on port 8082. This can be changed with `--port ` command-line argument. Rest of the settings are inside `appsettings.json`: * "General" section contains Telegram's bot token, API endpoint URL as seen from outside world (certainly not localhost:8082 as Kestrel would told you it listens to) and a boolean that controls whether new users can create tokens. Please note that `/api` will be appended to this address. So, for example, if you set `https://foo.example.com/bar` here, actual endpoint to be used with the app (both for Telegram API webhooks and for user messages) will be `https://foo.example.com/bar/api` @@ -75,4 +75,5 @@ Initial release. More an excercise in ASP.NET Core than an app I needed at the m Greatly increased the reliability of Markdown parsing in one of the most **not** straightforward ways you can imagine -- by converting the Markdown to HTML with a few custom convertion quirks. * **no version number**, 2020-05-14 Shelved attempt to improve the codebase. Consists of one architecture change and is fully ~~included~~ rewritten in the next release. -* TODO the current iteration, version number 1.5? 1.9? Certainly not 2.x _yet_. \ No newline at end of file +* **v 2.0**, not yet released, (hopefully) in development +Really proper markdown support this time (Telegram's version with questionable selection of characters to be escaped), option to send a silent notification, async everthing, and probably something else I forgot about. From 5f08f4450e27d752374453e4ec46beef6dbe52ac Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 29 Apr 2023 15:03:40 +0700 Subject: [PATCH 31/39] moved localizable text away from appsettings ...to class constants. LocalizationOptions are no longer filled at startup nor handled by DI. also some new quotes for /token command --- WebToTelegramCore/BotCommands/AboutCommand.cs | 16 +- .../BotCommands/BotCommandBase.cs | 19 +-- .../BotCommands/CancelCommand.cs | 27 +--- .../BotCommands/ConfirmCommand.cs | 27 +--- .../BotCommands/ConfirmationCommandBase.cs | 19 +-- .../BotCommands/CreateCommand.cs | 22 +-- .../BotCommands/DeleteCommand.cs | 21 +-- .../BotCommands/DirectiveCommand.cs | 3 +- .../BotCommands/GuestOnlyCommandBase.cs | 15 +- WebToTelegramCore/BotCommands/HelpCommand.cs | 15 +- .../BotCommands/RegenerateCommand.cs | 15 +- WebToTelegramCore/BotCommands/StartCommand.cs | 27 +--- WebToTelegramCore/BotCommands/TokenCommand.cs | 30 +--- .../BotCommands/UserOnlyCommandBase.cs | 14 +- .../Options/LocalizationOptions.cs | 140 +++++++++++++++--- .../Services/TelegramApiService.cs | 55 +++---- WebToTelegramCore/Startup.cs | 2 - WebToTelegramCore/appsettings.json | 23 --- 18 files changed, 196 insertions(+), 294 deletions(-) diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index 1525455..2a97a82 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -11,24 +11,15 @@ namespace WebToTelegramCore.BotCommands /// public class AboutCommand : BotCommandBase, IBotCommand { - /// - /// Template to message, {0} is assembly version. - /// Written in Telegram's MarkdownV2 with a lot of escaping. - /// - private const string _template = "**Dotnet Telegram forwarder** v {0}\n\n" + - "[Open\\-source\\!](https://github.com/bnfour/dotnet-telegram-forwarder)\n" + - "by bnfour, 2018, 2020\\-2023\\."; - /// /// Command's text. /// public override string Command => "/about"; /// - /// Constructor that passed localization options to base class. + /// Constructor. /// - /// Localization options to use. - public AboutCommand(LocalizationOptions locale) : base(locale) { } + public AboutCommand() : base() { } /// /// Method to process the command. @@ -39,8 +30,9 @@ public AboutCommand(LocalizationOptions locale) : base(locale) { } public override string Process(Record record) { var version = Assembly.GetExecutingAssembly().GetName().Version; + // imagine having to escape dot for "markdown" var prettyVersion = $"{version.Major}\\.{version.Minor}"; - return base.Process(record) ?? String.Format(_template, prettyVersion); + return base.Process(record) ?? String.Format(LocalizationOptions.About, prettyVersion); } } } diff --git a/WebToTelegramCore/BotCommands/BotCommandBase.cs b/WebToTelegramCore/BotCommands/BotCommandBase.cs index 8a4d62d..e28cdc8 100644 --- a/WebToTelegramCore/BotCommands/BotCommandBase.cs +++ b/WebToTelegramCore/BotCommands/BotCommandBase.cs @@ -11,25 +11,15 @@ namespace WebToTelegramCore.BotCommands /// public abstract class BotCommandBase : IBotCommand { - /// - /// Text somewhat explaining why processing of this Record - /// was cancelled in this class. - /// - private readonly string _error; - /// /// Command text; not implemented in abstract classes. /// public abstract string Command { get; } /// - /// Constructor that sets error message. + /// Constructor. /// - /// Locale options to use. - public BotCommandBase(LocalizationOptions locale) - { - _error = locale.ErrorConfirmationPending; - } + public BotCommandBase() { } /// /// Method of abstract base class that filters out users with pending @@ -40,8 +30,9 @@ public BotCommandBase(LocalizationOptions locale) /// or null otherwise. public virtual string Process(Record record) { - return (!string.IsNullOrEmpty(record.Token) && record.State != RecordState.Normal) ? - _error : null; + return (!string.IsNullOrEmpty(record.Token) && record.State != RecordState.Normal) + ? LocalizationOptions.ErrorConfirmationPending + : null; } } } diff --git a/WebToTelegramCore/BotCommands/CancelCommand.cs b/WebToTelegramCore/BotCommands/CancelCommand.cs index abbf5e1..4ea89a0 100644 --- a/WebToTelegramCore/BotCommands/CancelCommand.cs +++ b/WebToTelegramCore/BotCommands/CancelCommand.cs @@ -6,34 +6,20 @@ namespace WebToTelegramCore.BotCommands { /// - /// Class that implements /cancel command. + /// Class that implements /cancel command to cancel pending destructive + /// operations. /// public class CancelCommand : ConfirmationCommandBase, IBotCommand { - /// - /// Reply text when deletion was cancelled. - /// - private readonly string _deletionCancel; - - /// - /// Reply text when regeneration was cancelled. - /// - private readonly string _regenerationCancel; - /// /// Command's text. /// public override string Command => "/cancel"; /// - /// Constructor that sets error message. + /// Constructor. /// - /// Locale options to use. - public CancelCommand(LocalizationOptions locale) : base(locale) - { - _deletionCancel = locale.CancelDeletion; - _regenerationCancel = locale.CancelRegeneration; - } + public CancelCommand() : base() { } /// /// Method to process the command. Resets Record's State back to Normal. @@ -49,8 +35,9 @@ public override string Process(Record record) return baseResult; } - string reply = record.State == RecordState.PendingDeletion ? - _deletionCancel : _regenerationCancel; + string reply = record.State == RecordState.PendingDeletion + ? LocalizationOptions.CancelDeletion + : LocalizationOptions.CancelRegeneration; record.State = RecordState.Normal; return reply; diff --git a/WebToTelegramCore/BotCommands/ConfirmCommand.cs b/WebToTelegramCore/BotCommands/ConfirmCommand.cs index e43a058..62ff2be 100644 --- a/WebToTelegramCore/BotCommands/ConfirmCommand.cs +++ b/WebToTelegramCore/BotCommands/ConfirmCommand.cs @@ -7,22 +7,11 @@ namespace WebToTelegramCore.BotCommands { /// - /// Class that implements /cancel command which either deletes user's token or - /// replaces it with a new one. + /// Class that implements /confirm command which either deletes user's token or + /// replaces it with a new one after a request via previous command. /// public class ConfirmCommand : ConfirmationCommandBase, IBotCommand { - /// - /// Message to display when token is deleted. - /// - private readonly string _deletion; - - /// - /// Format string for message about token regeneration. The only argument {0} - /// is a newly generated token. - /// - private readonly string _regenration; - /// /// Command's text. /// @@ -46,19 +35,15 @@ public class ConfirmCommand : ConfirmationCommandBase, IBotCommand /// /// Constructor. /// - /// Locale options to use. /// Database context to use. /// Token generator to use. /// Record helper to use. - public ConfirmCommand(LocalizationOptions locale, RecordContext context, - ITokenGeneratorService generator, IRecordService recordService) : base(locale) + public ConfirmCommand(RecordContext context, ITokenGeneratorService generator, + IRecordService recordService) : base() { _context = context; _tokenGenerator = generator; _recordService = recordService; - - _deletion = locale.ConfirmDeletion; - _regenration = locale.ConfirmRegeneration; } /// @@ -97,7 +82,7 @@ private string Regenerate(Record record) _context.Remove(record); _context.Add(newRecord); _context.SaveChanges(); - return String.Format(_regenration, newToken); + return String.Format(LocalizationOptions.ConfirmRegeneration, newToken); } /// @@ -109,7 +94,7 @@ private string Delete(Record record) { _context.Remove(record); _context.SaveChanges(); - return _deletion; + return LocalizationOptions.ConfirmDeletion; } } } diff --git a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs index 428a27d..b52e275 100644 --- a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs +++ b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs @@ -12,25 +12,15 @@ namespace WebToTelegramCore.BotCommands /// public abstract class ConfirmationCommandBase : IBotCommand { - /// - /// Text somewhat explaining why processing of this Record - /// was cancelled in this class. - /// - private readonly string _error; - /// /// Command text; not implemented in abstract classes. /// public abstract string Command { get; } /// - /// Constructor that sets error message. + /// Constructor. /// - /// Locale options to use. - public ConfirmationCommandBase(LocalizationOptions locale) - { - _error = locale.ErrorNoConfirmationPending; - } + public ConfirmationCommandBase() { } /// /// Method of abstract base class that filters out users without pending @@ -41,8 +31,9 @@ public ConfirmationCommandBase(LocalizationOptions locale) /// or null otherwise. public virtual string Process(Record record) { - return (string.IsNullOrEmpty(record.Token) || record.State == RecordState.Normal) ? - _error : null; + return (string.IsNullOrEmpty(record.Token) || record.State == RecordState.Normal) + ? LocalizationOptions.ErrorNoConfirmationPending + : null; } } } diff --git a/WebToTelegramCore/BotCommands/CreateCommand.cs b/WebToTelegramCore/BotCommands/CreateCommand.cs index 857385e..2e419de 100644 --- a/WebToTelegramCore/BotCommands/CreateCommand.cs +++ b/WebToTelegramCore/BotCommands/CreateCommand.cs @@ -15,16 +15,6 @@ public class CreateCommand : GuestOnlyCommandBase, IBotCommand /// public override string Command => "/create"; - /// - /// Message to display on token creation. Must be formatted, {0} is token. - /// - private readonly string _message; - - /// - /// Message to display when registration is off. - /// - private readonly string _goAway; - /// /// Field to store database context reference. /// @@ -48,22 +38,18 @@ public class CreateCommand : GuestOnlyCommandBase, IBotCommand /// /// Constructor that injects dependencies and sets up registration state. /// - /// Locale options to use. /// Database context to use. /// Token generator service to use. /// Record helper service to use. /// State of registration. - public CreateCommand(LocalizationOptions locale, RecordContext context, - ITokenGeneratorService generator, IRecordService recordService, bool isRegistrationEnabled) : base(locale) + public CreateCommand(RecordContext context, ITokenGeneratorService generator, + IRecordService recordService, bool isRegistrationEnabled) : base() { _context = context; _generator = generator; _recordService = recordService; _isRegistrationEnabled = isRegistrationEnabled; - - _message = locale.CreateSuccess; - _goAway = locale.CreateGoAway; } /// @@ -90,11 +76,11 @@ private string InternalProcess(Record record) var r = _recordService.Create(token, record.AccountNumber); _context.Add(r); _context.SaveChanges(); - return String.Format(_message, token); + return String.Format(LocalizationOptions.CreateSuccess, token); } else { - return _goAway; + return LocalizationOptions.CreateGoAway; } } } diff --git a/WebToTelegramCore/BotCommands/DeleteCommand.cs b/WebToTelegramCore/BotCommands/DeleteCommand.cs index 22d01c7..4b3e7fa 100644 --- a/WebToTelegramCore/BotCommands/DeleteCommand.cs +++ b/WebToTelegramCore/BotCommands/DeleteCommand.cs @@ -16,16 +16,6 @@ public class DeleteCommand : UserOnlyCommandBase, IBotCommand /// public override string Command => "/delete"; - /// - /// Message that confirms deletion is now pending. - /// - private readonly string _message; - - /// - /// Additional warning shown when registration is turned off. - /// - private readonly string _noTurningBack; - /// /// Boolean that indicates whether this instance is accepting new users. /// True if it does. @@ -35,15 +25,10 @@ public class DeleteCommand : UserOnlyCommandBase, IBotCommand /// /// Constructor. /// - /// Locale options to use. /// Registration state. True is enabled. - public DeleteCommand(LocalizationOptions locale, bool registrationEnabled) - : base(locale) + public DeleteCommand(bool registrationEnabled) : base() { _registrationEnabled = registrationEnabled; - - _message = locale.DeletionPending; - _noTurningBack = locale.DeletionNoTurningBack; } /// @@ -64,7 +49,9 @@ public override string Process(Record record) private string InternalProcess(Record record) { record.State = RecordState.PendingDeletion; - return _registrationEnabled ? _message : _message + "\n\n" + _noTurningBack; + return _registrationEnabled + ? LocalizationOptions.DeletionPending + : LocalizationOptions.DeletionPending + "\n\n" + LocalizationOptions.DeletionNoTurningBack; } } } diff --git a/WebToTelegramCore/BotCommands/DirectiveCommand.cs b/WebToTelegramCore/BotCommands/DirectiveCommand.cs index 29abc81..5a53c94 100644 --- a/WebToTelegramCore/BotCommands/DirectiveCommand.cs +++ b/WebToTelegramCore/BotCommands/DirectiveCommand.cs @@ -25,8 +25,7 @@ public class DirectiveCommand : BotCommandBase, IBotCommand /// Constructor that does literally nothing yet required due to my "superb" /// planning skills. /// - /// Locale options to use. - public DirectiveCommand(LocalizationOptions locale) : base(locale) { } + public DirectiveCommand() : base() { } /// /// Method to process the command. diff --git a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs index b71deaf..39e4492 100644 --- a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs @@ -10,20 +10,11 @@ namespace WebToTelegramCore.BotCommands /// public abstract class GuestOnlyCommandBase : BotCommandBase, IBotCommand { - /// - /// Text somewhat explaining why processing of this Record - /// was cancelled in this class. - /// - private readonly string _error; - /// /// Constructor that sets up error message. /// /// Locale options to use. - public GuestOnlyCommandBase(LocalizationOptions locale) : base(locale) - { - _error = locale.ErrorMustBeGuest; - } + public GuestOnlyCommandBase() : base() { } /// /// Method of abstract base class that adds filtering out users @@ -43,7 +34,9 @@ public GuestOnlyCommandBase(LocalizationOptions locale) : base(locale) /// Error message if user has a token, or null otherwise. private string InternalProcess(Record record) { - return string.IsNullOrEmpty(record.Token) ? null : _error; + return string.IsNullOrEmpty(record.Token) + ? null + : LocalizationOptions.ErrorMustBeGuest; } } } diff --git a/WebToTelegramCore/BotCommands/HelpCommand.cs b/WebToTelegramCore/BotCommands/HelpCommand.cs index 0ce6395..3c21394 100644 --- a/WebToTelegramCore/BotCommands/HelpCommand.cs +++ b/WebToTelegramCore/BotCommands/HelpCommand.cs @@ -15,18 +15,9 @@ public class HelpCommand : BotCommandBase, IBotCommand public override string Command => "/help"; /// - /// Helpful help message to send. + /// Constructor. /// - private readonly string _message; - - /// - /// Constructor that sets up message. - /// - /// Locale options to use. - public HelpCommand(LocalizationOptions locale) : base(locale) - { - _message = locale.Help; - } + public HelpCommand() : base() { } /// /// Method to process the command. @@ -37,7 +28,7 @@ public HelpCommand(LocalizationOptions locale) : base(locale) /// corresponding error message otherwise. public override string Process(Record record) { - return base.Process(record) ?? _message; + return base.Process(record) ?? LocalizationOptions.Help; } } } diff --git a/WebToTelegramCore/BotCommands/RegenerateCommand.cs b/WebToTelegramCore/BotCommands/RegenerateCommand.cs index deb4c44..aadd0f2 100644 --- a/WebToTelegramCore/BotCommands/RegenerateCommand.cs +++ b/WebToTelegramCore/BotCommands/RegenerateCommand.cs @@ -17,18 +17,9 @@ public class RegenerateCommand : UserOnlyCommandBase, IBotCommand public override string Command => "/regenerate"; /// - /// Message that confirms regeneration is now pending. + /// Constructor. /// - private readonly string _message; - - /// - /// Constructor that sets up message. - /// - /// Locale options to use. - public RegenerateCommand(LocalizationOptions locale) : base(locale) - { - _message = locale.RegenerationPending; - } + public RegenerateCommand() : base() { } /// /// Method to process the command. @@ -48,7 +39,7 @@ public override string Process(Record record) private string InternalProcess(Record record) { record.State = RecordState.PendingRegeneration; - return _message; + return LocalizationOptions.RegenerationPending; } } } diff --git a/WebToTelegramCore/BotCommands/StartCommand.cs b/WebToTelegramCore/BotCommands/StartCommand.cs index 3c72403..f17a6fa 100644 --- a/WebToTelegramCore/BotCommands/StartCommand.cs +++ b/WebToTelegramCore/BotCommands/StartCommand.cs @@ -9,21 +9,6 @@ namespace WebToTelegramCore.BotCommands /// public class StartCommand : BotCommandBase, IBotCommand { - /// - /// Fisrt part of response to the command which is always displayed. - /// - private readonly string _startMessage; - - /// - /// Additional text to display when registration is open. - /// - private readonly string _registrationHint; - - /// - /// Additional text to display when registration is closed. - /// - private readonly string _noRegistration; - /// /// Field to store current state of registartion of new users. /// @@ -37,17 +22,11 @@ public class StartCommand : BotCommandBase, IBotCommand /// /// Constructor. /// - /// Locale options to use. /// Boolean indicating whether registration /// is enabled (true) or not. - public StartCommand(LocalizationOptions locale, bool registrationEnabled) - : base(locale) + public StartCommand(bool registrationEnabled) : base() { _isRegistrationOpen = registrationEnabled; - - _startMessage = locale.StartMessage; - _noRegistration = locale.StartGoAway; - _registrationHint = locale.StartRegistrationHint; } /// @@ -59,8 +38,8 @@ public StartCommand(LocalizationOptions locale, bool registrationEnabled) /// corresponding error message otherwise. public override string Process(Record record) { - string appendix = _isRegistrationOpen ? _registrationHint : _noRegistration; - return base.Process(record) ?? _startMessage + "\n\n" + appendix; + string appendix = _isRegistrationOpen ? LocalizationOptions.StartRegistrationHint : LocalizationOptions.StartGoAway; + return base.Process(record) ?? LocalizationOptions.StartMessage + "\n\n" + appendix; } } } diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index 66e0535..9960cd7 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -19,7 +19,7 @@ public class TokenCommand : UserOnlyCommandBase, IBotCommand /// /// Random quotes to display as message example. /// - private readonly List _examples = new List() + private readonly string[] _examples = new[] { "Hello world!", "Timeline lost", @@ -35,7 +35,9 @@ public class TokenCommand : UserOnlyCommandBase, IBotCommand "Try again later", "More than two and less than four", "Of course I still love you", - "それは何?" + "それは何?", + "There was nothing to be sad about", + "I never asked for this" }; /// @@ -43,29 +45,13 @@ public class TokenCommand : UserOnlyCommandBase, IBotCommand /// private readonly string _apiEndpoint; - /// - /// Template for reply with three formatters: {0} is token, {1} is API endpoint, - /// {2} is random vanity message example. - /// - private readonly string _templateOne; - - /// - /// Message explaining Response structure and roles of its fields. - /// - private readonly string _errors; - /// /// Constructor. /// - /// Locale options to use. /// API endpoint URL. - public TokenCommand(LocalizationOptions locale, string apiEndpoint) - : base(locale) + public TokenCommand(string apiEndpoint) : base() { _apiEndpoint = apiEndpoint; - - _templateOne = locale.TokenTemplate; - _errors = locale.TokenErrorsDescription; } /// @@ -86,9 +72,9 @@ public override string Process(Record record) /// Message with token and API usage example. private string InternalProcess(Record record) { - string text = _examples[new Random().Next(0, _examples.Count)]; - return String.Format(_templateOne, record.Token, _apiEndpoint + "/api", text) - + "\n\n" + _errors; + string text = _examples[new Random().Next(0, _examples.Length)]; + return String.Format(LocalizationOptions.TokenTemplate, record.Token, _apiEndpoint + "/api", text) + + "\n\n" + LocalizationOptions.TokenErrorsDescription; } } } diff --git a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs index 29769c0..1849548 100644 --- a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs @@ -10,17 +10,11 @@ namespace WebToTelegramCore.BotCommands /// public abstract class UserOnlyCommandBase : BotCommandBase, IBotCommand { + /// - /// Text somewhat explaining why processing of this Record - /// was cancelled in this class. + /// Constructor. /// - private readonly string _error; - - - public UserOnlyCommandBase(LocalizationOptions locale) : base(locale) - { - _error = locale.ErrorMustBeUser; - } + public UserOnlyCommandBase() : base() { } /// /// Method of abstract base class that adds filtering out users @@ -42,7 +36,7 @@ public UserOnlyCommandBase(LocalizationOptions locale) : base(locale) /// or null otherwise. private string InternalProcess(Record record) { - return string.IsNullOrEmpty(record.Token) ? _error : null; + return string.IsNullOrEmpty(record.Token) ? LocalizationOptions.ErrorMustBeUser : null; } } } diff --git a/WebToTelegramCore/Options/LocalizationOptions.cs b/WebToTelegramCore/Options/LocalizationOptions.cs index 0d8f42d..9c351fb 100644 --- a/WebToTelegramCore/Options/LocalizationOptions.cs +++ b/WebToTelegramCore/Options/LocalizationOptions.cs @@ -3,118 +3,214 @@ /// /// Class that holds all customizable string the bot may reply with. /// Also, descriptions of error codes. + /// Not ideal, but slightly better (is it?) than holding these in appsettings.json. /// - public class LocalizationOptions + + // Please note that these should be formatted as Telegram-flavoured Markdown + // see https://core.telegram.org/bots/api#markdownv2-style + // Templating parameters (like {0}) are filled in before sending to the API, + // so { and } in these should NOT be escaped. + + public static class LocalizationOptions { + /// + /// Template for reply for /about command. {0} is assembly version. + /// + public const string About = """ + **Dotnet Telegram forwarder** v {0} + + [Open\-source\!](https://github.com/bnfour/dotnet-telegram-forwarder) + by bnfour, 2018, 2020\-2023\. + """; + /// /// Message to show when token deletion was cancelled. /// - public string CancelDeletion { get; set; } + public const string CancelDeletion = """ + Token deletion cancelled\. + """; /// /// Message to show when token regeneration was cancelled. /// - public string CancelRegeneration { get; set; } + public const string CancelRegeneration = """ + Token regeneration cancelled\. + """; /// /// Message to show when token deletion was completed. /// - public string ConfirmDeletion { get; set; } + public const string ConfirmDeletion = """ + Token deleted\. + Thank you for giving us an oppurtunity to serve you\. + """; /// /// Template for message to show when token regeneration was completed. /// {0} is new token. /// - public string ConfirmRegeneration { get; set; } + public const string ConfirmRegeneration = """ + Your new token is + + `{0}` + + Don't forget to update your clients' settings\. + """; /// /// Message to reply to /create with when registration is disabled. /// - public string CreateGoAway { get; set; } + public const string CreateGoAway = """ + This instance of the bot is not accepting new users for now\. + """; /// /// Template for message to show when token was created successfully. /// {0} is new token. /// - public string CreateSuccess { get; set; } + public const string CreateSuccess = """ + Success\! Your token is: + + `{0}` + + Please consult /token for usage\. + """; /// /// Message to show when user initiated token deletion and registration is off. /// - public string DeletionNoTurningBack { get; set; } + public const string DeletionNoTurningBack = """ + This bot has registration turned *off*\. You won't be able to create new token\. Please be certain\. + """; /// /// Message to show when user initiated token deletion. /// - public string DeletionPending { get; set; } + public const string DeletionPending = """ + *Token deletion pending\!* + + Please either /confirm or /cancel it\. This cannot be undone\. + If you need to change your token, consider to /regenerate it instead of deleting and re\-creating\. + """; /// /// Message to show when confirmation is pending and user tries to use /// non-confirming command. /// - public string ErrorConfirmationPending { get; set; } + public const string ErrorConfirmationPending = """ + You have an operation pending confirmation\. Please /confirm or /cancel it before using other commands\. + """; /// /// Message to show to unknown commands starting with slash. /// - public string ErrorDave { get; set; } + public const string ErrorDave = """ + I'm afraid I can't let you do that\. + """; /// /// Message to show when usage of command requires no token set, /// but user has one. /// - public string ErrorMustBeGuest { get; set; } + public const string ErrorMustBeGuest = """ + In order to use this command, you must have no token associated with your account\. You can /delete your existing one, but why? + """; /// /// Message to show when usage of command requires a token, but user has none. /// - public string ErrorMustBeUser { get; set; } + public const string ErrorMustBeUser = """ + In order to use this command, you must have a token associated with your account\. /create one\." + """; /// /// Message to show when user is trying to use confirming command, but no /// confirmation is pending. /// - public string ErrorNoConfirmationPending { get; set; } + public const string ErrorNoConfirmationPending = """ + This command is only useful when you're trying to /delete or /regenerate your token\. + """; /// /// Message to show on unknown input. /// - public string ErrorWhat { get; set; } + public const string ErrorWhat = """ + Unfortunately, I'm not sure how to interpret this\. + """; /// /// Output of /help command. /// - public string Help { get; set; } + public const string Help = """ + This app provides a web API to route messages from API's endpoint to you in Telegram via this bot\. It can be used to notify you in Telegram from any Internet\-enabled device you want \(provided you know how to make POST requests from it\)\. + To start, /create your token\. You can /delete it anytime\. To change your token for whatever reason, please /regenerate it and not delete and re\-create\. + Once you have a token, see /token for additional usage help\. + + Other commands supported by bot include: + \- /confirm and /cancel are used to prevent accidental deletions and regenerations; + \- /help displays this message; + \- /about displays general info about this bot\. + + \-\-bnfour + """; /// /// Message to show when user initiated token regeneration. /// - public string RegenerationPending { get; set; } + public const string RegenerationPending = """ + *Token regenration pending\!* + + Please either /confirm or /cancel it\. It cannot be undone\. Please be certain\. + """; /// /// Message to warn new users that registration is closed. /// - public string StartGoAway { get; set; } + public const string StartGoAway = """ + Sorry, this instance of bot is not accepting new users for now\. + """; /// /// Message to show when user first engages the bot. /// - public string StartMessage { get; set; } + public const string StartMessage = """ + Hello\! + + This bot provides a standalone web API to relay messages from anything that can send web requests to Telegram as messages from the bot\. + + *Please note*: this requires some external tools and knowledge\. If you consider "Send a POST request" a magic gibberish you don't understand, this bot probably isn't much of use to you\. + """; /// /// Message to encourage new users to register. /// - public string StartRegistrationHint { get; set; } + public const string StartRegistrationHint = """ + If that does not stop you, feel free to /create your very own token\. + """; /// /// Helper text that explains web API output. /// - public string TokenErrorsDescription { get; set; } + public const string TokenErrorsDescription = """ + If everything is okay, the API will return a blank 200 OK response\. If something is not okay, a different status code will be returned\. Consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) to see possible error codes\. + """; /// /// Template for message to reply to /token command with. /// {0} is token, {1} is API endpoint URL, {2} is vanity quote. /// - public string TokenTemplate { get; set; } + public const string TokenTemplate = """ + Your token is + + `{0}` + + *Usage:* To deliver a message, send a POST request to {1} with JSON body\. The payload must contain two parameters: your token and your message\. There are also optional parameters, please consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) for details\. Example of a bare minimum payload: + ``` + { + "token": "{0}", + "message": "{2}" + } + ``` + """; } } diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index 841e879..ba2df54 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -48,35 +48,17 @@ public class TelegramApiService : ITelegramApiService /// private readonly List _commands; - /// - /// Indicates whether usage of /create command is enabled. - /// - private readonly bool _isRegistrationOpen; - - /// - /// Message to reply with when input is starting with slash, but none of the - /// commands fired in response. - /// - private readonly string _invalidCommandReply; - - /// - /// Message to reply with when input isn't even resembles a command. - /// - private readonly string _invalidReply; - /// /// Constructor that injects dependencies and configures list of commands. /// /// Options that include token. - /// Localization options. /// Database context to use. /// Bot service instance to use. /// Token generator service to use. /// Record helper service to use. - public TelegramApiService(IOptions options, - IOptions locale, RecordContext context, - ITelegramBotService bot, ITokenGeneratorService generator, - IRecordService recordService) + public TelegramApiService(IOptions options, + RecordContext context, ITelegramBotService bot, + ITokenGeneratorService generator, IRecordService recordService) { _token = options.Value.Token; _context = context; @@ -84,25 +66,20 @@ public TelegramApiService(IOptions options, _generator = generator; _recordService = recordService; - _isRegistrationOpen = options.Value.RegistrationEnabled; - - LocalizationOptions locOptions = locale.Value; - - _invalidCommandReply = locOptions.ErrorDave; - _invalidReply = locOptions.ErrorWhat; + var isRegistrationOpen = options.Value.RegistrationEnabled; _commands = new List() { - new StartCommand(locOptions, _isRegistrationOpen), - new TokenCommand(locOptions, options.Value.ApiEndpointUrl), - new RegenerateCommand(locOptions), - new DeleteCommand(locOptions, _isRegistrationOpen), - new ConfirmCommand(locOptions, _context, _generator, _recordService), - new CancelCommand(locOptions), - new HelpCommand(locOptions), - new DirectiveCommand(locOptions), - new AboutCommand(locOptions), - new CreateCommand(locOptions, _context, _generator, _recordService, _isRegistrationOpen) + new StartCommand(isRegistrationOpen), + new TokenCommand(options.Value.ApiEndpointUrl), + new RegenerateCommand(), + new DeleteCommand(isRegistrationOpen), + new ConfirmCommand(_context, _generator, _recordService), + new CancelCommand(), + new HelpCommand(), + new DirectiveCommand(), + new AboutCommand(), + new CreateCommand(_context, _generator, _recordService, isRegistrationOpen) }; } @@ -172,7 +149,9 @@ private async Task HandleUnknownText(long accountId, string text) } else { - string reply = text.StartsWith("/") ? _invalidCommandReply : _invalidReply; + string reply = text.StartsWith("/") + ? LocalizationOptions.ErrorDave + : LocalizationOptions.ErrorWhat; await _bot.Send(accountId, reply); } } diff --git a/WebToTelegramCore/Startup.cs b/WebToTelegramCore/Startup.cs index 1283934..b2ba015 100644 --- a/WebToTelegramCore/Startup.cs +++ b/WebToTelegramCore/Startup.cs @@ -36,10 +36,8 @@ public void ConfigureServices(IServiceCollection services) // so to deserialize it correctly, we need to use this library as well services.AddControllers().AddNewtonsoftJson(); - // Options pattern to the rescue? services.Configure(Configuration.GetSection("General")); services.Configure(Configuration.GetSection("Bandwidth")); - services.Configure(Configuration.GetSection("Strings")); } } } diff --git a/WebToTelegramCore/appsettings.json b/WebToTelegramCore/appsettings.json index 0844e25..1fc1bf1 100644 --- a/WebToTelegramCore/appsettings.json +++ b/WebToTelegramCore/appsettings.json @@ -16,28 +16,5 @@ "Bandwidth": { "InitialCount": 20, "SecondsPerRegeneration": 60 - }, - "Strings": { - "CancelDeletion": "Token deletion cancelled\\.", - "CancelRegeneration": "Token regeneration cancelled\\.", - "ConfirmDeletion": "Token deleted\\. Thank you for giving us an oppurtunity to serve you\\.", - "ConfirmRegeneration": "Your new token is\n\n`{0}`\n\nDon't forget to update your clients' settings\\.", - "CreateGoAway": "This instance of bot is not accepting new users for now\\.", - "CreateSuccess": "Success\\! Your token is:\n\n`{0}`\n\nPlease consult /token for usage\\.", - "DeletionNoTurningBack": "This bot has registration turned *off*\\. You won't be able to create new token\\. Please be certain\\.", - "DeletionPending": "*Token deletion pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\.\nIf you need to change your token, please consider to /regenerate it instead of deleting and re\\-creating it\\.", - "ErrorConfirmationPending": "You have an operation pending cancellation\\. Please /confirm or /cancel it before using other commands\\.", - "ErrorDave": "I'm afraid I can't let you do that\\.", - "ErrorMustBeGuest": "In order to use this command, you must have no token associated with your account\\. You can /delete your existing one, but why?", - "ErrorMustBeUser": "In order to use this command, you must have a token associated with your account\\. Try running /create first\\.", - "ErrorNoConfirmationPending": "This command is only useful when you're trying to /delete or /regenerate your token\\.", - "ErrorWhat": "Unfortunately, I'm not sure how to interpret this\\.", - "Help": "This bot provides web API to route messages from API's endpoint to you in Telegram via bot\\. It can be used to notify you in Telegram from any Internet\\-enabled device you want \\(provided you know how to make POST requests from it\\)\\.\nTo start, /create your token \\(if this particular instance of bot has registration of new users open\\)\\. You can /delete it anytime\\. To change your token for whatever reason, please /regenerate it and not delete and re\\-create\\.\nOnce you have a token, see /token for additional usage help\\.\nOther commands supported by bot include:\n\\- /confirm and /cancel are used to prevent accidental deletions and regenerations;\n\\- /help displays this message;\n\\- /about displays general info about this bot\\.\n\nThere's also an easter egg command and a rare response to unknown commands: one way to satisfy your curiosity is to check out bot's source code\\.\n\n\\-\\-bnfour", - "RegenerationPending": "*Token regenration pending\\!*\n\nPlease either /confirm or /cancel it\\. It cannot be undone\\. Please be certain\\.", - "StartGoAway": "Sorry, this instance of bot is not accepting new users for now\\.", - "StartMessage": "Hello\\!\n\nThis bot provides a standalone web API to relay messages from anything that can send web requests to Telegram as messages from the bot\\.\n\n*Please note*: this requires some external tools and knowledge\\. If you consider phrases like \"Send a POST request with JSON body\" a magic gibberish you don't understand, this bot probably isn't much of use to you\\.", - "StartRegistrationHint": "If that does not stop you, feel free to /create your very own token\\.", - "TokenErrorsDescription": "If everything is okay, the API will return a blank 200 OK response. If something is not okay, a different status code will be returned\\. Consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) to see descriptions of possible error status codes\\.", - "TokenTemplate": "Your token is\n\n`{0}`\n\n*Usage:* To deliver a message, send a POST request to {1} with JSON body\\. The payload must contain two parameters: your token and your message\\. There are also two optional parameters: parsing type and disabling the notification, please consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) for details\\. Example of a bare minimum payload:\n\n```\n{\n \"token\": \"{0}\",\n \"message\": \"{2}\"\n}```" } } From 3f7c5f8dcbfaa5fb7a428614df55ef0e6926f17a Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 29 Apr 2023 15:12:34 +0700 Subject: [PATCH 32/39] moved and renamed former LocalizationOptions since they are no longer options --- WebToTelegramCore/BotCommands/AboutCommand.cs | 4 ++-- WebToTelegramCore/BotCommands/BotCommandBase.cs | 4 ++-- WebToTelegramCore/BotCommands/CancelCommand.cs | 6 +++--- WebToTelegramCore/BotCommands/ConfirmCommand.cs | 6 +++--- WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs | 4 ++-- WebToTelegramCore/BotCommands/CreateCommand.cs | 6 +++--- WebToTelegramCore/BotCommands/DeleteCommand.cs | 6 +++--- WebToTelegramCore/BotCommands/DirectiveCommand.cs | 1 - WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs | 4 ++-- WebToTelegramCore/BotCommands/HelpCommand.cs | 4 ++-- WebToTelegramCore/BotCommands/RegenerateCommand.cs | 4 ++-- WebToTelegramCore/BotCommands/StartCommand.cs | 6 +++--- WebToTelegramCore/BotCommands/TokenCommand.cs | 7 +++---- WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs | 4 ++-- .../LocalizationOptions.cs => Resources/Locale.cs} | 4 ++-- WebToTelegramCore/Services/TelegramApiService.cs | 5 +++-- 16 files changed, 37 insertions(+), 38 deletions(-) rename WebToTelegramCore/{Options/LocalizationOptions.cs => Resources/Locale.cs} (99%) diff --git a/WebToTelegramCore/BotCommands/AboutCommand.cs b/WebToTelegramCore/BotCommands/AboutCommand.cs index 2a97a82..2560bce 100644 --- a/WebToTelegramCore/BotCommands/AboutCommand.cs +++ b/WebToTelegramCore/BotCommands/AboutCommand.cs @@ -2,7 +2,7 @@ using System.Reflection; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -32,7 +32,7 @@ public override string Process(Record record) var version = Assembly.GetExecutingAssembly().GetName().Version; // imagine having to escape dot for "markdown" var prettyVersion = $"{version.Major}\\.{version.Minor}"; - return base.Process(record) ?? String.Format(LocalizationOptions.About, prettyVersion); + return base.Process(record) ?? String.Format(Locale.About, prettyVersion); } } } diff --git a/WebToTelegramCore/BotCommands/BotCommandBase.cs b/WebToTelegramCore/BotCommands/BotCommandBase.cs index e28cdc8..924dd31 100644 --- a/WebToTelegramCore/BotCommands/BotCommandBase.cs +++ b/WebToTelegramCore/BotCommands/BotCommandBase.cs @@ -1,7 +1,7 @@ using WebToTelegramCore.Data; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -31,7 +31,7 @@ public BotCommandBase() { } public virtual string Process(Record record) { return (!string.IsNullOrEmpty(record.Token) && record.State != RecordState.Normal) - ? LocalizationOptions.ErrorConfirmationPending + ? Locale.ErrorConfirmationPending : null; } } diff --git a/WebToTelegramCore/BotCommands/CancelCommand.cs b/WebToTelegramCore/BotCommands/CancelCommand.cs index 4ea89a0..fb0e503 100644 --- a/WebToTelegramCore/BotCommands/CancelCommand.cs +++ b/WebToTelegramCore/BotCommands/CancelCommand.cs @@ -1,7 +1,7 @@ using WebToTelegramCore.Data; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -36,8 +36,8 @@ public override string Process(Record record) } string reply = record.State == RecordState.PendingDeletion - ? LocalizationOptions.CancelDeletion - : LocalizationOptions.CancelRegeneration; + ? Locale.CancelDeletion + : Locale.CancelRegeneration; record.State = RecordState.Normal; return reply; diff --git a/WebToTelegramCore/BotCommands/ConfirmCommand.cs b/WebToTelegramCore/BotCommands/ConfirmCommand.cs index 62ff2be..1f4dedd 100644 --- a/WebToTelegramCore/BotCommands/ConfirmCommand.cs +++ b/WebToTelegramCore/BotCommands/ConfirmCommand.cs @@ -2,7 +2,7 @@ using WebToTelegramCore.Data; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -82,7 +82,7 @@ private string Regenerate(Record record) _context.Remove(record); _context.Add(newRecord); _context.SaveChanges(); - return String.Format(LocalizationOptions.ConfirmRegeneration, newToken); + return String.Format(Locale.ConfirmRegeneration, newToken); } /// @@ -94,7 +94,7 @@ private string Delete(Record record) { _context.Remove(record); _context.SaveChanges(); - return LocalizationOptions.ConfirmDeletion; + return Locale.ConfirmDeletion; } } } diff --git a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs index b52e275..986513a 100644 --- a/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs +++ b/WebToTelegramCore/BotCommands/ConfirmationCommandBase.cs @@ -1,7 +1,7 @@ using WebToTelegramCore.Data; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -32,7 +32,7 @@ public ConfirmationCommandBase() { } public virtual string Process(Record record) { return (string.IsNullOrEmpty(record.Token) || record.State == RecordState.Normal) - ? LocalizationOptions.ErrorNoConfirmationPending + ? Locale.ErrorNoConfirmationPending : null; } } diff --git a/WebToTelegramCore/BotCommands/CreateCommand.cs b/WebToTelegramCore/BotCommands/CreateCommand.cs index 2e419de..0c96faa 100644 --- a/WebToTelegramCore/BotCommands/CreateCommand.cs +++ b/WebToTelegramCore/BotCommands/CreateCommand.cs @@ -1,7 +1,7 @@ using System; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -76,11 +76,11 @@ private string InternalProcess(Record record) var r = _recordService.Create(token, record.AccountNumber); _context.Add(r); _context.SaveChanges(); - return String.Format(LocalizationOptions.CreateSuccess, token); + return String.Format(Locale.CreateSuccess, token); } else { - return LocalizationOptions.CreateGoAway; + return Locale.CreateGoAway; } } } diff --git a/WebToTelegramCore/BotCommands/DeleteCommand.cs b/WebToTelegramCore/BotCommands/DeleteCommand.cs index 4b3e7fa..74eddad 100644 --- a/WebToTelegramCore/BotCommands/DeleteCommand.cs +++ b/WebToTelegramCore/BotCommands/DeleteCommand.cs @@ -1,7 +1,7 @@ using WebToTelegramCore.Data; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -50,8 +50,8 @@ private string InternalProcess(Record record) { record.State = RecordState.PendingDeletion; return _registrationEnabled - ? LocalizationOptions.DeletionPending - : LocalizationOptions.DeletionPending + "\n\n" + LocalizationOptions.DeletionNoTurningBack; + ? Locale.DeletionPending + : Locale.DeletionPending + "\n\n" + Locale.DeletionNoTurningBack; } } } diff --git a/WebToTelegramCore/BotCommands/DirectiveCommand.cs b/WebToTelegramCore/BotCommands/DirectiveCommand.cs index 5a53c94..d8390e1 100644 --- a/WebToTelegramCore/BotCommands/DirectiveCommand.cs +++ b/WebToTelegramCore/BotCommands/DirectiveCommand.cs @@ -1,6 +1,5 @@ using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; namespace WebToTelegramCore.BotCommands { diff --git a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs index 39e4492..8e6ca04 100644 --- a/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/GuestOnlyCommandBase.cs @@ -1,6 +1,6 @@ using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -36,7 +36,7 @@ private string InternalProcess(Record record) { return string.IsNullOrEmpty(record.Token) ? null - : LocalizationOptions.ErrorMustBeGuest; + : Locale.ErrorMustBeGuest; } } } diff --git a/WebToTelegramCore/BotCommands/HelpCommand.cs b/WebToTelegramCore/BotCommands/HelpCommand.cs index 3c21394..f699884 100644 --- a/WebToTelegramCore/BotCommands/HelpCommand.cs +++ b/WebToTelegramCore/BotCommands/HelpCommand.cs @@ -1,6 +1,6 @@ using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -28,7 +28,7 @@ public HelpCommand() : base() { } /// corresponding error message otherwise. public override string Process(Record record) { - return base.Process(record) ?? LocalizationOptions.Help; + return base.Process(record) ?? Locale.Help; } } } diff --git a/WebToTelegramCore/BotCommands/RegenerateCommand.cs b/WebToTelegramCore/BotCommands/RegenerateCommand.cs index aadd0f2..b5e6931 100644 --- a/WebToTelegramCore/BotCommands/RegenerateCommand.cs +++ b/WebToTelegramCore/BotCommands/RegenerateCommand.cs @@ -1,7 +1,7 @@ using WebToTelegramCore.Data; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -39,7 +39,7 @@ public override string Process(Record record) private string InternalProcess(Record record) { record.State = RecordState.PendingRegeneration; - return LocalizationOptions.RegenerationPending; + return Locale.RegenerationPending; } } } diff --git a/WebToTelegramCore/BotCommands/StartCommand.cs b/WebToTelegramCore/BotCommands/StartCommand.cs index f17a6fa..e98947f 100644 --- a/WebToTelegramCore/BotCommands/StartCommand.cs +++ b/WebToTelegramCore/BotCommands/StartCommand.cs @@ -1,6 +1,6 @@ using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -38,8 +38,8 @@ public StartCommand(bool registrationEnabled) : base() /// corresponding error message otherwise. public override string Process(Record record) { - string appendix = _isRegistrationOpen ? LocalizationOptions.StartRegistrationHint : LocalizationOptions.StartGoAway; - return base.Process(record) ?? LocalizationOptions.StartMessage + "\n\n" + appendix; + string appendix = _isRegistrationOpen ? Locale.StartRegistrationHint : Locale.StartGoAway; + return base.Process(record) ?? Locale.StartMessage + "\n\n" + appendix; } } } diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index 9960cd7..18adca5 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -1,8 +1,7 @@ using System; -using System.Collections.Generic; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -73,8 +72,8 @@ public override string Process(Record record) private string InternalProcess(Record record) { string text = _examples[new Random().Next(0, _examples.Length)]; - return String.Format(LocalizationOptions.TokenTemplate, record.Token, _apiEndpoint + "/api", text) - + "\n\n" + LocalizationOptions.TokenErrorsDescription; + return String.Format(Locale.TokenTemplate, record.Token, _apiEndpoint + "/api", text) + + "\n\n" + Locale.TokenErrorsDescription; } } } diff --git a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs index 1849548..90c038a 100644 --- a/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs +++ b/WebToTelegramCore/BotCommands/UserOnlyCommandBase.cs @@ -1,6 +1,6 @@ using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; -using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.BotCommands { @@ -36,7 +36,7 @@ public UserOnlyCommandBase() : base() { } /// or null otherwise. private string InternalProcess(Record record) { - return string.IsNullOrEmpty(record.Token) ? LocalizationOptions.ErrorMustBeUser : null; + return string.IsNullOrEmpty(record.Token) ? Locale.ErrorMustBeUser : null; } } } diff --git a/WebToTelegramCore/Options/LocalizationOptions.cs b/WebToTelegramCore/Resources/Locale.cs similarity index 99% rename from WebToTelegramCore/Options/LocalizationOptions.cs rename to WebToTelegramCore/Resources/Locale.cs index 9c351fb..1e63d70 100644 --- a/WebToTelegramCore/Options/LocalizationOptions.cs +++ b/WebToTelegramCore/Resources/Locale.cs @@ -1,4 +1,4 @@ -namespace WebToTelegramCore.Options +namespace WebToTelegramCore.Resources { /// /// Class that holds all customizable string the bot may reply with. @@ -11,7 +11,7 @@ // Templating parameters (like {0}) are filled in before sending to the API, // so { and } in these should NOT be escaped. - public static class LocalizationOptions + public static class Locale { /// /// Template for reply for /about command. {0} is assembly version. diff --git a/WebToTelegramCore/Services/TelegramApiService.cs b/WebToTelegramCore/Services/TelegramApiService.cs index ba2df54..200ab74 100644 --- a/WebToTelegramCore/Services/TelegramApiService.cs +++ b/WebToTelegramCore/Services/TelegramApiService.cs @@ -10,6 +10,7 @@ using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Options; +using WebToTelegramCore.Resources; namespace WebToTelegramCore.Services { @@ -150,8 +151,8 @@ private async Task HandleUnknownText(long accountId, string text) else { string reply = text.StartsWith("/") - ? LocalizationOptions.ErrorDave - : LocalizationOptions.ErrorWhat; + ? Locale.ErrorDave + : Locale.ErrorWhat; await _bot.Send(accountId, reply); } } From eab3cb9673e2297915e1bd6beb238d62ab6f17bc Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 29 Apr 2023 16:42:11 +0700 Subject: [PATCH 33/39] updated year in license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e0a6f98..b6aa777 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018, 2020 bnfour +Copyright (c) 2018, 2020-2023 bnfour Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 0258ae33fdd0cbdfcb3e6feb35349e09ce1d30bf Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sun, 30 Apr 2023 00:50:59 +0700 Subject: [PATCH 34/39] some bugfixing (imagine not testing and having bugs for years) - fixed timezone issue that made impossible to post more than one message ever - fixed not escaping mdv2 things in /token command - wording fixes --- WebToTelegramCore/BotCommands/TokenCommand.cs | 8 +++-- .../Helpers/TelegramMarkdownFormatter.cs | 31 +++++++++++++++++++ WebToTelegramCore/Resources/Locale.cs | 15 ++++----- WebToTelegramCore/Services/RecordService.cs | 2 +- 4 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 WebToTelegramCore/Helpers/TelegramMarkdownFormatter.cs diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index 18adca5..bfcc27e 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -1,4 +1,5 @@ using System; +using WebToTelegramCore.Helpers; using WebToTelegramCore.Interfaces; using WebToTelegramCore.Models; using WebToTelegramCore.Resources; @@ -71,9 +72,10 @@ public override string Process(Record record) /// Message with token and API usage example. private string InternalProcess(Record record) { - string text = _examples[new Random().Next(0, _examples.Length)]; - return String.Format(Locale.TokenTemplate, record.Token, _apiEndpoint + "/api", text) - + "\n\n" + Locale.TokenErrorsDescription; + var text = _examples[new Random().Next(0, _examples.Length)]; + return String.Format(Locale.TokenTemplate, TelegramMarkdownFormatter.Escape(record.Token), + TelegramMarkdownFormatter.Escape(_apiEndpoint + "/api"), TelegramMarkdownFormatter.Escape(text)) + + "\n" + Locale.TokenErrorsDescription; } } } diff --git a/WebToTelegramCore/Helpers/TelegramMarkdownFormatter.cs b/WebToTelegramCore/Helpers/TelegramMarkdownFormatter.cs new file mode 100644 index 0000000..b4213ea --- /dev/null +++ b/WebToTelegramCore/Helpers/TelegramMarkdownFormatter.cs @@ -0,0 +1,31 @@ +namespace WebToTelegramCore.Helpers +{ + /// + /// Static helper to make not hardcoded texts suitable for sending. + /// + public static class TelegramMarkdownFormatter + { + /// + /// List of characters to escape in order to satisfy the API. + /// + private readonly static string[] _toEscape = new[] + { + "_", "*", "[", "]", "(", ")", "~", "`", ">", + "#", "+", "-", "=", "|", "{", "}", ".", "!" + }; + + /// + /// Escapes the dangerous symbols in the string. + /// + /// String to process. + /// Telegram Markdown v2 friendly string. + public static string Escape(string s) + { + foreach (var c in _toEscape) + { + s = s.Replace(c, @"\" + c); + } + return s; + } + } +} diff --git a/WebToTelegramCore/Resources/Locale.cs b/WebToTelegramCore/Resources/Locale.cs index 1e63d70..71ed115 100644 --- a/WebToTelegramCore/Resources/Locale.cs +++ b/WebToTelegramCore/Resources/Locale.cs @@ -9,7 +9,7 @@ // Please note that these should be formatted as Telegram-flavoured Markdown // see https://core.telegram.org/bots/api#markdownv2-style // Templating parameters (like {0}) are filled in before sending to the API, - // so { and } in these should NOT be escaped. + // so { and } in these should NOT be escaped. But the actual text in these should be. public static class Locale { @@ -17,7 +17,7 @@ public static class Locale /// Template for reply for /about command. {0} is assembly version. /// public const string About = """ - **Dotnet Telegram forwarder** v {0} + **Dotnet Telegram forwarder** v {0}\. [Open\-source\!](https://github.com/bnfour/dotnet-telegram-forwarder) by bnfour, 2018, 2020\-2023\. @@ -120,7 +120,7 @@ Please either /confirm or /cancel it\. This cannot be undone\. /// Message to show when usage of command requires a token, but user has none. /// public const string ErrorMustBeUser = """ - In order to use this command, you must have a token associated with your account\. /create one\." + In order to use this command, you must have a token associated with your account\. /create one\. """; /// @@ -192,24 +192,25 @@ This bot provides a standalone web API to relay messages from anything that can /// Helper text that explains web API output. /// public const string TokenErrorsDescription = """ - If everything is okay, the API will return a blank 200 OK response\. If something is not okay, a different status code will be returned\. Consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) to see possible error codes\. + If everything is okay, the API will return a blank 200 OK response\. If something is not okay, a different status code will be returned\. Consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) to see error code list\. """; /// /// Template for message to reply to /token command with. /// {0} is token, {1} is API endpoint URL, {2} is vanity quote. /// + // double braces are escaping for formatting public const string TokenTemplate = """ Your token is `{0}` - *Usage:* To deliver a message, send a POST request to {1} with JSON body\. The payload must contain two parameters: your token and your message\. There are also optional parameters, please consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) for details\. Example of a bare minimum payload: + *Usage:* To deliver a message, send a POST request to {1} with JSON body\. The payload must contain two parameters: your token and your message\. There are also optional parameters, please consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) for details\. Example of a payload: ``` - { + {{ "token": "{0}", "message": "{2}" - } + }} ``` """; } diff --git a/WebToTelegramCore/Services/RecordService.cs b/WebToTelegramCore/Services/RecordService.cs index 5fa5682..75ce038 100644 --- a/WebToTelegramCore/Services/RecordService.cs +++ b/WebToTelegramCore/Services/RecordService.cs @@ -49,7 +49,7 @@ public bool CheckIfCanSend(Record record) if (record.UsageCounter > 0) { - record.LastSuccessTimestamp = DateTime.Now; + record.LastSuccessTimestamp = DateTime.UtcNow; record.UsageCounter--; return true; } From 5fdef609edbaa6782ce657b1bf67673cd33d8c8c Mon Sep 17 00:00:00 2001 From: Bn4 Date: Mon, 1 May 2023 00:42:33 +0700 Subject: [PATCH 35/39] simplified locale for /token command --- WebToTelegramCore/BotCommands/TokenCommand.cs | 3 +-- WebToTelegramCore/Resources/Locale.cs | 11 +++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/WebToTelegramCore/BotCommands/TokenCommand.cs b/WebToTelegramCore/BotCommands/TokenCommand.cs index bfcc27e..1e02253 100644 --- a/WebToTelegramCore/BotCommands/TokenCommand.cs +++ b/WebToTelegramCore/BotCommands/TokenCommand.cs @@ -74,8 +74,7 @@ private string InternalProcess(Record record) { var text = _examples[new Random().Next(0, _examples.Length)]; return String.Format(Locale.TokenTemplate, TelegramMarkdownFormatter.Escape(record.Token), - TelegramMarkdownFormatter.Escape(_apiEndpoint + "/api"), TelegramMarkdownFormatter.Escape(text)) - + "\n" + Locale.TokenErrorsDescription; + TelegramMarkdownFormatter.Escape(_apiEndpoint + "/api"), TelegramMarkdownFormatter.Escape(text)); } } } diff --git a/WebToTelegramCore/Resources/Locale.cs b/WebToTelegramCore/Resources/Locale.cs index 71ed115..05fc594 100644 --- a/WebToTelegramCore/Resources/Locale.cs +++ b/WebToTelegramCore/Resources/Locale.cs @@ -187,14 +187,7 @@ This bot provides a standalone web API to relay messages from anything that can public const string StartRegistrationHint = """ If that does not stop you, feel free to /create your very own token\. """; - - /// - /// Helper text that explains web API output. - /// - public const string TokenErrorsDescription = """ - If everything is okay, the API will return a blank 200 OK response\. If something is not okay, a different status code will be returned\. Consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) to see error code list\. - """; - + /// /// Template for message to reply to /token command with. /// {0} is token, {1} is API endpoint URL, {2} is vanity quote. @@ -212,6 +205,8 @@ Your token is "message": "{2}" }} ``` + + If everything is okay, the API will return a blank 200 OK response\. If something is not okay, a different status code will be returned\. Consult [the documentation](https://github.com/bnfour/dotnet-telegram-forwarder#web-api) to see error code list\. """; } } From 52909fc386f4f30c4e310c47484023322a17902d Mon Sep 17 00:00:00 2001 From: Bn4 Date: Mon, 1 May 2023 00:43:00 +0700 Subject: [PATCH 36/39] imports cleaning in a file --- WebToTelegramCore/RecordContext.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/WebToTelegramCore/RecordContext.cs b/WebToTelegramCore/RecordContext.cs index e4ba5dc..0c0e154 100644 --- a/WebToTelegramCore/RecordContext.cs +++ b/WebToTelegramCore/RecordContext.cs @@ -1,6 +1,4 @@ using Microsoft.EntityFrameworkCore; -using System; -using System.Linq; using System.Threading.Tasks; using WebToTelegramCore.Models; From 4c1ffaa3dc8ee01e42455a05f85111c5f016198e Mon Sep 17 00:00:00 2001 From: Bn4 Date: Mon, 1 May 2023 00:44:58 +0700 Subject: [PATCH 37/39] fixed token regeneration resetting the usage counters not sure if that's a bug and not a feature though --- WebToTelegramCore/BotCommands/ConfirmCommand.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WebToTelegramCore/BotCommands/ConfirmCommand.cs b/WebToTelegramCore/BotCommands/ConfirmCommand.cs index 1f4dedd..42f224b 100644 --- a/WebToTelegramCore/BotCommands/ConfirmCommand.cs +++ b/WebToTelegramCore/BotCommands/ConfirmCommand.cs @@ -77,11 +77,17 @@ public override string Process(Record record) private string Regenerate(Record record) { string newToken = _tokenGenerator.Generate(); - // so apparently, primary key cannot be changed + // so apparently, primary key cannot be changed, + // create a new record and transfer all data but token and state (it's pending regeneration right now) var newRecord = _recordService.Create(newToken, record.AccountNumber); + // consider moving these to Create params with default values? + newRecord.UsageCounter = record.UsageCounter; + newRecord.LastSuccessTimestamp = record.LastSuccessTimestamp; + _context.Remove(record); _context.Add(newRecord); _context.SaveChanges(); + return String.Format(Locale.ConfirmRegeneration, newToken); } From 1fe4d37b6364c0ca21f8bc1d9e0194ecd45293cb Mon Sep 17 00:00:00 2001 From: Bn4 Date: Mon, 1 May 2023 01:12:45 +0700 Subject: [PATCH 38/39] token generator now checks whether the existing Record already using the same token this should prevent _extremely_ rarely cases of generating the same token and getting an exception about duplicating primary key --- .../Services/TokenGeneratorService.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/WebToTelegramCore/Services/TokenGeneratorService.cs b/WebToTelegramCore/Services/TokenGeneratorService.cs index 62498b0..92ddbf0 100644 --- a/WebToTelegramCore/Services/TokenGeneratorService.cs +++ b/WebToTelegramCore/Services/TokenGeneratorService.cs @@ -1,5 +1,6 @@ using System; using System.Security.Cryptography; +using System.Linq; using System.Text; using WebToTelegramCore.Interfaces; @@ -22,11 +23,18 @@ internal class TokenGeneratorService : ITokenGeneratorService private static readonly char[] _alphabet = ("0123456789" + "+=" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz").ToCharArray(); + /// + /// DB context. Used to check for collisions with existing tokens. + /// + private readonly RecordContext _context; + /// /// Class constructor. /// - public TokenGeneratorService() + public TokenGeneratorService(RecordContext context) { + _context = context; + // sanity check for random evenness // TODO consider it to be a warning instead of an exception if (256 % _alphabet.Length != 0) @@ -37,10 +45,26 @@ public TokenGeneratorService() } /// - /// Token generation method. + /// Generates a token and ensures it is not yet assigned to other accounts. /// - /// Token. + /// An unique token. public string Generate() + { + string token = null; + var done = false; + while (!done) + { + token = GenerateRandom(); + done = !_context.Records.Any(r => r.Token == token); + } + return token; + } + + /// + /// Actual token generation method. + /// + /// A completely random token. + private string GenerateRandom() { var randomBytes = new byte[_tokenLength]; // let's pretend we're serious business for a moment From a27727cf15e48769fa72d409e6f6783fc18d763c Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 6 May 2023 20:03:49 +0700 Subject: [PATCH 39/39] minor readme changes in preparation for release --- readme.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/readme.md b/readme.md index 555bbf6..af3c620 100644 --- a/readme.md +++ b/readme.md @@ -1,15 +1,16 @@ # Dotnet Telegram forwarder -("temporary" generic name from the very start of development that stuck forever) -An app providing HTTP API to deliver arbitrary text notifications via Telegram bot from anywhere where making HTTP POST requests is available. +("temporary" generic name from the very start of development) +An app providing HTTP API to deliver arbitrary text notifications via associated Telegram bot from anywhere where making HTTP POST requests is available. ## Status -Operational. First version has been serving me flawlessly (as I can tell) since mid 2018, and a long overdue refactoring/framework update (spoiler: one of the major updates two planned) is currently underway. +Operational. First version has been serving me flawlessly (as I can tell) since mid 2018. ## Description Let's try [readme driven development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html) this time. So this app consists of two equally important parts: Telegram bot and a web API. ### Web API Has only one method to send the notification. Its endpoint listens for POST requests with JSON body. Actual endpoint URL will be provided via the bot itself when a new token is created. + #### Request Request's body has this structure: ``` @@ -20,26 +21,26 @@ Request's body has this structure: "silent": (optional) boolean } ``` -* Token is this service's user identifier, randomly generated per Telegram user, with abilities to withdraw it or replace with a new one anytime. (Implement a cooldown on consequent resets?) It's a 16 characters long string that may contain alphanumerics, and plus and equals signs (So `[0-9a-zA-Z+=]{16}`). -* Type is used to select between two supported parse modes: `"plaintext"` for plain text, and `"markdown"` for MarkdownV2 as described in Telegram docs [here](https://core.telegram.org/bots/api#markdownv2-style). If value is not supplied, defaults to `"plaintext"`. These two are separated, because Telegram flavoured Markdown requires escaping for a fairly common plaintext punctuation marks. -* Message is the text of the message to be sent via the bot. Maximum length is 4096, and preferred encoding is UTF-8. -* Silent is boolean to indicate whether them message from the bot in Telegram will come with a notification. Behaves what you'd expect. If not supplied, defaults to `false`. Please note that the end user is able to mute the bot, effectively rendering this option useless. +* Token is this service's user identifier, randomly generated per Telegram user, with abilities to withdraw it or replace with a new one anytime. It's a 16 characters long string that may contain alphanumerics, and plus and equals signs (So `[0-9a-zA-Z+=]{16}`). +* Type is used to select between two supported parse modes: `"plaintext"` for plain text, and `"markdown"` for MarkdownV2 as described in Telegram docs [here](https://core.telegram.org/bots/api#markdownv2-style). If value is not supplied, defaults to `"plaintext"`. These two are separated, because Telegram flavoured Markdown requires escaping for a fairly common plaintext punctuation marks, and will fail if not formed correctly. +* Message is the text of the message to be sent via the bot. Maximum length is 4096 (also happens to be a maximum length of one Telegram message). +* Silent is boolean to indicate whether them message from the bot in Telegram will come with a notification with sound. Behaves what you'd expect. If not supplied, defaults to `false`. Please note that the end user is able to mute the bot, effectively rendering this option useless. #### Response API returns an empty HTTP response with any of the following status codes: * `200 OK` if everything is indeed OK and message should be expeted to be delivered via the bot -No further actions from the client required +No further actions from the client required. * `400 Bad Request` if the user request is malformed and cannot be processed -Client should check that the request is well-formed and meet the specifications, and retry with the fixed request +Client should check that the request is well-formed and meet the specifications, and retry with the fixed request. * `404 Not Found` if supplied token is not present in the database -Client should check that the token they provided is valid and has not been removed via commands, and retry with the correct one -* `429 Too Many Requests` if current limit of messages sent is exhausted -Client should retry later, after waiting at least one minute (on default throughput config) +Client should check that the token they provided is valid and has not been removed or changed, and retry with the correct one. +* `429 Too Many Requests` if current limit of sent messages is exhausted +Client should retry later, after waiting at least one minute (on default throughput config). * `500 Internal Server Error` in case anything goes wrong Client can try to retry later, but ¯\\\_(ツ)\_/¯ #### Rate limitation -The API has a rate limitation, preventing large(ish) amount of notifications in a short amount of time. By default (can be adjusted via config files), every user has 20 ...message points, I guess? Every notification sent removes 1 message point, and requests will be refused with `429 Too Many Requests` status code when points are depleted. A single point is regenerated every minute after last message was sent. +The API has a rate limitation, preventing large(ish) amount of notifications in a short amount of time. By default (can be adjusted via config files), every user has 20 ...message points, I guess? Every notification sent removes 1 message point, and requests will be refused with `429 Too Many Requests` status code when all points are depleted. A single point is regenerated every minute after last message was sent. For instance, if API is used to send 40 notifications in quick succession, only 20 first messages will be sent to the user. If client waits 5 minutes after API starts responding with 429's, they will be able to send 5 more messages instantenously before hitting the limit again. After 20 minutes of idle time since the last successfully sent message, the API will behave as usual. ### Telegram bot @@ -75,5 +76,5 @@ Initial release. More an excercise in ASP.NET Core than an app I needed at the m Greatly increased the reliability of Markdown parsing in one of the most **not** straightforward ways you can imagine -- by converting the Markdown to HTML with a few custom convertion quirks. * **no version number**, 2020-05-14 Shelved attempt to improve the codebase. Consists of one architecture change and is fully ~~included~~ rewritten in the next release. -* **v 2.0**, not yet released, (hopefully) in development -Really proper markdown support this time (Telegram's version with questionable selection of characters to be escaped), option to send a silent notification, async everthing, and probably something else I forgot about. +* **v 2.0**, 2023-05-06 +Really proper markdown support this time (Telegram's version with questionable selection of characters to be escaped), option to send a silent notification, async everthing, .NET 7, HTTP status codes instead of custom errors, and probably something else I forgot about.