From 46714173c8d24ad27d97c069faa708962c3dc8bb Mon Sep 17 00:00:00 2001 From: Dmitriy yawaflua Andreev Date: Tue, 30 Jul 2024 23:01:30 +0300 Subject: [PATCH] Add readme and template base --- Controllers/ExampleController.cs | 2 +- Dockerfile | 9 +- MyTemplate.vstemplate | 35 +++++ Program.cs | 34 ++-- README.md | 45 ++++++ Startup.cs | 2 +- TelegramBotService.cs | 256 +++++++++++++++---------------- __TemplateIcon.ico | Bin 0 -> 44043 bytes 8 files changed, 229 insertions(+), 154 deletions(-) create mode 100644 MyTemplate.vstemplate create mode 100644 README.md create mode 100644 __TemplateIcon.ico diff --git a/Controllers/ExampleController.cs b/Controllers/ExampleController.cs index b209d80..c43c26a 100644 --- a/Controllers/ExampleController.cs +++ b/Controllers/ExampleController.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace TG_Bot_Template.Controllers +namespace $safeprojectname$.Controllers { /// /// Example controller diff --git a/Dockerfile b/Dockerfile index c3e03cc..dc3f5e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,17 +8,16 @@ EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["TG-Bot-Template.csproj", "."] -RUN dotnet restore "./TG-Bot-Template.csproj" COPY . . +RUN dotnet restore WORKDIR "/src/." -RUN dotnet build "./TG-Bot-Template.csproj" -c $BUILD_CONFIGURATION -o /app/build +RUN dotnet build -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./TG-Bot-Template.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +RUN dotnet publish -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "TG-Bot-Template.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "$safeprojectname$"] \ No newline at end of file diff --git a/MyTemplate.vstemplate b/MyTemplate.vstemplate new file mode 100644 index 0000000..c0cead8 --- /dev/null +++ b/MyTemplate.vstemplate @@ -0,0 +1,35 @@ + + + Telegram.Bit + Template of new ASP .NET 8 Telegram bot project + CSharp + + + 1000 + true + Telegram.Bot + true + Enabled + true + true + __TemplateIcon.ico + + + + + launchSettings.json + + + + ExampleController.cs + + appsettings.json + appsettings.Development.json + Dockerfile + .dockerignore + Program.cs + Startup.cs + TelegramBotService.cs + + + \ No newline at end of file diff --git a/Program.cs b/Program.cs index b593410..565e2aa 100644 --- a/Program.cs +++ b/Program.cs @@ -1,25 +1,23 @@ - -using Microsoft.AspNetCore; - -namespace TG_Bot_Template +namespace $safeprojectname$ { public class Program +{ + public static void Main() { - public static void Main() + CreateHostBuilder() + .Build() + .Run(); + } + private static IHostBuilder CreateHostBuilder() + { + return Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults(webHost => { - CreateHostBuilder() - .Build() - .Run(); - } - private static IHostBuilder CreateHostBuilder() - { - return Host.CreateDefaultBuilder() - .ConfigureWebHostDefaults(webHost => { - webHost.UseStartup(); - webHost.UseStaticWebAssets(); - webHost.UseKestrel(kestrelOptions => { kestrelOptions.ListenAnyIP(80); }); - }); + webHost.UseStartup(); + webHost.UseStaticWebAssets(); + webHost.UseKestrel(kestrelOptions => { kestrelOptions.ListenAnyIP(80); }); + }); - } } } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..91b1f09 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Template for Telegram BOT project +## About it +This is a template for VS 2023 Community/Enterprice for creating project with Telegram Bot service and base settings. + + + +## Used stack: +- .NET 8 Sdk Web +- ASP +- Swagger UI with commas setup +- Telegram.Bots (For .NET) + +# Use as template +For start used this project as a template, u should to download it. +After that, open it with Visual Studio, press "Project" -> "Export Template". +In window u need to select "Project export"(For exporting exactly Project) and press "Next". +In next window u can edit template name, description and icon. +After pressing "Finish" button, you can create project with this template. + +### OR + +Another way to use this project as a template: ZIP to folder with VS. +Download this project and compress it to .zip file with any name. After that, go to this destination: +```bash +C:\Users\{{YOUR_USERNAME}}\AppData\Roaming\Microsoft\VisualStudio\{{VS_VERSION}}\ +``` +Create folder with this name: `ProjectTemplatesCache` and pull zipped project to created folder. +After reloading VS you can create new project with this template. + +# Use as base for project +Its very simple to use, just download it and in file `appsettings.json` change in +"tg-token" (`"tg-token": "TOKEN"`) variable from TOKEN to your Bot`s token from [@BotFather](https://t.me/BotFather). + +After that, change press RMB on project and select "Sync Namespaces". If you need to use Dockerfile, change from "$safeprojectname$" to your project name in last stroke + +# appsettings.json + +Your appsettings.json file must be looked like this: +```json +{ + "tg-token": "123456:AaBbCcDd" +} +``` +or any with this variable + diff --git a/Startup.cs b/Startup.cs index e4c7174..fb9af1e 100644 --- a/Startup.cs +++ b/Startup.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Hosting.Server; using System; -namespace TG_Bot_Template +namespace $safeprojectname$ { /// /// Startup class for ASP.NET with setting and configuration diff --git a/TelegramBotService.cs b/TelegramBotService.cs index 63648a2..0eb9f9c 100644 --- a/TelegramBotService.cs +++ b/TelegramBotService.cs @@ -1,141 +1,139 @@ -using System; -using Telegram.Bot.Types; -using Telegram.Bot; +using Telegram.Bot; using Telegram.Bot.Polling; +using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -using System.Runtime.CompilerServices; using Telegram.Bot.Types.InlineQueryResults; -namespace TG_Bot_Template +namespace $safeprojectname$ { public class TelegramBotService(IConfiguration conf, ILogger logger) : TelegramBotClient(conf.GetValue("tg-token")), IHostedService +{ + private static Dictionary _userStates = new Dictionary(); + + // For add any property, u should to add it in StartAsync func, like this: + // _PROPERTY_NAME = PROPERTY_NAME_FROM_CLASS + + private static ILogger _logger { get; set; } + + + private static async Task MainHandler(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { - private static Dictionary _userStates = new Dictionary(); - - // For add any property, u should to add it in StartAsync func, like this: - // _PROPERTY_NAME = PROPERTY_NAME_FROM_CLASS - - private static ILogger _logger { get; set; } - - - private static async Task MainHandler(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) - { - _logger.Log( - LogLevel.Information, - $"Received an update: {update.Type}, Caller ID: {update.Message?.From?.Id ?? update.CallbackQuery?.From.Id ?? update.InlineQuery?.From.Id}" - ); - var handler = update switch - { - - { Message: { } message } => HandleUpdateAsync(botClient, message), - //{ EditedMessage: { } message } => BotOnMessageReceived(message, cancellationToken), - { CallbackQuery: { } callbackQuery } => QueryUpdateHandler(botClient, callbackQuery), - { InlineQuery: { } inlineQuery } => InlineUpdateHandler(botClient, inlineQuery), - //{ ChosenInlineResult: { } chosenInlineResult } => BotOnChosenInlineResultReceived(chosenInlineResult, cancellationToken), - _ => Task.CompletedTask - }; - - await handler; - - - } - private static async Task HandleUpdateAsync(ITelegramBotClient botClient, Message message) - { - try - { - string messageText = message.Text ?? message.Caption; - switch (messageText) - { - case "/start": - await botClient.SendTextMessageAsync(message.From.Id, "Start"); - break; - - default: - break; - } - } - catch (Exception ex) - { - _logger.LogError($"{ex.Message}"); - _logger.LogError($"Stack: {ex.StackTrace}"); - } - } - - private static async Task QueryUpdateHandler(ITelegramBotClient botClient, CallbackQuery callbackQuery) - { - try - { - string queryData = callbackQuery.Data; - switch (queryData) - { - case "test": - await botClient.AnswerCallbackQueryAsync(callbackQuery.Id, "Tested"); - break; - - default: - break; - } - } - catch (Exception ex) - { - _logger.LogError($"{ex.Message}"); - _logger.LogError($"Stack: {ex.StackTrace}"); - } - } - - private static async Task InlineUpdateHandler(ITelegramBotClient botClient, InlineQuery inlineQuery) - { - try - { - string inlineData = inlineQuery.Query; - switch (inlineData) - { - case "test": - await botClient.AnswerInlineQueryAsync(inlineQuery.Id, new InlineQueryResult[] { }); - break; - - default: - break; - } - } - catch (Exception ex) - { - _logger.LogError($"{ex.Message}"); - _logger.LogError($"Stack: {ex.StackTrace}"); - } - } - - - public async Task StartAsync(CancellationToken cancellationToken) - { - logger.Log(LogLevel.Information, $"Starting build {this.GetType().Name}"); - // StartReceiving does not block the caller thread. Receiving is done on the ThreadPool. - ReceiverOptions receiverOptions = new() - { - AllowedUpdates = [UpdateType.Message, UpdateType.CallbackQuery, UpdateType.InlineQuery] // receive all update types except ChatMember related updates - }; - - _logger = logger; - - this.StartReceiving( - updateHandler: MainHandler, - pollingErrorHandler: (k, ex, ctx) => { - Console.WriteLine(ex.Message); - - return Task.CompletedTask; - }, - receiverOptions: receiverOptions, - cancellationToken: cancellationToken + _logger.Log( + LogLevel.Information, + $"Received an update: {update.Type}, Caller ID: {update.Message?.From?.Id ?? update.CallbackQuery?.From.Id ?? update.InlineQuery?.From.Id}" ); - var me = await this.GetMeAsync(cancellationToken: cancellationToken); - logger.Log(LogLevel.Information, $"Start listening bot @{me.Username}"); - } - - public async Task StopAsync(CancellationToken cancellationToken) + var handler = update switch { - logger.Log(LogLevel.Information, $"Stopping service"); - await this.LogOutAsync(cancellationToken); - } + + { Message: { } message } => HandleUpdateAsync(botClient, message), + //{ EditedMessage: { } message } => BotOnMessageReceived(message, cancellationToken), + { CallbackQuery: { } callbackQuery } => QueryUpdateHandler(botClient, callbackQuery), + { InlineQuery: { } inlineQuery } => InlineUpdateHandler(botClient, inlineQuery), + //{ ChosenInlineResult: { } chosenInlineResult } => BotOnChosenInlineResultReceived(chosenInlineResult, cancellationToken), + _ => Task.CompletedTask + }; + + await handler; + } + private static async Task HandleUpdateAsync(ITelegramBotClient botClient, Message message) + { + try + { + string messageText = message.Text ?? message.Caption; + switch (messageText) + { + case "/start": + await botClient.SendTextMessageAsync(message.From.Id, "Start"); + break; + + default: + break; + } + } + catch (Exception ex) + { + _logger.LogError($"{ex.Message}"); + _logger.LogError($"Stack: {ex.StackTrace}"); + } + } + + private static async Task QueryUpdateHandler(ITelegramBotClient botClient, CallbackQuery callbackQuery) + { + try + { + string queryData = callbackQuery.Data; + switch (queryData) + { + case "test": + await botClient.AnswerCallbackQueryAsync(callbackQuery.Id, "Tested"); + break; + + default: + break; + } + } + catch (Exception ex) + { + _logger.LogError($"{ex.Message}"); + _logger.LogError($"Stack: {ex.StackTrace}"); + } + } + + private static async Task InlineUpdateHandler(ITelegramBotClient botClient, InlineQuery inlineQuery) + { + try + { + string inlineData = inlineQuery.Query; + switch (inlineData) + { + case "test": + await botClient.AnswerInlineQueryAsync(inlineQuery.Id, new InlineQueryResult[] { }); + break; + + default: + break; + } + } + catch (Exception ex) + { + _logger.LogError($"{ex.Message}"); + _logger.LogError($"Stack: {ex.StackTrace}"); + } + } + + + public async Task StartAsync(CancellationToken cancellationToken) + { + logger.Log(LogLevel.Information, $"Starting build {this.GetType().Name}"); + // StartReceiving does not block the caller thread. Receiving is done on the ThreadPool. + ReceiverOptions receiverOptions = new() + { + AllowedUpdates = [UpdateType.Message, UpdateType.CallbackQuery, UpdateType.InlineQuery] // receive all update types except ChatMember related updates + }; + + _logger = logger; + + this.StartReceiving( + updateHandler: MainHandler, + pollingErrorHandler: (k, ex, ctx) => + { + logger.LogError(ex.StackTrace); + + return Task.CompletedTask; + }, + receiverOptions: receiverOptions, + cancellationToken: cancellationToken + ); + var me = await this.GetMeAsync(cancellationToken: cancellationToken); + logger.Log(LogLevel.Information, $"Start listening bot @{me.Username}"); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + logger.Log(LogLevel.Information, $"Stopping service"); + } + +} } diff --git a/__TemplateIcon.ico b/__TemplateIcon.ico new file mode 100644 index 0000000000000000000000000000000000000000..aae70d3b83cf76e276e5209bae014f83bc61eb75 GIT binary patch literal 44043 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 EAKlW`b^rhX literal 0 HcmV?d00001