Skip to content

Instantly share code, notes, and snippets.

@MihaZupan
Last active September 30, 2019 16:07
Show Gist options
  • Save MihaZupan/e36e61282b0309cf820a5d3a4b439c94 to your computer and use it in GitHub Desktop.
Save MihaZupan/e36e61282b0309cf820a5d3a4b439c94 to your computer and use it in GitHub Desktop.
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);
}
}
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