Initial commit
This commit is contained in:
commit
b9c43550aa
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
.idea/
|
||||
/_ReSharper.Caches/
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/macos
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=macos
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
.apdisk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos
|
172
Program.cs
Normal file
172
Program.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
|
||||
var token = Environment.GetEnvironmentVariable("TELEGRAM_BOT_TOKEN")!;
|
||||
|
||||
var bot = new TelegramBotClient(token);
|
||||
var me = await bot.GetMeAsync();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await bot.SetMyCommandsAsync(new[] {
|
||||
new BotCommand { Command = "help", Description = "Lists all commands" },
|
||||
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." },
|
||||
});
|
||||
|
||||
var iBotCommands = new List<string> {
|
||||
"coinflip",
|
||||
"shouldi",
|
||||
"yesno",
|
||||
"makedecision",
|
||||
"choose",
|
||||
"help",
|
||||
"start"
|
||||
};
|
||||
|
||||
bot.StartReceiving(HandleUpdateAsync, PollingErrorHandler, null, cts.Token);
|
||||
Console.WriteLine($"Start listening for @{me.Username}");
|
||||
Console.ReadLine();
|
||||
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 {
|
||||
await (update.Type switch {
|
||||
UpdateType.Message => BotOnMessageReceived(ibot, update.Message!),
|
||||
_ => Task.CompletedTask
|
||||
});
|
||||
}
|
||||
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..];
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<string> { "Syntax error. (Can't select from zero options)" };
|
||||
await botClient.SendTextMessageAsync(message.Chat.Id, RandomOption(options).Trim(), 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)
|
||||
|
||||
In direct messages, these 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's an error, please forward the stacktrace message to @zotan (my creator). You can also DM them for feature requests.
|
||||
|
||||
Enjoy!
|
||||
|
||||
- Powered by [~zotan](https://zotan.pw)'s [Telegram.Bot.DecisionMaker](https://git.ztn.sh/zotan/Telegram.Bot.DecisionMaker) v1.0
|
||||
""", 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}`
|
||||
""", 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<string> options) {
|
||||
var random = new Random(Guid.NewGuid().GetHashCode());
|
||||
var index = random.Next(options.Count);
|
||||
return options[index];
|
||||
}
|
26
README.md
Normal file
26
README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Telegram.Bot.DecisionMaker
|
||||
Telegram bot that helps you make decisions. Production instance running on [@decisionparalysisbot](https://t.me/decisionparalysisbot).
|
||||
|
||||
In case the official instance ever goes down, here's how to set this up yourself:
|
||||
- Obtain your own bot token from [@BotFather](https://t.me/BotFather)
|
||||
- Start this bot using `TELEGRAM_BOT_TOKEN=yourtoken dotnet run`
|
||||
|
||||
systemd service example:
|
||||
```
|
||||
# /etc/systemd/system/decisionbot.service
|
||||
[Unit]
|
||||
Description=Telegram.Bot.DecisionMaker
|
||||
Wants=network-online.target nss-lookup.target
|
||||
After=network-online.target nss-lookup.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=botuser
|
||||
WorkingDirectory=/opt/Telegram.Bot.DecisionMaker
|
||||
Environment=TELEGRAM_BOT_TOKEN='yourtoken'
|
||||
ExecStart=/usr/bin/dotnet run
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
14
Telegram.Bot.DecisionMaker.csproj
Normal file
14
Telegram.Bot.DecisionMaker.csproj
Normal file
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Telegram.Bot" Version="18.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
16
Telegram.Bot.DecisionMaker.sln
Normal file
16
Telegram.Bot.DecisionMaker.sln
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Bot.DecisionMaker", "Telegram.Bot.DecisionMaker.csproj", "{DF92F931-77E4-4DA3-BC79-2E56CC4C016B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DF92F931-77E4-4DA3-BC79-2E56CC4C016B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DF92F931-77E4-4DA3-BC79-2E56CC4C016B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DF92F931-77E4-4DA3-BC79-2E56CC4C016B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DF92F931-77E4-4DA3-BC79-2E56CC4C016B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
Loading…
Reference in a new issue