{"id":30302140,"url":"https://github.com/kira-nt/termigram","last_synced_at":"2025-08-17T05:07:06.671Z","repository":{"id":98397245,"uuid":"248775821","full_name":"Kira-NT/Termigram","owner":"Kira-NT","description":"🤖 The simplest way to build Telegram Bot in the whole multiverse","archived":false,"fork":false,"pushed_at":"2020-05-26T01:19:21.000Z","size":567,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-14T23:53:22.066Z","etag":null,"topics":["bot","chatbot","chatbot-framework","netcore","netstandard","telegram"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Kira-NT.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-20T14:29:29.000Z","updated_at":"2025-06-10T21:25:22.000Z","dependencies_parsed_at":"2023-03-13T16:01:33.183Z","dependency_job_id":null,"html_url":"https://github.com/Kira-NT/Termigram","commit_stats":null,"previous_names":["kira-nt/termigram","kir-antipov/termigram"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Kira-NT/Termigram","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kira-NT%2FTermigram","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kira-NT%2FTermigram/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kira-NT%2FTermigram/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kira-NT%2FTermigram/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kira-NT","download_url":"https://codeload.github.com/Kira-NT/Termigram/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kira-NT%2FTermigram/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270807934,"owners_count":24649346,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-17T02:00:09.016Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bot","chatbot","chatbot-framework","netcore","netstandard","telegram"],"created_at":"2025-08-17T05:06:42.870Z","updated_at":"2025-08-17T05:07:06.654Z","avatar_url":"https://github.com/Kira-NT.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Termigram\n\n [![NuGet](https://img.shields.io/nuget/v/Termigram.svg?style=flat-square\u0026label=Termigram\u0026cacheSeconds=3600)](https://www.nuget.org/packages/Termigram/)\n [![License](https://img.shields.io/github/license/Kir-Antipov/Termigram.svg?style=flat-square\u0026label=License\u0026cacheSeconds=1296000)](https://raw.githubusercontent.com/Kir-Antipov/Termigram/master/LICENSE.md)\n\n\u003cimg src=\"./media/icon.png\" alt=\"Termigram Logo\" width=200 height=200 /\u003e\n\nThe **simplest** way to build Telegram Bot in the whole multiverse 🤖\n\n## Getting Started\n\n![Morpheus](media/morpheus.png)\n\nSometimes we just want to write code, run it and make it work. And this framework is just about it.\n\n```csharp\npublic class MyBot : BotBase\n{\n    public MyBot(IOptions options) : base(options) { }\n\n    [Command]\n    public string Start() =\u003e \"Hello, I'm bot\";\n}\n\n...\n\n_ = new MyBot(new DefaultOptions(token)).RunAsync();\n```\n\nYep, no more\u003cbr\u003e\nYep, so simple\n\nAre you interested? Then, let's go!\n\n## Library Details\n\n - This project is based on [Telegram.Bot](https://github.com/TelegramBots/Telegram.Bot) - the most popular and current wrapper over the Telegram Bot API, so there is a good chance that you're already partially familiar with what is under the hood of this framework\n\n - **.NET Standart 2.1+** (.NET Core 3.0+), cause I don't see this project without using such cool things as [`IAsyncEnumerable\u003cT\u003e`](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.iasyncenumerable-1?view=netcore-3.1) (I'll show some use cases later)\n\n - This library uses the **Nullable feature**, so it will be easier for you to understand where null is a valid value and where it's not\n\n - Alas, I had no enough time to write documentation for this project yet 😕 I'll fix this in the nearest future, but for now I hope that an example will be enough to bring you up to date\n\n## Example\n\nSince the library doesn't have any documentation yet, I'll try to explain the [example](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram.Example) as intelligibly as possible so that you can understand what is going on here :)\n\nLet's take a look at creating a simple bot step by step at first:\n\n### 0. Create a new class\n\n```csharp\npublic class TestBot\n{\n\n}\n```\n\n### 1. Inherit it from one of the base classes\n\nThere're 2 default base classes available: [`BotBase`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Bot/BotBase.cs) and [`StateBotBase`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Bot/StateBotBase.cs). The last one has special `State` property, which can be used to store some user-specific data between command calls, that’s the whole difference.\n\nSo your class will be look like\n\n```csharp\npublic class TestBot : BotBase\n{\n    public TestBot(IOptions options) : base(options) { }\n}\n```\n\nor\n\n```csharp\npublic class TestBot : StateBotBase\n{\n    public TestBot(IStateOptions options) : base(options) { }\n}\n```\n\n### 2. Create a command\n\nBy default, any method *(static, instance, public or non public)* that is tagged with the [`CommandAttribute`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Commands/CommandAttribute.cs) attribute is a command.\n\n```csharp\npublic class TestBot : StateBotBase\n{\n    public TestBot(IStateOptions options) : base(options) { }\n\n    [Command(\"start\", \"begin\")]\n    public string Start() =\u003e \"Hi, I'm bot\";\n}\n```\n\nIt means that our bot has one command, which can be triggered somehow like this:\n\n\u003e /start\u003cbr\u003e\n\u003e /begin\u003cbr\u003e\n\u003e start\u003cbr\u003e\n\u003e begin\u003cbr\u003e\n\u003e StArt 42\u003cbr\u003e\n\u003e BEGIn foo\n\nCommand name is case insensitive and it's not required to start it with slash.\n\n### 3. BotAttribute\n\nI marked my [example class](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram.Example/TestBot.cs) with [`BotAttribute`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Bot/BotAttribute.cs):\n\n```csharp\n[Bot(Bindings.AnyPublic)]\npublic class TestBot : StateBotBase\n```\n\nThis overrides the behavior of [default `ICommandExtractor`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/CommandExtractors/DefaultCommandExtractor.cs). Now, as any public method will be tagged as a command, there's no need to use [`CommandAttribute`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Commands/CommandAttribute.cs) anymore, so these lines of code become identical:\n\n```csharp\npublic string Start() =\u003e ...\n\n[Command]\npublic string Start() =\u003e ...\n\n[Command(\"Start\")]\npublic string Start() =\u003e ...\n```\n\n### 4. IgnoreCommandAttribute\n\nIf you don't want some method to become a command in any cases, just mark it with [`IgnoreCommandAttribute`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Commands/IgnoreCommandAttribute.cs):\n\n```csharp\n[IgnoreCommand]\npublic void NotCommand() =\u003e ...\n```\n\n### 5. DefaultCommandAttribute\n\nIf you want your bot to respond with a prepared message in cases where the user hasn't called any of available commands, just mark one of the methods by [`DefaultCommandAttribute`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Commands/DefaultCommandAttribute.cs):\n\n```csharp\n[DefaultCommand]\npublic static string Default() =\u003e \"Sorry, I have no such command\";\n```\n\n### 6. Available return types\n\nThe value returned by the method processes using one of the [`IResultProcessors`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/ResultProcessors/). Here's the list of types that can be processed by default:\n\n- `string`\n- `ChatAction`\n- [`AnimationMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/AnimationMessage.cs)\n- [`AudioMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/AudioMessage.cs)\n- [`ChatActionMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/ChatActionMessage.cs)\n- [`ContactMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/ContactMessage.cs)\n- [`DiceMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/DiceMessage.cs)\n- [`DocumentMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/DocumentMessage.cs)\n- [`GameMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/GameMessage.cs)\n- [`LocationMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/LocationMessage.cs)\n- [`MediaGroupMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/MediaGroupMessage.cs)\n- [`PhotoMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/PhotoMessage.cs)\n- [`PollMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/PollMessage.cs)\n- [`StickerMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/StickerMessage.cs)\n- [`TextMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/TextMessage.cs)\n- [`VenueMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/VenueMessage.cs)\n- [`VideoMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/VideoMessage.cs)\n- [`VideoNoteMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/VideoNoteMessage.cs)\n- [`VoiceMessage`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Messages/VoiceMessage.cs)\n\nAny value could be wrapped with `Task`, `Task\u003cT\u003e`, `IEnumerable`, `IEnumerable\u003cT\u003e` and `IAsyncEnumerable\u003cT\u003e`. So feel free to write asynchronous code and return more then 1 value from the method:\n\n```csharp\n// This command will send 2 messages to its invoker\n[Command(\"values\")]\npublic IEnumerable\u003cstring\u003e GetValues()\n{\n    yield return \"0\";\n    yield return \"1\";\n}\n\n// This command will send 2 messages with a 1 second break to its invoker\n[Command(\"valuesdelay\")]\npublic async IAsyncEnumerable\u003cstring\u003e GetValuesAsync()\n{\n    yield return \"0\";\n    await Task.Delay(1000);\n    yield return \"1\";\n}\n\n// This command will send 1 message with text \"Hello!\" to the chat with id 1\n// (not to its invoker)\n[Command(\"send\")]\npublic async Task\u003cTextMessage\u003e SendAsync()\n{\n    await SomeWorkAsync();\n    return new TextMessage(\"Hello!\", chatId: 1); \n}\n```\n\n### 7. Parameters\n\n```csharp\npublic string Sum(int a, int? b = null, int c = 1) =\u003e $\"Sum of \u003cb\u003e{a}\u003c/b\u003e, \u003cb\u003e{b ?? 0}\u003c/b\u003e and \u003cb\u003e{c}\u003c/b\u003e is {a + (b ?? 0) + c}\";\n```\n\nAs you can see, there's a possibility to pass parameters to the method by invoking a command. \n\n\u003e /sum\u003cbr\u003e\n\u003e Sum of **0**, **0** and **1** is 1\u003cbr\u003e\u003cbr\u003e\n\u003e /sum 2\u003cbr\u003e\n\u003e Sum of **2**, **0** and **1** is 3\u003cbr\u003e\u003cbr\u003e\n\u003e /sum 3 5 2\u003cbr\u003e\n\u003e Sum of **3**, **5** and **2** is 10\n\nThere're some inbuilt special values which can be provided by [`ISpecialValueProvider`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/SpecialValueProviders/ISpecialValueProvider.cs)s:\n\n - (`User` *anyName*) - user who called the command\n - (`ChatId` *anyName*) - Id of the chat from which the message came. If chat id isn't available, user id will be returned\n - (`Update` *anyName*) - `Update` object associated with this command\n - (`Message` *anyName*) - `Message` object associated with this command\n - (`ICommand` *anyName*) - this command info\n\n### 8. Overloads\n\n```csharp\npublic string Overload(DateTime? date) =\u003e $\"Date: {date}\";\n\npublic string Overload(string begin, string end) =\u003e $\"Messages: \\\"{begin}\\\" \u0026 \\\"{end}\\\"\";\n\npublic string Overload(int number) =\u003e $\"Number: {number}\";\n\npublic string Overload(string message) =\u003e $\"Message: \\\"{message}\\\"\";\n```\n\nThe bot is able to determine the most appropriate method's overload for the called command:\n\n\u003e /overload 1\u003cbr\u003e\n\u003e Number: 1\u003cbr\u003e\u003cbr\u003e\n\u003e /overload 01.01.1970\u003cbr\u003e\n\u003e Date: 01.01.1970 00:00:00\u003cbr\u003e\u003cbr\u003e\n\u003e /overload String\u003cbr\u003e\n\u003e Message: \"String\"\u003cbr\u003e\u003cbr\u003e\n\u003e /overload String1 String2\u003cbr\u003e\n\u003e Messages: \"String1\" \u0026 \"String2\"\u003cbr\u003e\u003cbr\u003e\n\u003e /overload String1 String2 String3\u003cbr\u003e\n\u003e Messages: \"String1\" \u0026 \"String2\"\u003cbr\u003e\u003cbr\u003e\n\n\u003cbr\u003e\nOverloads are those methods that have the same command names, but they themselves aren't required to bear the same method name, so\n\nthese are overloads:\n\n```csharp\n[Command(\"RyanReynolds\")]\npublic string Deadpool(...) =\u003e \"Chimichangas\";\n\n[Command(\"RyanReynolds\")]\npublic string WadeWilson(...) =\u003e \"Chimichangas\";\n```\n\nand these are not:\n\n```csharp\n[Command(\"ChristianBale\")]\npublic string Batman(...) =\u003e \"I'm the night\";\n\n[Command(\"BenAffleck\")]\npublic string Batman(...) =\u003e \"I'm the night\";\n```\n\n\u003cbr\u003e\nIf you define a lot of aliases for a command, there's no need to duplicate them for each overload: it's sufficient that the overloads have just one common name:\n\n```csharp\n[Command(nameof(Overload), \"alias0\", \"alias1\")]\npublic string Overload(...) =\u003e ...;\n\n[Command(nameof(Overload), \"alias2\")]\npublic string Overload(...) =\u003e ...;\n\n[Command]\npublic string Overload(...) =\u003e ...;\n\npublic string Overload(...) =\u003e ...;\n```\n\nEach of these overloads can be triggered by any of these names:\n\n\u003e Overload\u003cbr\u003e\n\u003e alias0\u003cbr\u003e\n\u003e alias1\u003cbr\u003e\n\u003e alias2\u003cbr\u003e\n\n### 9. Exceptions\n\nEach exception is perceived as a user error, not an error of your code, since, from a semantic point of view, it was the user who could call the command in a wrong way. So these commands\n\n```csharp\n[Command]\npublic void EnterPrivateZone() =\u003e throw new UnauthorizedAccessException(\"Sorry, you're blacklisted!\");\n\n[Command]\npublic async Task EnterPrivateZoneAsync() =\u003e throw new UnauthorizedAccessException(\"Sorry, you're blacklisted!\");\n```\n\nwill send an error message to the user who invoked the command.\n\nIf you don't like this behavior, you can change it by providing another [`IResultProcessor`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/ResultProcessors/IResultProcessor.cs) for `Exception` objects instead of [`ExceptionProcessor`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/ResultProcessors/ExceptionProcessor.cs)\n\n### 10. Helpful methods\n\n[`BotBase`](https://github.com/Kir-Antipov/Termigram/tree/master/Termigram/Bot/BotBase.cs) has some helpful methods for inherited classes:\n\n - `WaitForUpdateAsync` - The execution of the method will continue only after receiving a response from the user. In case of cancellation request `null` will be returned (by default there's 10 minutes timeout).\n - `WaitForAnswerAsync` - The same as `WaitForUpdateAsync`, but it returns only text from `Update` object:\n \n ```csharp\npublic async IAsyncEnumerable\u003cTextMessage\u003e Lucky(User user)\n{\n    const int min = 1;\n    const int max = 10;\n\n    int guessed = new Random().Next(min, max + 1);\n\n    yield return \"Let's see how lucky you are!\";\n    yield return $@\"I've made a number from \u003cb\u003e{min}\u003c/b\u003e to \u003cb\u003e{max}\u003c/b\u003e. Try to guess it!\";\n\n    string? userAssumption = await WaitForAnswerAsync(user);\n\n    if (string.IsNullOrEmpty(userAssumption))\n        throw new TimeoutException(\"Sorry, but you haven't answered for too long. Let's play next time!\");\n\n    if (userAssumption.Trim() == guessed.ToString())\n    {\n        yield return \"Lucky guy!\";\n    }\n    else\n    {\n        yield return $@\"This time you're out of luck. I figured out the number \u003cb\u003e{guessed}\u003c/b\u003e\";\n    }\n}\n\n```\n \n - `ReplyKeyboard` - Generates `ReplyKeyboardMarkup` by command methods' names:\n```csharp\nMainMenu = ReplyKeyboard\n(\n    nameof(Lucky),\n    nameof(Memento),\n    nameof(Sum),\n    nameof(Sticker),\n    nameof(Help)\n);\n```\n - `InlineKeyboard` -  Generates `InlineKeyboardMarkup` by command methods' names\n\n### 11. Starting the bot\n\nWhen your bot is ready, it remains just to create an instance of it and call the `RunAsync` method :)\n\n```csharp\nstring token = \"token\";\nIStateOptions options = new DefaultStateOptions(token);\nIBot bot = new TestBot(options);\n\nCancellationTokenSource source = new CancellationTokenSource();\n_ = bot.RunAsync(source.Token);\n\nConsole.WriteLine(\"Press any key to stop the bot...\");\nConsole.ReadKey();\n\nsource.Cancel();\n```\n\n## TODO\n\n [ ] Documentation","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkira-nt%2Ftermigram","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkira-nt%2Ftermigram","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkira-nt%2Ftermigram/lists"}