diff --git a/.gitignore b/.gitignore index 39c9242..d0e2a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ obj/ riderModule.iml /_ReSharper.Caches/ .idea/ +/.vs/TgBotFramework diff --git a/examples/EchoBotProject/EchoBotProject.csproj b/examples/EchoBotProject/EchoBotProject.csproj index 0995763..8352342 100644 --- a/examples/EchoBotProject/EchoBotProject.csproj +++ b/examples/EchoBotProject/EchoBotProject.csproj @@ -4,6 +4,16 @@ net5.0 + + + + + + + + + + diff --git a/examples/EchoBotProject/Handlers/PublicChatEcho.cs b/examples/EchoBotProject/Handlers/PublicChatEcho.cs index aa283ca..0a290b0 100644 --- a/examples/EchoBotProject/Handlers/PublicChatEcho.cs +++ b/examples/EchoBotProject/Handlers/PublicChatEcho.cs @@ -3,21 +3,27 @@ using Microsoft.Extensions.Logging; using Telegram.Bot; using TgBotFramework; +using TgBotFramework.Attributes; +using TgBotFramework.MessageReader; using TgBotFramework.WrapperExtensions; namespace EchoBotProject.Handlers { + [Handler("publicChat")] public class PublicChatEcho : IUpdateHandler { private readonly ILogger _logger; + private readonly IMessageReader _messageReader; - public PublicChatEcho(ILogger logger) + public PublicChatEcho(ILogger logger, IMessageReader messageReader) { + _messageReader = messageReader; _logger = logger; } public async Task HandleAsync(BotExampleContext context, UpdateDelegate next, CancellationToken cancellationToken) { + await context.Client.SendTextMessageAsync(context.Update.GetSenderId(), _messageReader.GetMessage("greetings")); _logger.LogInformation("This update {0} was from public chat {1}", context.Update.Id, context.Update.GetChat()); } } diff --git a/examples/EchoBotProject/Resources/ru/game.stage.json b/examples/EchoBotProject/Resources/ru/game.stage.json new file mode 100644 index 0000000..d414d58 --- /dev/null +++ b/examples/EchoBotProject/Resources/ru/game.stage.json @@ -0,0 +1,5 @@ +{ + "exit": "Okay, you got out, lets play ping-pong with counter", + "exit.provocativeMessage": "Cmon, find EXIT" + +} \ No newline at end of file diff --git a/examples/EchoBotProject/Resources/ru/publicChat.handler.json b/examples/EchoBotProject/Resources/ru/publicChat.handler.json new file mode 100644 index 0000000..763a574 --- /dev/null +++ b/examples/EchoBotProject/Resources/ru/publicChat.handler.json @@ -0,0 +1,3 @@ +{ + "greetings": "Hello, my dear friend!" +} \ No newline at end of file diff --git a/examples/EchoBotProject/States/GameState.cs b/examples/EchoBotProject/States/GameState.cs index 289a778..6489e60 100644 --- a/examples/EchoBotProject/States/GameState.cs +++ b/examples/EchoBotProject/States/GameState.cs @@ -5,22 +5,30 @@ using TgBotFramework; using TgBotFramework.Attributes; using TgBotFramework.WrapperExtensions; +using TgBotFramework.MessageReader; namespace EchoBotProject.States { [State(Stage = "game")] public class GameState : BasicState where TContext : IUpdateContext { + private readonly IMessageReader, TContext> _messageReader; + + public GameState(IMessageReader, TContext> messageReader) + { + _messageReader = messageReader; + } + public override async Task HandleAsync(TContext context, UpdateDelegate next, CancellationToken cancellationToken) { if (context.Update.Message?.Text != null && context.Update.Message.Text == "/exit") { - await context.Client.SendTextMessageAsync(context.Update.GetChat(), "Okay, you got out, lets play ping-pong with counter", cancellationToken: cancellationToken); + await context.Client.SendTextMessageAsync(context.Update.GetChat(), _messageReader.GetMessage("exit"), cancellationToken: cancellationToken); await Exit(context); } else { - await context.Client.SendTextMessageAsync(context.Update.GetChat(), "Cmon, find EXIT", cancellationToken: cancellationToken); + await context.Client.SendTextMessageAsync(context.Update.GetChat(), _messageReader.GetMessage("exit.provocativeMessage"), cancellationToken: cancellationToken); } } } diff --git a/examples/EchoBotWebExample/EchoBotWebExample.csproj.user b/examples/EchoBotWebExample/EchoBotWebExample.csproj.user new file mode 100644 index 0000000..cff74a9 --- /dev/null +++ b/examples/EchoBotWebExample/EchoBotWebExample.csproj.user @@ -0,0 +1,6 @@ + + + + IIS Express + + \ No newline at end of file diff --git a/examples/EchoBotWebExample/Properties/launchSettings.json b/examples/EchoBotWebExample/Properties/launchSettings.json new file mode 100644 index 0000000..5cfef87 --- /dev/null +++ b/examples/EchoBotWebExample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56028/", + "sslPort": 44336 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "EchoBotWebExample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/examples/EchoBotWebExample/Startup.cs b/examples/EchoBotWebExample/Startup.cs index b1cecc9..c466bf8 100644 --- a/examples/EchoBotWebExample/Startup.cs +++ b/examples/EchoBotWebExample/Startup.cs @@ -15,9 +15,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Telegram.Bot; using TgBotFramework; -using TgBotFramework.UpdatePipeline; namespace EchoBotWebExample { @@ -42,11 +40,9 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); - // register deps for pipeline services.ServicesForExamplePipelineBuilder(); - - + services.AddDbContext(x => x.UseSqlite("Data Source=BotFramework.db", builder => builder.MigrationsAssembly("EchoBotProject"))); @@ -59,7 +55,9 @@ public void ConfigureServices(IServiceCollection services) .UseMiddleware() - + .AddMessageReader() + .AddMessageReader>() + // you may use this approach to logging but be aware that not all update objects can be converted back to json .UseMiddleware() // if you want to use states... diff --git a/examples/WebhookExample/Properties/launchSettings.json b/examples/WebhookExample/Properties/launchSettings.json new file mode 100644 index 0000000..a473bdf --- /dev/null +++ b/examples/WebhookExample/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56027/", + "sslPort": 44374 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebhookExample": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/examples/WebhookExample/WebhookExample.csproj.user b/examples/WebhookExample/WebhookExample.csproj.user new file mode 100644 index 0000000..cff74a9 --- /dev/null +++ b/examples/WebhookExample/WebhookExample.csproj.user @@ -0,0 +1,6 @@ + + + + IIS Express + + \ No newline at end of file diff --git a/src/TgBotFramework/Attributes/HandlerAttribute.cs b/src/TgBotFramework/Attributes/HandlerAttribute.cs new file mode 100644 index 0000000..77a0e92 --- /dev/null +++ b/src/TgBotFramework/Attributes/HandlerAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TgBotFramework.Attributes +{ + public class HandlerAttribute : System.Attribute + { + public string Stage { get; } + + public HandlerAttribute(string stage) + { + Stage = stage; + } + } +} diff --git a/src/TgBotFramework/BotFrameworkBuilder.cs b/src/TgBotFramework/BotFrameworkBuilder.cs index e0edbbc..fe7803c 100644 --- a/src/TgBotFramework/BotFrameworkBuilder.cs +++ b/src/TgBotFramework/BotFrameworkBuilder.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TgBotFramework.Attributes; +using TgBotFramework.MessageReader; using TgBotFramework.StageManaging; using TgBotFramework.UpdatePipeline; @@ -42,6 +43,13 @@ public BotFrameworkBuilder(IServiceCollection services) services.AddSingleton(provider => provider.GetService()); } + public IBotFrameworkBuilder AddMessageReader(string rootDirectory = "Resources", string language = "ru", Assembly resourceAssembly = null) + where TUpdateHandler : class + { + Services.AddSingleton>(provider => new MessageReader(rootDirectory, language, resourceAssembly)); + return this; + } + public IBotFrameworkBuilder UseLongPolling(LongPollingOptions longPollingOptions) where T : BackgroundService, IPollingManager { Services.AddHostedService(); diff --git a/src/TgBotFramework/IBotFrameworkBuilder.cs b/src/TgBotFramework/IBotFrameworkBuilder.cs index 7608f1d..0e4731c 100644 --- a/src/TgBotFramework/IBotFrameworkBuilder.cs +++ b/src/TgBotFramework/IBotFrameworkBuilder.cs @@ -25,5 +25,8 @@ IBotFrameworkBuilder SetPipeline( IBotFrameworkBuilder UseStates(Assembly assembly); IBotFrameworkBuilder UseCommands(Assembly getAssembly); + + IBotFrameworkBuilder AddMessageReader(string rootDirectory = "Resources", string language = "ru", Assembly resourceAssembly = null) + where TUpdateHandler : class; } } \ No newline at end of file diff --git a/src/TgBotFramework/MessageReader/IMessageReader.cs b/src/TgBotFramework/MessageReader/IMessageReader.cs new file mode 100644 index 0000000..6c11ea1 --- /dev/null +++ b/src/TgBotFramework/MessageReader/IMessageReader.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TgBotFramework.MessageReader +{ + public interface IMessageReader + where TContext : IUpdateContext + where TUpdateHandler : class + { + string GetMessage(string jsonPath); + } +} diff --git a/src/TgBotFramework/MessageReader/MessageReader.cs b/src/TgBotFramework/MessageReader/MessageReader.cs new file mode 100644 index 0000000..7e79359 --- /dev/null +++ b/src/TgBotFramework/MessageReader/MessageReader.cs @@ -0,0 +1,78 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.IO; +using System.Reflection; +using TgBotFramework.Attributes; + +namespace TgBotFramework.MessageReader +{ + public class MessageReader : IMessageReader + where TContext : IUpdateContext + where TUpdateHandler : class + { + private readonly Assembly _resourceAssembly; + private readonly string _rootDirectory; + private readonly string _language; + private readonly string _stage; + private readonly Stream _stream; + private readonly StreamReader _fileReader; + + public MessageReader(string rootDirectory = "Resources", string language = "ru", Assembly resourceAssembly = null) + { + _rootDirectory = rootDirectory; + _language = language; + _resourceAssembly = resourceAssembly ?? Assembly.GetCallingAssembly(); + + var handlerAttribute = typeof(TUpdateHandler).GetCustomAttribute(); + if (handlerAttribute != null) + { + _stage = handlerAttribute.Stage + ".handler"; + } + else + { + var stateAttribute = typeof(TUpdateHandler).GetCustomAttribute(); + if (stateAttribute != null) + { + _stage = stateAttribute.Stage + ".stage"; + } + } + + if (_stage == null) + { + throw new InvalidOperationException("The IUpdateHandler class should contain State or Handler attribute"); + } + + var resourcePath = $"{_resourceAssembly.GetName().Name}.{_rootDirectory}.{_language}.{_stage}.json"; + _stream = _resourceAssembly.GetManifestResourceStream(resourcePath); + + if (_stream == null) + { + throw new InvalidOperationException( + $"Assembly {_resourceAssembly.FullName} doesn't contain EmbeddedResource at path {resourcePath}. Resource file cannot be loaded"); + } + + _fileReader = new StreamReader(_stream); + MessageFileText = _fileReader.ReadToEnd(); + } + + public string MessageFileText { get; private set; } + + public void Dispose() + { + _fileReader.Close(); + _stream.Close(); + } + + public string GetMessage(string jsonPath) + { + JObject JsonObject = JsonConvert.DeserializeObject(MessageFileText); + var node = JsonObject.SelectToken(jsonPath); + if (node == null) + { + throw new ArgumentException($"There are no values found by path '{jsonPath}' in stage JSON file '{_stage}'"); + } + return node.ToString(); + } + } +}