Add ability to approve or deny a request directly from a Telegram chat

This commit is contained in:
Namaneo 2019-12-05 00:52:48 +01:00
commit a9055efbd2
10 changed files with 229 additions and 20 deletions

View file

@ -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<string, string> data);
}
}

View file

@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace Ombi.Api.Telegram
{
public interface ITelegramRequestAnswer
{
Task<string> ApproveMovie(int id);
Task<string> DenyMovie(int id);
Task<string> ApproveTv(int id);
Task<string> DenyTv(int id);
}
}

View file

@ -1,9 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Telegram.Bot" Version="15.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>

View file

@ -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<string, string> 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<string, string> 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}"),
})
);
}
}
}

View file

@ -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<TMovieEngine, TTvEngine, TResult>(
this IServiceCollection services,
Func<IServiceProvider, TMovieEngine> initMovie,
Func<TMovieEngine, int, Task<TResult>> approveMovie,
Func<TMovieEngine, int, Task<TResult>> denyMovie,
Func<IServiceProvider, TTvEngine> initTvShow,
Func<TTvEngine, int, Task<TResult>> approveTv,
Func<TTvEngine, int, Task<TResult>> denyTv,
Func<TResult, string> formater
)
{
services.AddSingleton<ITelegramRequestAnswer>(rootProvider =>
{
return new TelegramRequestAnswer<TMovieEngine, TTvEngine>(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;
}
}
}

View file

@ -0,0 +1,46 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
namespace Ombi.Api.Telegram
{
public class TelegramRequestAnswer<TMovieEngine, TTvEngine> : ITelegramRequestAnswer
{
public TelegramRequestAnswer(
IServiceProvider rootProvider,
Func<IServiceProvider, TMovieEngine> initMovie,
Func<IServiceProvider, TTvEngine> initTv
)
{
_rootProvider = rootProvider;
_initMovie = initMovie;
_initTv = initTv;
}
private readonly IServiceProvider _rootProvider;
private readonly Func<IServiceProvider, TMovieEngine> _initMovie;
private readonly Func<IServiceProvider, TTvEngine> _initTv;
public Func<TMovieEngine, int, Task<string>> OnApproveMovie { get; set; }
public Func<TMovieEngine, int, Task<string>> OnDenyMovie { get; set; }
public Func<TTvEngine, int, Task<string>> OnApproveTv { get; set; }
public Func<TTvEngine, int, Task<string>> OnDenyTv { get; set; }
public async Task<string> ApproveMovie(int id) => await Execute(_initMovie, OnApproveMovie, id);
public async Task<string> DenyMovie(int id) => await Execute(_initMovie, OnDenyMovie, id);
public async Task<string> ApproveTv(int id) => await Execute(_initTv, OnApproveTv, id);
public async Task<string> DenyTv(int id) => await Execute(_initTv, OnDenyTv, id);
private async Task<string> Execute<TEngine>(Func<IServiceProvider, TEngine> init, Func<TEngine, int, Task<string>> action, int id)
{
using (var scope = _rootProvider.CreateScope())
{
var engine = init(scope.ServiceProvider);
return await action(engine, id);
}
}
}
}

View file

@ -101,7 +101,7 @@ namespace Ombi.DependencyInjection
public static void RegisterHttp(this IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IPrincipal>(sp => sp.GetService<IHttpContextAccessor>().HttpContext.User);
services.AddScoped<IPrincipal>(sp => sp.GetService<IHttpContextAccessor>().HttpContext?.User);
}
public static void RegisterApi(this IServiceCollection services)

View file

@ -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<string, string>
{
[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);
}
}

View file

@ -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<string, string> Curlys => new Dictionary<string, string>
{
{nameof(ItemId), ItemId },
{nameof(RequestedUser), RequestedUser },
{nameof(Title), Title },
{nameof(RequestedDate), RequestedDate },

View file

@ -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<IMovieRequestEngine>(),
async (engine, id) => await engine.ApproveMovieById(id),
async (engine, id) => await engine.DenyMovieById(id, string.Empty),
provider => provider.GetRequiredService<ITvRequestEngine>(),
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();
}