From a9055efbd267bb7ede5645d700034bc1f9edab0e Mon Sep 17 00:00:00 2001 From: Namaneo Date: Thu, 5 Dec 2019 00:52:48 +0100 Subject: [PATCH] Add ability to approve or deny a request directly from a Telegram chat --- src/Ombi.Api.Telegram/ITelegramApi.cs | 5 +- .../ITelegramRequestAnswer.cs | 12 ++ .../Ombi.Api.Telegram.csproj | 6 +- src/Ombi.Api.Telegram/TelegramApi.cs | 104 +++++++++++++++--- .../TelegramApiExtensions.cs | 37 +++++++ .../TelegramRequestAnswer.cs | 46 ++++++++ src/Ombi.DependencyInjection/IocExtensions.cs | 2 +- .../Agents/TelegramNotification.cs | 16 ++- .../NotificationMessageCurlys.cs | 6 + src/Ombi/Startup.cs | 15 ++- 10 files changed, 229 insertions(+), 20 deletions(-) create mode 100644 src/Ombi.Api.Telegram/ITelegramRequestAnswer.cs create mode 100644 src/Ombi.Api.Telegram/TelegramApiExtensions.cs create mode 100644 src/Ombi.Api.Telegram/TelegramRequestAnswer.cs diff --git a/src/Ombi.Api.Telegram/ITelegramApi.cs b/src/Ombi.Api.Telegram/ITelegramApi.cs index 6b4e2007a..11cba9e6a 100644 --- a/src/Ombi.Api.Telegram/ITelegramApi.cs +++ b/src/Ombi.Api.Telegram/ITelegramApi.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Ombi.Api.Telegram { public interface ITelegramApi { - Task Send(string message, string botApi, string chatId, string parseMode); + Task Send(string message, string botApi, string chatId, string parseMode, IDictionary data); } } \ No newline at end of file diff --git a/src/Ombi.Api.Telegram/ITelegramRequestAnswer.cs b/src/Ombi.Api.Telegram/ITelegramRequestAnswer.cs new file mode 100644 index 000000000..9d19a4e13 --- /dev/null +++ b/src/Ombi.Api.Telegram/ITelegramRequestAnswer.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace Ombi.Api.Telegram +{ + public interface ITelegramRequestAnswer + { + Task ApproveMovie(int id); + Task DenyMovie(int id); + Task ApproveTv(int id); + Task DenyTv(int id); + } +} diff --git a/src/Ombi.Api.Telegram/Ombi.Api.Telegram.csproj b/src/Ombi.Api.Telegram/Ombi.Api.Telegram.csproj index a3651df3c..0c6c66366 100644 --- a/src/Ombi.Api.Telegram/Ombi.Api.Telegram.csproj +++ b/src/Ombi.Api.Telegram/Ombi.Api.Telegram.csproj @@ -1,9 +1,13 @@ - + netstandard2.0 + + + + diff --git a/src/Ombi.Api.Telegram/TelegramApi.cs b/src/Ombi.Api.Telegram/TelegramApi.cs index 6956c29c8..7cdfc54ad 100644 --- a/src/Ombi.Api.Telegram/TelegramApi.cs +++ b/src/Ombi.Api.Telegram/TelegramApi.cs @@ -1,33 +1,109 @@ using System; -using System.Net.Http; +using System.Collections.Generic; using System.Threading.Tasks; +using Telegram.Bot; +using Telegram.Bot.Types.Enums; +using Telegram.Bot.Types.InputFiles; +using Telegram.Bot.Types.ReplyMarkups; namespace Ombi.Api.Telegram { public class TelegramApi : ITelegramApi { - public TelegramApi(IApi api) + public TelegramApi(ITelegramRequestAnswer answer) { - _api = api; + _answer = answer; } - private readonly IApi _api; - private const string BaseUrl = "https://api.telegram.org/"; + + private readonly ITelegramRequestAnswer _answer; + + private static string _botApi; + private static TelegramBotClient _client; - public async Task Send(string message, string botApi, string chatId, string parseMode) + public async Task Send(string message, string botApi, string chatId, string parseMode, IDictionary data) { - var request = new Request($"bot{botApi}/sendMessage", BaseUrl, HttpMethod.Post); - - var body = new + if (_client == null || _botApi != botApi) { - text = message, - parse_mode = parseMode, - chat_id= chatId + UpdateClient(botApi); + } + + string notificationType = data["NotificationType"]; + if (notificationType != "NewRequest") + { + await SendSimpleMessage(message, chatId, parseMode); + } + else + { + await SendQueryMessage(message, chatId, parseMode, data); + } + } + + + private void UpdateClient(string botApi) + { + if (_client != null) + { + _client.StopReceiving(); + } + + _botApi = botApi; + _client = new TelegramBotClient(_botApi); + _client.OnCallbackQuery += async (sender, args) => + { + await _client.EditMessageCaptionAsync( + chatId: args.CallbackQuery.Message.Chat.Id, + messageId: args.CallbackQuery.Message.MessageId, + caption: args.CallbackQuery.Message.Caption + ); + + var parameters = args.CallbackQuery.Data.Split(' '); + var answer = parameters[0]; + var id = int.Parse(parameters[1]); + + var result = + answer == "approve_Movie" ? await _answer.ApproveMovie(id) : + answer == "deny_Movie" ? await _answer.DenyMovie(id) : + answer == "approve_TvShow" ? await _answer.ApproveTv(id) : + answer == "deny_TvShow" ? await _answer.DenyTv(id) : + "Incorrect answer"; + + if (result != null) + { + await SendSimpleMessage(result, args.CallbackQuery.Message.Chat.Id.ToString()); + } }; - request.AddJsonBody(body); - await _api.Request(request); + _client.StartReceiving(); + } + + private async Task SendSimpleMessage(string message, string chatId, string parseMode = "default") + { + await _client.SendTextMessageAsync( + chatId: chatId, + text: message, + parseMode: (ParseMode)Enum.Parse(typeof(ParseMode), parseMode, true) + ); + } + + private async Task SendQueryMessage(string message, string chatId, string parseMode, IDictionary data) + { + string image = data["PosterImage"]; + string type = data["Type"]; + string id = data["ItemId"]; + + await _client.SendPhotoAsync( + chatId: chatId, + photo: new InputOnlineFile(image), + caption: message, + parseMode: (ParseMode)Enum.Parse(typeof(ParseMode), parseMode, true), + replyMarkup: new InlineKeyboardMarkup(new[] + { + InlineKeyboardButton.WithCallbackData("Approve", $"approve_{type} {id}"), + InlineKeyboardButton.WithCallbackData("Deny", $"deny_{type} {id}"), + }) + ); } } } diff --git a/src/Ombi.Api.Telegram/TelegramApiExtensions.cs b/src/Ombi.Api.Telegram/TelegramApiExtensions.cs new file mode 100644 index 000000000..c443ae4f5 --- /dev/null +++ b/src/Ombi.Api.Telegram/TelegramApiExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; + +namespace Ombi.Api.Telegram +{ + public static class TelegramApiExtensions + { + public static IServiceCollection AddTelegramCallbacks( + this IServiceCollection services, + + Func initMovie, + Func> approveMovie, + Func> denyMovie, + + Func initTvShow, + Func> approveTv, + Func> denyTv, + + Func formater + ) + { + services.AddSingleton(rootProvider => + { + return new TelegramRequestAnswer(rootProvider, initMovie, initTvShow) + { + OnApproveMovie = async (engine, id) => formater(await approveMovie(engine, id)), + OnDenyMovie = async (engine, id) => formater(await denyMovie(engine, id)), + OnApproveTv = async (engine, id) => formater(await approveTv(engine, id)), + OnDenyTv = async (engine, id) => formater(await denyTv(engine, id)), + }; + }); + + return services; + } + } +} diff --git a/src/Ombi.Api.Telegram/TelegramRequestAnswer.cs b/src/Ombi.Api.Telegram/TelegramRequestAnswer.cs new file mode 100644 index 000000000..4d908e0d9 --- /dev/null +++ b/src/Ombi.Api.Telegram/TelegramRequestAnswer.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; + +namespace Ombi.Api.Telegram +{ + public class TelegramRequestAnswer : ITelegramRequestAnswer + { + public TelegramRequestAnswer( + IServiceProvider rootProvider, + Func initMovie, + Func initTv + ) + { + _rootProvider = rootProvider; + _initMovie = initMovie; + _initTv = initTv; + } + + + private readonly IServiceProvider _rootProvider; + private readonly Func _initMovie; + private readonly Func _initTv; + + public Func> OnApproveMovie { get; set; } + public Func> OnDenyMovie { get; set; } + public Func> OnApproveTv { get; set; } + public Func> OnDenyTv { get; set; } + + + public async Task ApproveMovie(int id) => await Execute(_initMovie, OnApproveMovie, id); + public async Task DenyMovie(int id) => await Execute(_initMovie, OnDenyMovie, id); + public async Task ApproveTv(int id) => await Execute(_initTv, OnApproveTv, id); + public async Task DenyTv(int id) => await Execute(_initTv, OnDenyTv, id); + + + private async Task Execute(Func init, Func> action, int id) + { + using (var scope = _rootProvider.CreateScope()) + { + var engine = init(scope.ServiceProvider); + return await action(engine, id); + } + } + } +} diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index f50417189..fb0aa1dbc 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -101,7 +101,7 @@ namespace Ombi.DependencyInjection public static void RegisterHttp(this IServiceCollection services) { services.AddSingleton(); - services.AddScoped(sp => sp.GetService().HttpContext.User); + services.AddScoped(sp => sp.GetService().HttpContext?.User); } public static void RegisterApi(this IServiceCollection services) diff --git a/src/Ombi.Notifications/Agents/TelegramNotification.cs b/src/Ombi.Notifications/Agents/TelegramNotification.cs index 3acfc7331..586183e58 100644 --- a/src/Ombi.Notifications/Agents/TelegramNotification.cs +++ b/src/Ombi.Notifications/Agents/TelegramNotification.cs @@ -10,6 +10,8 @@ using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; using Ombi.Api.Telegram; +using System.Linq; +using System.Collections.Generic; namespace Ombi.Notifications.Agents { @@ -83,7 +85,7 @@ namespace Ombi.Notifications.Agents { try { - await Api.Send(model.Message, settings.BotApi, settings.ChatId, settings.ParseMode); + await Api.Send(model.Message, settings.BotApi, settings.ChatId, settings.ParseMode, model.Data); } catch (Exception e) { @@ -97,6 +99,13 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = message, + Data = new Dictionary + { + [nameof(NotificationType)] = NotificationType.NewRequest.ToString(), + [nameof(NotificationMessageCurlys.PosterImage)] = "https://image.tmdb.org/t/p/w300//btTdmkgIvOi0FFip1sPuZI2oQG6.jpg", + [nameof(NotificationMessageCurlys.Type)] = RequestType.Movie.ToString(), + [nameof(NotificationMessageCurlys.ItemId)] = 0.ToString(), + } }; await Send(notification, settings); } @@ -109,10 +118,15 @@ namespace Ombi.Notifications.Agents Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Telegram}"); return; } + + var notificationData = parsed.Data.ToDictionary(x => x.Key, x => x.Value); + notificationData[nameof(NotificationType)] = type.ToString(); var notification = new NotificationMessage { Message = parsed.Message, + Data = notificationData, }; + await Send(notification, settings); } } diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index cedda3735..ecbc5d173 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -18,6 +18,7 @@ namespace Ombi.Notifications { LoadIssues(opts); + ItemId = req.Id.ToString(); string title; if (req == null) { @@ -68,6 +69,7 @@ namespace Ombi.Notifications { LoadIssues(opts); + ItemId = req.Id.ToString(); string title; if (req == null) { @@ -114,6 +116,8 @@ namespace Ombi.Notifications public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, UserNotificationPreferences pref) { LoadIssues(opts); + + ItemId = req.Id.ToString(); string title; if (req == null) { @@ -216,6 +220,7 @@ namespace Ombi.Notifications } // User Defined + public string ItemId { get; set; } public string RequestedUser { get; set; } public string UserName { get; set; } public string IssueUser => UserName; @@ -248,6 +253,7 @@ namespace Ombi.Notifications public Dictionary Curlys => new Dictionary { + {nameof(ItemId), ItemId }, {nameof(RequestedUser), RequestedUser }, {nameof(Title), Title }, {nameof(RequestedDate), RequestedDate }, diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 56e19bb11..4d58cd6ef 100755 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Ombi.Core.Authentication; +using Ombi.Core.Engine.Interfaces; using Ombi.Core.Settings; using Ombi.DependencyInjection; using Ombi.Extensions; @@ -24,7 +25,7 @@ using Ombi.Store.Context; using Ombi.Store.Entities; using Ombi.Store.Repository; using Serilog; -using SQLitePCL; +using Ombi.Api.Telegram; using ILogger = Serilog.ILogger; namespace Ombi @@ -96,6 +97,18 @@ namespace Ombi .AllowAnyHeader(); })); + services.AddTelegramCallbacks( + provider => provider.GetRequiredService(), + async (engine, id) => await engine.ApproveMovieById(id), + async (engine, id) => await engine.DenyMovieById(id, string.Empty), + + provider => provider.GetRequiredService(), + async (engine, id) => await engine.ApproveChildRequest(id), + async (engine, id) => await engine.DenyChildRequest(id, string.Empty), + + result => result.Message ?? result.ErrorMessage + ); + // Build the intermediate service provider return services.BuildServiceProvider(); }