Last active
September 30, 2019 16:07
-
-
Save MihaZupan/e36e61282b0309cf820a5d3a4b439c94 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RedisDatabase : ISimpleDatabase | |
{ | |
private readonly ConnectionMultiplexer _redis = ConnectionMultiplexer.Connect("localhost"); | |
public async Task<string> GetAsync(string dataSet, string key) | |
{ | |
return await _redis.GetDatabase().HashGetAsync(dataSet, key); | |
} | |
public async Task SetAsync(string dataSet, string key, string value) | |
{ | |
await _redis.GetDatabase().HashSetAsync(dataSet, key, value); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
using Telegram.Bot; | |
using Telegram.Bot.Types; | |
using Telegram.Bot.Types.Enums; | |
namespace TelegramBotState | |
{ | |
public class TelegramBot | |
{ | |
private readonly ITelegramBotClient _client; | |
private readonly ISimpleDatabase _database; | |
private readonly Func<Conversation, Task> _defaultConversationHandler; | |
private readonly Dictionary<string, Func<Conversation, Task>> _conversationHandlers; | |
public TelegramBot(ITelegramBotClient client, ISimpleDatabase database, Func<Conversation, Task> defaultConversationHandler) | |
{ | |
_client = client ?? throw new ArgumentNullException(nameof(client)); | |
_database = database ?? throw new ArgumentNullException(nameof(database)); | |
_defaultConversationHandler = defaultConversationHandler ?? throw new ArgumentNullException(nameof(defaultConversationHandler)); | |
_conversationHandlers = new Dictionary<string, Func<Conversation, Task>>(); | |
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) | |
{ | |
foreach (TypeInfo type in assembly.DefinedTypes) | |
{ | |
foreach (MethodInfo method in type.DeclaredMethods) | |
{ | |
if (!method.IsStatic) | |
continue; | |
if (!method.ReturnType.Equals(typeof(Task))) | |
continue; | |
var parameters = method.GetParameters(); | |
if (parameters.Length != 1) | |
continue; | |
var parameter = parameters[0]; | |
if (!parameter.ParameterType.Equals(typeof(Conversation))) | |
continue; | |
if (parameter.IsIn || parameter.IsOut) | |
continue; | |
if (method.DeclaringType is null) | |
continue; // Shouldn't happen | |
var name = GetMethodDescriptorString(method); | |
var func = (Func<Conversation, Task>)method.CreateDelegate(typeof(Func<Conversation, Task>)); | |
_conversationHandlers.Add(name, func); | |
} | |
} | |
} | |
} | |
internal static string GetMethodDescriptorString(MethodInfo method) | |
{ | |
return string.Concat(method.DeclaringType.FullName, "!", method.Name); | |
} | |
public async Task HandleUpdateAsync(Update update) | |
{ | |
if (update is null) | |
throw new ArgumentNullException(nameof(update)); | |
Message message = update.Message; | |
if (message is null) | |
return; | |
if (message.Chat.Type != ChatType.Private) | |
return; | |
string id = message.From.Id.ToString(); | |
string serializedUserState = await _database.GetAsync(DataSets.UserState, id); | |
JToken userState = string.IsNullOrEmpty(serializedUserState) | |
? null | |
: (JToken)JsonConvert.DeserializeObject(serializedUserState); | |
Conversation conversation = new Conversation(_client, _database, message, userState); | |
string handlerDescriptor = await _database.GetAsync(DataSets.Handlers, id); | |
if (handlerDescriptor is null) | |
{ | |
await _defaultConversationHandler(conversation); | |
} | |
else | |
{ | |
if (_conversationHandlers.TryGetValue(handlerDescriptor, out Func<Conversation, Task> handler)) | |
{ | |
await handler(conversation); | |
} | |
else | |
{ | |
throw new ArgumentException("A conversation handler no longer exists", handlerDescriptor); | |
} | |
} | |
} | |
} | |
public class Conversation | |
{ | |
public User User => Message.From; | |
public readonly Message Message; | |
public readonly ITelegramBotClient Client; | |
private readonly JToken _userState; | |
private object _potentiallyModifiedState; | |
public T State<T>(bool storeChanges = true) | |
{ | |
T t = _userState.ToObject<T>(); | |
if (storeChanges) | |
{ | |
_potentiallyModifiedState = t; | |
} | |
return t; | |
} | |
private readonly ISimpleDatabase _database; | |
internal Conversation(ITelegramBotClient client, ISimpleDatabase database, Message message, JToken userState) | |
{ | |
Client = client; | |
_database = database; | |
Message = message; | |
_userState = userState; | |
} | |
public async Task ContinueWithAsync(Func<Conversation, Task> continuation, object userState = null) | |
{ | |
var method = continuation.Method; | |
if (!method.IsStatic) | |
throw new ArgumentException("Continuation delegate must be a static method"); | |
if (method.DeclaringType is null) | |
throw new ArgumentException("Continuation delegate must be a proper member method (not a lambda) - this exception should not be thrown?"); | |
await _database.SetAsync( | |
DataSets.Handlers, | |
User.Id.ToString(), | |
TelegramBot.GetMethodDescriptorString(method)); | |
userState ??= _potentiallyModifiedState; | |
if (userState is { }) | |
{ | |
await _database.SetAsync( | |
DataSets.UserState, | |
User.Id.ToString(), | |
JsonConvert.SerializeObject(userState)); | |
} | |
} | |
} | |
internal static class DataSets | |
{ | |
public const string Handlers = "transitions"; | |
public const string UserState = "user-states"; | |
} | |
public interface ISimpleDatabase | |
{ | |
Task SetAsync(string dataSet, string key, string value); | |
Task<string> GetAsync(string dataSet, string key); | |
} | |
public static class ConversationExtensions | |
{ | |
public static Task<Message> SendTextMessageAsync(this Conversation conversation, string message) | |
{ | |
return conversation.Client.SendTextMessageAsync(conversation.Message.Chat, message); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment