├── .appveyor.yml ├── .dockerignore ├── .gitattributes ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── Telegram.Bot.Framework.Net45.sln ├── Telegram.Bot.Framework.NetCore.sln ├── Telegram.Bot.Framework.sln ├── docs ├── icon.png └── wiki │ ├── README.md │ ├── deployment │ ├── docker-letsencrypt.md │ ├── ubuntu-nginx-selfsigned.md │ └── ubuntu-nginx.md │ ├── games │ └── games-in-telegram.md │ └── quick-start │ ├── crazy-circle-game.md │ └── echo-bot.md ├── sample ├── Quickstart.AspNet45 │ ├── App_Start │ │ └── WebApiConfig.cs │ ├── Controllers │ │ └── WebhookController.cs │ ├── Global.asax │ ├── Global.asax.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Quickstart.AspNet45.csproj │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Web.config │ └── packages.config ├── Quickstart.AspNetCore │ ├── EchoBot.cs │ ├── Extensions │ │ └── AppStartupExtensions.cs │ ├── Handlers │ │ ├── CallbackQueryHandler.cs │ │ ├── Commands │ │ │ ├── PingCommand.cs │ │ │ └── StartCommand.cs │ │ ├── ExceptionHandler.cs │ │ ├── StickerHandler.cs │ │ ├── TextEchoer.cs │ │ ├── UpdateMembersList.cs │ │ ├── WeatherReporter.cs │ │ └── WebhookLogger.cs │ ├── Options │ │ └── CustomBotOptions.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Quickstart.AspNetCore.csproj │ ├── Services │ │ ├── BotServiceProvider.cs │ │ ├── CurrentWeather.cs │ │ ├── IWeatherService.cs │ │ └── WeatherService.cs │ ├── Startup.cs │ ├── When.cs │ └── appsettings.json ├── Quickstart.Net45 │ ├── EchoBot.cs │ ├── Handlers │ │ ├── CallbackQueryHandler.cs │ │ ├── Commands │ │ │ ├── PingCommand.cs │ │ │ └── StartCommand.cs │ │ ├── ExceptionHandler.cs │ │ ├── StickerHandler.cs │ │ ├── TextEchoer.cs │ │ ├── UpdateMembersList.cs │ │ ├── WeatherReporter.cs │ │ └── WebhookLogger.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Quickstart.Net45.csproj │ ├── Services │ │ ├── SimpleInjector │ │ │ └── BotServiceProvider.cs │ │ └── WeatherService.cs │ ├── When.cs │ └── packages.config ├── SampleBots │ ├── BotUpdateGetterTask.cs │ ├── Bots │ │ ├── EchoBot │ │ │ ├── EchoerBot.cs │ │ │ └── TextMessageEchoer.cs │ │ └── GreeterBot │ │ │ ├── GreeterBot.cs │ │ │ ├── HiCommand.cs │ │ │ ├── PhotoForwarder.cs │ │ │ └── StartCommand.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleBots.csproj │ ├── Startup.cs │ └── appsettings.json ├── SampleEchoBot │ ├── Data │ │ └── IRepository.cs │ ├── EchoBot.cs │ ├── EchoCommand.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleEchoBot.csproj │ ├── Startup.cs │ ├── TextMessageHandler.cs │ └── appsettings.json └── SampleGames │ ├── BotUpdateGetterTask.cs │ ├── Bots │ └── CrazyCircle │ │ ├── CrazyCircleBot.cs │ │ ├── CrazyCircleGameHandler.cs │ │ └── StartCommand.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── SampleGames.csproj │ ├── Startup.cs │ ├── appsettings.json │ └── wwwroot │ ├── CrazyCircleBot │ └── Games │ │ └── CrazyCircle │ │ ├── assets │ │ ├── scripts │ │ │ ├── TgBF.js │ │ │ ├── libs │ │ │ │ ├── easeljs-0.8.0.min.js │ │ │ │ └── tweenjs-0.6.0.min.js │ │ │ └── script.js │ │ └── styles │ │ │ └── game.css │ │ └── index.html │ └── favicon.ico ├── scripts ├── build │ └── index.js ├── deploy │ ├── deploy_docker_registry.js │ ├── deploy_heroku.js │ ├── deploy_settings.js │ └── index.js ├── logging.js ├── package-lock.json ├── package.json ├── publish-bots.sh └── test │ ├── index.js │ └── unit_tests.js ├── src └── Telegram.Bot.Framework │ ├── ASP.NET Core │ ├── TelegramBotMiddleware.cs │ └── TelegramBotMiddlewareExtensions.cs │ ├── Abstractions │ ├── IBot.cs │ ├── IBotBuilder.cs │ ├── IBotOptions.cs │ ├── IBotServiceProvider.cs │ ├── IUpdateContext.cs │ ├── IUpdateHandler.cs │ ├── IUpdatePollingManager.cs │ └── UpdateDelegate.cs │ ├── BotBase.cs │ ├── BotOptions.cs │ ├── CommandBase.cs │ ├── Extensions │ └── BotExtensions.cs │ ├── Telegram.Bot.Framework.csproj │ ├── Update Pipeline │ ├── BotBuilder.cs │ ├── BotBuilderExtensions.cs │ ├── MapWhenMiddleware.cs │ └── UseWhenMiddleware.cs │ ├── UpdateContext.cs │ └── UpdatePollingManager.cs └── test ├── UnitTests.Net45 ├── Command Handling.cs ├── Properties │ └── AssemblyInfo.cs ├── UnitTests.Net45.csproj ├── app.config └── packages.config └── UnitTests.NetCore ├── Commands ├── Command Handling.cs ├── Command Parsing.cs └── MockCommand.cs ├── UnitTests.NetCore.csproj └── test-update.json /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | 3 | nuget: 4 | disable_publish_on_pr: true 5 | 6 | before_build: 7 | - nuget restore Telegram.Bot.Framework.sln 8 | 9 | configuration: 10 | - Debug 11 | - Release 12 | 13 | build: 14 | project: Telegram.Bot.Framework.sln 15 | publish_nuget: true 16 | 17 | # test: 18 | # assemblies: 19 | # only: 20 | # - '**\*Tests*.dll' 21 | 22 | deploy: 23 | - provider: NuGet 24 | artifact: /.*Telegram.Bot.Framework.*\.nupkg/ 25 | api_key: 26 | secure: StZxOHLR6O7NZ2xY4nVNWoiFNi9Kz8vQFpF1SyWzsZJbvcralJEKpnqOAIHX3qtH 27 | on: 28 | branch: /r\/.+/ 29 | configuration: Release 30 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | */bin 8 | */obj 9 | **/.toolstarget -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | *.pubxml* 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ 246 | 247 | # Settings for specific environments 248 | appsettings.*.json 249 | 250 | # Rider IDE 251 | .idea 252 | 253 | # Deployments files 254 | /dist/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - node 6 | notifications: 7 | email: false 8 | services: 9 | - docker 10 | 11 | install: 12 | - cd scripts && npm install && cd .. 13 | 14 | script: 15 | - node scripts/build 16 | # - node scripts/test 17 | 18 | deploy: 19 | - provider: script 20 | skip_cleanup: true 21 | script: node scripts/deploy Production 22 | on: 23 | branch: master 24 | 25 | 26 | # Disable "Build pushed pull requests" 27 | 28 | 29 | # env: 30 | # DEPLOY_SETTINGS_JSON: A mapping of environment to the deployment types and their options. 31 | # { 32 | # "Production": [ 33 | # { "type": "heroku", 34 | # "options": { 35 | # "app": "foo-staging", "source": "foo:latest", "dyno": "web", "user": "foo@example.org", "token": "TOKEN" 36 | # } 37 | # } 38 | # ] 39 | # } 40 | ##### 41 | # export DEPLOY_SETTINGS_JSON='{"Production":[{"type":"heroku","options": {"app": "sample-tgbot","source":"quickstart:latest","dyno":"web","user":"foo@example.org","token":"TOKEN"}}]}' -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base 2 | WORKDIR /app 3 | 4 | FROM microsoft/dotnet:2.1-sdk AS build 5 | WORKDIR /src 6 | COPY ["sample/Quickstart.AspNetCore/Quickstart.AspNetCore.csproj", "sample/Quickstart.AspNetCore/"] 7 | RUN dotnet restore "sample/Quickstart.AspNetCore/Quickstart.AspNetCore.csproj" 8 | COPY . . 9 | WORKDIR "/src/sample/Quickstart.AspNetCore" 10 | RUN dotnet build "Quickstart.AspNetCore.csproj" -c Release -o /app 11 | 12 | FROM build AS publish 13 | RUN dotnet publish "Quickstart.AspNetCore.csproj" -c Release -o /app 14 | 15 | FROM base AS final 16 | WORKDIR /app 17 | COPY --from=publish /app . 18 | CMD ASPNETCORE_URLS=http://+:${PORT:-80} dotnet Quickstart.AspNetCore.dll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Poulad Ashraf pour 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Bot Framework for .NET Core 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/Telegram.Bot.Framework.svg?style=flat-square&label=Telegram.Bot.Framework&maxAge=3600)](https://www.nuget.org/packages/Telegram.Bot.Framework) 4 | [![Build Status](https://img.shields.io/travis/pouladpld/Telegram.Bot.Framework.svg?style=flat-square&maxAge=3600)](https://travis-ci.org/pouladpld/Telegram.Bot.Framework) 5 | [![License](https://img.shields.io/github/license/pouladpld/Telegram.Bot.Framework.svg?style=flat-square&maxAge=2592000)](https://raw.githubusercontent.com/pouladpld/Telegram.Bot.Framework/master/LICENSE) 6 | 7 | Telegram Bot Framework Logo 8 | 9 | Simple framework for building Telegram bots 🤖. Ideal for running multiple chat bots inside a single ASP.NET Core app. 10 | 11 | See some **sample bots** in action: 12 | 13 | - Echo bot: [`@Sample_Echoer_Bot`](https://t.me/sample_echoer_bot) 14 | - Games bot: [`@CrazyCircleBot`](https://t.me/CrazyCircleBot) 15 | 16 | ## Getting Started 17 | 18 | This project targets .NET Standard 1.6 so make sure you have Visual Studio 2017 or [.NET Core](https://www.microsoft.com/net/download/core#/current) (v1.1 or above) installed. 19 | 20 | Creating a bot with good architecture becomes very simple using this framework. Have a look at the [**Quick Start** wiki](./docs/wiki/quick-start/echo-bot.md) to make your fist _Echo Bot_. 21 | 22 | There is much more you can do with your bot. See what's available at [**wikis**](./docs/wiki/README.md). 23 | 24 | ## Framework Features 25 | 26 | - Allows you to have multiple bots running inside one app 27 | - Able to share code(update handlers) between multiple bots 28 | - Easy to use with webhooks(specially with Docker deployments) 29 | - Optimized for making Telegram Games 30 | - Simplifies many repititive tasks in developing bots 31 | 32 | ## Samples 33 | 34 | Don't wanna read wikis? Read C# code of sample projects in [samples directory](./sample/). 35 | -------------------------------------------------------------------------------- /Telegram.Bot.Framework.Net45.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{807C83E6-6E64-49CF-9D3B-BEF1DB6A5534}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Telegram.Bot.Framework", "src\Telegram.Bot.Framework\Telegram.Bot.Framework.csproj", "{EC991578-D67E-4208-B540-7FBB603B7D8F}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A77F7142-8E1D-4843-B862-12D7D4A8CD67}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Net45", "test\UnitTests.Net45\UnitTests.Net45.csproj", "{4E485E65-FBDA-400B-9393-6E70C9E19C3D}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quickstart.Net45", "sample\Quickstart.Net45\Quickstart.Net45.csproj", "{C3672842-82EE-49F3-90A1-0F07A7857EBE}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quickstart.AspNet45", "sample\Quickstart.AspNet45\Quickstart.AspNet45.csproj", "{19B20342-5F00-4B54-B33A-7FD676590208}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {19B20342-5F00-4B54-B33A-7FD676590208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {19B20342-5F00-4B54-B33A-7FD676590208}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {19B20342-5F00-4B54-B33A-7FD676590208}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {19B20342-5F00-4B54-B33A-7FD676590208}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(NestedProjects) = preSolution 47 | {EC991578-D67E-4208-B540-7FBB603B7D8F} = {807C83E6-6E64-49CF-9D3B-BEF1DB6A5534} 48 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D} = {A77F7142-8E1D-4843-B862-12D7D4A8CD67} 49 | {C3672842-82EE-49F3-90A1-0F07A7857EBE} = {A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1} 50 | {19B20342-5F00-4B54-B33A-7FD676590208} = {A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1} 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {12F28A44-91F3-4309-8F0B-4C6ED9346D38} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /Telegram.Bot.Framework.NetCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{807C83E6-6E64-49CF-9D3B-BEF1DB6A5534}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Telegram.Bot.Framework", "src\Telegram.Bot.Framework\Telegram.Bot.Framework.csproj", "{EC991578-D67E-4208-B540-7FBB603B7D8F}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A77F7142-8E1D-4843-B862-12D7D4A8CD67}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests.NetCore", "test\UnitTests.NetCore\UnitTests.NetCore.csproj", "{D7706BF5-300E-4705-86FA-ECD073EF3F7E}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Quickstart.AspNetCore", "sample\Quickstart.AspNetCore\Quickstart.AspNetCore.csproj", "{1CF205C0-A8C2-4F47-A5A6-ED53729B567D}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {EC991578-D67E-4208-B540-7FBB603B7D8F} = {807C83E6-6E64-49CF-9D3B-BEF1DB6A5534} 42 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E} = {A77F7142-8E1D-4843-B862-12D7D4A8CD67} 43 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D} = {A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1} 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {12F28A44-91F3-4309-8F0B-4C6ED9346D38} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /Telegram.Bot.Framework.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{807C83E6-6E64-49CF-9D3B-BEF1DB6A5534}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Telegram.Bot.Framework", "src\Telegram.Bot.Framework\Telegram.Bot.Framework.csproj", "{EC991578-D67E-4208-B540-7FBB603B7D8F}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A77F7142-8E1D-4843-B862-12D7D4A8CD67}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Net45", "test\UnitTests.Net45\UnitTests.Net45.csproj", "{4E485E65-FBDA-400B-9393-6E70C9E19C3D}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests.NetCore", "test\UnitTests.NetCore\UnitTests.NetCore.csproj", "{D7706BF5-300E-4705-86FA-ECD073EF3F7E}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quickstart.Net45", "sample\Quickstart.Net45\Quickstart.Net45.csproj", "{C3672842-82EE-49F3-90A1-0F07A7857EBE}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quickstart.AspNet45", "sample\Quickstart.AspNet45\Quickstart.AspNet45.csproj", "{19B20342-5F00-4B54-B33A-7FD676590208}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Quickstart.AspNetCore", "sample\Quickstart.AspNetCore\Quickstart.AspNetCore.csproj", "{1CF205C0-A8C2-4F47-A5A6-ED53729B567D}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {EC991578-D67E-4208-B540-7FBB603B7D8F}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {C3672842-82EE-49F3-90A1-0F07A7857EBE}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {19B20342-5F00-4B54-B33A-7FD676590208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {19B20342-5F00-4B54-B33A-7FD676590208}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {19B20342-5F00-4B54-B33A-7FD676590208}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {19B20342-5F00-4B54-B33A-7FD676590208}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D}.Release|Any CPU.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(NestedProjects) = preSolution 59 | {EC991578-D67E-4208-B540-7FBB603B7D8F} = {807C83E6-6E64-49CF-9D3B-BEF1DB6A5534} 60 | {4E485E65-FBDA-400B-9393-6E70C9E19C3D} = {A77F7142-8E1D-4843-B862-12D7D4A8CD67} 61 | {D7706BF5-300E-4705-86FA-ECD073EF3F7E} = {A77F7142-8E1D-4843-B862-12D7D4A8CD67} 62 | {C3672842-82EE-49F3-90A1-0F07A7857EBE} = {A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1} 63 | {19B20342-5F00-4B54-B33A-7FD676590208} = {A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1} 64 | {1CF205C0-A8C2-4F47-A5A6-ED53729B567D} = {A5BE1637-1C1D-49F4-B98D-C1CD734F1CF1} 65 | EndGlobalSection 66 | GlobalSection(ExtensibilityGlobals) = postSolution 67 | SolutionGuid = {12F28A44-91F3-4309-8F0B-4C6ED9346D38} 68 | EndGlobalSection 69 | EndGlobal 70 | -------------------------------------------------------------------------------- /docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramBots/Telegram.Bot.Framework/6ac27f80d1ae29f86ccf790d7d2a5b21759182ec/docs/icon.png -------------------------------------------------------------------------------- /docs/wiki/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to `Telegram.Bot.Framework` wikis 2 | 3 | ## Quick Start 4 | 5 | If you are looking for a few clues to begin with, look at the guides here. 6 | 7 | > Make sure you have registered a bot and have its API token. Talk to **[BotFather](http://t.me/botfather)** on Telegram to get one. 8 | 9 | ### 🙄 I'm new to this. Show me how to run a bot 🤖 10 | 11 | - [How about a quick **Echo Bot**?](./quick-start/echo-bot.md) 12 | 13 | ### Telegram games 🎮 are fun. I'm ready to have fun 😎 14 | 15 | - [Crazy Circle 🔴 Game](./quick-start/crazy-circle-game.md) 16 | 17 | ## Games 18 | 19 | Developing Telegram games is not as easy as an Echo Bot. Check the wikis in this section to 20 | get some help. 21 | 22 | - [Games in Telegram](./games/games-in-telegram.md) 23 | 24 | ## Deployment 25 | 26 | This section helps you to deploy your bot on a server and set its webhook. 27 | 28 | - [Deploy to Docker with Let's Encrypt Certificate](./deployment/docker-letsencrypt.md) 29 | - [Deploy to Ubuntu with Nginx and Self-Signed Certificate](./deployment/ubuntu-nginx-selfsigned.md) 30 | -------------------------------------------------------------------------------- /docs/wiki/deployment/docker-letsencrypt.md: -------------------------------------------------------------------------------- 1 | # Deploy to Docker with Let's Encrypt Certificate 2 | 3 | ## ToDo 4 | -------------------------------------------------------------------------------- /docs/wiki/deployment/ubuntu-nginx-selfsigned.md: -------------------------------------------------------------------------------- 1 | # Deploy to Ubuntu with Nginx and Self-Signed Certificate 2 | 3 | This tutorial shows you how to deploy your bot to an **Ubuntu 16.04** server, use **Nginx** as its reverse proxy, 4 | and setup **webhook**. 5 | 6 | A self-signed certificate is generated and used in this process. ASP.NET Core application 7 | is deployed to Ubuntu server in a Framework Dependant Deployment manner. 8 | 9 | [SampleEchoBot project](../../../sample/SampleEchoBot/) is used in this tutorial for simplicity. 10 | 11 | First of all, make sure you have installed [.NET Core SDK](https://www.microsoft.com/net/core#linuxubuntu) and 12 | [Nginx](https://help.ubuntu.com/community/Nginx) on the server. 13 | 14 | > `www.example.com` represents our arbitrary domain name here. 15 | 16 | ## TLS Certificate 17 | 18 | Telegram uses your bot's certificate to authorize and encrypt webhook messages to bot. Read more about it on 19 | Telegram documentations [here](https://core.telegram.org/bots/api#setwebhook) and [here](https://core.telegram.org/bots/self-signed) 20 | 21 | If you don't have a trusted TLS certificate, use the command below to generate a self-signed certificate. 22 | 23 | ```bash 24 | openssl req -newkey rsa:2048 -sha256 -nodes -keyout sample-echobot.key -x509 -days 365 -out sample-echobot.pem -subj "/C=CA/ST=Ontario/L=Toronto/O=Telegram Bot Framework Organization/CN=example.com" 25 | ``` 26 | 27 | > Note that the CN, `example.com` here, should exactly match the domain name in webhook URL you set 28 | in bot's settings. 29 | 30 | > If you don't have a DNS name for your server, you might be able to obtain one for free from [Freenom](https://www.freenom.com). 31 | 32 | Copy bot certificate files to Nginx configuration directory. 33 | 34 | ```bash 35 | sudo mkdir /etc/nginx/certificates 36 | sudo cp sample-echobot.{key,pem} /etc/nginx/certificates 37 | sudo chown www-data:www-data /etc/nginx/certificates/sample-echobot.{key,pem} 38 | sudo chmod 400 /etc/nginx/certificates/sample-echobot.key 39 | ``` 40 | 41 | ## Publish 42 | 43 | Get the source code and with .NET Core SDK installed, build the app. 44 | 45 | ```bash 46 | git clone "https://github.com/pouladpld/Telegram.Bot.Framework.git" 47 | 48 | cd Telegram.Bot.Framework 49 | 50 | dotnet restore 51 | dotnet build 52 | 53 | cd src/Telegram.Bot.Sample/ 54 | dotnet publish -c Release -o bin/publish 55 | ``` 56 | 57 | Create app's directory, give it necessary permissions and copy the app to it. 58 | 59 | ```bash 60 | sudo mkdir -p /var/www/aspnet/sample-echobot/ 61 | sudo chown -R :www-data /var/www/aspnet/sample-echobot 62 | sudo chmod -R g+s /var/www/aspnet/sample-echobot 63 | 64 | sudo cp -r bin/publish/* /var/www/aspnet/sample-echobot/ 65 | ``` 66 | 67 | ## App Configurations 68 | 69 | Create file `/var/www/aspnet/sample-echobot/appsettings.Production.json` 70 | and store the configurations there. 71 | 72 | ```json 73 | { 74 | "EchoBot": { 75 | "ApiToken": "{your-api-token}", 76 | "BotUserName": "{your-bot-username}", 77 | "PathToCertificate": "/etc/nginx/certificates/sample-echobot.pem", 78 | "WebhookUrl": "https://example.com/bots/{bot}/webhook/{token}" 79 | } 80 | } 81 | ``` 82 | 83 | > Replace the values for _ApiToken_ and _BotUserName_, and domain name in _WebhookUrl_. 84 | 85 | ## App Service 86 | 87 | Add a system service for bot. Create file `/etc/systemd/system/sample-echobot.service` and 88 | write the following configuration to it. 89 | 90 | ```text 91 | [Unit] 92 | Description=Sample Echo Bot 93 | 94 | [Service] 95 | ExecStart=/bin/bash -c "cd /var/www/aspnet/sample-echobot && dotnet ./SampleEchoBot.dll" 96 | Restart=always 97 | RestartSec=10 98 | SyslogIdentifier=sample-echobot 99 | User=www-data 100 | Environment=ASPNETCORE_ENVIRONMENT=Production 101 | 102 | [Install] 103 | WantedBy=multi-user.target 104 | ``` 105 | 106 | Reload the system settings so the new service will be available. 107 | 108 | ```bash 109 | sudo systemctl daemon-reload 110 | ``` 111 | 112 | ## Nginx 113 | 114 | First of all, make a backup of default site's configuration. 115 | 116 | ```bash 117 | sudo cp /etc/nginx/sites-available/default{,~} 118 | ``` 119 | 120 | Open file `/etc/nginx/sites-available/default` and edit Nginx configurations: 121 | 122 | ```nginx 123 | server { 124 | # Change domain name here 125 | server_name example.com localhost; 126 | 127 | listen 80; 128 | listen [::]:80; 129 | 130 | root /var/www/html; 131 | index index.html index.htm index.nginx-debian.html; 132 | 133 | location / { 134 | try_files $uri $uri/ =404; 135 | } 136 | 137 | location ~* ^/bots/.+/webhook/.+$ { 138 | return 301 https://$host$request_uri; 139 | } 140 | } 141 | 142 | server { 143 | # Change domain name here 144 | server_name example.com localhost; 145 | 146 | listen 443 ssl; 147 | listen 8080 ssl; 148 | listen 8443 ssl; 149 | 150 | ssl_certificate /etc/nginx/certificates/sample-echobot.pem; 151 | ssl_certificate_key /etc/nginx/certificates/sample-echobot.key; 152 | 153 | ssl_protocols TLSv1.2 TLSv1.1 TLSv1; 154 | ssl_prefer_server_ciphers on; 155 | 156 | # Change {your-bot-username} here 157 | location ~* ^/bots/{your-bot-username}/webhook/.+$ { 158 | proxy_pass http://0.0.0.0:5000; 159 | proxy_http_version 1.1; 160 | proxy_set_header Upgrade $http_upgrade; 161 | proxy_set_header Connection keep-alive; 162 | proxy_set_header Host $host; 163 | proxy_cache_bypass $http_upgrade; 164 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 165 | } 166 | } 167 | ``` 168 | 169 | > Replace values for domain name in _server_name_ and _{your-bot-username}_ in location url. 170 | 171 | Test new Nginx configurations and restart the web server. 172 | 173 | ```bash 174 | sudo nginx -t && sudo systemctl restart nginx 175 | ``` 176 | 177 | ## Start 178 | 179 | That's all. Start the app. App sets webhook at startup and you should be able to chat with it. 180 | Try `/echo` command in chat. 181 | 182 | ```bash 183 | sudo systemctl start sample-echobot 184 | 185 | # See app logs 186 | sudo journalctl --identifier=sample-echobot --follow 187 | ``` 188 | -------------------------------------------------------------------------------- /docs/wiki/deployment/ubuntu-nginx.md: -------------------------------------------------------------------------------- 1 | # Deploy a Telegram bot and run it with Webhooks 2 | 3 | In this tutorial, I discuss how to configure and deploy a Telegram bot to an **Ubuntu 17.04 64bit**. Project [Telegram.Bot.Sample](https://github.com/pouladpld/Telegram.Bot.Framework/tree/master/src/Telegram.Bot.Sample) is deployed and it contains two Telegram bots inside. 4 | 5 | ## TLS Certificate 6 | 7 | If you don't have a TLS certificate, use the command below to generate a self-signed certificate. 8 | 9 | ```bash 10 | openssl req -newkey rsa:2048 -sha256 -nodes -keyout samplebot.key -x509 -days 365 -out samplebot.pem -subj "/C=CA/ST=Ontario/L=Toronto/O=.NET Telegram Bot Framework Organization/CN=example.org" 11 | ``` 12 | 13 | > Replace the values such as `example.org` in the above command. 14 | 15 | Note that the CN, `example.org` here, should exactly match the webhook URL you send to Telegram. For keeping this tutorial simple, I am using 1 certificate for both of the bots. 16 | 17 | ## .NET Core CLI 18 | 19 | Install [.NET Core](https://www.microsoft.com/net/core#linuxubuntu) 20 | 21 | > I installed version `dotnet-dev-2.0.0-preview1-005977` for this tutorial because I had problems with installing version `dotnet-dev-1.0.4`. 22 | 23 | ## Application Service 24 | 25 | ```bash 26 | sudo vi /etc/systemd/system/samplebot.service 27 | ``` 28 | 29 | content: 30 | 31 | ```text 32 | [Unit] 33 | Description=SampleBot - Telegram bot app 34 | 35 | [Service] 36 | ExecStart=/bin/bash -c "cd /var/www/aspnet/samplebot && ./Telegram.Bot.Sample" 37 | Restart=always 38 | RestartSec=10 39 | SyslogIdentifier=samplebot 40 | User=www-data 41 | Environment=ASPNETCORE_ENVIRONMENT=Production 42 | 43 | [Install] 44 | WantedBy=multi-user.target 45 | ``` 46 | 47 | ```bash 48 | sudo systemctl daemon-reload 49 | ``` 50 | 51 | ## Nginx 52 | 53 | ```bash 54 | sudo apt-get install nginx -y 55 | ``` 56 | 57 | Create directories to publish the app to. 58 | 59 | ```bash 60 | sudo mkdir -p /var/www/aspnet/samplebot 61 | sudo chown -R :www-data /var/www/aspnet/samplebot 62 | sudo chmod -R g+s /var/www/aspnet/samplebot 63 | ``` 64 | 65 | Copy the bot certificates to Nginx configuration directory. 66 | 67 | ```bash 68 | sudo mkdir /etc/nginx/certificates 69 | sudo cp samplebot.{key,pem} /etc/nginx/certificates 70 | sudo chown www-data:www-data /etc/nginx/certificates/samplebot.{key,pem} 71 | sudo chmod 400 /etc/nginx/certificates/samplebot.key 72 | ``` 73 | 74 | ### Site Configuration 75 | 76 | ```bash 77 | # Make a backup of default site's configuration 78 | sudo cp /etc/nginx/sites-available/default{,~} 79 | 80 | sudo vi /etc/nginx/sites-available/default 81 | ``` 82 | 83 | Content: 84 | 85 | ```nginx 86 | server { 87 | server_name samplebot.com localhost; 88 | listen 80; 89 | listen [::]:80; 90 | 91 | root /var/www/html; 92 | 93 | index index.html index.htm index.nginx-debian.html; 94 | 95 | location / { 96 | try_files $uri $uri/ =404; 97 | } 98 | 99 | location /sample_greeter_bot { 100 | proxy_pass http://localhost:5000; 101 | } 102 | 103 | location /sample_echoer_bot { 104 | proxy_pass http://localhost:5000; 105 | } 106 | } 107 | 108 | server { 109 | server_name samplebot.com localhost; 110 | 111 | listen 443 ssl; 112 | listen 8080 ssl; 113 | listen 8443 ssl; 114 | 115 | ssl_certificate /etc/nginx/certificates/samplebot.pem; 116 | ssl_certificate_key /etc/nginx/certificates/samplebot.key; 117 | 118 | ssl_protocols TLSv1.2 TLSv1.1 TLSv1; 119 | ssl_prefer_server_ciphers on; 120 | 121 | location /sample_greeter_bot { 122 | proxy_pass http://localhost:5000; 123 | proxy_http_version 1.1; 124 | proxy_set_header Upgrade $http_upgrade; 125 | proxy_set_header Connection keep-alive; 126 | proxy_set_header Host $host; 127 | proxy_cache_bypass $http_upgrade; 128 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 129 | } 130 | 131 | location /sample_echoer_bot { 132 | proxy_pass http://localhost:5000; 133 | proxy_http_version 1.1; 134 | proxy_set_header Upgrade $http_upgrade; 135 | proxy_set_header Connection keep-alive; 136 | proxy_set_header Host $host; 137 | proxy_cache_bypass $http_upgrade; 138 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 139 | } 140 | } 141 | ``` 142 | 143 | ```bash 144 | sudo nginx -t 145 | sudo systemctl restart nginx 146 | ``` 147 | 148 | ## Publish the App 149 | 150 | ```bash 151 | mkdir source-code && cd source-code 152 | git clone https://github.com/pouladpld/Telegram.Bot.Framework.git . 153 | 154 | # Build the project 155 | dotnet restore -r ubuntu.16.10-x64 156 | 157 | cd src/Telegram.Bot.Sample 158 | # rm -rf bin/publish/* 159 | dotnet publish -c Release -o bin/publish -r ubuntu.16.10-x64 160 | 161 | # sudo rm -rf /var/www/aspnet/samplebot/* 162 | sudo cp -r bin/publish/* /var/www/aspnet/samplebot/ 163 | sudo chmod ug+x /var/www/aspnet/samplebot/Telegram.Bot.Sample 164 | ``` 165 | 166 | In addition to _appsettings.json_ file, you can store the production secrets in a file like the one below: 167 | 168 | ```json 169 | // appsettings.Production.json 170 | { 171 | "EchoerBot": { 172 | "ApiToken": "", 173 | "PathToCertificate": "/etc/nginx/certificates/samplebot.pem", 174 | "WebhookUrl": "https://example.org/{botname}/{token}/webhook" 175 | }, 176 | "GreeterBot": { 177 | "ApiToken": "", 178 | "PathToCertificate": "/etc/nginx/certificates/samplebot.pem", 179 | "WebhookUrl": "https://example.org/{botname}/{token}/webhook" 180 | } 181 | } 182 | ``` 183 | 184 | ### Run the app 185 | 186 | ```bash 187 | sudo systemctl start samplebot.service 188 | ``` 189 | -------------------------------------------------------------------------------- /docs/wiki/games/games-in-telegram.md: -------------------------------------------------------------------------------- 1 | # Games in Telegram 2 | 3 | Games are simply HTML5(HTML/CSS/JS) pages loaded into a Telegram client's browser. Users also 4 | have option to open the game page in a browser. Check Telegram's post about [Gaming Platform](https://telegram.org/blog/games) 5 | to learn more. 6 | 7 | For example, [LumberJack](https://telegram.me/gamebot?game=Lumberjack) is a Telegram game. Go ahead and 8 | play it! 9 | 10 | ## Creating a Game 11 | 12 | This all happens in your chat with [BotFather](http://t.me/botfather). Enable [Inline Mode](https://core.telegram.org/bots/#inline-mode) for your bot and use `/newgame` command to create your game. You can read more about it [here](https://core.telegram.org/bots/games#creating-a-game). 13 | 14 | Each game has a _short name_ that is game's unique id whithin a bot. 15 | 16 | ## Redirecting user to Game 17 | 18 | In the chat, when user clicks on _Play LumberJack_, for instance, your bot receives a CallbackQuery update 19 | containing `game_short_name`. In response, bot makes a `answerCallbackQuery` request passing the url to the 20 | game's page. Telegram client opens the browser and the fun starts! 21 | 22 | For example, start [LumberJack](https://telegram.me/gamebot?game=Lumberjack) game on your phone and click on 23 | _Open in..._ while in game to open it in a browser. You will see the link to game is something like: 24 | 25 | `https://tbot.xyz/lumber/#{some-encoded-value}&tgShareScoreUrl=tgb://share_game_score?hash={some-hash-value}` 26 | 27 | ## High Scores 28 | 29 | Games can set high scores for users in the chat that they got opened from. This means when game is finished, 30 | a request is usually made to backend (bot's ASP.NET Core app) and bot makes a `setGameScore` call to Telegram API. 31 | 32 | Similarly, for getting high scores, a request from HTML page(game) could be sent to backend. Then, bot makes a `getGameHighScores` call to Bot API and passes back the high scores to the game. 33 | 34 | ## Deoployment 35 | 36 | Your Game's HTML page can be anywhere on the internet. It's worth mentioning as a web developer, you should consider many things in your deployment such as: 37 | 38 | - Preferring HTTPS over HTTP 39 | - Using a trusted TLS certificate 40 | - Allowing Cross-Origin requests, if necessary 41 | - Guarding against attacks 42 | - Preventing cheaters 43 | - And many more... 44 | 45 | > Sample games in this project are kept simple and do not follow best practices. 46 | -------------------------------------------------------------------------------- /docs/wiki/quick-start/crazy-circle-game.md: -------------------------------------------------------------------------------- 1 | # Crazy Circle Game 2 | 3 | Crazy Circle 🔴 is a sample game to demo this framework's features. 4 | 5 | You can 🎮 [play it on Telegram](https://telegram.me/CrazyCircleBot?game=crazycircle) 🎮. 6 | 7 | Telegram games are just a HTML5 page loaded in browser inside Telegram client 8 | app or in user's browser. Luckily, you can just go ahead and **use Crazy Circle sample** 9 | to begin with. 10 | 11 | This guide assumes that: 12 | 13 | - You already completed [Echo Bot](./echo-bot.md) quick start 14 | - You are able to deploy the project. See [deployment guides](../README.md#deployment) 15 | - Your bot's user name is `CrazyCircleBot` (Replace it with your own bot's user name) 16 | 17 | ## New Telegarm Game 18 | 19 | Have a chat with **[BotFather](http://t.me/botfather)** to create a new game for your bot and 20 | **set its short_name to `crazycircle`**. 21 | 22 | ## Project Setup 23 | 24 | Create a new ASP.NET Core app and add the following NuGet packages to it: 25 | 26 | - `Telegram.Bot.Framework` 27 | - `Microsoft.AspNetCore.StaticFiles` 28 | - `Microsoft.AspNetCore.DataProtection` 29 | 30 | Copy files from [`sample/SampleGames/wwwroot/bots`](../../../sample/SampleGames/wwwroot/bots/) 31 | into this project's `wwwroot/bots/` directory. 32 | 33 | Rename the bot's direcotry name according to this format: `/wwwroot/bots/{your-bot-user-name}/`. 34 | 35 | ## Code 36 | 37 | ### Bot 38 | 39 | Create your bot class: 40 | 41 | ```c# 42 | public class CrazyCircleBot : BotBase { 43 | public CrazyCircleBot(IOptions> botOptions) 44 | : base(botOptions) { } 45 | public override Task HandleUnknownMessage(Update update) => Task.CompletedTask; 46 | public override Task HandleFaultedUpdate(Update update, Exception e) => Task.CompletedTask; 47 | } 48 | ``` 49 | 50 | ### Update Handlers 51 | 52 | Create a `/start` command that replies with the CrazyCircle game: 53 | 54 | ```c# 55 | public class StartCommandArgs : ICommandArgs { 56 | public string RawInput { get; set; } 57 | public string ArgsInput { get; set; } 58 | } 59 | 60 | public class StartCommand : CommandBase { 61 | public StartCommand() : base(name: "start") {} 62 | 63 | public override async Task HandleCommand(Update update, StartCommandArgs args) { 64 | await Bot.Client.SendGameAsync(update.Message.Chat.Id, gameShortName: "crazycircle"); 65 | return UpdateHandlingResult.Handled; 66 | } 67 | } 68 | ``` 69 | 70 | And add the game handler: 71 | 72 | ```c# 73 | public class CrazyCircleGameHandler : GameHandlerBase { 74 | public CrazyCircleGameHandler(IDataProtectionProvider protectionProvider) 75 | : base(protectionProvider, shortName: "crazycircle") {} 76 | } 77 | ``` 78 | 79 | ### Startup 80 | 81 | In `Startup` class, add the following code: 82 | 83 | ```c# 84 | public void ConfigureServices(IServiceCollection services) { 85 | services.AddDataProtection(); 86 | services.AddTelegramBot(_configuration.GetSection("CrazyCircleBot")) 87 | .AddUpdateHandler() 88 | .AddUpdateHandler() 89 | .Configure(); 90 | } 91 | 92 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { 93 | app.UseStaticFiles(); 94 | app.UseTelegramGame(); 95 | app.UseTelegramBotWebhook(); 96 | } 97 | ``` 98 | 99 | ## Configurations 100 | 101 | Add the following configurations to your `appsettings.json` or use any other similar method. 102 | 103 | ```json 104 | { 105 | "CrazyCircleBot": { 106 | "ApiToken": "", 107 | "BotUserName": "CrazyCircleBot", 108 | "PathToCertificate": "", 109 | "BaseUrl": "https://example.com/bots/", 110 | "GameOptions": [ { "ShortName": "crazycircle" } ] 111 | } 112 | } 113 | ``` 114 | 115 | ## Deploy 116 | 117 | Deploy your application and enjoy playing! 😃 -------------------------------------------------------------------------------- /docs/wiki/quick-start/echo-bot.md: -------------------------------------------------------------------------------- 1 | # Echo Bot 🤖 2 | 3 | This guide shows you how to quickly run your first bot using Visual Studio 2017 and ASP.NET Core. 4 | 5 | **Complete code is in [SampleEchoBot project](../../../sample/SampleEchoBot)**. 6 | 7 | This guide assumes that 8 | 9 | - You already have registered a bot and have its API Token. 10 | 11 | ## Project Setup 12 | 13 | Create a new ASP.NET Core (empty) version 1.1 or above app and add `Telegram.Bot.Framework` NuGet package to it. 14 | 15 | ## Code 16 | 17 | ### Bot 18 | 19 | Create your bot class: 20 | 21 | ```c# 22 | // EchoBot.cs 23 | public class EchoBot : BotBase { 24 | public EchoBot(IOptions> botOptions) 25 | : base(botOptions) { } 26 | 27 | public override Task HandleUnknownUpdate(Update update) => Task.CompletedTask; 28 | 29 | public override Task HandleFaultedUpdate(Update update, Exception e) => Task.CompletedTask; 30 | } 31 | ``` 32 | 33 | ### Update Handlers 34 | 35 | Create an `/echo` command that echoes user input back: 36 | 37 | ```c# 38 | // EchoCommand.cs 39 | public class EchoCommandArgs : ICommandArgs { 40 | public string RawInput { get; set; } 41 | public string ArgsInput { get; set; } 42 | } 43 | public class EchoCommand : CommandBase { 44 | public EchoCommand() : base(name: "echo") {} 45 | 46 | public override async Task HandleCommand(Update update, EchoCommandArgs args) { 47 | string replyText = string.IsNullOrWhiteSpace(args.ArgsInput) ? "Echo What?" : args.ArgsInput; 48 | 49 | await Bot.Client.SendTextMessageAsync( 50 | update.Message.Chat.Id, 51 | replyText, 52 | replyToMessageId: update.Message.MessageId); 53 | 54 | return UpdateHandlingResult.Handled; 55 | } 56 | } 57 | ``` 58 | 59 | > Pros only: Add a `/start` command as well that replies with text "Hello World!". 60 | 61 | ### Startup 62 | 63 | In `Startup` class, add the following code. This Adds Telegram bot and its update handlers to the DI 64 | container and also uses long-polling method to get new updates every 3 seconds. If you are running this 65 | app in a terminal, pressing Enter key will stop bot manager from getting updates. 66 | 67 | ```c# 68 | // Startup.cs 69 | private readonly IConfigurationRoot _configuration; 70 | 71 | public Startup(IHostingEnvironment env) { 72 | _configuration = new ConfigurationBuilder() 73 | .SetBasePath(env.ContentRootPath) 74 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 75 | .Build(); 76 | } 77 | 78 | public void ConfigureServices(IServiceCollection services) { 79 | services.AddTelegramBot(_configuration.GetSection("EchoBot")) 80 | .AddUpdateHandler() 81 | .Configure(); 82 | } 83 | 84 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) { 85 | var source = new CancellationTokenSource(); 86 | Task.Factory.StartNew(() => { 87 | Console.WriteLine("## Press Enter to stop bot manager..."); 88 | Console.ReadLine(); 89 | source.Cancel(); 90 | }); 91 | Task.Factory.StartNew(async () => { 92 | var botManager = app.ApplicationServices.GetRequiredService>(); 93 | while (!source.IsCancellationRequested) { 94 | await Task.Delay(3_000); 95 | await botManager.GetAndHandleNewUpdatesAsync(); 96 | } 97 | Console.WriteLine("## Bot manager stopped."); 98 | Environment.Exit(0); 99 | }).ContinueWith(t => { 100 | if (t.IsFaulted) throw t.Exception; 101 | }); 102 | } 103 | ``` 104 | 105 | > Pros only: Add the `/start` command you created to the list of handlers as well. 106 | 107 | ## Configurations 108 | 109 | Add the following configurations to `appsettings.json` file in the project's root directory. 110 | 111 | ```json 112 | { 113 | "EchoBot": { 114 | "ApiToken": "your bot's api token", 115 | "BotUserName": "your bot's username without @" 116 | } 117 | } 118 | ``` 119 | 120 | ## Run 121 | 122 | That's it! Run your the app and write to your bot: `/echo Hello` 123 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/App_Start/WebApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Http; 2 | 3 | namespace Quickstart.AspNet45 4 | { 5 | public static class WebApiConfig 6 | { 7 | public static void Register(HttpConfiguration config) 8 | { 9 | // Web API configuration and services 10 | 11 | // Web API routes 12 | config.MapHttpAttributeRoutes(); 13 | 14 | config.Routes.MapHttpRoute( 15 | name: "DefaultApi", 16 | routeTemplate: "api/{controller}/{id}", 17 | defaults: new { id = RouteParameter.Optional } 18 | ); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/Controllers/WebhookController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System.Web.Http; 3 | 4 | namespace Quickstart.AspNet45.Controllers 5 | { 6 | public class WebhookController : ApiController 7 | { 8 | [HttpPost] 9 | public async Task Webhook() 10 | { 11 | await Task.Delay(1); 12 | //this.ActionContext. 13 | return this.BadRequest(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="Quickstart.AspNet45.WebApiApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Http; 3 | 4 | namespace Quickstart.AspNet45 5 | { 6 | public class WebApiApplication : HttpApplication 7 | { 8 | protected void Application_Start() 9 | { 10 | GlobalConfiguration.Configure(WebApiConfig.Register); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Quickstart.AspNet45")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Quickstart.AspNet45")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("19b20342-5f00-4b54-b33a-7fd676590208")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNet45/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/EchoBot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Telegram.Bot.Framework; 3 | 4 | namespace Quickstart.AspNetCore 5 | { 6 | public class EchoBot : BotBase 7 | { 8 | public EchoBot(IOptions> options) 9 | : base(options.Value) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Extensions/AppStartupExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Extensions.Options; 4 | using Quickstart.AspNetCore; 5 | using Quickstart.AspNetCore.Options; 6 | using Quickstart.AspNetCore.Services; 7 | using System; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Telegram.Bot.Framework; 11 | using Telegram.Bot.Framework.Abstractions; 12 | 13 | namespace Microsoft.AspNetCore.Builder 14 | { 15 | static class AppStartupExtensions 16 | { 17 | public static IApplicationBuilder UseTelegramBotLongPolling( 18 | this IApplicationBuilder app, 19 | IBotBuilder botBuilder, 20 | TimeSpan startAfter = default, 21 | CancellationToken cancellationToken = default 22 | ) 23 | where TBot : BotBase 24 | { 25 | if (startAfter == default) 26 | { 27 | startAfter = TimeSpan.FromSeconds(2); 28 | } 29 | 30 | var updateManager = new UpdatePollingManager(botBuilder, new BotServiceProvider(app)); 31 | 32 | Task.Run(async () => 33 | { 34 | await Task.Delay(startAfter, cancellationToken); 35 | await updateManager.RunAsync(cancellationToken: cancellationToken); 36 | }, cancellationToken) 37 | .ContinueWith(t => 38 | {// ToDo use logger 39 | Console.ForegroundColor = ConsoleColor.Red; 40 | Console.WriteLine(t.Exception); 41 | Console.ResetColor(); 42 | throw t.Exception; 43 | }, TaskContinuationOptions.OnlyOnFaulted); 44 | 45 | return app; 46 | } 47 | 48 | public static IApplicationBuilder EnsureWebhookSet( 49 | this IApplicationBuilder app 50 | ) 51 | where TBot : IBot 52 | { 53 | using (var scope = app.ApplicationServices.CreateScope()) 54 | { 55 | var logger = scope.ServiceProvider.GetRequiredService>(); 56 | var bot = scope.ServiceProvider.GetRequiredService(); 57 | var options = scope.ServiceProvider.GetRequiredService>>(); 58 | var url = new Uri(new Uri(options.Value.WebhookDomain), options.Value.WebhookPath); 59 | 60 | logger.LogInformation("Setting webhook for bot \"{0}\" to URL \"{1}\"", typeof(TBot).Name, url); 61 | 62 | bot.Client.SetWebhookAsync(url.AbsoluteUri) 63 | .GetAwaiter().GetResult(); 64 | } 65 | 66 | return app; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/CallbackQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Types; 4 | 5 | namespace Quickstart.AspNetCore.Handlers 6 | { 7 | public class CallbackQueryHandler : IUpdateHandler 8 | { 9 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | CallbackQuery cq = context.Update.CallbackQuery; 12 | 13 | await context.Bot.Client.AnswerCallbackQueryAsync(cq.Id, "PONG", showAlert: true); 14 | 15 | await next(context); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/Commands/PingCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Types; 4 | using Telegram.Bot.Types.Enums; 5 | using Telegram.Bot.Types.ReplyMarkups; 6 | 7 | namespace Quickstart.AspNetCore.Handlers 8 | { 9 | class PingCommand : CommandBase 10 | { 11 | public override async Task HandleAsync(IUpdateContext context, UpdateDelegate next, string[] args) 12 | { 13 | Message msg = context.Update.Message; 14 | 15 | await context.Bot.Client.SendTextMessageAsync( 16 | msg.Chat, 17 | "*PONG*", 18 | ParseMode.Markdown, 19 | replyToMessageId: msg.MessageId, 20 | replyMarkup: new InlineKeyboardMarkup( 21 | InlineKeyboardButton.WithCallbackData("Ping", "PONG") 22 | ) 23 | ); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/Commands/StartCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | 4 | namespace Quickstart.AspNetCore.Handlers 5 | { 6 | class StartCommand : CommandBase 7 | { 8 | public override async Task HandleAsync(IUpdateContext context, UpdateDelegate next, string[] args) 9 | { 10 | await context.Bot.Client.SendTextMessageAsync(context.Update.Message.Chat, "Hello, World!"); 11 | await next(context); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/ExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace Quickstart.AspNetCore.Handlers 6 | { 7 | public class ExceptionHandler : IUpdateHandler 8 | { 9 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | var u = context.Update; 12 | 13 | try 14 | { 15 | await next(context); 16 | } 17 | catch (Exception e) 18 | { 19 | Console.BackgroundColor = ConsoleColor.Black; 20 | Console.ForegroundColor = ConsoleColor.Red; 21 | Console.WriteLine("An error occured in handling update {0}.{1}{2}", u.Id, Environment.NewLine, e); 22 | Console.ResetColor(); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/StickerHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | 6 | namespace Quickstart.AspNetCore.Handlers 7 | { 8 | class StickerHandler : IUpdateHandler 9 | { 10 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 11 | { 12 | Message msg = context.Update.Message; 13 | Sticker incomingSticker = msg.Sticker; 14 | 15 | StickerSet evilMindsSet = await context.Bot.Client.GetStickerSetAsync("EvilMinds"); 16 | 17 | Sticker similarEvilMindSticker = evilMindsSet.Stickers.FirstOrDefault( 18 | sticker => incomingSticker.Emoji.Contains(sticker.Emoji) 19 | ); 20 | 21 | Sticker replySticker = similarEvilMindSticker ?? evilMindsSet.Stickers.First(); 22 | 23 | await context.Bot.Client.SendStickerAsync( 24 | msg.Chat, 25 | replySticker.FileId, 26 | replyToMessageId: msg.MessageId 27 | ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/TextEchoer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Types; 4 | 5 | namespace Quickstart.AspNetCore.Handlers 6 | { 7 | public class TextEchoer : IUpdateHandler 8 | { 9 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | Message msg = context.Update.Message; 12 | 13 | await context.Bot.Client.SendTextMessageAsync( 14 | msg.Chat, "You said:\n" + msg.Text 15 | ); 16 | 17 | await next(context); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/UpdateMembersList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace Quickstart.AspNetCore.Handlers 6 | { 7 | class UpdateMembersList : IUpdateHandler 8 | { 9 | public Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | Console.BackgroundColor = ConsoleColor.Black; 12 | Console.ForegroundColor = ConsoleColor.Yellow; 13 | Console.WriteLine("Updating chat members list..."); 14 | Console.ResetColor(); 15 | 16 | return next(context); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/WeatherReporter.cs: -------------------------------------------------------------------------------- 1 | using Quickstart.AspNetCore.Services; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | using Telegram.Bot.Types.Enums; 6 | 7 | namespace Quickstart.AspNetCore.Handlers 8 | { 9 | class WeatherReporter : IUpdateHandler 10 | { 11 | private readonly IWeatherService _weatherService; 12 | 13 | public WeatherReporter(IWeatherService weatherService) 14 | { 15 | _weatherService = weatherService; 16 | } 17 | 18 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 19 | { 20 | Message msg = context.Update.Message; 21 | Location location = msg.Location; 22 | 23 | var weather = await _weatherService.GetWeatherAsync(location.Latitude, location.Longitude); 24 | 25 | await context.Bot.Client.SendTextMessageAsync( 26 | msg.Chat, 27 | $"Weather status is *{weather.Status}* with the temperature of {weather.Temp:F1}.\n" + 28 | $"Min: {weather.MinTemp:F1}\n" + 29 | $"Max: {weather.MaxTemp:F1}\n\n\n" + 30 | "powered by [MetaWeather](https://www.metaweather.com)", 31 | ParseMode.Markdown, 32 | replyToMessageId: msg.MessageId 33 | ); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Handlers/WebhookLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Logging; 3 | using System.Threading.Tasks; 4 | using Telegram.Bot.Framework.Abstractions; 5 | 6 | namespace Quickstart.AspNetCore.Handlers 7 | { 8 | class WebhookLogger : IUpdateHandler 9 | { 10 | private readonly ILogger _logger; 11 | 12 | public WebhookLogger( 13 | ILogger logger 14 | ) 15 | { 16 | _logger = logger; 17 | } 18 | 19 | public Task HandleAsync(IUpdateContext context, UpdateDelegate next) 20 | { 21 | var httpContext = (HttpContext)context.Items[nameof(HttpContext)]; 22 | 23 | _logger.LogInformation( 24 | "Received update {0} in a webhook at {1}.", 25 | context.Update.Id, 26 | httpContext.Request.Host 27 | ); 28 | 29 | return next(context); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Options/CustomBotOptions.cs: -------------------------------------------------------------------------------- 1 | using Telegram.Bot.Framework; 2 | using Telegram.Bot.Framework.Abstractions; 3 | 4 | namespace Quickstart.AspNetCore.Options 5 | { 6 | public class CustomBotOptions : BotOptions 7 | where TBot : IBot 8 | { 9 | public string WebhookDomain { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Quickstart.AspNetCore 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateWebHostBuilder(args).Build().Run(); 12 | } 13 | 14 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .ConfigureAppConfiguration((hostBuilder, configBuilder) => configBuilder 17 | .AddJsonFile("appsettings.json") 18 | .AddJsonFile($"appsettings.{hostBuilder.HostingEnvironment.EnvironmentName}.json", true) 19 | .AddJsonEnvVar("QUICKSTART_SETTINGS", true) 20 | ).UseStartup(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51740", 7 | "sslPort": 44394 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "Quickstart.AspNetCore": { 18 | "commandName": "Project", 19 | "environmentVariables": { 20 | "ASPNETCORE_ENVIRONMENT": "Development" 21 | }, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 23 | }, 24 | "Docker": { 25 | "commandName": "Docker", 26 | "launchBrowser": true, 27 | "launchUrl": "{Scheme}://localhost:{ServicePort}" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Quickstart.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Services/BotServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using Telegram.Bot.Framework.Abstractions; 5 | 6 | namespace Quickstart.AspNetCore.Services 7 | { 8 | internal class BotServiceProvider : IBotServiceProvider 9 | { 10 | private readonly IServiceProvider _container; 11 | 12 | private readonly IServiceScope _scope; 13 | 14 | public BotServiceProvider(IApplicationBuilder app) 15 | { 16 | _container = app.ApplicationServices; 17 | } 18 | 19 | public BotServiceProvider(IServiceScope scope) 20 | { 21 | _scope = scope; 22 | } 23 | 24 | public object GetService(Type serviceType) => 25 | _scope != null 26 | ? _scope.ServiceProvider.GetService(serviceType) 27 | : _container.GetService(serviceType) 28 | ; 29 | 30 | public IBotServiceProvider CreateScope() => 31 | new BotServiceProvider(_container.CreateScope()); 32 | 33 | public void Dispose() 34 | { 35 | _scope?.Dispose(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Services/CurrentWeather.cs: -------------------------------------------------------------------------------- 1 | namespace Quickstart.AspNetCore.Services 2 | { 3 | struct CurrentWeather 4 | { 5 | public string Status; 6 | public float Temp; 7 | public float MinTemp; 8 | public float MaxTemp; 9 | } 10 | } -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Services/IWeatherService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Quickstart.AspNetCore.Services 4 | { 5 | interface IWeatherService 6 | { 7 | Task GetWeatherAsync(float lat, float lon); 8 | } 9 | } -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Services/WeatherService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace Quickstart.AspNetCore.Services 7 | { 8 | class WeatherService : IWeatherService 9 | { 10 | private readonly HttpClient _client; 11 | 12 | public WeatherService() 13 | { 14 | _client = new HttpClient 15 | { 16 | BaseAddress = new Uri("https://www.metaweather.com/api/") 17 | }; 18 | } 19 | 20 | public async Task GetWeatherAsync(float lat, float lon) 21 | { 22 | string location = await FindLocationIdAsync(lat, lon) 23 | .ConfigureAwait(false); 24 | 25 | DateTime today = DateTime.Today; 26 | 27 | string json = await _client.GetStringAsync($"location/{location}/{today.Year}/{today.Month}/{today.Day}") 28 | .ConfigureAwait(false); 29 | 30 | dynamic arr = JsonConvert.DeserializeObject(json); 31 | 32 | return new CurrentWeather 33 | { 34 | Status = arr[0].weather_state_name, 35 | Temp = arr[0].the_temp, 36 | MinTemp = arr[0].min_temp, 37 | MaxTemp = arr[0].max_temp, 38 | }; 39 | } 40 | 41 | private async Task FindLocationIdAsync(float lat, float lon) 42 | { 43 | string json = await _client.GetStringAsync($"location/search?lattlong={lat},{lon}") 44 | .ConfigureAwait(false); 45 | dynamic arr = JsonConvert.DeserializeObject(json); 46 | return arr[0].woeid; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Quickstart.AspNetCore.Handlers; 7 | using Quickstart.AspNetCore.Options; 8 | using Quickstart.AspNetCore.Services; 9 | using System; 10 | using Telegram.Bot.Framework; 11 | using Telegram.Bot.Framework.Abstractions; 12 | 13 | namespace Quickstart.AspNetCore 14 | { 15 | public class Startup 16 | { 17 | private IConfiguration Configuration { get; } 18 | 19 | public Startup(IConfiguration configuration) 20 | { 21 | Configuration = configuration; 22 | } 23 | 24 | public void ConfigureServices(IServiceCollection services) 25 | { 26 | services.AddTransient() 27 | .Configure>(Configuration.GetSection("EchoBot")) 28 | .Configure>(Configuration.GetSection("EchoBot")) 29 | .AddScoped() 30 | .AddScoped() 31 | .AddScoped() 32 | .AddScoped() 33 | .AddScoped() 34 | .AddScoped() 35 | .AddScoped() 36 | .AddScoped() 37 | .AddScoped() 38 | ; 39 | services.AddScoped(); 40 | } 41 | 42 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 43 | { 44 | if (env.IsDevelopment()) 45 | { 46 | app.UseDeveloperExceptionPage(); 47 | 48 | // get bot updates from Telegram via long-polling approach during development 49 | // this will disable Telegram webhooks 50 | app.UseTelegramBotLongPolling(ConfigureBot(), startAfter: TimeSpan.FromSeconds(2)); 51 | } 52 | else 53 | { 54 | // use Telegram bot webhook middleware in higher environments 55 | app.UseTelegramBotWebhook(ConfigureBot()); 56 | // and make sure webhook is enabled 57 | app.EnsureWebhookSet(); 58 | } 59 | 60 | app.Run(async context => 61 | { 62 | await context.Response.WriteAsync("Hello World!"); 63 | }); 64 | } 65 | 66 | private IBotBuilder ConfigureBot() 67 | { 68 | return new BotBuilder() 69 | .Use() 70 | 71 | // .Use() 72 | .UseWhen(When.Webhook) 73 | 74 | .UseWhen(When.MembersChanged) 75 | 76 | .MapWhen(When.NewMessage, msgBranch => msgBranch 77 | .MapWhen(When.NewTextMessage, txtBranch => txtBranch 78 | .Use() 79 | .MapWhen(When.NewCommand, cmdBranch => cmdBranch 80 | .UseCommand("ping") 81 | .UseCommand("start") 82 | ) 83 | //.Use() 84 | ) 85 | .MapWhen(When.StickerMessage) 86 | .MapWhen(When.LocationMessage) 87 | ) 88 | 89 | .MapWhen(When.CallbackQuery) 90 | 91 | // .Use() 92 | ; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/When.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Linq; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types.Enums; 5 | 6 | namespace Quickstart.AspNetCore 7 | { 8 | public static class When 9 | { 10 | public static bool Webhook(IUpdateContext context) 11 | => context.Items.ContainsKey(nameof(HttpContext)); 12 | 13 | public static bool NewMessage(IUpdateContext context) => 14 | context.Update.Message != null; 15 | 16 | public static bool NewTextMessage(IUpdateContext context) => 17 | context.Update.Message?.Text != null; 18 | 19 | public static bool NewCommand(IUpdateContext context) => 20 | context.Update.Message?.Entities?.FirstOrDefault()?.Type == MessageEntityType.BotCommand; 21 | 22 | public static bool MembersChanged(IUpdateContext context) => 23 | context.Update.Message?.NewChatMembers != null || 24 | context.Update.Message?.LeftChatMember != null || 25 | context.Update.ChannelPost?.NewChatMembers != null || 26 | context.Update.ChannelPost?.LeftChatMember != null 27 | ; 28 | 29 | public static bool LocationMessage(IUpdateContext context) => 30 | context.Update.Message?.Location != null; 31 | 32 | public static bool StickerMessage(IUpdateContext context) => 33 | context.Update.Message?.Sticker != null; 34 | 35 | public static bool CallbackQuery(IUpdateContext context) => 36 | context.Update.CallbackQuery != null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sample/Quickstart.AspNetCore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoBot": { 3 | "Username": "quickstart_tgbot", 4 | "ApiToken": "1234567:4TT8bAc8GHUspu3ERYn-KGcvsvGB9u_n4ddy", 5 | "WebhookDomain": "https://quickstart-tgbot.herokuapp.com", 6 | "WebhookPath": "/api/bots/1234567:4TT8bAc8GHUspu3ERYn-KGcvsvGB9u_n4ddy/webhook" 7 | } 8 | } -------------------------------------------------------------------------------- /sample/Quickstart.Net45/EchoBot.cs: -------------------------------------------------------------------------------- 1 | using Telegram.Bot.Framework; 2 | 3 | namespace Quickstart.Net45 4 | { 5 | class EchoBot : BotBase 6 | { 7 | public EchoBot(string apiToken) 8 | : base(username: "quickstart_tgbot", token: apiToken) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/CallbackQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Types; 4 | 5 | namespace Quickstart.Net45.Handlers 6 | { 7 | public class CallbackQueryHandler : IUpdateHandler 8 | { 9 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | CallbackQuery cq = context.Update.CallbackQuery; 12 | 13 | await context.Bot.Client.AnswerCallbackQueryAsync(cq.Id, "PONG", showAlert: true); 14 | 15 | await next(context); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/Commands/PingCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Types; 4 | using Telegram.Bot.Types.Enums; 5 | using Telegram.Bot.Types.ReplyMarkups; 6 | 7 | namespace Quickstart.Net45.Handlers 8 | { 9 | class PingCommand : CommandBase 10 | { 11 | public override async Task HandleAsync(IUpdateContext context, UpdateDelegate next, string[] args) 12 | { 13 | Message msg = context.Update.Message; 14 | 15 | await context.Bot.Client.SendTextMessageAsync( 16 | msg.Chat, 17 | "*PONG*", 18 | ParseMode.Markdown, 19 | replyToMessageId: msg.MessageId, 20 | replyMarkup: new InlineKeyboardMarkup( 21 | InlineKeyboardButton.WithCallbackData("Ping", "PONG") 22 | ) 23 | ); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/Commands/StartCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | 4 | namespace Quickstart.Net45.Handlers 5 | { 6 | class StartCommand : CommandBase 7 | { 8 | public override async Task HandleAsync(IUpdateContext context, UpdateDelegate next, string[] args) 9 | { 10 | await context.Bot.Client.SendTextMessageAsync(context.Update.Message.Chat, "Hello, World!"); 11 | await next(context); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/ExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace Quickstart.Net45.Handlers 6 | { 7 | public class ExceptionHandler : IUpdateHandler 8 | { 9 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | var u = context.Update; 12 | 13 | try 14 | { 15 | await next(context); 16 | } 17 | catch (Exception e) 18 | { 19 | Console.BackgroundColor = ConsoleColor.Black; 20 | Console.ForegroundColor = ConsoleColor.Red; 21 | Console.WriteLine("An error occured in handling update {0}.{1}{2}", u.Id, Environment.NewLine, e); 22 | Console.ResetColor(); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/StickerHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | 6 | namespace Quickstart.Net45.Handlers 7 | { 8 | class StickerHandler : IUpdateHandler 9 | { 10 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 11 | { 12 | Message msg = context.Update.Message; 13 | Sticker incomingSticker = msg.Sticker; 14 | 15 | StickerSet evilMindsSet = await context.Bot.Client.GetStickerSetAsync("EvilMinds"); 16 | 17 | Sticker similarEvilMindSticker = evilMindsSet.Stickers.FirstOrDefault( 18 | sticker => incomingSticker.Emoji.Contains(sticker.Emoji) 19 | ); 20 | 21 | Sticker replySticker = similarEvilMindSticker ?? evilMindsSet.Stickers.First(); 22 | 23 | await context.Bot.Client.SendStickerAsync( 24 | msg.Chat, 25 | replySticker.FileId, 26 | replyToMessageId: msg.MessageId 27 | ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/TextEchoer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Types; 4 | 5 | namespace Quickstart.Net45.Handlers 6 | { 7 | public class TextEchoer : IUpdateHandler 8 | { 9 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | Message msg = context.Update.Message; 12 | 13 | await context.Bot.Client.SendTextMessageAsync( 14 | msg.Chat, "You said:\n" + msg.Text 15 | ); 16 | 17 | await next(context); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/UpdateMembersList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace Quickstart.Net45.Handlers 6 | { 7 | class UpdateMembersList : IUpdateHandler 8 | { 9 | public Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | Console.BackgroundColor = ConsoleColor.Black; 12 | Console.ForegroundColor = ConsoleColor.Yellow; 13 | Console.WriteLine("Updating chat members list..."); 14 | Console.ResetColor(); 15 | 16 | return next(context); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/WeatherReporter.cs: -------------------------------------------------------------------------------- 1 | using Quickstart.Net45.Services; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | using Telegram.Bot.Types.Enums; 6 | 7 | namespace Quickstart.Net45.Handlers 8 | { 9 | class WeatherReporter : IUpdateHandler 10 | { 11 | private readonly IWeatherService _weatherService; 12 | 13 | public WeatherReporter(IWeatherService weatherService) 14 | { 15 | _weatherService = weatherService; 16 | } 17 | 18 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 19 | { 20 | Message msg = context.Update.Message; 21 | Location location = msg.Location; 22 | 23 | var weather = await _weatherService.GetWeatherAsync(location.Latitude, location.Longitude); 24 | 25 | await context.Bot.Client.SendTextMessageAsync( 26 | msg.Chat, 27 | $"Weather status is *{weather.Status}* with the temperature of {weather.Temp:F1}.\n" + 28 | $"Min: {weather.MinTemp:F1}\n" + 29 | $"Max: {weather.MaxTemp:F1}\n\n\n" + 30 | "powered by [MetaWeather](https://www.metaweather.com)", 31 | ParseMode.Markdown, 32 | replyToMessageId: msg.MessageId 33 | ); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Handlers/WebhookLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace Quickstart.Net45.Handlers 6 | { 7 | class WebhookLogger : IUpdateHandler 8 | { 9 | public Task HandleAsync(IUpdateContext context, UpdateDelegate next) 10 | { 11 | Console.BackgroundColor = ConsoleColor.Black; 12 | Console.ForegroundColor = ConsoleColor.Yellow; 13 | Console.WriteLine("Received update {0} as a webhook.", context.Update.Id); 14 | Console.ResetColor(); 15 | 16 | return next(context); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Program.cs: -------------------------------------------------------------------------------- 1 | using Quickstart.Net45.Handlers; 2 | using Quickstart.Net45.Services; 3 | using Quickstart.Net45.Services.SimpleInjector; 4 | using SimpleInjector; 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Telegram.Bot.Framework; 9 | using Telegram.Bot.Framework.Abstractions; 10 | 11 | namespace Quickstart.Net45 12 | { 13 | class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | string token = Environment.GetEnvironmentVariable("BOT_API_TOKEN") ?? "YOUR_API_TOKEN_HERE"; 18 | 19 | var container = new Container(); 20 | container.Register(() => new EchoBot(token)); 21 | container.Register(); 22 | container.Register(); 23 | container.Verify(); 24 | 25 | var mgr = new UpdatePollingManager(ConfigureBot(), new BotServiceProvider(container)); 26 | 27 | var tokenSrc = new CancellationTokenSource(); 28 | Task.Run(() => 29 | { 30 | Console.ReadLine(); 31 | tokenSrc.Cancel(); 32 | }); 33 | 34 | mgr.RunAsync(cancellationToken: tokenSrc.Token).GetAwaiter().GetResult(); 35 | } 36 | 37 | static IBotBuilder ConfigureBot() 38 | { 39 | return new BotBuilder() 40 | .Use() 41 | 42 | // .Use() 43 | .UseWhen(When.Webhook) 44 | 45 | .UseWhen(When.MembersChanged) 46 | 47 | .MapWhen(When.NewMessage, msgBranch => msgBranch 48 | .MapWhen(When.NewTextMessage, txtBranch => txtBranch 49 | .Use() 50 | .MapWhen(When.NewCommand, cmdBranch => cmdBranch 51 | .UseCommand("ping") 52 | .UseCommand("start") 53 | ) 54 | //.Use() 55 | ) 56 | .MapWhen(When.StickerMessage, branch => branch.Use()) 57 | .MapWhen(When.LocationMessage, branch => branch.Use()) 58 | ) 59 | 60 | .MapWhen(When.CallbackQuery) 61 | 62 | // .Use() 63 | ; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Quickstart.Net45")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Quickstart.Net45")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("C3672842-82EE-49F3-90A1-0F07A7857EBE")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Quickstart.Net45.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C3672842-82EE-49F3-90A1-0F07A7857EBE} 8 | Exe 9 | Properties 10 | Quickstart.Net45 11 | Quickstart.Net45 12 | v4.5 13 | 512 14 | latest 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll 39 | True 40 | 41 | 42 | ..\..\packages\SimpleInjector.4.3.0\lib\net45\SimpleInjector.dll 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ..\..\packages\Telegram.Bot.14.10.0\lib\net45\Telegram.Bot.dll 51 | True 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {ec991578-d67e-4208-b540-7fbb603b7d8f} 77 | Telegram.Bot.Framework 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Services/SimpleInjector/BotServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using SimpleInjector; 2 | using SimpleInjector.Lifestyles; 3 | using System; 4 | using Telegram.Bot.Framework.Abstractions; 5 | 6 | namespace Quickstart.Net45.Services.SimpleInjector 7 | { 8 | class BotServiceProvider : IBotServiceProvider 9 | { 10 | private readonly Container _container; 11 | 12 | private readonly Scope _scope; 13 | 14 | public BotServiceProvider(Container container) 15 | { 16 | _container = container; 17 | } 18 | 19 | private BotServiceProvider(Scope scope) 20 | { 21 | _scope = scope; 22 | } 23 | 24 | public object GetService(Type serviceType) => 25 | _scope != null 26 | ? _scope.GetInstance(serviceType) 27 | : _container.GetInstance(serviceType) 28 | ; 29 | 30 | public IBotServiceProvider CreateScope() => 31 | new BotServiceProvider(ThreadScopedLifestyle.BeginScope(_container)); 32 | 33 | public void Dispose() 34 | { 35 | _scope?.Dispose(); 36 | _container?.Dispose(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/Services/WeatherService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace Quickstart.Net45.Services 7 | { 8 | interface IWeatherService 9 | { 10 | Task GetWeatherAsync(float lat, float lon); 11 | } 12 | 13 | struct CurrentWeather 14 | { 15 | public string Status; 16 | public float Temp; 17 | public float MinTemp; 18 | public float MaxTemp; 19 | } 20 | 21 | class WeatherService : IWeatherService 22 | { 23 | private readonly HttpClient _client; 24 | 25 | public WeatherService() 26 | { 27 | _client = new HttpClient 28 | { 29 | BaseAddress = new Uri("https://www.metaweather.com/api/") 30 | }; 31 | } 32 | 33 | public async Task GetWeatherAsync(float lat, float lon) 34 | { 35 | string location = await FindLocationIdAsync(lat, lon) 36 | .ConfigureAwait(false); 37 | 38 | DateTime today = DateTime.Today; 39 | 40 | string json = await _client.GetStringAsync($"location/{location}/{today.Year}/{today.Month}/{today.Day}") 41 | .ConfigureAwait(false); 42 | 43 | dynamic arr = JsonConvert.DeserializeObject(json); 44 | 45 | return new CurrentWeather 46 | { 47 | Status = arr[0].weather_state_name, 48 | Temp = arr[0].the_temp, 49 | MinTemp = arr[0].min_temp, 50 | MaxTemp = arr[0].max_temp, 51 | }; 52 | } 53 | 54 | private async Task FindLocationIdAsync(float lat, float lon) 55 | { 56 | string json = await _client.GetStringAsync($"location/search?lattlong={lat},{lon}") 57 | .ConfigureAwait(false); 58 | dynamic arr = JsonConvert.DeserializeObject(json); 59 | return arr[0].woeid; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/When.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Types.Enums; 4 | 5 | namespace Quickstart.Net45 6 | { 7 | public static class When 8 | { 9 | public static bool Webhook(IUpdateContext context) 10 | => context.Items.ContainsKey("HttpContext"); 11 | 12 | public static bool NewMessage(IUpdateContext context) => 13 | context.Update.Message != null; 14 | 15 | public static bool NewTextMessage(IUpdateContext context) => 16 | context.Update.Message?.Text != null; 17 | 18 | public static bool NewCommand(IUpdateContext context) => 19 | context.Update.Message?.Entities?.FirstOrDefault()?.Type == MessageEntityType.BotCommand; 20 | 21 | public static bool MembersChanged(IUpdateContext context) => 22 | context.Update.Message?.NewChatMembers != null || 23 | context.Update.Message?.LeftChatMember != null || 24 | context.Update.ChannelPost?.NewChatMembers != null || 25 | context.Update.ChannelPost?.LeftChatMember != null 26 | ; 27 | 28 | public static bool LocationMessage(IUpdateContext context) => 29 | context.Update.Message?.Location != null; 30 | 31 | public static bool StickerMessage(IUpdateContext context) => 32 | context.Update.Message?.Sticker != null; 33 | 34 | public static bool CallbackQuery(IUpdateContext context) => 35 | context.Update.CallbackQuery != null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sample/Quickstart.Net45/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /sample/SampleBots/BotUpdateGetterTask.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using RecurrentTasks; 5 | using Telegram.Bot.Framework.Abstractions; 6 | 7 | namespace SampleBots 8 | { 9 | public class BotUpdateGetterTask : IRunnable 10 | where TBot : class, IBot 11 | { 12 | private readonly IBotManager _botManager; 13 | 14 | private readonly ILogger _logger; 15 | 16 | public BotUpdateGetterTask(IBotManager botManager, ILogger> logger) 17 | { 18 | _botManager = botManager; 19 | _logger = logger; 20 | } 21 | 22 | public void Run(ITask currentTask, CancellationToken cancellationToken) 23 | { 24 | Task.Factory.StartNew(async () => 25 | { 26 | _logger.LogTrace($"{typeof(TBot).Name}: Checking for updates..."); 27 | await _botManager.GetAndHandleNewUpdatesAsync(); 28 | _logger.LogTrace($"{typeof(TBot).Name}: Handling updates finished"); 29 | }, cancellationToken).ContinueWith(task => 30 | { 31 | if (task.IsFaulted) 32 | { 33 | throw task.Exception.InnerException; 34 | } 35 | }, cancellationToken); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /sample/SampleBots/Bots/EchoBot/EchoerBot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | using Telegram.Bot.Framework; 6 | using Telegram.Bot.Types; 7 | using Telegram.Bot.Types.Enums; 8 | 9 | namespace SampleBots.Bots.EchoBot 10 | { 11 | public class EchoerBot : BotBase 12 | { 13 | private readonly ILogger _logger; 14 | 15 | public EchoerBot(IOptions> botOptions, ILogger logger) 16 | : base(botOptions) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | public override async Task HandleUnknownUpdate(Update update) 22 | { 23 | _logger.LogWarning("Unable to handle an update"); 24 | 25 | const string unknownUpdateText = "Sorry! I don't know what to do with this message"; 26 | 27 | if (update.Type == UpdateType.Message) 28 | { 29 | await Client.SendTextMessageAsync(update.Message.Chat.Id, 30 | unknownUpdateText, 31 | replyToMessageId: update.Message.MessageId); 32 | } 33 | else 34 | { 35 | 36 | } 37 | } 38 | 39 | public override Task HandleFaultedUpdate(Update update, Exception e) 40 | { 41 | _logger.LogCritical("Exception thrown while handling an update"); 42 | return Task.CompletedTask; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sample/SampleBots/Bots/EchoBot/TextMessageEchoer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | using Telegram.Bot.Types.Enums; 6 | 7 | namespace SampleBots.Bots.EchoBot 8 | { 9 | public class TextMessageEchoer : UpdateHandlerBase 10 | { 11 | public override bool CanHandleUpdate(IBot bot, Update update) 12 | { 13 | return !string.IsNullOrEmpty(update.Message?.Text); 14 | } 15 | 16 | public override async Task HandleUpdateAsync(IBot bot, Update update) 17 | { 18 | string replyText = $"You said:\n`{update.Message.Text.Replace("\n", "`\n`")}`"; 19 | 20 | await bot.Client.SendTextMessageAsync( 21 | update.Message.Chat.Id, 22 | replyText, 23 | ParseMode.Markdown, 24 | replyToMessageId: update.Message.MessageId); 25 | 26 | return UpdateHandlingResult.Continue; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sample/SampleBots/Bots/GreeterBot/GreeterBot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | using Telegram.Bot.Framework; 6 | using Telegram.Bot.Types; 7 | using Telegram.Bot.Types.Enums; 8 | 9 | namespace SampleBots.Bots.GreeterBot 10 | { 11 | public class GreeterBot : BotBase 12 | { 13 | private readonly ILogger _logger; 14 | 15 | public GreeterBot(IOptions> botOptions, ILogger logger) 16 | : base(botOptions) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | public override async Task HandleUnknownUpdate(Update update) 22 | { 23 | _logger.LogWarning("Unable to handle an update"); 24 | 25 | const string unknownUpdateText = "Sorry! I don't know what to do with this message"; 26 | 27 | if (update.Type == UpdateType.Message) 28 | { 29 | await Client.SendTextMessageAsync(update.Message.Chat.Id, 30 | unknownUpdateText, 31 | replyToMessageId: update.Message.MessageId); 32 | } 33 | else 34 | { 35 | 36 | } 37 | } 38 | 39 | public override Task HandleFaultedUpdate(Update update, Exception e) 40 | { 41 | _logger.LogCritical("Exception thrown while handling an update"); 42 | return Task.CompletedTask; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sample/SampleBots/Bots/GreeterBot/HiCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text.RegularExpressions; 3 | using System.Threading.Tasks; 4 | using Telegram.Bot.Framework; 5 | using Telegram.Bot.Framework.Abstractions; 6 | using Telegram.Bot.Types; 7 | using Telegram.Bot.Types.Enums; 8 | 9 | namespace SampleBots.Bots.GreeterBot 10 | { 11 | public class HiCommandArgs : ICommandArgs 12 | { 13 | public string RawInput { get; set; } 14 | 15 | public string ArgsInput { get; set; } 16 | 17 | public string PersonName { get; set; } 18 | } 19 | 20 | public class HiCommand : CommandBase 21 | { 22 | private const string CommandName = "hi"; 23 | 24 | private const string HiMessageFormat = "Hello, *{0}*!"; 25 | 26 | private const string HelpText = "Here is a tip to use this command:\n" + 27 | "```\n" + 28 | "/hi John" + 29 | "```"; 30 | 31 | public HiCommand() 32 | : base(CommandName) 33 | { 34 | 35 | } 36 | 37 | protected override HiCommandArgs ParseInput(Update update) 38 | { 39 | var tokens = Regex.Split(update.Message.Text.Trim(), @"\s+"); 40 | var args = base.ParseInput(update); 41 | 42 | if (tokens.Length > 1) 43 | { 44 | args.PersonName = string.Join(" ", tokens.Skip(1)); 45 | } 46 | 47 | return args; 48 | } 49 | 50 | public override async Task HandleCommand(Update update, HiCommandArgs args) 51 | { 52 | string text; 53 | if (args.PersonName is null) 54 | { 55 | text = HelpText; 56 | } 57 | else 58 | { 59 | text = string.Format(HiMessageFormat, args.PersonName); 60 | } 61 | 62 | await Bot.Client.SendTextMessageAsync( 63 | update.Message.Chat.Id, 64 | text, 65 | ParseMode.Markdown, 66 | replyToMessageId: update.Message.MessageId 67 | ); 68 | 69 | return UpdateHandlingResult.Continue; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sample/SampleBots/Bots/GreeterBot/PhotoForwarder.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | 6 | namespace SampleBots.Bots.GreeterBot 7 | { 8 | public class PhotoForwarder : UpdateHandlerBase 9 | { 10 | public override bool CanHandleUpdate(IBot bot, Update update) 11 | { 12 | return update.Message?.Photo != null; 13 | } 14 | 15 | public override async Task HandleUpdateAsync(IBot bot, Update update) 16 | { 17 | await bot.Client.ForwardMessageAsync(update.Message.Chat.Id, 18 | update.Message.Chat.Id, 19 | update.Message.MessageId); 20 | 21 | return UpdateHandlingResult.Handled; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/SampleBots/Bots/GreeterBot/StartCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | using Telegram.Bot.Types.Enums; 6 | 7 | namespace SampleBots.Bots.GreeterBot 8 | { 9 | public class StartCommandArgs : ICommandArgs 10 | { 11 | public string RawInput { get; set; } 12 | 13 | public string ArgsInput { get; set; } 14 | } 15 | 16 | public class StartCommand : CommandBase 17 | { 18 | private const string CommandName = "start"; 19 | 20 | private const string StartMessageFormat = "Hey *{0}*!\n\n" + 21 | "Try my _hi_ command like this:\n" + 22 | "`/hi John`\n\n" + 23 | "By the way, I _forward back any photo you send_ here ;)"; 24 | 25 | public StartCommand() 26 | : base(CommandName) 27 | { 28 | 29 | } 30 | 31 | public override async Task HandleCommand(Update update, StartCommandArgs args) 32 | { 33 | await Bot.Client.SendTextMessageAsync(update.Message.Chat.Id, 34 | string.Format(StartMessageFormat, update.Message.From.FirstName), 35 | ParseMode.Markdown, 36 | replyToMessageId: update.Message.ForwardFromMessageId); 37 | 38 | return UpdateHandlingResult.Handled; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/SampleBots/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace SampleBots 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | Console.Title = "Telegram.Bot.Framework - Sample Bots"; 13 | 14 | var config = new ConfigurationBuilder() 15 | .AddCommandLine(args) 16 | .Build(); 17 | 18 | var host = new WebHostBuilder() 19 | .UseKestrel() 20 | .UseContentRoot(Directory.GetCurrentDirectory()) 21 | .UseIISIntegration() 22 | .UseConfiguration(config) 23 | .UseStartup() 24 | .UseApplicationInsights() 25 | .Build(); 26 | 27 | host.Run(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/SampleBots/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "anonymousAuthentication": true, 4 | "iisExpress": { 5 | "applicationUrl": "http://localhost:53136/", 6 | "sslPort": 0 7 | }, 8 | "windowsAuthentication": false 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "SampleBots": { 18 | "applicationUrl": "http://localhost:53137", 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/SampleBots/SampleBots.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp1.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/SampleBots/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | using RecurrentTasks; 10 | using SampleBots.Bots.EchoBot; 11 | using SampleBots.Bots.GreeterBot; 12 | using Telegram.Bot.Framework; 13 | 14 | namespace SampleBots 15 | { 16 | public class Startup 17 | { 18 | public IConfigurationRoot Configuration { get; } 19 | 20 | public Startup(IHostingEnvironment env) 21 | { 22 | Configuration = new ConfigurationBuilder() 23 | .SetBasePath(env.ContentRootPath) 24 | .AddJsonFile("appsettings.json") 25 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 26 | .AddEnvironmentVariables("SampleEchoBot_") 27 | .Build(); 28 | } 29 | 30 | public void ConfigureServices(IServiceCollection services) 31 | { 32 | #region Echoer Bot 33 | 34 | var echoBotOptions = new BotOptions(); 35 | Configuration.GetSection("EchoerBot").Bind(echoBotOptions); 36 | 37 | services.AddTelegramBot(echoBotOptions) 38 | .AddUpdateHandler() 39 | .Configure(); 40 | services.AddTask>(); 41 | 42 | #endregion 43 | 44 | #region Greeter Bot 45 | 46 | services.AddTelegramBot(Configuration.GetSection("GreeterBot")) 47 | .AddUpdateHandler() 48 | .AddUpdateHandler() 49 | .AddUpdateHandler() 50 | .Configure(); 51 | services.AddTask>(); 52 | 53 | #endregion 54 | } 55 | 56 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, 57 | ILoggerFactory loggerFactory) 58 | { 59 | loggerFactory.AddConsole(Configuration.GetSection("Logging")); 60 | loggerFactory.AddDebug(); 61 | 62 | ILogger logger = loggerFactory.CreateLogger(); 63 | 64 | if (env.IsDevelopment()) 65 | { 66 | app.UseDeveloperExceptionPage(); 67 | } 68 | else 69 | { 70 | app.UseExceptionHandler(appBuilder => 71 | appBuilder.Run(context => 72 | { 73 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 74 | return Task.CompletedTask; 75 | }) 76 | ); 77 | } 78 | 79 | #region Echoer Bot 80 | 81 | if (env.IsDevelopment()) 82 | { 83 | app.UseTelegramBotLongPolling(); 84 | app.StartTask>(TimeSpan.FromSeconds(8), TimeSpan.FromSeconds(3)); 85 | logger.LogInformation("Update getting task is scheduled for bot " + nameof(EchoerBot)); 86 | } 87 | else 88 | { 89 | app.UseTelegramBotWebhook(); 90 | logger.LogInformation("Webhook is set for bot " + nameof(EchoerBot)); 91 | } 92 | 93 | #endregion 94 | 95 | #region Greeter Bot 96 | 97 | if (env.IsDevelopment()) 98 | { 99 | app.UseTelegramBotLongPolling(); 100 | app.StartTask>(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(3)); 101 | logger.LogInformation("Update getting task is scheduled for bot " + nameof(GreeterBot)); 102 | } 103 | else 104 | { 105 | app.UseTelegramBotWebhook(); 106 | logger.LogInformation("Webhook is set for bot " + nameof(GreeterBot)); 107 | } 108 | 109 | #endregion 110 | 111 | app.Run(async context => { await context.Response.WriteAsync("Hello World!"); }); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /sample/SampleBots/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "EchoerBot": { 3 | "ApiToken": "", 4 | "BotUserName": "sample_echoer_bot", 5 | "PathToCertificate": "", 6 | "BaseUrl": "https://example.com/bots/" 7 | }, 8 | "GreeterBot": { 9 | "ApiToken": "", 10 | "BotUserName": "sample_greeter_bot", 11 | "PathToCertificate": "", 12 | "BaseUrl": "https://example.com/bots/" 13 | }, 14 | "Logging": { 15 | "IncludeScopes": false, 16 | "LogLevel": { 17 | "Default": "Debug", 18 | "Microsoft": "Information", 19 | "System": "Information" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/SampleEchoBot/Data/IRepository.cs: -------------------------------------------------------------------------------- 1 | namespace SampleEchoBot.Data 2 | { 3 | public interface IRepository 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /sample/SampleEchoBot/EchoBot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using Telegram.Bot.Framework; 7 | using Telegram.Bot.Types; 8 | using Telegram.Bot.Types.Enums; 9 | 10 | namespace SampleEchoBot 11 | { 12 | public class EchoBot : BotBase 13 | { 14 | private readonly ILogger _logger; 15 | 16 | public EchoBot(IOptions> botOptions, ILogger logger) 17 | : base(botOptions) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public override async Task HandleUnknownUpdate(Update update) 23 | { 24 | _logger.LogWarning("Unable to handle update of type `{0}`", update.Type); 25 | 26 | string text; 27 | int replyToMesageId = default(int); 28 | 29 | switch (update.Type) 30 | { 31 | case UpdateType.Message when 32 | new[] {ChatType.Private, ChatType.Group, ChatType.Supergroup}.Contains(update.Message.Chat.Type): 33 | text = $"Unable to handle message update of type `{update.Message.Type}`."; 34 | replyToMesageId = update.Message.MessageId; 35 | break; 36 | default: 37 | text = null; 38 | break; 39 | } 40 | 41 | if (text != null) 42 | { 43 | await Client.SendTextMessageAsync(update.Message.Chat.Id, text, ParseMode.Markdown, 44 | replyToMessageId: replyToMesageId); 45 | } 46 | } 47 | 48 | public override Task HandleFaultedUpdate(Update update, Exception e) 49 | { 50 | _logger.LogError($"Exception occured in handling update of type `{0}`: {1}", update.Type, e.Message); 51 | 52 | return Task.CompletedTask; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /sample/SampleEchoBot/EchoCommand.cs: -------------------------------------------------------------------------------- 1 | //using System.Threading.Tasks; 2 | //using Telegram.Bot.Abstractions; 3 | //using Telegram.Bot.Framework; 4 | //using Telegram.Bot.Types; 5 | // 6 | //namespace SampleEchoBot 7 | //{ 8 | // public class EchoCommand : CommandBase 9 | // { 10 | // public EchoCommand() : base(name: "echo") 11 | // { 12 | // } 13 | // 14 | // public override async Task HandleCommandAsync(Update update, CommandArgs args) 15 | // { 16 | // string replyText = string.IsNullOrWhiteSpace(args.ArgsInput) ? "Echo What?" : args.ArgsInput; 17 | // 18 | // await Bot.Client.SendTextMessageAsync( 19 | // update.Message.Chat.Id, 20 | // replyText, 21 | // replyToMessageId: update.Message.MessageId); 22 | // } 23 | // } 24 | // 25 | // public class CommandArgs : ICommandArgs 26 | // { 27 | // public string RawInput { get; set; } 28 | // public string ArgsInput { get; set; } 29 | // } 30 | //} -------------------------------------------------------------------------------- /sample/SampleEchoBot/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace SampleEchoBot 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .UseApplicationInsights() 16 | .Build(); 17 | 18 | host.Run(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/SampleEchoBot/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:58090/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "SampleEchoBot": { 18 | "commandName": "Project", 19 | "environmentVariables": { 20 | "ASPNETCORE_ENVIRONMENT": "Development" 21 | }, 22 | "applicationUrl": "http://localhost:58091" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/SampleEchoBot/SampleEchoBot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp1.1 4 | latest 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample/SampleEchoBot/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Logging; 10 | using Telegram.Bot.Abstractions; 11 | using Telegram.Bot.Framework; 12 | 13 | namespace SampleEchoBot 14 | { 15 | public class Startup 16 | { 17 | private readonly IConfigurationRoot _configuration; 18 | 19 | public Startup(IHostingEnvironment env) 20 | { 21 | var builder = new ConfigurationBuilder() 22 | .SetBasePath(env.ContentRootPath) 23 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 24 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 25 | .AddEnvironmentVariables(); 26 | _configuration = builder.Build(); 27 | } 28 | 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | // services.AddTelegramBot(_configuration.GetSection("EchoBot")) 32 | //// .AddUpdateHandler() 33 | // .AddUpdateHandler<>() 34 | // .Configure(); 35 | } 36 | 37 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 38 | { 39 | loggerFactory.AddConsole(_configuration.GetSection("Logging")); 40 | loggerFactory.AddDebug(); 41 | ILogger logger = loggerFactory.CreateLogger(); 42 | 43 | if (env.IsDevelopment()) 44 | { 45 | app.UseDeveloperExceptionPage(); 46 | 47 | var source = new CancellationTokenSource(); 48 | Task.Factory.StartNew(() => 49 | { 50 | logger.LogDebug("Press Enter to stop bot manager..."); 51 | Console.ReadLine(); 52 | source.Cancel(); 53 | }); 54 | 55 | Task.Factory.StartNew(async () => 56 | { 57 | var botManager = app.ApplicationServices.GetRequiredService>(); 58 | 59 | // make sure webhook is disabled so we can use long-polling 60 | await botManager.SetWebhookStateAsync(false); 61 | logger.LogDebug("Webhook is disabled. Staring update handling..."); 62 | 63 | while (!source.IsCancellationRequested) 64 | { 65 | await Task.Delay(3_000); 66 | await botManager.GetAndHandleNewUpdatesAsync(); 67 | } 68 | logger.LogDebug("Bot manager stopped."); 69 | }).ContinueWith(t => 70 | { 71 | if (t.IsFaulted) throw t.Exception; 72 | }); 73 | } 74 | else 75 | { 76 | app.UseExceptionHandler(appBuilder => 77 | appBuilder.Run(context => 78 | { 79 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 80 | return Task.CompletedTask; 81 | }) 82 | ); 83 | 84 | logger.LogInformation($"Setting webhook for {nameof(EchoBot)}..."); 85 | // app.UseTelegramBotWebhook(); 86 | logger.LogInformation("Webhook is set for bot " + nameof(EchoBot)); 87 | } 88 | 89 | 90 | app.Run(async context => 91 | { 92 | await context.Response.WriteAsync("Hello World!"); 93 | }); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /sample/SampleEchoBot/TextMessageHandler.cs: -------------------------------------------------------------------------------- 1 | //using System.Threading.Tasks; 2 | //using Telegram.Bot; 3 | //using Telegram.Bot.Abstractions; 4 | //using Telegram.Bot.Types; 5 | // 6 | //namespace SampleEchoBot 7 | //{ 8 | // public class TextMessageHandler : IUpdateHandler 9 | // { 10 | // private ITelegramBotClient BotClient => _bot.Client; 11 | // private readonly IBot _bot; 12 | // 13 | // public TextMessageHandler(IBot bot) 14 | // { 15 | // _bot = bot; 16 | // } 17 | // 18 | // public bool CanHandle(IUpdateContext context) => 19 | // context.Update.Message?.Text != null; 20 | // 21 | // public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 22 | // { 23 | // Message msg = context.Update.Message; 24 | // 25 | // await BotClient.SendTextMessageAsync( 26 | // msg.Chat, "You said:\n" + msg.Text 27 | // ); 28 | // 29 | // await next(context); 30 | // } 31 | // } 32 | //} -------------------------------------------------------------------------------- /sample/SampleEchoBot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "Microsoft": "Information", 7 | "System": "Information" 8 | } 9 | }, 10 | "EchoBot": { 11 | "ApiToken": "{your-bots-api-token}", 12 | "BotUserName": "{your-bots-username}", 13 | "PathToCertificate": "", 14 | "WebhookUrl": "https://example.com/bots/{bot}/webhook/{token}" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/SampleGames/BotUpdateGetterTask.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using RecurrentTasks; 5 | using Telegram.Bot.Framework.Abstractions; 6 | 7 | namespace SampleGames 8 | { 9 | public class BotUpdateGetterTask : IRunnable 10 | where TBot : class, IBot 11 | { 12 | private readonly IBotManager _botManager; 13 | 14 | private readonly ILogger _logger; 15 | 16 | public BotUpdateGetterTask(IBotManager botManager, 17 | ILogger> logger) 18 | { 19 | _botManager = botManager; 20 | _logger = logger; 21 | } 22 | 23 | public void Run(ITask currentTask, CancellationToken cancellationToken) 24 | { 25 | Task.Factory.StartNew(async () => 26 | { 27 | _logger.LogTrace($"{typeof(TBot).Name}: Checking for updates..."); 28 | await _botManager.GetAndHandleNewUpdatesAsync(); 29 | _logger.LogTrace($"{typeof(TBot).Name}: Handling updates finished"); 30 | }, cancellationToken).ContinueWith(task => 31 | { 32 | if (task.IsFaulted) 33 | { 34 | _logger.LogError("{0}", task.Exception.Message); 35 | throw task.Exception; 36 | } 37 | }, cancellationToken); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /sample/SampleGames/Bots/CrazyCircle/CrazyCircleBot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.Options; 5 | using Telegram.Bot.Framework; 6 | using Telegram.Bot.Types; 7 | 8 | namespace SampleGames.Bots.CrazyCircle 9 | { 10 | public class CrazyCircleBot : BotBase 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public CrazyCircleBot(IOptions> botOptions, 15 | ILogger logger) 16 | : base(botOptions) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | public override Task HandleUnknownUpdate(Update update) 22 | { 23 | _logger.LogWarning("Don't know how to handle update of type `{0}`", update.Type); 24 | return Task.CompletedTask; 25 | } 26 | 27 | public override Task HandleFaultedUpdate(Update update, Exception e) 28 | { 29 | _logger.LogError("Error in handling update of type `{0}`.{1}{2}", 30 | update.Type, Environment.NewLine, e); 31 | return Task.CompletedTask; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /sample/SampleGames/Bots/CrazyCircle/CrazyCircleGameHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using Microsoft.Extensions.Logging; 3 | using Telegram.Bot.Framework; 4 | 5 | namespace SampleGames.Bots.CrazyCircle 6 | { 7 | public class CrazyCircleGameHandler : GameHandlerBase 8 | { 9 | public CrazyCircleGameHandler(IDataProtectionProvider protectionProvider, ILogger logger) 10 | : base(protectionProvider, Constants.GameShortName, logger) 11 | { 12 | 13 | } 14 | 15 | private static class Constants 16 | { 17 | public const string GameShortName = "crazycircle"; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/SampleGames/Bots/CrazyCircle/StartCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Telegram.Bot.Framework; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | 6 | namespace SampleGames.Bots.CrazyCircle 7 | { 8 | public class StartCommandArgs : ICommandArgs 9 | { 10 | public string RawInput { get; set; } 11 | 12 | public string ArgsInput { get; set; } 13 | } 14 | 15 | public class StartCommand : CommandBase 16 | { 17 | public StartCommand() 18 | : base(Constants.CommandName) 19 | { 20 | 21 | } 22 | 23 | public override async Task HandleCommand(Update update, StartCommandArgs args) 24 | { 25 | await Bot.Client.SendGameAsync(update.Message.Chat.Id, "crazycircle"); 26 | 27 | return UpdateHandlingResult.Handled; 28 | } 29 | 30 | private static class Constants 31 | { 32 | public const string CommandName = "start"; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/SampleGames/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace SampleGames 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | Console.Title = "Telegram.Bot.Framework - Sample Games"; 13 | 14 | var config = new ConfigurationBuilder() 15 | .AddCommandLine(args) 16 | .Build(); 17 | 18 | var host = new WebHostBuilder() 19 | .UseKestrel() 20 | .UseContentRoot(Directory.GetCurrentDirectory()) 21 | .UseIISIntegration() 22 | .UseConfiguration(config) 23 | .UseStartup() 24 | .UseApplicationInsights() 25 | .Build(); 26 | 27 | host.Run(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/SampleGames/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "anonymousAuthentication": true, 4 | "iisExpress": { 5 | "applicationUrl": "http://localhost:60058/", 6 | "sslPort": 0 7 | }, 8 | "windowsAuthentication": false 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "SampleGame": { 18 | "applicationUrl": "http://localhost:60059", 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /sample/SampleGames/SampleGames.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp1.1 4 | 5 | 6 | $(PackageTargetFallback);portable-net45+win8+wp8+wpa81; 7 | SampleGames 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /sample/SampleGames/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Logging; 9 | using RecurrentTasks; 10 | using SampleGames.Bots.CrazyCircle; 11 | using Telegram.Bot.Framework; 12 | 13 | namespace SampleGames 14 | { 15 | public class Startup 16 | { 17 | private readonly IConfigurationRoot _configuration; 18 | 19 | public Startup(IHostingEnvironment env) 20 | { 21 | var builder = new ConfigurationBuilder() 22 | .SetBasePath(env.ContentRootPath) 23 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 24 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 25 | .AddEnvironmentVariables(); 26 | _configuration = builder.Build(); 27 | } 28 | 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | services.AddCors(); 32 | services.AddDataProtection(); 33 | 34 | #region CrazyCircle Bot 35 | 36 | services.AddTelegramBot(_configuration.GetSection("CrazyCircleBot")) 37 | .AddUpdateHandler() 38 | .AddUpdateHandler() 39 | .Configure(); 40 | services.AddTask>(); 41 | 42 | #endregion 43 | } 44 | 45 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 46 | { 47 | loggerFactory.AddConsole(_configuration.GetSection("Logging")); 48 | loggerFactory.AddDebug(); 49 | 50 | ILogger logger = loggerFactory.CreateLogger(); 51 | 52 | if (env.IsDevelopment()) 53 | { 54 | app.UseDeveloperExceptionPage(); 55 | app.UseBrowserLink(); 56 | } 57 | else 58 | { 59 | app.UseExceptionHandler(appBuilder => 60 | appBuilder.Run(context => 61 | { 62 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 63 | return Task.CompletedTask; 64 | }) 65 | ); 66 | } 67 | 68 | app.UseCors(builder => builder 69 | .WithOrigins("http://crazy-circle-game.apphb.com") 70 | .WithMethods(HttpMethods.Get, HttpMethods.Post) 71 | .DisallowCredentials() 72 | ); 73 | 74 | app.UseStaticFiles(); 75 | 76 | #region CrazyCircle Bot 77 | 78 | app.UseTelegramGame(); 79 | 80 | if (env.IsDevelopment()) 81 | { 82 | app.UseTelegramBotLongPolling(); 83 | app.StartTask>(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(3)); 84 | logger.LogInformation("Update getting task is scheduled for bot " + nameof(CrazyCircleBot)); 85 | } 86 | else 87 | { 88 | app.UseTelegramBotWebhook(); 89 | logger.LogInformation("Webhook is set for bot " + nameof(CrazyCircleBot)); 90 | } 91 | 92 | #endregion 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /sample/SampleGames/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Warning" 6 | } 7 | }, 8 | "CrazyCircleBot": { 9 | "ApiToken": "", 10 | "BotUserName": "CrazyCircleBot", 11 | "PathToCertificate": "", 12 | "WebhookUrl": "https://example.com/bots/{bot}/webhook/{token}", 13 | "GameOptions": [ 14 | { 15 | "ShortName": "crazycircle", 16 | "Url": "http://crazy-circle-game.apphb.com/index.html", 17 | "ScoresUrl": "http://example.com/bots/{bot}/games/{game}/scores" 18 | } 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/SampleGames/wwwroot/CrazyCircleBot/Games/CrazyCircle/assets/scripts/TgBF.js: -------------------------------------------------------------------------------- 1 | function getValueFromUrl(url, key) { 2 | var value = null; 3 | var tokens = url.substr(url.indexOf('#') + 1).split('&'); 4 | for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { 5 | var t = tokens_1[_i]; 6 | if (t.match("^" + key + "=.+$")) { 7 | value = t.substr(key.length + 1); 8 | value = decodeURIComponent(value); 9 | } 10 | } 11 | return value; 12 | } 13 | function getPlayerId() { 14 | return getValueFromUrl(location.href, "id"); 15 | } 16 | function getScoreUrl() { 17 | return getValueFromUrl(location.href, "gameScoreUrl"); 18 | } 19 | function botGameScoreUrlExists() { 20 | var scoreUrl = getScoreUrl(); 21 | return scoreUrl !== null && scoreUrl.toString().length > 1; 22 | } 23 | //# sourceMappingURL=TgBF.js.map -------------------------------------------------------------------------------- /sample/SampleGames/wwwroot/CrazyCircleBot/Games/CrazyCircle/assets/scripts/script.js: -------------------------------------------------------------------------------- 1 | var Script; 2 | (function (Script) { 3 | var Stage = createjs.Stage; 4 | var Shape = createjs.Shape; 5 | var Ticker = createjs.Ticker; 6 | var Tween = createjs.Tween; 7 | var Ease = createjs.Ease; 8 | var Touch = createjs.Touch; 9 | var Text = createjs.Text; 10 | var isMobile = Touch.isSupported() === true; 11 | var canvas; 12 | var stage; 13 | var bg; 14 | var circle; 15 | var scoreText; 16 | var score = 0; 17 | var counter = 7; 18 | var counterInterval; 19 | var counterText; 20 | var hexValues = "0123456789ABCDEF"; 21 | function generateColor() { 22 | var color = "#"; 23 | for (var i = 0; i < 6; i++) { 24 | var i_1 = (Math.random() * 832740) % 16; 25 | color += hexValues.charAt(i_1); 26 | } 27 | return color; 28 | } 29 | function showScores() { 30 | if (!(this.readyState === 4 && this.status === 200)) { 31 | return; 32 | } 33 | var highScoreTexts = []; 34 | var highScores = JSON.parse(this.response); 35 | for (var i = 0; i < highScores.length; i++) { 36 | var score_1 = highScores[i]; 37 | var text = new Text(score_1.score + " " + score_1.user.first_name, "12pt sans", "yellow"); 38 | text.x = (canvas.width / 2) - (text.getMeasuredWidth() / 2); 39 | text.y = 5 + (1.5 * text.getMeasuredLineHeight() * i); 40 | highScoreTexts[i] = text; 41 | stage.addChild(text); 42 | } 43 | } 44 | function sendScore() { 45 | var scoresUrl = getScoreUrl(); 46 | var playerid = getPlayerId(); 47 | var xhr = new XMLHttpRequest(); 48 | var data = { 49 | playerId: playerid, 50 | score: score 51 | }; 52 | xhr.open("POST", scoresUrl); 53 | xhr.send(JSON.stringify(data)); 54 | xhr.addEventListener("readystatechange", function () { 55 | if (xhr.readyState === 4) { 56 | getScores(); 57 | } 58 | }); 59 | } 60 | function getScores() { 61 | var scoresUrl = getScoreUrl(); 62 | var playerid = getPlayerId(); 63 | var xhr = new XMLHttpRequest(); 64 | xhr.addEventListener("load", showScores); 65 | xhr.open("GET", scoresUrl + ("?id=" + encodeURIComponent(playerid))); 66 | xhr.send(); 67 | } 68 | function handleCounterInterval() { 69 | if (counter < 2) { 70 | clearInterval(counterInterval); 71 | stage.removeChild(circle, counterText); 72 | circle.removeEventListener("pressmove", beginMoveCircle); 73 | counter = null; 74 | counterInterval = null; 75 | if (botGameScoreUrlExists()) { 76 | sendScore(); 77 | } 78 | } 79 | else { 80 | counter--; 81 | counterText.text = counter.toString(); 82 | } 83 | } 84 | function beginMoveCircle(ev) { 85 | function setBgToDark() { 86 | bg.graphics 87 | .clear() 88 | .beginFill("#555") 89 | .drawRect(0, 0, canvas.width, canvas.height); 90 | } 91 | var distance = Math.sqrt(Math.pow(circle.x - ev.stageX, 2) + 92 | Math.pow(circle.y - ev.stageY, 2)); 93 | distance = Math.floor(distance * Math.random() / 10); 94 | score += distance; 95 | scoreText.text = score.toString(); 96 | var bgColor = "#458"; 97 | var distanceThreshold = 12; 98 | if (isMobile) { 99 | distanceThreshold = 3; 100 | } 101 | if (distance > distanceThreshold) { 102 | bgColor = generateColor(); 103 | } 104 | bg.graphics 105 | .clear() 106 | .beginFill(bgColor) 107 | .drawRect(0, 0, canvas.width, canvas.height); 108 | Tween.get(circle) 109 | .to({ 110 | x: ev.stageX, 111 | y: ev.stageY 112 | }, undefined, Ease.backInOut) 113 | .call(setBgToDark); 114 | } 115 | function draw() { 116 | bg = new Shape(); 117 | bg.graphics 118 | .beginFill('#555') 119 | .drawRect(0, 0, canvas.width, canvas.height); 120 | stage.addChild(bg); 121 | circle = new Shape(); 122 | circle.graphics 123 | .beginFill("red") 124 | .beginStroke('yellow') 125 | .drawCircle(0, 0, 20); 126 | circle.x = canvas.width / 2; 127 | circle.y = canvas.height / 2; 128 | stage.addChild(circle); 129 | circle.addEventListener("pressmove", beginMoveCircle); 130 | scoreText = new Text(score.toString(), "14pt sans", "yellow"); 131 | scoreText.x = 4; 132 | scoreText.y = 7; 133 | stage.addChild(scoreText); 134 | counterText = new Text(counter.toString(), "14pt sans", "#fff"); 135 | counterText.x = canvas.width - counterText.getMeasuredWidth() - 10; 136 | counterText.y = 7; 137 | stage.addChild(counterText); 138 | } 139 | function resizeCanvas() { 140 | canvas.width = window.innerWidth; 141 | canvas.height = window.innerHeight; 142 | } 143 | function init() { 144 | canvas = document.getElementById("canvas"); 145 | window.addEventListener("resize", resizeCanvas, false); 146 | resizeCanvas(); 147 | stage = new Stage(canvas); 148 | Touch.enable(stage); 149 | Ticker.framerate = 60; 150 | Ticker.addEventListener("tick", function () { return stage.update(); }); 151 | draw(); 152 | counterInterval = setInterval(handleCounterInterval, 1 * 1000); 153 | } 154 | Script.init = init; 155 | })(Script || (Script = {})); 156 | window.addEventListener("load", Script.init); 157 | //# sourceMappingURL=script.js.map -------------------------------------------------------------------------------- /sample/SampleGames/wwwroot/CrazyCircleBot/Games/CrazyCircle/assets/styles/game.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, body { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | canvas { 12 | display: block; 13 | background-color: #555; 14 | } 15 | -------------------------------------------------------------------------------- /sample/SampleGames/wwwroot/CrazyCircleBot/Games/CrazyCircle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/SampleGames/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramBots/Telegram.Bot.Framework/6ac27f80d1ae29f86ccf790d7d2a5b21759182ec/sample/SampleGames/wwwroot/favicon.ico -------------------------------------------------------------------------------- /scripts/build/index.js: -------------------------------------------------------------------------------- 1 | const $ = require('shelljs') 2 | const path = require('path') 3 | require('../logging') 4 | 5 | $.config.fatal = true 6 | const root = path.resolve(`${__dirname}/../..`) 7 | 8 | 9 | try { 10 | console.info(`building Docker image`) 11 | 12 | console.debug('building the solution with "Release" configuration and "quickstart:latest" tag') 13 | $.cd(root) 14 | $.exec( 15 | `docker build --tag "quickstart:latest" --no-cache .` 16 | ) 17 | } catch (e) { 18 | console.error(e) 19 | process.exit(1) 20 | } 21 | 22 | console.info(`✅ Build succeeded`) 23 | -------------------------------------------------------------------------------- /scripts/deploy/deploy_docker_registry.js: -------------------------------------------------------------------------------- 1 | const $ = require('shelljs') 2 | require('../logging') 3 | 4 | $.config.fatal = true 5 | 6 | 7 | exports.deploy = function (source, target, user, pass) { 8 | console.info(`pushing docker local image ${source} to ${target}`) 9 | 10 | $.exec(`docker tag ${source} ${target}`) 11 | $.exec(`docker login --username ${user} --password ${pass}`) 12 | $.exec(`docker push ${target}`) 13 | $.exec('docker logout') 14 | } 15 | -------------------------------------------------------------------------------- /scripts/deploy/deploy_heroku.js: -------------------------------------------------------------------------------- 1 | const $ = require('shelljs') 2 | require('../logging') 3 | 4 | $.config.fatal = true 5 | 6 | 7 | function validate_args(...args) { 8 | if (!args.every(x => x && x.length)) { 9 | throw `All the required parameters should have value.` 10 | } 11 | } 12 | 13 | function push_image_to_heroku(app, source, dyno, user, token) { 14 | console.info('pushing docker image to heroku') 15 | 16 | console.debug('connecting to heroku docker registry') 17 | $.exec(`docker login --username ${user} --password ${token} registry.heroku.com`) 18 | 19 | console.debug('tagging the image') 20 | $.exec(`docker tag ${source} registry.heroku.com/${app}/${dyno}`) 21 | 22 | console.debug('pushing the image') 23 | $.exec(`docker push registry.heroku.com/${app}/${dyno}`) 24 | } 25 | 26 | function release_heroku_app(app, source, dyno, token) { 27 | console.info('deploying the image to heroku dyno') 28 | 29 | console.debug(`getting docker image ID`) 30 | const image_id = $.exec(`docker inspect ${source} --format={{.Id}}`).stdout.trim() 31 | 32 | console.debug(`upgrading to new release`) 33 | const post_data = JSON.stringify({ 34 | updates: [{ 35 | type: dyno, 36 | docker_image: image_id 37 | }] 38 | }) 39 | 40 | $.exec( 41 | `curl -X PATCH https://api.heroku.com/apps/${app}/formation ` + 42 | `-H 'Authorization: Bearer ${token}' ` + 43 | `-H "Content-Type: application/json" ` + 44 | `-H "Accept: application/vnd.heroku+json; version=3.docker-releases" ` + 45 | `-d ${JSON.stringify(post_data)}` 46 | ) 47 | } 48 | 49 | 50 | exports.deploy = function (app, source, dyno, user, token) { 51 | validate_args(app, source, dyno, user, token) 52 | push_image_to_heroku(app, source, dyno, user, token) 53 | release_heroku_app(app, source, dyno, token) 54 | } -------------------------------------------------------------------------------- /scripts/deploy/deploy_settings.js: -------------------------------------------------------------------------------- 1 | exports.get_deployment_settings = () => { 2 | const jsonValue = process.env['DEPLOY_SETTINGS_JSON'] 3 | let settings; 4 | try { 5 | settings = JSON.parse(jsonValue) 6 | } catch (e) { 7 | throw `Value of "DEPLOY_SETTINGS_JSON" environment variable is not valid JSON.` 8 | } 9 | 10 | return settings 11 | } 12 | -------------------------------------------------------------------------------- /scripts/deploy/index.js: -------------------------------------------------------------------------------- 1 | require('../logging') 2 | 3 | 4 | function get_environment_name() { 5 | console.info('verifying environment name') 6 | 7 | const environment_name = process.argv[process.argv.length - 1] 8 | if (environment_name && environment_name.length) { 9 | console.debug(`environment is ${environment_name}.`) 10 | return environment_name 11 | } else { 12 | throw `No environment name is passed.\n` + 13 | `\tExample: node ci/deploy Staging` 14 | } 15 | } 16 | 17 | function get_deployments_for_env(environment_name) { 18 | console.info(`finding deployments for environment ${environment_name}.`) 19 | 20 | const jsonValue = process.env['DEPLOY_SETTINGS_JSON'] 21 | let deployment_map; 22 | try { 23 | deployment_map = JSON.parse(jsonValue) 24 | } catch (e) { 25 | throw `Value of "DEPLOY_SETTINGS_JSON" environment variable is not valid JSON.` 26 | } 27 | 28 | const env_deployments = deployment_map[environment_name]; 29 | if (!env_deployments) { 30 | throw `There are no field for environment ${environment_name} in "DEPLOY_SETTINGS_JSON" value.` 31 | } 32 | if (!(Array.isArray(env_deployments) && env_deployments.length)) { 33 | console.warn(`There are deployments specified for environment ${environment_name}.`) 34 | } 35 | 36 | console.debug(`${env_deployments.length || 0} deployments found.`) 37 | 38 | return env_deployments 39 | } 40 | 41 | function deploy(environment_name, deployment) { 42 | console.info(`deploying to ${deployment.type} for environment ${environment_name}.`) 43 | const docker = require('./deploy_docker_registry') 44 | const heorku = require('./deploy_heroku') 45 | 46 | if (deployment.type === 'docker') { 47 | docker.deploy( 48 | deployment.options.source, 49 | deployment.options.target, 50 | deployment.options.user, 51 | deployment.options.pass 52 | ) 53 | } else if (deployment.type === 'heroku') { 54 | heorku.deploy( 55 | deployment.options.app, 56 | deployment.options.source, 57 | deployment.options.dyno, 58 | deployment.options.user, 59 | deployment.options.token 60 | ) 61 | } else { 62 | throw `Invalid deployment type ${deployment.type}.` 63 | } 64 | } 65 | 66 | 67 | try { 68 | const environment_name = get_environment_name() 69 | get_deployments_for_env(environment_name) 70 | .forEach(d => deploy(environment_name, d)) 71 | } catch (e) { 72 | console.error(e) 73 | process.exit(1) 74 | } 75 | -------------------------------------------------------------------------------- /scripts/logging.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const $ = require('shelljs') 3 | 4 | if (process.env['TRAVIS'] && process.env['CI']) { 5 | console.info = m => $.echo("\033[1;34m", m, "\033[0m") 6 | console.debug = m => $.echo("\033[0;32m", m, "\033[0m") 7 | console.warn = m => $.echo("\033[1;33m", m, "\033[0m") 8 | console.error = m => $.echo("\033[1;31m", m, "\033[0m") 9 | } else { 10 | console.info = m => console.log(chalk.blue.bold(m)) 11 | console.debug = m => console.log(chalk.green.bold(m)) 12 | console.warn = m => console.log(chalk.yellow.bold(m)) 13 | console.error = m => console.log(chalk.red.bold(m)) 14 | } -------------------------------------------------------------------------------- /scripts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-styles": { 8 | "version": "3.2.1", 9 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 10 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 11 | "requires": { 12 | "color-convert": "1.9.3" 13 | } 14 | }, 15 | "balanced-match": { 16 | "version": "1.0.0", 17 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 18 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 19 | }, 20 | "brace-expansion": { 21 | "version": "1.1.11", 22 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 23 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 24 | "requires": { 25 | "balanced-match": "1.0.0", 26 | "concat-map": "0.0.1" 27 | } 28 | }, 29 | "chalk": { 30 | "version": "2.4.1", 31 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 32 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 33 | "requires": { 34 | "ansi-styles": "3.2.1", 35 | "escape-string-regexp": "1.0.5", 36 | "supports-color": "5.5.0" 37 | } 38 | }, 39 | "color-convert": { 40 | "version": "1.9.3", 41 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 42 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 43 | "requires": { 44 | "color-name": "1.1.3" 45 | } 46 | }, 47 | "color-name": { 48 | "version": "1.1.3", 49 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 50 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 51 | }, 52 | "concat-map": { 53 | "version": "0.0.1", 54 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 55 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 56 | }, 57 | "escape-string-regexp": { 58 | "version": "1.0.5", 59 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 60 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 61 | }, 62 | "fs.realpath": { 63 | "version": "1.0.0", 64 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 65 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 66 | }, 67 | "glob": { 68 | "version": "7.1.3", 69 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 70 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 71 | "requires": { 72 | "fs.realpath": "1.0.0", 73 | "inflight": "1.0.6", 74 | "inherits": "2.0.3", 75 | "minimatch": "3.0.4", 76 | "once": "1.4.0", 77 | "path-is-absolute": "1.0.1" 78 | } 79 | }, 80 | "has-flag": { 81 | "version": "3.0.0", 82 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 83 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 84 | }, 85 | "inflight": { 86 | "version": "1.0.6", 87 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 88 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 89 | "requires": { 90 | "once": "1.4.0", 91 | "wrappy": "1.0.2" 92 | } 93 | }, 94 | "inherits": { 95 | "version": "2.0.3", 96 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 97 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 98 | }, 99 | "interpret": { 100 | "version": "1.1.0", 101 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", 102 | "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" 103 | }, 104 | "minimatch": { 105 | "version": "3.0.4", 106 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 107 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 108 | "requires": { 109 | "brace-expansion": "1.1.11" 110 | } 111 | }, 112 | "once": { 113 | "version": "1.4.0", 114 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 115 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 116 | "requires": { 117 | "wrappy": "1.0.2" 118 | } 119 | }, 120 | "path-is-absolute": { 121 | "version": "1.0.1", 122 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 123 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 124 | }, 125 | "path-parse": { 126 | "version": "1.0.6", 127 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 128 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" 129 | }, 130 | "rechoir": { 131 | "version": "0.6.2", 132 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 133 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 134 | "requires": { 135 | "resolve": "1.8.1" 136 | } 137 | }, 138 | "resolve": { 139 | "version": "1.8.1", 140 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 141 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 142 | "requires": { 143 | "path-parse": "1.0.6" 144 | } 145 | }, 146 | "shelljs": { 147 | "version": "0.8.2", 148 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", 149 | "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", 150 | "requires": { 151 | "glob": "7.1.3", 152 | "interpret": "1.1.0", 153 | "rechoir": "0.6.2" 154 | } 155 | }, 156 | "supports-color": { 157 | "version": "5.5.0", 158 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 159 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 160 | "requires": { 161 | "has-flag": "3.0.0" 162 | } 163 | }, 164 | "wrappy": { 165 | "version": "1.0.2", 166 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 167 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "0.1.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "npm run build && npm run test", 7 | "build": "node build", 8 | "test": "node test" 9 | }, 10 | "license": "ISC", 11 | "dependencies": { 12 | "chalk": "^2.3.1", 13 | "shelljs": "^0.8.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scripts/publish-bots.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | current_dir="`pwd`" 5 | root_dir="${current_dir}/.." 6 | 7 | declare -a sample_projects=("SampleEchoBot" "SampleGames") 8 | 9 | for project in "${sample_projects[@]}" 10 | do 11 | echo; echo "@> Build and publish project ${project}"; echo; 12 | 13 | cd "${root_dir}/sample/${project}" && 14 | rm -rf bin/publish/ && 15 | dotnet publish -c Release -o bin/publish/ && 16 | cp -v "${current_dir}/Dockerfile" Dockerfile 17 | done 18 | 19 | echo; echo "@> Copy nginx Dockerfile to its context"; echo; 20 | 21 | cd "${current_dir}" && 22 | cp -v nginx.Dockerfile nginx/Dockerfile 23 | 24 | echo; echo "@> Build docker compose"; echo; 25 | 26 | cd "${current_dir}" && 27 | docker-compose build 28 | 29 | echo; echo "@> Remove copied Dockerfiles"; echo; 30 | 31 | find "${root_dir}/sample" -type f -name Dockerfile -print0 | xargs -0 rm -v 32 | rm -v "${current_dir}/nginx/Dockerfile" 33 | 34 | echo; echo "@> Restart and update containers"; echo; 35 | 36 | cd "${current_dir}" && 37 | docker-compose down && 38 | docker-compose up -d 39 | -------------------------------------------------------------------------------- /scripts/test/index.js: -------------------------------------------------------------------------------- 1 | const $ = require('shelljs') 2 | require('../logging') 3 | 4 | $.config.fatal = true 5 | 6 | const unit_test_script = require('./unit_tests') 7 | 8 | try { 9 | unit_test_script.run_unit_tests('Release') 10 | } catch (e) { 11 | console.error(e) 12 | process.exit(1) 13 | } 14 | 15 | console.info(`Tests succeeded.`) -------------------------------------------------------------------------------- /scripts/test/unit_tests.js: -------------------------------------------------------------------------------- 1 | const $ = require('shelljs') 2 | const path = require('path') 3 | require('../logging') 4 | 5 | $.config.fatal = true 6 | const root = path.join(__dirname, '..', '..') 7 | 8 | 9 | module.exports.run_unit_tests = function (configuration) { 10 | console.info(`Running unit tests with configuartion ${configuration}`) 11 | 12 | $.exec( 13 | `docker run --rm ` + 14 | `--volume "${root}:/project" ` + 15 | `--workdir /project/test/UnitTests.NetCore/ ` + 16 | `microsoft/dotnet:2.1.402-sdk ` + 17 | `dotnet test --configuration ${configuration} --framework netcoreapp2.1` 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/ASP.NET Core/TelegramBotMiddleware.cs: -------------------------------------------------------------------------------- 1 | #if !NETFRAMEWORK 2 | 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using Newtonsoft.Json; 7 | using System; 8 | using System.IO; 9 | using System.Threading.Tasks; 10 | using Telegram.Bot.Framework.Abstractions; 11 | using Telegram.Bot.Types; 12 | 13 | namespace Telegram.Bot.Framework 14 | { 15 | internal class TelegramBotMiddleware 16 | where TBot : BotBase 17 | { 18 | private readonly RequestDelegate _next; 19 | 20 | private readonly UpdateDelegate _updateDelegate; 21 | 22 | private readonly ILogger> _logger; 23 | 24 | /// 25 | /// Initializes an instance of middleware 26 | /// 27 | /// Instance of request delegate 28 | /// Logger for this middleware 29 | public TelegramBotMiddleware( 30 | RequestDelegate next, 31 | UpdateDelegate updateDelegate, 32 | ILogger> logger 33 | ) 34 | { 35 | _next = next; 36 | _updateDelegate = updateDelegate; 37 | _logger = logger; 38 | } 39 | 40 | /// 41 | /// Gets invoked to handle the incoming request 42 | /// 43 | /// 44 | public async Task Invoke(HttpContext context) 45 | { 46 | if (context.Request.Method != HttpMethods.Post) 47 | { 48 | await _next.Invoke(context) 49 | .ConfigureAwait(false); 50 | return; 51 | } 52 | 53 | string payload; 54 | using (var reader = new StreamReader(context.Request.Body)) 55 | { 56 | payload = await reader.ReadToEndAsync() 57 | .ConfigureAwait(false); 58 | } 59 | 60 | _logger.LogDebug("Update payload:\n{0}", payload); 61 | 62 | Update update = null; 63 | try 64 | { 65 | update = JsonConvert.DeserializeObject(payload); 66 | } 67 | catch (JsonException e) 68 | { 69 | _logger.LogError($"Unable to deserialize update payload. {e.Message}"); 70 | } 71 | 72 | if (update == null) 73 | { 74 | // it is unlikely of Telegram to send an invalid update object. 75 | // respond with "404 Not Found" in case an attacker is trying to find the webhook URL 76 | context.Response.StatusCode = 404; 77 | return; 78 | } 79 | 80 | using (var scope = context.RequestServices.CreateScope()) 81 | { 82 | var bot = scope.ServiceProvider.GetRequiredService(); 83 | var updateContext = new UpdateContext(bot, update, scope.ServiceProvider); 84 | updateContext.Items.Add(nameof(HttpContext), context); 85 | 86 | try 87 | { 88 | await _updateDelegate(updateContext) 89 | .ConfigureAwait(false); 90 | } 91 | catch (Exception e) 92 | { 93 | _logger.LogError($"Error occured while handling update `{update.Id}`. {e.Message}"); 94 | context.Response.StatusCode = StatusCodes.Status500InternalServerError; 95 | } 96 | } 97 | 98 | if (!context.Response.HasStarted) 99 | { 100 | context.Response.StatusCode = 201; 101 | } 102 | } 103 | } 104 | } 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/ASP.NET Core/TelegramBotMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NETFRAMEWORK 2 | 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Options; 5 | using Telegram.Bot.Framework; 6 | using Telegram.Bot.Framework.Abstractions; 7 | 8 | namespace Microsoft.AspNetCore.Builder 9 | { 10 | /// 11 | /// Extenstion methods for adding Telegram Bot framework to the ASP.NET Core middleware 12 | /// 13 | public static class TelegramBotMiddlewareExtensions 14 | { 15 | /// 16 | /// Add Telegram bot webhook handling functionality to the pipeline 17 | /// 18 | /// Type of bot 19 | /// Instance of IApplicationBuilder 20 | /// Whether to set the webhook immediately by making a request to Telegram bot API 21 | /// Instance of IApplicationBuilder 22 | public static IApplicationBuilder UseTelegramBotWebhook( 23 | this IApplicationBuilder app, 24 | IBotBuilder botBuilder 25 | ) 26 | where TBot : BotBase 27 | { 28 | var updateDelegate = botBuilder.Build(); 29 | 30 | var options = app.ApplicationServices.GetRequiredService>>(); 31 | app.Map( 32 | options.Value.WebhookPath, 33 | builder => builder.UseMiddleware>(updateDelegate) 34 | ); 35 | 36 | return app; 37 | } 38 | 39 | ///// 40 | ///// Removes and disables webhooks for bot 41 | ///// 42 | ///// Type of bot 43 | ///// Instance of IApplicationBuilder 44 | ///// If true, a request is immediately made to delete webhook 45 | ///// Instance of IApplicationBuilder 46 | //public static IApplicationBuilder UseTelegramBotLongPolling(this IApplicationBuilder app, bool ensureWebhookDisabled = true) 47 | // where TBot : BotBase 48 | //{ 49 | // IBotManager botManager = FindBotManager(app); 50 | 51 | // if (ensureWebhookDisabled) 52 | // { 53 | // botManager.SetWebhookStateAsync(false).Wait(); 54 | // } 55 | 56 | // return app; 57 | //} 58 | 59 | ///// 60 | ///// Add a Telegram game score middleware to the app 61 | ///// 62 | ///// Type of bot 63 | ///// Instance of IApplicationBuilder 64 | ///// Instance of IApplicationBuilder 65 | //public static IApplicationBuilder UseTelegramGame(this IApplicationBuilder app) 66 | // where TBot : BotBase 67 | //{ 68 | // app.UseMiddleware>(); 69 | 70 | // return app; 71 | //} 72 | 73 | //private static IBotManager FindBotManager(IApplicationBuilder app) 74 | // where TBot : BotBase 75 | //{ 76 | // IBotManager botManager; 77 | // try 78 | // { 79 | // botManager = app.ApplicationServices.GetRequiredService>(); 80 | // if (botManager == null) 81 | // { 82 | // throw new NullReferenceException(); 83 | // } 84 | // } 85 | // catch (Exception) 86 | // { 87 | // throw new ConfigurationException( 88 | // "Bot Manager service is not available", string.Format("Use services.{0}<{1}>()", 89 | // nameof(TelegramBotFrameworkIServiceCollectionExtensions.AddTelegramBot), typeof(TBot).Name)); 90 | // } 91 | // return botManager; 92 | //} 93 | } 94 | } 95 | 96 | #endif -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/IBot.cs: -------------------------------------------------------------------------------- 1 | namespace Telegram.Bot.Framework.Abstractions 2 | { 3 | /// 4 | /// A wrapper around TelegramBot class. Used to make calls to the Bot API 5 | /// 6 | public interface IBot 7 | { 8 | string Username { get; } 9 | 10 | /// 11 | /// Instance of Telegram bot client 12 | /// 13 | ITelegramBotClient Client { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/IBotBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Telegram.Bot.Framework.Abstractions 4 | { 5 | public interface IBotBuilder 6 | { 7 | IBotBuilder Use(Func middleware); 8 | 9 | IBotBuilder Use(Func component); 10 | 11 | IBotBuilder Use() 12 | where THandler : IUpdateHandler; 13 | 14 | IBotBuilder Use(THandler handler) 15 | where THandler : IUpdateHandler; 16 | 17 | UpdateDelegate Build(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/IBotOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Telegram.Bot.Framework.Abstractions 2 | { 3 | /// 4 | /// Configurations for the bot 5 | /// 6 | public interface IBotOptions 7 | { 8 | string Username { get; } 9 | 10 | /// 11 | /// Optional if client not needed. Telegram API token 12 | /// 13 | string ApiToken { get; } 14 | 15 | string WebhookPath { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/IBotServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Telegram.Bot.Framework.Abstractions 4 | { 5 | public interface IBotServiceProvider : IServiceProvider, IDisposable 6 | { 7 | IBotServiceProvider CreateScope(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/IUpdateContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Telegram.Bot.Types; 4 | 5 | namespace Telegram.Bot.Framework.Abstractions 6 | { 7 | public interface IUpdateContext 8 | { 9 | IBot Bot { get; } 10 | 11 | Update Update { get; } 12 | 13 | IServiceProvider Services { get; } 14 | 15 | IDictionary Items { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/IUpdateHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Telegram.Bot.Framework.Abstractions 4 | { 5 | /// 6 | /// Processes an update 7 | /// 8 | public interface IUpdateHandler 9 | { 10 | /// 11 | /// Handles the update for bot. This method will be called only if CanHandleUpdate returns true 12 | /// 13 | /// Instance of the bot this command is operating for 14 | /// The update to be handled 15 | /// Result of handling this update 16 | Task HandleAsync(IUpdateContext context, UpdateDelegate next); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/IUpdatePollingManager.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable UnusedTypeParameter 2 | 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Telegram.Bot.Requests; 6 | 7 | namespace Telegram.Bot.Framework.Abstractions 8 | { 9 | public interface IUpdatePollingManager 10 | where TBot : IBot 11 | { 12 | Task RunAsync( 13 | GetUpdatesRequest requestParams = default, 14 | CancellationToken cancellationToken = default 15 | ); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Abstractions/UpdateDelegate.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Telegram.Bot.Framework.Abstractions 4 | { 5 | public delegate Task UpdateDelegate(IUpdateContext context); 6 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/BotBase.cs: -------------------------------------------------------------------------------- 1 | using Telegram.Bot.Framework.Abstractions; 2 | 3 | namespace Telegram.Bot.Framework 4 | { 5 | public abstract class BotBase : IBot 6 | { 7 | public ITelegramBotClient Client { get; } 8 | 9 | public string Username { get; } 10 | 11 | protected BotBase(string username, ITelegramBotClient client) 12 | { 13 | Username = username; 14 | Client = client; 15 | } 16 | 17 | protected BotBase(string username, string token) 18 | : this(username, new TelegramBotClient(token)) 19 | { 20 | } 21 | 22 | protected BotBase(IBotOptions options) 23 | : this(options.Username, new TelegramBotClient(options.ApiToken)) 24 | { 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/BotOptions.cs: -------------------------------------------------------------------------------- 1 | using Telegram.Bot.Framework.Abstractions; 2 | 3 | namespace Telegram.Bot.Framework 4 | { 5 | /// 6 | /// Configurations for the bot 7 | /// 8 | /// Type of Bot 9 | public class BotOptions : IBotOptions 10 | where TBot : IBot 11 | { 12 | public string Username { get; set; } 13 | 14 | /// 15 | /// Optional if client not needed. Telegram API token 16 | /// 17 | public string ApiToken { get; set; } 18 | 19 | public string WebhookPath { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/CommandBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | using Telegram.Bot.Types; 7 | using Telegram.Bot.Types.Enums; 8 | 9 | namespace Telegram.Bot.Framework.Abstractions 10 | { 11 | /// 12 | /// Base handler implementation for a command such as "/start" 13 | /// 14 | public abstract class CommandBase : IUpdateHandler 15 | { 16 | public abstract Task HandleAsync(IUpdateContext context, UpdateDelegate next, string[] args); 17 | 18 | public Task HandleAsync(IUpdateContext context, UpdateDelegate next) 19 | { 20 | return HandleAsync(context, next, ParseCommandArgs(context.Update.Message)); 21 | } 22 | 23 | public static string[] ParseCommandArgs(Message message) 24 | { 25 | if (message is null) 26 | throw new ArgumentNullException(nameof(message)); 27 | if (message.Entities?.FirstOrDefault()?.Type != MessageEntityType.BotCommand) 28 | throw new ArgumentException("Message is not a command", nameof(message)); 29 | 30 | var argsList = new List(); 31 | string allArgs = message.Text.Substring(message.Entities[0].Length).TrimStart(); 32 | argsList.Add(allArgs); 33 | 34 | var expandedArgs = Regex.Split(allArgs, @"\s+"); 35 | if (expandedArgs.Length > 1) 36 | { 37 | argsList.AddRange(expandedArgs); 38 | } 39 | 40 | string[] args = argsList 41 | .Where(x => !string.IsNullOrWhiteSpace(x)) 42 | .ToArray(); 43 | 44 | return args; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Extensions/BotExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | using Telegram.Bot.Framework.Abstractions; 5 | using Telegram.Bot.Types; 6 | using Telegram.Bot.Types.Enums; 7 | 8 | namespace Telegram.Bot.Framework 9 | { 10 | public static class BotExtensions 11 | { 12 | public static bool CanHandleCommand(this IBot bot, string commandName, Message message) 13 | { 14 | if (string.IsNullOrWhiteSpace(commandName)) 15 | throw new ArgumentException("Invalid command name", nameof(commandName)); 16 | if (message == null) 17 | throw new ArgumentNullException(nameof(message)); 18 | if (commandName.StartsWith("/")) 19 | throw new ArgumentException("Command name must not start with '/'.", nameof(commandName)); 20 | 21 | { 22 | bool isTextMessage = message.Text != null; 23 | if (!isTextMessage) 24 | return false; 25 | } 26 | 27 | { 28 | bool isCommand = message.Entities?.FirstOrDefault()?.Type == MessageEntityType.BotCommand; 29 | if (!isCommand) 30 | return false; 31 | } 32 | 33 | return Regex.IsMatch( 34 | message.EntityValues.First(), 35 | $@"^/{commandName}(?:@{bot.Username})?$", 36 | RegexOptions.IgnoreCase 37 | ); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Telegram.Bot.Framework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net45;netstandard1.3 5 | Telegram.Bot.Framework 6 | 2.0.0-alpha4 7 | 8 | latest 9 | True 10 | True 11 | 12 | Telegram Bot Framework 13 | Simple framework for building powerful Telegram bots 🤖 14 | Poulad,TelegramBots 15 | telegram;bot;chatbot;chat-framework;framework;netcore 16 | false 17 | git 18 | https://github.com/TelegramBots/Telegram.Bot.Framework 19 | https://github.com/TelegramBots/Telegram.Bot.Framework.git 20 | https://raw.githubusercontent.com/TelegramBots/Telegram.Bot.Framework/master/docs/icon.png 21 | https://raw.githubusercontent.com/TelegramBots/Telegram.Bot.Framework/master/LICENSE 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Update Pipeline/BotBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Telegram.Bot.Framework.Abstractions; 6 | 7 | namespace Telegram.Bot.Framework 8 | { 9 | public class BotBuilder : IBotBuilder 10 | { 11 | internal UpdateDelegate UpdateDelegate { get; private set; } 12 | 13 | private readonly ICollection> _components; 14 | 15 | public BotBuilder() 16 | { 17 | _components = new List>(); 18 | } 19 | 20 | public IBotBuilder Use(Func middleware) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public IBotBuilder Use() 26 | where THandler : IUpdateHandler 27 | { 28 | _components.Add( 29 | next => 30 | context => 31 | ((IUpdateHandler)context.Services.GetService(typeof(THandler))) 32 | .HandleAsync(context, next) 33 | ); 34 | 35 | return this; 36 | } 37 | 38 | public IBotBuilder Use(THandler handler) 39 | where THandler : IUpdateHandler 40 | { 41 | _components.Add(next => 42 | context => handler.HandleAsync(context, next) 43 | ); 44 | 45 | return this; 46 | } 47 | 48 | public IBotBuilder Use(Func component) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | 53 | public UpdateDelegate Build() 54 | { 55 | UpdateDelegate handle = context => 56 | { 57 | // use Logger 58 | Console.WriteLine("No handler for update {0} of type {1}.", context.Update.Id, context.Update.Type); 59 | return Task.FromResult(1); 60 | }; 61 | 62 | foreach (var component in _components.Reverse()) 63 | { 64 | handle = component(handle); 65 | } 66 | 67 | return UpdateDelegate = handle; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Update Pipeline/BotBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Telegram.Bot.Framework.Abstractions; 3 | using Telegram.Bot.Framework.Pipeline; 4 | 5 | namespace Telegram.Bot.Framework 6 | { 7 | public static class BotBuilderExtensions 8 | { 9 | public static IBotBuilder UseWhen( 10 | this IBotBuilder builder, 11 | Predicate predicate, 12 | Action configure 13 | ) 14 | { 15 | var branchBuilder = new BotBuilder(); 16 | configure(branchBuilder); 17 | UpdateDelegate branchDelegate = branchBuilder.Build(); 18 | 19 | builder.Use(new UseWhenMiddleware(predicate, branchDelegate)); 20 | 21 | return builder; 22 | } 23 | 24 | public static IBotBuilder UseWhen( 25 | this IBotBuilder builder, 26 | Predicate predicate 27 | ) 28 | where THandler : IUpdateHandler 29 | { 30 | var branchDelegate = new BotBuilder().Use().Build(); 31 | builder.Use(new UseWhenMiddleware(predicate, branchDelegate)); 32 | return builder; 33 | } 34 | 35 | public static IBotBuilder MapWhen( 36 | this IBotBuilder builder, 37 | Predicate predicate, 38 | Action configure 39 | ) 40 | { 41 | var mapBuilder = new BotBuilder(); 42 | configure(mapBuilder); 43 | UpdateDelegate mapDelegate = mapBuilder.Build(); 44 | 45 | builder.Use(new MapWhenMiddleware(predicate, mapDelegate)); 46 | 47 | return builder; 48 | } 49 | 50 | public static IBotBuilder MapWhen( 51 | this IBotBuilder builder, 52 | Predicate predicate 53 | ) 54 | where THandler : IUpdateHandler 55 | { 56 | var branchDelegate = new BotBuilder().Use().Build(); 57 | builder.Use(new MapWhenMiddleware(predicate, branchDelegate)); 58 | return builder; 59 | } 60 | 61 | public static IBotBuilder UseCommand( 62 | this IBotBuilder builder, 63 | string command 64 | ) 65 | where TCommand : CommandBase 66 | => builder 67 | .MapWhen( 68 | ctx => ctx.Bot.CanHandleCommand(command, ctx.Update.Message), 69 | botBuilder => botBuilder.Use() 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Update Pipeline/MapWhenMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace Telegram.Bot.Framework.Pipeline 6 | { 7 | internal class MapWhenMiddleware : IUpdateHandler 8 | { 9 | private readonly Predicate _predicate; 10 | 11 | private readonly UpdateDelegate _branch; 12 | 13 | public MapWhenMiddleware(Predicate predicate, UpdateDelegate branch) 14 | { 15 | _predicate = predicate; 16 | _branch = branch; 17 | } 18 | 19 | public Task HandleAsync(IUpdateContext context, UpdateDelegate next) 20 | => _predicate(context) 21 | ? _branch(context) 22 | : next(context); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/Update Pipeline/UseWhenMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace Telegram.Bot.Framework.Pipeline 6 | { 7 | internal class UseWhenMiddleware : IUpdateHandler 8 | { 9 | private readonly Predicate _predicate; 10 | 11 | private readonly UpdateDelegate _branch; 12 | 13 | public UseWhenMiddleware(Predicate predicate, UpdateDelegate branch) 14 | { 15 | _predicate = predicate; 16 | _branch = branch; 17 | } 18 | 19 | public async Task HandleAsync(IUpdateContext context, UpdateDelegate next) 20 | { 21 | if (_predicate(context)) 22 | { 23 | await _branch(context).ConfigureAwait(false); 24 | } 25 | 26 | await next(context).ConfigureAwait(false); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/UpdateContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using Telegram.Bot.Framework.Abstractions; 5 | using Telegram.Bot.Types; 6 | 7 | namespace Telegram.Bot.Framework 8 | { 9 | public class UpdateContext : IUpdateContext 10 | { 11 | public IBot Bot { get; } 12 | 13 | public Update Update { get; } 14 | 15 | public IServiceProvider Services { get; } 16 | 17 | public IDictionary Items { get; } 18 | 19 | public UpdateContext(IBot bot, Update u, IServiceProvider services) 20 | { 21 | Bot = bot; 22 | Update = u; 23 | Services = services; 24 | Items = new ConcurrentDictionary(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Telegram.Bot.Framework/UpdatePollingManager.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Requests; 5 | using Telegram.Bot.Types; 6 | using Telegram.Bot.Types.Enums; 7 | 8 | namespace Telegram.Bot.Framework 9 | { 10 | public class UpdatePollingManager : IUpdatePollingManager 11 | where TBot : IBot 12 | { 13 | private readonly UpdateDelegate _updateDelegate; 14 | 15 | private readonly IBotServiceProvider _rootProvider; 16 | 17 | public UpdatePollingManager( 18 | IBotBuilder botBuilder, 19 | IBotServiceProvider rootProvider 20 | ) 21 | { 22 | // ToDo Receive update types array 23 | _updateDelegate = botBuilder.Build(); 24 | _rootProvider = rootProvider; 25 | } 26 | 27 | public async Task RunAsync( 28 | GetUpdatesRequest requestParams = default, 29 | CancellationToken cancellationToken = default 30 | ) 31 | { 32 | var bot = (TBot)_rootProvider.GetService(typeof(TBot)); 33 | 34 | await bot.Client.DeleteWebhookAsync(cancellationToken) 35 | .ConfigureAwait(false); 36 | 37 | requestParams = requestParams ?? new GetUpdatesRequest 38 | { 39 | Offset = 0, 40 | Timeout = 500, 41 | AllowedUpdates = new UpdateType[0], 42 | }; 43 | 44 | while (!cancellationToken.IsCancellationRequested) 45 | { 46 | Update[] updates = await bot.Client.MakeRequestAsync( 47 | requestParams, 48 | cancellationToken 49 | ).ConfigureAwait(false); 50 | 51 | foreach (var update in updates) 52 | { 53 | using (var scopeProvider = _rootProvider.CreateScope()) 54 | { 55 | var context = new UpdateContext(bot, update, scopeProvider); 56 | // ToDo deep clone bot instance for each update 57 | await _updateDelegate(context) 58 | .ConfigureAwait(false); 59 | } 60 | } 61 | 62 | if (updates.Length > 0) 63 | { 64 | requestParams.Offset = updates[updates.Length - 1].Id + 1; 65 | } 66 | } 67 | 68 | cancellationToken.ThrowIfCancellationRequested(); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /test/UnitTests.Net45/Command Handling.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using Telegram.Bot.Framework; 4 | using Telegram.Bot.Framework.Abstractions; 5 | using Telegram.Bot.Types; 6 | using Telegram.Bot.Types.Enums; 7 | using Xunit; 8 | 9 | namespace UnitTests.Net45 10 | { 11 | public class CommandHandling 12 | { 13 | [Theory(DisplayName = "Should accept handling valid \"/test\" commands for bot \"@Test_bot\"")] 14 | [InlineData("/test", "/test")] 15 | [InlineData("/test ", "/test")] 16 | [InlineData("/test abc", "/test")] 17 | [InlineData("/TesT", "/tESt")] 18 | [InlineData("/test@test_bot", "/test@test_bot")] 19 | [InlineData("/test@test_bot ", "/test@test_bot")] 20 | [InlineData("/test@test_bot !", "/test@test_bot")] 21 | public void Should_Parse_Valid_Commands(string text, string commandValue) 22 | { 23 | Mock mockBot = new Mock(); 24 | mockBot.SetupGet(x => x.Username).Returns("Test_Bot"); 25 | 26 | IBot bot = mockBot.Object; 27 | Message message = new Message 28 | { 29 | Text = text, 30 | Entities = new[] 31 | { 32 | new MessageEntity 33 | { 34 | Type = MessageEntityType.BotCommand, 35 | Offset = text.IndexOf(commandValue, StringComparison.OrdinalIgnoreCase), 36 | Length = commandValue.Length 37 | }, 38 | }, 39 | }; 40 | 41 | bool result = bot.CanHandleCommand("test", message); 42 | 43 | Assert.True(result); 44 | } 45 | 46 | [Theory(DisplayName = "Should reject parsing non-command text messages as command \"/test\"")] 47 | [InlineData(null)] 48 | [InlineData(" ")] 49 | [InlineData("/\t")] 50 | [InlineData(" ")] 51 | [InlineData("I AM NOT A COMMAND")] 52 | [InlineData("/testt")] 53 | [InlineData("/@test_bot")] 54 | [InlineData("/tes@test_bot")] 55 | public void Should_Not_Parse_Invalid_Command_Text(string text) 56 | { 57 | Mock mockBot = new Mock(); 58 | mockBot.SetupGet(x => x.Username).Returns("Test_Bot"); 59 | 60 | IBot bot = mockBot.Object; 61 | Message message = new Message 62 | { 63 | Text = text, 64 | }; 65 | 66 | bool result = bot.CanHandleCommand("test", message); 67 | 68 | Assert.False(result); 69 | } 70 | 71 | [Fact(DisplayName = "Should not accept handling non-text messages")] 72 | public void Should_Refuse_Handling_Non_Message_Updates() 73 | { 74 | Mock mockBot = new Mock(); 75 | mockBot.SetupGet(x => x.Username).Returns("Test_Bot"); 76 | 77 | IBot bot = mockBot.Object; 78 | Message message = new Message 79 | { 80 | Text = null, 81 | }; 82 | 83 | bool result = bot.CanHandleCommand("test", message); 84 | 85 | Assert.False(result); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /test/UnitTests.Net45/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("UnitTests.Net45")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("UnitTests.Net45")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4e485e65-fbda-400b-9393-6e70c9e19c3d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /test/UnitTests.Net45/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/UnitTests.Net45/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/UnitTests.NetCore/Commands/Command Handling.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using Telegram.Bot.Framework; 4 | using Telegram.Bot.Framework.Abstractions; 5 | using Telegram.Bot.Types; 6 | using Telegram.Bot.Types.Enums; 7 | using Xunit; 8 | 9 | namespace UnitTests.NetCore 10 | { 11 | public class CommandHandling 12 | { 13 | [Theory(DisplayName = "Should accept handling valid \"/test\" commands for bot \"@Test_bot\"")] 14 | [InlineData("/test", "/test")] 15 | [InlineData("/test ", "/test")] 16 | [InlineData("/test abc", "/test")] 17 | [InlineData("/TesT", "/tESt")] 18 | [InlineData("/test@test_bot", "/test@test_bot")] 19 | [InlineData("/test@test_bot ", "/test@test_bot")] 20 | [InlineData("/test@test_bot !", "/test@test_bot")] 21 | public void Should_Parse_Valid_Commands(string text, string commandValue) 22 | { 23 | Mock mockBot = new Mock(); 24 | mockBot.SetupGet(x => x.Username).Returns("Test_Bot"); 25 | 26 | IBot bot = mockBot.Object; 27 | Message message = new Message 28 | { 29 | Text = text, 30 | Entities = new[] 31 | { 32 | new MessageEntity 33 | { 34 | Type = MessageEntityType.BotCommand, 35 | Offset = text.IndexOf(commandValue, StringComparison.OrdinalIgnoreCase), 36 | Length = commandValue.Length 37 | }, 38 | }, 39 | }; 40 | 41 | bool result = bot.CanHandleCommand("test", message); 42 | 43 | Assert.True(result); 44 | } 45 | 46 | [Theory(DisplayName = "Should reject parsing non-command text messages as command \"/test\"")] 47 | [InlineData(null)] 48 | [InlineData(" ")] 49 | [InlineData("/\t")] 50 | [InlineData(" ")] 51 | [InlineData("I AM NOT A COMMAND")] 52 | [InlineData("/testt")] 53 | [InlineData("/@test_bot")] 54 | [InlineData("/tes@test_bot")] 55 | public void Should_Not_Parse_Invalid_Command_Text(string text) 56 | { 57 | Mock mockBot = new Mock(); 58 | mockBot.SetupGet(x => x.Username).Returns("Test_Bot"); 59 | 60 | IBot bot = mockBot.Object; 61 | Message message = new Message 62 | { 63 | Text = text, 64 | }; 65 | 66 | bool result = bot.CanHandleCommand("test", message); 67 | 68 | Assert.False(result); 69 | } 70 | 71 | [Fact(DisplayName = "Should not accept handling non-text messages")] 72 | public void Should_Refuse_Handling_Non_Message_Updates() 73 | { 74 | Mock mockBot = new Mock(); 75 | mockBot.SetupGet(x => x.Username).Returns("Test_Bot"); 76 | 77 | IBot bot = mockBot.Object; 78 | Message message = new Message 79 | { 80 | Text = null, 81 | }; 82 | 83 | bool result = bot.CanHandleCommand("test", message); 84 | 85 | Assert.False(result); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /test/UnitTests.NetCore/Commands/Command Parsing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Telegram.Bot.Framework.Abstractions; 4 | using Telegram.Bot.Types; 5 | using Xunit; 6 | 7 | namespace UnitTests.NetCore.Commands 8 | { 9 | public class ArgumentParsingTests 10 | { 11 | [Theory(DisplayName = "Should parse valid single command in a message")] 12 | [InlineData("/start")] 13 | [InlineData("/_")] 14 | [InlineData("/2")] 15 | [InlineData("/3_")] 16 | [InlineData("/start@ab3BOT")] 17 | [InlineData("/1@N_BoT")] 18 | public void Should_Parse_Valid_Commands(string command) 19 | { 20 | Message message = JsonConvert.DeserializeObject($@"{{ 21 | message_id: 2, 22 | chat: {{ id: 333, type: ""private"" }}, 23 | from: {{ id: 333, first_name: ""Poulad"", is_bot: false }}, 24 | text: {JsonConvert.SerializeObject(command)}, 25 | entities: [ {{ offset: 0, length: {command.Length}, type: ""bot_command"" }} ], 26 | date: 1000 27 | }}"); 28 | 29 | string[] args = CommandBase.ParseCommandArgs(message); 30 | 31 | Assert.Empty(args); 32 | } 33 | 34 | [Theory(DisplayName = "Should parse valid a command with its arguments")] 35 | [InlineData("/1 bar", 2, "bar")] 36 | [InlineData("/foo bar baz", 4, "bar baz", "bar", "baz")] 37 | [InlineData("/foo\tbar baz", 4, "bar baz", "bar", "baz")] 38 | [InlineData("/foo\nbar baz", 4, "bar baz", "bar", "baz")] 39 | [InlineData("/_@9", 2, "@9")] 40 | [InlineData("/1-5 ab cd", 2, "-5 ab cd", "-5", "ab", "cd")] 41 | [InlineData("/1@T_Bot arg_1", 8, "arg_1")] 42 | [InlineData("/1@T_Bot arg_1 \"test\"", 8, "arg_1 \"test\"", "arg_1", @"""test""")] 43 | public void Should_Parse_Valid_Commands2(string text, int commandLength, params string[] expectedArgs) 44 | { 45 | Message message = JsonConvert.DeserializeObject($@"{{ 46 | message_id: 2, 47 | chat: {{ id: 333, type: ""private"" }}, 48 | from: {{ id: 333, first_name: ""Poulad"", is_bot: false }}, 49 | text: {JsonConvert.SerializeObject(text)}, 50 | entities: [ {{ offset: 0, length: {commandLength}, type: ""bot_command"" }} ], 51 | date: 1000 52 | }}"); 53 | 54 | string[] args = CommandBase.ParseCommandArgs(message); 55 | 56 | Assert.Equal(expectedArgs, args); 57 | } 58 | 59 | [Fact(DisplayName = "Should throw exception if the message is null")] 60 | public void Should_Throw_When_Null_Message() 61 | { 62 | ArgumentNullException e = Assert.Throws(() => 63 | CommandBase.ParseCommandArgs(null) 64 | ); 65 | 66 | Assert.Equal("Value cannot be null.\nParameter name: message", e.Message); 67 | Assert.Equal("message", e.ParamName); 68 | } 69 | 70 | [Fact(DisplayName = "Should throw exception if the message does not have any entities")] 71 | public void Should_Throw_When_No_Message_Entity() 72 | { 73 | Message message = JsonConvert.DeserializeObject($@"{{ 74 | message_id: 2, 75 | chat: {{ id: 333, type: ""private"" }}, 76 | from: {{ id: 333, first_name: ""Poulad"", is_bot: false }}, 77 | date: 1000 78 | }}"); 79 | 80 | ArgumentException e = Assert.Throws(() => 81 | CommandBase.ParseCommandArgs(message) 82 | ); 83 | 84 | Assert.Equal("Message is not a command\nParameter name: message", e.Message); 85 | Assert.Equal("message", e.ParamName); 86 | } 87 | 88 | [Fact(DisplayName = "Should throw exception if the message entities array is empty")] 89 | public void Should_Throw_When_Empty_Message_Entities() 90 | { 91 | Message message = JsonConvert.DeserializeObject($@"{{ 92 | message_id: 2, 93 | chat: {{ id: 333, type: ""private"" }}, 94 | from: {{ id: 333, first_name: ""Poulad"", is_bot: false }}, 95 | entities: [ ], 96 | date: 1000 97 | }}"); 98 | 99 | ArgumentException e = Assert.Throws(() => 100 | CommandBase.ParseCommandArgs(message) 101 | ); 102 | 103 | Assert.Equal("Message is not a command\nParameter name: message", e.Message); 104 | Assert.Equal("message", e.ParamName); 105 | } 106 | 107 | [Fact(DisplayName = "Should throw exception if the first message entity is not a command")] 108 | public void Should_Throw_When_First_Message_Entity_Not_Command() 109 | { 110 | Message message = JsonConvert.DeserializeObject($@"{{ 111 | message_id: 2, 112 | chat: {{ id: 333, type: ""private"" }}, 113 | from: {{ id: 333, first_name: ""Poulad"", is_bot: false }}, 114 | entities: [ {{ offset: 0, length: 4, type: ""bold"" }} ], 115 | date: 1000 116 | }}"); 117 | 118 | ArgumentException e = Assert.Throws(() => 119 | CommandBase.ParseCommandArgs(message) 120 | ); 121 | 122 | Assert.Equal("Message is not a command\nParameter name: message", e.Message); 123 | Assert.Equal("message", e.ParamName); 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /test/UnitTests.NetCore/Commands/MockCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Telegram.Bot.Framework.Abstractions; 4 | 5 | namespace UnitTests.NetCore.Commands 6 | { 7 | class MockCommand : CommandBase 8 | { 9 | private readonly Func _handler; 10 | 11 | public MockCommand(Func handler) 12 | { 13 | _handler = handler; 14 | } 15 | 16 | public override Task HandleAsync(IUpdateContext context, UpdateDelegate next, string[] args) 17 | => _handler(context, next, args); 18 | } 19 | } -------------------------------------------------------------------------------- /test/UnitTests.NetCore/UnitTests.NetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | false 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/UnitTests.NetCore/test-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "update_id": 594924625, 3 | "message": { 4 | "message_id": 17, 5 | "from": { 6 | "id": 195793677, 7 | "first_name": "Test Name", 8 | "username": "test_user" 9 | }, 10 | "chat": { 11 | "id": 325426679, 12 | "first_name": "Test Name", 13 | "username": "test_user", 14 | "type": "private" 15 | }, 16 | "date": 1494906408, 17 | "text": "hi" 18 | } 19 | } --------------------------------------------------------------------------------