using System.Reflection; using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; var token = Environment.GetEnvironmentVariable("TELEGRAM_BOT_TOKEN")!; var version = ((AssemblyInformationalVersionAttribute)Assembly.GetEntryAssembly()!.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false)[0]) .InformationalVersion[6..]; using var cts = new CancellationTokenSource(); var bot = new TelegramBotClient(token); var me = await bot.GetMeAsync(); await bot.SetMyCommandsAsync(new[] { new BotCommand { Command = "coinflip", Description = "Flips a coin" }, new BotCommand { Command = "shouldi", Description = "Tells you if you should or shouldn't do something" }, new BotCommand { Command = "yesno", Description = "Answes binary questions" }, new BotCommand { Command = "makedecision", Description = "Makes a decision. Expects a list of comma separated options." }, new BotCommand { Command = "choose", Description = "Chooses an item. Expects a list of comma separated options." }, new BotCommand { Command = "spinbottle", Description = "Spins a bottle (for truth or dare). Expects a list of users participating separated by a space." }, new BotCommand { Command = "help", Description = "Lists all commands" }, }); var iBotCommands = new List { "coinflip", "shouldi", "yesno", "makedecision", "choose", "spinbottle", "help", "start" }; bot.StartReceiving(HandleUpdateAsync, PollingErrorHandler, null, cts.Token); Console.WriteLine($"Start listening for @{me.Username}"); var exitEvent = new ManualResetEvent(false); Console.CancelKeyPress += (sender, eventArgs) => { eventArgs.Cancel = true; exitEvent.Set(); }; exitEvent.WaitOne(); cts.Cancel(); Task PollingErrorHandler(ITelegramBotClient ibot, Exception ex, CancellationToken ct) { Console.WriteLine($"Exception while polling for updates: {ex}"); return Task.CompletedTask; } async Task HandleUpdateAsync(ITelegramBotClient ibot, Update update, CancellationToken ct) { try { if (update.Type != UpdateType.Message) return; await BotOnMessageReceived(ibot, update.Message!); } catch (Exception ex) { Console.WriteLine($"Exception while handling {update.Type}: {ex}"); } } async Task BotOnMessageReceived(ITelegramBotClient botClient, Message message) { var command = ""; var query = ""; try { // if we didn't receive any text, return if (string.IsNullOrWhiteSpace(message.Text)) return; // we need to figure out which way of sending commands is being used // let's get the first word of the message var firstword = message.Text!.Split(" ")[0]; // is it a slash command? if (firstword.StartsWith("/")) { // trim explicit username mention command = firstword.Replace($"@{me.Username}", "").TrimStart('/'); // remove command (and optional username) from message text query = message.Text?[firstword.Length..]; // ignore unknown slash commands in groups not explicitly directed at us if (!iBotCommands.Contains(command) && message.Chat.Type != ChatType.Private && !firstword.Contains($"@{me.Username}")) return; } else { // are we sure the bot is being asked? if (message.Chat.Type == ChatType.Private || message.Text!.Contains($"@{me.Username}")) { var iquery = message.Text.Replace($"@{me.Username}", "").Trim(); if (string.IsNullOrWhiteSpace(iquery)) return; // is the first word a command? firstword = iquery.Split(" ")[0]; if (iBotCommands.Contains(firstword)) { command = firstword; query = iquery[firstword.Length..]; } else { // try to guess what was meant if (iquery.ToLowerInvariant().Contains(" or ") && !iquery.Contains(',')) { command = "makedecision"; query = iquery.Replace(" or ", ",").TrimEnd('?'); } else if (iquery.EndsWith("?")) { command = "yesno"; } else if (iquery.ToLowerInvariant().Contains("flip") && iquery.ToLowerInvariant().Contains("coin")) { command = "coinflip"; } } } else { // likely not a message meant for us return; } } switch (command) { case "coinflip": await botClient.SendTextMessageAsync(message.Chat.Id, RandomOption(new[] { "Heads", "Tails" }), replyToMessageId: message.MessageId); break; case "shouldi": await botClient.SendTextMessageAsync(message.Chat.Id, RandomOption(new[] { "Go for it!", "Can't hurt to try!", "Probably not.", "I wouldn't recommend it." }), replyToMessageId: message.MessageId); break; case "yesno": await botClient.SendTextMessageAsync(message.Chat.Id, RandomOption(new[] { "Yeah!", "Nah..." }), replyToMessageId: message.MessageId); break; case "makedecision": case "choose": var options = query?.Split(",").Where(p => !string.IsNullOrWhiteSpace(p)).ToList(); if (options == null || !options.Any()) options = new List { "Syntax error. (Can't select from zero options)" }; await botClient.SendTextMessageAsync(message.Chat.Id, RandomOption(options).Trim(), replyToMessageId: message.MessageId); break; case "spinbottle": var people = query?.Split(" ").Where(p => !string.IsNullOrWhiteSpace(p)).ToList(); if (people == null || !people.Any()) { await botClient.SendTextMessageAsync(message.Chat.Id, "Syntax error. (Can't select from zero options)", replyToMessageId: message.MessageId); break; } await botClient.SendTextMessageAsync(message.Chat.Id, RandomOption(people).Trim().TrimEnd(',') + ", truth or dare?", replyToMessageId: message.MessageId); break; case "help": case "start": await botClient.SendTextMessageAsync(message.Chat.Id, $""" Heya, I am a bot that can help you make decisions! Here's a list of things I can do: /coinflip - flips a coin /shouldi - when you are unsure if you should do something /yesno - answers more generic yes/no questions /makedecision - helps you make a decision (chooses from a comma separated list of options; aliased to /choose) Feel free to add me to your group chats! Be aware that in those contexts, I can't read (and therefore not respond to) any messages unless they use the syntax `/@{me.Username}` except when I'm the only bot in the chat, in which case `/` is enough. In direct messages, commands also work without the / at the start. If your message doesn't start with a recognized command, I'll do my best to figure out what you are asking. This currently works with the following queries: - `thing1 or thing2 or thing3` gets translated into `/makedecision thing1,thing2,thing3` - `flip a coin` gets translated into `/coinflip` - `this is a question?` gets translated to `/yesno this is a question` If there is an error, please forward the stacktrace message to @zotan (my creator). You can also DM them for feature requests. Enjoy! I'm powered by [~zotan](https://zotan.pw)'s [Telegram.Bot.DecisionMaker](https://git.ztn.sh/zotan/Telegram.Bot.DecisionMaker) rev. [{version}](https://git.ztn.sh/zotan/Telegram.Bot.DecisionMaker/commit/{version.Replace("-dirty", "")}) """, replyToMessageId: message.MessageId, parseMode: ParseMode.Markdown, disableWebPagePreview: true); break; default: await botClient.SendTextMessageAsync(message.Chat.Id, $""" Apologies, I can't figure out what you want me to do >w< Message received: `{message.Text}` Parsed command: `{command}` Parsed query: `{query}` Try /help for examples on how to help me understand your query! """, replyToMessageId: message.MessageId, parseMode: ParseMode.Markdown); break; } } catch (Exception e) { await botClient.SendTextMessageAsync(message.Chat.Id, $""" Error processing message. Message content: `{message.Text}` Parsed command: `{command}` Parsed query: `{query}` Exception: `{e.Message.Replace("`", "'")}` Stacktrace: `{e.StackTrace?.Replace("`", "'")}` """, replyToMessageId: message.MessageId, parseMode: ParseMode.Markdown); throw; } } static string RandomOption(IReadOnlyList options) { var random = new Random(Guid.NewGuid().GetHashCode()); var index = random.Next(options.Count); return options[index]; }