Compare commits

..
226 Commits
Author SHA1 Message Date
Florian Dahn 3340ef7b76 Update README.md 2022-01-10 13:59:26 +01:00
Florian Dahn 211424569b Merge pull request #18 from MajMcCloud/New-Version-v5
New version v5
2022-01-10 13:58:50 +01:00
FlorianDahn 8303b0c87f Adding private constructor
- Adding private constructor
- some spacing for regions
2022-01-06 16:54:50 +01:00
FlorianDahn 19856ab044 Update Solution File 2022-01-06 15:56:16 +01:00
FlorianDahn 5453add956 Renaming TelegramBotBaseTest -> TelegramBotBase.Test 2022-01-06 15:53:41 +01:00
FlorianDahn ff3c22566a Comment Cleanup 2022-01-05 18:40:44 +01:00
FlorianDahn 50fd5255c8 Adding SourceLink to Image Extension 2022-01-05 18:39:35 +01:00
FlorianDahn 4f85677deb Comment Cleanup 2022-01-05 18:39:06 +01:00
FlorianDahn 7d2703138c Readme fix to "generics" like in earlier versions. 2022-01-05 18:31:30 +01:00
FlorianDahn d0d93a07c0 Adding QuickStart to readme. 2022-01-05 18:30:25 +01:00
FlorianDahn f03e67f345 Adding QuickStart Option for BotBaseBuilder 2022-01-05 18:26:10 +01:00
FlorianDahn 18bb6584f4 Adding OnlyStart BotBaseBuilder command option 2022-01-05 18:25:33 +01:00
FlorianDahn d5a89a4ed8 Adding regions to BotBaseBuilder 2022-01-05 18:25:04 +01:00
FlorianDahn 09d7164385 Update README.md 2022-01-05 14:22:21 +01:00
FlorianDahn 2b914f6f58 Changing CustomMessageLoop parameter 2022-01-05 01:17:44 +02:00
FlorianDahn b65ec13d8b Simplifying BotBuilder BotCommands
- Simplifying BotBuilder BotCommands
- updating test project
2022-01-05 01:16:22 +02:00
FlorianDahn ca1c382924 Removing old commented stuff 2022-01-04 23:44:43 +02:00
FlorianDahn 584bb76e0f Adding comments 2022-01-04 23:42:13 +02:00
FlorianDahn c5fa153d54 Replace KickChatMemberAsync/KickUser 2022-01-04 23:40:33 +02:00
FlorianDahn c6106f17ec Removing .Net 4.7.2 and adding .Net Standard 2.0 instead of 2.1
- removing .Net 4.7.2 compilation target
- changing .Net Standard from 2.1 to 2.0 for backwards compatibility
2021-12-25 17:52:17 +01:00
FlorianDahn 4f97e2a919 Changing debugtype to portable to allow snupkg file to be attached. 2021-12-25 17:47:11 +01:00
FlorianDahn 21c6b2fde5 removing .NET 472 compilation target from test project 2021-12-25 17:34:47 +01:00
FlorianDahn caf9b3cc8e Change version of polling library to 1.0.1 2021-12-25 17:34:20 +01:00
FlorianDahn 27e45b397d Changing debugtype to portable to allow snupkg file to be attached.
- removing double Releae config in project settings
- change debugtype to portable to allow correct snupkg package upload
2021-12-25 17:32:21 +01:00
FlorianDahn b8bdc756d4 Rename file to class name 2021-12-25 16:13:31 +01:00
FlorianDahn e5d8e403a0 Update readme with new BotBaseBuilder 2021-12-25 16:12:26 +01:00
FlorianDahn 2c58f86524 Adding localization selection step to BotBaseBuilder
- Adding localization step
- Updating test project
2021-12-25 16:10:32 +01:00
Florian Dahn 4bd43de0fe Update README.md 2021-12-22 15:40:14 +01:00
Florian Dahn 871d25b95b Create README.md 2021-12-22 15:38:46 +01:00
Florian Dahn b508a7e3ce Update README.md 2021-12-20 14:16:28 +01:00
Florian Dahn 0ebba5e204 Update README.md 2021-12-20 14:15:52 +01:00
FlorianDahn 1cc0a2bdf7 Ignore Callback queries with original image attached to message, no SentData event 2021-12-19 14:55:23 +01:00
FlorianDahn fc33da0496 Fixing empty message field 2021-12-17 15:15:58 +01:00
FlorianDahn 35045f3aeb Removing Newtonsoft Dependency 2021-12-16 15:08:56 +01:00
FlorianDahn 3b8b24593f Dependency Update
- downgrade NetStandard from 2.1 to 2.0 to allow .NetFramework usage from 4.6.1 to 4.8
- remove .Net Framework 4.7.2 compilation target
- replacing conditioned compilation for .Net Framework with .Net Standard
- removing System.Net.Http dependency (was only necessary for .Net Framework)
2021-12-16 14:57:28 +01:00
FlorianDahn 03565aba98 Replacing Nuget packages with project references 2021-12-06 02:41:55 +01:00
FlorianDahn bbc436d1d6 Merge branch 'New-Version-v5' of https://github.com/MajMcCloud/TelegramBotFramework into New-Version-v5 2021-12-06 02:37:01 +01:00
FlorianDahn 4026f9ba4e Update for easier serialization during BotBaseBuilder 2021-12-06 02:36:59 +01:00
Florian Dahn fb38d32e9e Update README.md 2021-11-29 15:57:49 +01:00
FlorianDahn 077bd1384f Removing System.ComponentModel dependency 2021-11-28 23:58:23 +01:00
FlorianDahn e28663138b Integrating Images Project 2021-11-28 23:20:56 +01:00
FlorianDahn 65ec7f1c2b Update TelegramBotBase.csproj 2021-11-26 17:59:58 +01:00
FlorianDahn 1fc581b959 V17 - Testproject Update 2021-11-26 17:58:49 +01:00
FlorianDahn 31e52887ba V17 - Big Update
- Adding a message loop interface to build custom message loops
- extracting default message loop from BotBase into seperate class
- updates to BotBaseBuilder for integration of custom message loops
- updating all result classes for using the new Update object of V17
- improving MessageResult and UpdateResult classes
- BotBase has been prepared for cleanup (a lot of comments)
- Comment cleanup at the next build
- updating Readme
2021-11-26 17:57:49 +01:00
FlorianDahn e9c25ea9e4 New ConfirmAction method 2021-11-26 17:49:33 +01:00
FlorianDahn 2aee486370 V17 - Replacing default enum values 2021-11-26 17:49:17 +01:00
FlorianDahn c1a0b12a94 Replace Configure with CustomCommands 2021-11-26 17:47:42 +01:00
FlorianDahn 2bd4634624 V17 - Remove default selection for enums 2021-11-26 17:45:24 +01:00
FlorianDahn 0bc77ca79c Update README.md 2021-11-26 17:43:38 +01:00
FlorianDahn d0edf8531c Replacing Configure method in BBB with CustomCommands 2021-11-22 22:07:55 +01:00
FlorianDahn b001d5e9c4 Adding LambdaStartFormFactory 2021-10-17 18:38:35 +02:00
FlorianDahn e1d5051288 Update README.md 2021-10-17 18:31:28 +02:00
FlorianDahn d00c5e1799 Updating BotBaseBuilder with more functionality 2021-10-17 18:21:05 +02:00
FlorianDahn c4a31e987b Replacing BotBase instance with BotBaseBuilder 2021-10-17 18:07:53 +02:00
Florian Dahn eafb81bf7b Merge pull request #17 from MajMcCloud/master
PR for master changes branch
2021-10-17 17:35:13 +02:00
FlorianDahn 1dff9d5ffe Update IAPIKeySelectionStage.cs 2021-10-17 17:33:49 +02:00
FlorianDahn 755f5a245b Renaming 2021-10-17 17:27:06 +02:00
FlorianDahn a22ede0f4f Changing BotBase behaviour to fluent api
- removing unecessary constructors from BotBase
- removing generics from BotBase
- removing generics from SessionBase
- adding StartFormFactory interface
- adding DefaultStartFormFactory
- adding multiple methods to BotBaseBuilder
2021-10-17 17:25:17 +02:00
FlorianDahn 8e29652148 Fixing possible Deadlock in AutoCleanForm 2021-10-17 15:39:31 +02:00
Florian Dahn 5bb316d874 Merge pull request #15 from victorrar/f/update-readme
Fix documentation inaccuracy

Thanks !
2021-10-04 21:35:59 +02:00
Victor d2ae08771e Fix documentation inaccuracy 2021-10-04 21:55:41 +03:00
Florian Dahn 9162c0894c Merge pull request #14 from MajMcCloud/development
AutoCleanForm FIX
2021-10-01 18:36:24 +02:00
FlorianDahn e0ec133209 AutoCleanForm FIX
- adding MessageDeleted event to AutoCleanForm for catching messages getting deleted outside of AutoCleanForm
- added OnMessageDelete invoke to DeleteMessage in DeviceSession
2021-10-01 18:35:05 +02:00
Florian Dahn 7004ee5963 Merge pull request #13 from MajMcCloud/development
Development
2021-08-13 15:33:12 +03:00
FlorianDahn ef8ca3c988 Adding basic example for different types of ConfirmAction 2021-08-01 13:50:18 +03:00
FlorianDahn f021f12486 Update Localization.cs
- change to abstract so it can't be used accidentally
2021-07-27 17:00:16 +02:00
FlorianDahn d06e1eaa52 Update README.md 2021-07-26 15:52:59 +02:00
FlorianDahn 23969881c1 Update README.md 2021-07-26 15:44:06 +02:00
FlorianDahn 9fa3df2889 Update README.md 2021-07-26 15:42:04 +02:00
FlorianDahn 6ed91224eb Update README.md 2021-07-26 15:39:47 +02:00
FlorianDahn aabc7f04b7 Adding documentation about NavigationController 2021-07-26 15:38:47 +02:00
FlorianDahn 71433c0e4e MAJOR CHANGE for ButtonGrids, Dynamic data sources, etc
- introducing a dynamic data source class (IDataSource)
- introducing a ButtonRow class for better managability
- replacing that List<ButtonBase> with ButtonRow object
- introducing ButtonFormDataSource with special methods for ButtonGrid controls
- updating ButtonGrid and refactoring of the Load/Action methods
- updating CheckButtonList and refactoring of the Load/Action methods
- updating TaggedButtonGrid and refactoring of the Load/Action methods
- adding example to the Test project
2021-07-26 15:10:10 +02:00
FlorianDahn 894d0799c8 Localizations updated
- moved German Localization to its seperate file
- cleaned up Localization class
2021-07-26 14:55:29 +02:00
FlorianDahn 6c2543630d Adding .NET 4.7.2 back into with conditional compilation
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives
2021-07-26 13:32:32 +02:00
FlorianDahn 84d18f6f45 Small readability improvements 2021-07-26 01:26:33 +02:00
FlorianDahn 5f49e25458 Adding CheckAll and UncheckAllTags methods 2021-07-26 01:23:31 +02:00
FlorianDahn f28c9b1e22 Add DeleteReplyMessage property 2021-07-26 01:22:58 +02:00
FlorianDahn 0db0c00cd0 Update example project 2021-07-25 22:25:29 +02:00
FlorianDahn 2158f53cde Fixing ReplyKeyboard flicker on TaggedButtonGrid
- fixing ReplyKeyboard flicker
- updating message handling on existing messages
2021-07-25 17:44:43 +02:00
FlorianDahn fd7d72b986 Update NavigationController.cs 2021-07-25 16:40:09 +02:00
FlorianDahn 47a5b502b9 Updating package targetFramework to .NET 5 2021-07-25 16:38:53 +02:00
FlorianDahn ce4c7b6f1c Update TelegramBotBase.csproj 2021-07-25 16:36:43 +02:00
FlorianDahn 5bae650edc Updating compilation targets
- Removing .NET 4.7.2 and  and replacing it with .NET 5
2021-07-25 16:35:15 +02:00
FlorianDahn 9eae63dae6 Updating Testproject
- adding example for NavigationController
- readability improvement in Menu
2021-07-25 16:34:29 +02:00
FlorianDahn 6f005114bc Update NavigationController.cs
- Adding PopToRootAsync method
- small comment change
2021-07-25 16:33:05 +02:00
Florian Dahn 00440a50e8 Merge pull request #11 from MajMcCloud/master
Move Master changes to development branch
2021-07-25 05:07:31 +03:00
FlorianDahn ae9dd17ae3 Adding NavigationController for better and optional Push/Pop navigation 2021-07-25 01:54:16 +02:00
FlorianDahn be2660239a Creating DeviceSession and other stuff slight earlier in the LoadSessionStates methods 2021-07-25 01:53:55 +02:00
Florian Dahn e88114049e Merge pull request #8 from Xilosof/feat/QuickDeletionOfMsgs
Thanks for the added change. I will approve it now.
2021-07-25 02:10:30 +03:00
Xilosof 177c198916 Added event call 2021-07-23 16:47:42 +03:00
FlorianDahn 1f7a9900c4 Updating TelegramBotBaseTest project
- adding new simple AddStartCommand, AddHelpCommand and AddSettingsCommand to Program.cs to make it easier
2021-07-17 18:36:56 +02:00
Florian Dahn d6051ce607 Merge pull request #10 from MajMcCloud/development
Updating to TelegramBotFramework 16.0.2
2021-07-17 19:35:17 +03:00
FlorianDahn 5613e84bc3 Updating to TelegramBotFramework 16.0.2
ATTENTION: UserIds will no longer be "int", instead it has to be of the long datatype
- updating DeviceSession methods for new datatype
- updating LinkReplaceTest class example
- adding .NET 5 as target framework
2021-07-17 18:34:27 +02:00
Florian Dahn d52fd41467 Merge pull request #9 from MajMcCloud/development
Development
2021-07-14 23:11:32 +03:00
Florian Dahn 8df6e4ce55 Merge pull request #7 from NTDV/master
CalendarPicker fixes
2021-07-14 22:35:13 +03:00
Xilosof 9012ec76b1 feat(AutoCleanForm): change deletion of msgs
- made faster deletion of old messages;
- added server error handling
2021-07-13 22:48:01 +03:00
Danich Volk 36fb40b6bd CalendarPicker:
+ Added days, monthes and years range check
+ Added language selection
* Fixed default language (it is English now)
2021-07-13 21:18:55 +03:00
FlorianDahn 47aa8c2aaa Extracting custom field conversion for use in other places 2021-06-07 01:35:02 +02:00
FlorianDahn 1709631965 adding GetObject and SaveObject methods for custom session serialization
- adding GetObject and SaveObject methods for custom session serialization
2021-06-07 01:33:57 +02:00
FlorianDahn 264a998052 Update FormBase.cs
- making more basic methods virtual for easiert overriding
- adding existing control check to AddControl method
2021-06-07 01:33:19 +02:00
FlorianDahn a00aaacbfb Update ButtonGrid.cs
- adding DeleteReplyMessage property for seperating now deleting the message from the user and the message (buttons) send by the control
2021-06-07 01:31:41 +02:00
Florian Dahn bd908db867 Merge pull request #4 from MajMcCloud/development
A lot of updates and fixes
2021-05-16 15:09:09 +02:00
FlorianDahn e2c485aede Adding some conversion checks for JSON State serialization 2021-04-25 15:51:19 +02:00
FlorianDahn c8936d0748 Adding more details to PromptDialog
- replacing the default EventArgs within the Completed event with a more detailed one
- adding new PromptDialogCompletedEventArgs class for easier details access
2021-03-29 23:51:39 +02:00
FlorianDahn 92f12735c8 Adding more details on ConfirmDialog
- adding a Tag property for optional use in ArrayPromptDialog and ConfirmDialog
- adding it to the ButtonClickedEventArgs
2021-03-29 23:50:42 +02:00
Florian Dahn 8142b626b2 Merge pull request #3 from Xilosof/bug/ApiRequestException-handling
Fix handling ApiRequestException when sending a request.
2021-03-14 20:34:07 +01:00
Xilosof 5dcbe04a73 Added initialization of MaxNumberOfRetries setting 2021-03-14 22:03:13 +03:00
Xilosof e742ded371 Change exception handling when sending a request.
1) Now RetryAfter  is multiplied by 1000, because request contains value in second instead milliseconds. (see https://core.telegram.org/bots/api#responseparameters)
2) Now calls occur in a loop, but a limited number of times. This also ensures that exceptions are caught during a repeated request.
2021-03-14 22:00:51 +03:00
Xilosof 706b0f522a Add static propert MaxNumberOfRetries.
This is necessary so that the DeviceSession instance can get this setting. There is no direct access to the settings in BotBase at the moment.
This property will be set when calling the Start method for the bot and is the same for all bots in the same application.
2021-03-14 21:55:07 +03:00
Xilosof 61e3652edf Add MaxNumberOfRetries setting.
Indicates the maximum number of times a request that received error 429 will be sent again after a timeout until it receives code 200 or an error code not equal to 429.
2021-03-14 21:43:07 +03:00
FlorianDahn c484edd427 Update README.md 2021-03-14 16:52:19 +01:00
FlorianDahn 181902336c Adding ChannelCreated messagetype to GroupForm 2021-03-14 16:51:25 +01:00
FlorianDahn dc364b5fd3 Fix for Cleanup method
- there was an collection changed exception which blocked the method from working
2021-03-04 15:17:06 +01:00
FlorianDahn 1fd4752c0e Slash fix 2021-03-02 15:25:35 +01:00
FlorianDahn d84be50243 Adding AutoCleanForm Fix for TaggedButtonGrid 2021-03-02 15:19:28 +01:00
FlorianDahn 1caa71cd25 Update to fix some bad behaviour with combination of AutoCleanForm 2021-03-01 22:53:32 +01:00
FlorianDahn e81e5f1854 Bot command extensions for default commands 2021-03-01 22:53:03 +01:00
FlorianDahn 399e056a8f Deleting messages update
- new constant for how much message deletions per second
- updating AutoCleanForm "Cleanup" process to use constant and using MessageDeleted eventhandler
2021-03-01 22:52:45 +01:00
FlorianDahn 26c4f479fa New internal MessageDeleted event handler for better control and form use. 2021-03-01 22:50:42 +01:00
FlorianDahn 72d94df907 FIX: new message loop will now correctly manage events and form switches 2021-03-01 22:14:36 +01:00
FlorianDahn 31e0683699 FIX: for using ButtonGrid within AutoCleanForm can cause to no message 2021-03-01 22:13:40 +01:00
FlorianDahn 676be49a62 Update MultiToggleButtons.cs 2021-03-01 01:54:49 +01:00
FlorianDahn 5b67f334ff MultiToggleButton minor updates
- updating localization
- adding AllowEmptySelection property
- updating test project
2021-03-01 01:48:17 +01:00
FlorianDahn 6a556af070 New control: MultiToggleButton
- new control for having multiple simple toggle options
2021-03-01 01:36:07 +01:00
FlorianDahn 4910a04923 Update README.md 2021-02-28 18:25:12 +01:00
FlorianDahn 10f4e1b745 New example project for async form updates 2021-02-28 18:23:21 +01:00
FlorianDahn 42b624c28f Fix for MessageType use when no Message is available 2021-02-28 18:22:08 +01:00
FlorianDahn 75013d51bd Remove old comments 2021-02-28 17:47:52 +01:00
FlorianDahn 7f3bcfe418 Speed/Performance optimization for AutoCleanForm 2021-02-28 17:46:49 +01:00
FlorianDahn f5e26924d8 Adding InvokeMessageLoop method
- For asyncronous form updates adding InvokeMessageLoop
2021-02-28 17:45:28 +01:00
FlorianDahn 884aa60fac Joining both message loops for easier maintainability 2021-02-28 17:44:35 +01:00
FlorianDahn adc18891e0 Update MessageResult.cs 2021-02-28 17:40:02 +01:00
FlorianDahn 07eb4bef75 Adding new controls to Readme 2021-02-28 15:44:05 +01:00
FlorianDahn dfe64b22ab New control: CheckedButtonList
- new control for having a checked listview with paging possible
- new CheckedChangedEventArgs class
- adding Test for CheckedButtonList
- refactoring MultiView Test
2021-02-28 15:35:49 +01:00
FlorianDahn 15a8b8897f Updates and improvements
- refactoring of ButtonGrid control for more readability
- refactoring of TaggedButtonGrid control for more readability
- adding Index parameter to ButtenClickedEventArgs
- adding FindRowByButton method to ButtonForm to get the row index
-
2021-02-28 15:34:04 +01:00
FlorianDahn 91048b4f7c Async invoke fixes and ButtonGrid update
- removing unused imports
- adding null check for async handler invokes
- fixing PageCount calculation on ButtonGrid
2021-02-27 20:51:47 +01:00
FlorianDahn 426bb078af Adding DebuggerDisplay attribute 2021-02-27 15:18:23 +01:00
FlorianDahn 63deb978a4 PageCount Fix for ButtonGrid
- replace constant 3 with LayoutRows property
2021-02-27 15:17:21 +01:00
FlorianDahn 1a2b5818aa New Control: TaggedButtonGrid
- tag your list with some strings and filter it right away
- adding Test to test project
- adding a new localization

PS: it is an improved copy of the normal ButtonGrid
2021-02-20 01:53:49 +01:00
FlorianDahn fc44b7d38c New Control: MultiView
- adding basic paging options
- adding GetOrigin method to find control or form who has sent a message
- adding Test for MultiView
2021-02-20 01:51:01 +01:00
FlorianDahn dc8cb81284 Adding Init method to controls 2021-02-20 01:39:37 +01:00
FlorianDahn 0202fc29a8 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2021-02-19 15:03:33 +01:00
FlorianDahn 7e51126fd3 Fix for double sessions in sessionstate 2021-02-19 15:03:28 +01:00
Florian Dahn 67e82acf20 Update FUNDING.yml 2021-02-02 15:59:03 +01:00
Florian Dahn c32fc2c3da Update FUNDING.yml 2021-02-02 15:57:02 +01:00
Florian Dahn 4351fc7c68 Create FUNDING.yml 2021-02-02 15:56:27 +01:00
FlorianDahn 80d43ac369 Update DeviceSession.cs 2021-01-15 19:36:13 +01:00
FlorianDahn 376ba68e8e MAJOR CHANGE: Refactoring namespace for controls
Chaning namespace for controls depending on their use area to:

Controls.Hybrid
Controls.Inline
Controls.Reply

Updating example project as well.
2021-01-15 19:33:44 +01:00
FlorianDahn 6b404545df Update MessageResult.cs
- adding more parameters to ConfirmAction method
2021-01-15 16:53:45 +01:00
FlorianDahn a524fef0d5 Update DeviceSession.cs
Workaround: Unknown API differences
2020-12-23 03:09:47 +01:00
FlorianDahn 73ba36ea2b Optimization of API calls within DeviceSession
- replacing all API calls with new method "API" to cover APIRequestExceptions
- adding IDeviceSession interface for future use
- adding "API" method without result
2020-12-22 21:55:47 +01:00
FlorianDahn 46160b54ae Update DataResult.cs
- adding DownloadRawDocument & DownloadRawTextDocument functions
2020-12-22 15:40:18 +01:00
FlorianDahn 87690a3630 Update DeviceSession.cs
Adding a feature for making future API calls.
2020-12-22 15:37:05 +01:00
FlorianDahn 975a942993 Update ButtonGrid.cs
Small refactoring for ButtonGrid
2020-12-06 22:43:39 +01:00
FlorianDahn 4336ff1681 Session Updates 2020-12-04 17:19:11 +01:00
Florian Dahn 567a9f937f Update README.md 2020-11-23 13:51:25 +01:00
FlorianDahn 95b4ff77c5 Update README.md 2020-11-23 13:48:13 +01:00
FlorianDahn ba82c11d66 Update README.md 2020-11-23 13:46:28 +01:00
FlorianDahn c2e33da277 Adding SaveSessionState to SessionBase
Feature request: https://github.com/MajMcCloud/TelegramBotFramework/issues/2
2020-11-22 23:15:31 +01:00
FlorianDahn 259fa54236 Update ModalDialog.cs
- Adding await keyword
2020-11-03 23:52:35 +01:00
FlorianDahn f790a16e85 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2020-11-03 20:38:40 +01:00
FlorianDahn 97fea333f7 Update ConfirmDialog.cs
- adding additional checks to ConfirmDialog
- adding AutoCloseOnClick property, to close this modal form after button click
2020-11-03 20:38:36 +01:00
FlorianDahn e1b31b0b9a Update PromptDialog.cs
- adding additional checks to promptdialog
2020-11-03 20:38:07 +01:00
Florian Dahn 7aca5a648f Update README.md 2020-10-31 16:22:47 +01:00
FlorianDahn f24c309f63 Update DeviceSession.cs
- adding ParseMode to Edit functions
- adding a Send method which the parameter of the DeviceId
2020-10-22 22:14:40 +02:00
FlorianDahn ba9fd8062c Update PromptDialog.cs
- added ReceivedMessage for further use
2020-10-20 19:11:04 +02:00
FlorianDahn b37c31f2bd Update BotBase.cs
- Bugfix, Unhandled actions has been producing a loop
2020-10-20 19:10:30 +02:00
FlorianDahn 2c34a178ee Update DeviceSession.cs
- adding caption parameter
2020-10-12 23:16:42 +02:00
FlorianDahn 2f030f3c12 Updates to ButtonGrid
- adding IsNavigationBarVisible property to ButtonGrid
- adding SubHeadLayout row for column "descriptions" like a table design
- uses FilterDuplicate with ByRow enabled to keep the full row on matches
2020-10-05 14:24:34 +02:00
FlorianDahn a364562fd3 Update ButtonForm.cs
- Modifying FilterDuplicate to allow full row filter or not
This will keep only the matches, or the full row where it matches.
2020-10-05 14:22:47 +02:00
FlorianDahn 8ef8733a2b Update DeviceSession.cs
- Adding the name parameter to SendPhoto methods
- Adding caption parameter to SendVideo methods
2020-10-05 14:21:57 +02:00
FlorianDahn 049b2081ec Update SessionBase.cs
Use of Fallback form has been missed in Session loading. Added now.
2020-09-15 23:46:30 +02:00
FlorianDahn 48b76a84be Update SessionBase.cs
- improving handling for Sessions loading with no default constructors available.
2020-09-15 16:42:40 +02:00
FlorianDahn 31c8231b2d Adding NavigationBarVisibility property to hide/show navigation buttons auto/never/always 2020-09-15 16:38:36 +02:00
FlorianDahn e9504f3b36 Update ButtonForm.cs
- adding method for adding one single button row
- fix for ToList method if no buttons are available
2020-09-15 16:36:54 +02:00
FlorianDahn 8592f4279d Update ButtonGrid.cs
Fix "Page 1 of 0" when no rows are existing.
2020-09-08 15:32:04 +02:00
FlorianDahn 2457773a12 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2020-09-05 02:08:28 +02:00
FlorianDahn 46e65faf31 Adding Enum deserialization
Fixing issue in SessionBase where it does not correctly deserialize Enum's
2020-09-05 02:08:19 +02:00
FlorianDahn 193a4811bf Simplifiy AutoCleanForm
Improving serialization by replacing the message with just the ID
2020-09-05 02:07:48 +02:00
Florian Dahn 08a0de8087 Update README.md 2020-09-01 19:00:11 +02:00
FlorianDahn 82469cff70 - Adding not about search feature 2020-08-30 20:48:44 +02:00
Florian Dahn 81eafe1219 Merge pull request #1 from JTOne123/master
[PR] The proj files have been updated to enable SourceLink
2020-08-09 17:25:00 +02:00
FlorianDahn 183e89c36f Adding paging/search example 2020-08-09 17:22:19 +02:00
FlorianDahn ad6cf9539c Adding filter functionality to ButtonGrid
Adding filter functionality
2020-08-09 17:00:04 +02:00
FlorianDahn 46576eb819 Update ButtonForm.cs
adding method to create a ButtonForm duplicate and filter the single buttons, used for ButtonGrid
2020-08-09 16:59:25 +02:00
FlorianDahn cf9e2285f1 Adding DynamicButton for Button with dynamic Text updates
For use, call the ButtonGrid.Updated() method
2020-07-25 15:11:15 +02:00
FlorianDahn ecfacdadbb Update AssemblyInfo.cs 2020-07-25 15:09:01 +02:00
FlorianDahn c00c2e8d8f Update DeviceSession.cs
- adding Unban method
2020-07-25 15:08:55 +02:00
FlorianDahn bfd905d590 Update TelegramBotBase.csproj 2020-07-25 15:08:41 +02:00
FlorianDahn 7b1bf75dde Update ToggleButton
- adding ChangedString variable for custom message
2020-07-25 15:08:22 +02:00
FlorianDahn 7a7a65c3a7 Update FormBase.cs
- adding RemoveAllControls method
2020-07-25 15:07:36 +02:00
Pavlo Datsiuk 8a42a861cf [COMMIT] The proj files have been updated to enable SourceLink [TelegramBotBase.csproj] 2020-06-25 15:54:40 +03:00
FlorianDahn 47876681e0 IgnoreState and SaveState addings for better session serialization
- adding IgnoreState to all ModalDialogs to prevent them get saved.
- adding SaveState to AutoCleanForm to keep state
- change OldMessages property of AutoCleanForm to public
2020-05-07 23:49:08 +02:00
FlorianDahn 9314b4c0e5 Update ButtonGrid.cs
- Fix for async event timings, button click got sometimes fired after Render event
2020-05-06 20:56:38 +02:00
FlorianDahn 01c19b9900 Update README.md 2020-05-04 15:33:50 +02:00
FlorianDahn 0af587d27f Updating readme and test project 2020-05-04 15:33:21 +02:00
FlorianDahn 97b9176c3a Updating packages 2020-05-04 15:22:33 +02:00
FlorianDahn dfa479d409 Fix in StateMachines 2020-05-04 15:18:48 +02:00
FlorianDahn 07dec0dc9d Small fixes and clarifications 2020-05-04 02:20:16 +02:00
FlorianDahn 2fc16b18da DefaultStateForm
- adding DefaultStateForm to redirect Sessions after reloading Sessions to a different Form instead of "restart" the session (i.e. on an in Bot wizard you just want to let the user continue on the main menu)
- adding to SaveSessions to save the "main menu" instead of skipping the ignored form
2020-05-04 02:12:50 +02:00
FlorianDahn 11eeb0dcab Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2020-04-29 22:11:29 +02:00
FlorianDahn fbfcbe99fb Update SystemCommandsBot.csproj
adding NugetPackage instead of direct Project reference
2020-04-29 22:11:26 +02:00
Florian Dahn 948c1368d7 Update README.md 2020-04-28 20:18:24 +02:00
FlorianDahn d66df4978d Create Program.cs 2020-04-28 20:17:18 +02:00
FlorianDahn 7d8895850a Example for a Join/Leave Messages delete Bot 2020-04-28 20:10:18 +02:00
FlorianDahn 5e87d00c58 Localization updates
- small localization updates
- correction in enumration from safe to save
2020-04-21 21:50:59 +02:00
FlorianDahn efe5fef7d0 Adding setting for automated session serialization on shutdown
- adding automated session serialization on application ending (console or shutdown/logoff)
- adding console class for managing system events
2020-04-21 21:45:21 +02:00
FlorianDahn eebb5cb6f5 Create eSettings.cs
- adding eSettings enumeration
2020-04-21 21:44:01 +02:00
FlorianDahn 992065d289 Update BotBase.cs
- improving system settings for bot
2020-04-21 21:43:25 +02:00
FlorianDahn 1acfeb873f Buttongrid Paging support and Localization updates
- adding Paging functionality to ButtonGrid
- adding localization for ButtonGrid
2020-04-21 21:40:47 +02:00
Florian Dahn de35ebb66e Update README.md 2020-04-18 13:16:53 +02:00
FlorianDahn 2c3e707673 Session serialization update
- updating Session serialization for using new method GetChatTitle() to get the chat title for debugging purposes
- removing unncessary use of getting the chat title at every call (improving performance)
2020-04-15 20:30:42 +02:00
FlorianDahn 6d39d2fb73 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2020-04-15 20:27:54 +02:00
FlorianDahn ef667d12d8 Update DeviceSession.cs
- adding EditReplyMarkup to Device methods
- adding GetChatTitle method to get easier the name of a chat/group/channel
- some cleanup
2020-04-15 20:27:46 +02:00
FlorianDahn d24f8e7ac5 Localizations
- adding localizations in english and german for default controls
- updating default controls
2020-04-15 20:26:39 +02:00
FlorianDahn e5dc3fd2bc Update ButtonForm.cs
- adding a Duplicate function
- improving Count property
- adding InsertButtonRow methods
- adding a dictionary property to get all buttons in a specific row
2020-04-15 20:23:39 +02:00
Florian Dahn 6ebb96bd93 Update README.md 2020-04-15 20:20:57 +02:00
FlorianDahn a1b3b9246b Update BotBase.cs
fixing new command handling
2020-04-11 19:42:24 +02:00
FlorianDahn 25a0ff2e3e Update BotCommandEventArgs.cs
updating documentation
2020-04-11 19:41:56 +02:00
FlorianDahn b74233d66d Update BotBase.cs
Small fix
2020-04-11 19:24:31 +02:00
FlorianDahn fb7b624ad8 BotCommands and refactoring
- refactoring SystemCalls to BotCommand (due new Telegram Bot update)
- Adding UploadBotCommands method to BotBase
- refactoring and simplifying BotBase constructors
- adding getBotCommands and SetBotCommands to MessageClient
2020-04-11 19:12:34 +02:00
Florian Dahn 965e6e18b9 Update README.md 2020-04-10 10:24:29 +02:00
Florian Dahn b1c518cbd3 Update README.md 2020-04-10 10:16:25 +02:00
Florian Dahn ab0ae5cda4 Update README.md 2020-04-10 10:11:20 +02:00
FlorianDahn b850330f65 update readme 2020-04-10 10:09:03 +02:00
FlorianDahn 317dad7d24 - adding state machine documentation 2020-04-10 10:08:02 +02:00
145 changed files with 8566 additions and 1192 deletions
+2
View File
@@ -0,0 +1,2 @@
github: #[MajMcCloud]
custom: ["https://paypal.me/majmccloud", dahn.partners]
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{673A56F5-6110-4AED-A68D-562FD6ED3EA6}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>AsyncFormUpdates</RootNamespace>
<AssemblyName>AsyncFormUpdates</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="Telegram.Bot, Version=15.7.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Telegram.Bot.15.7.1\lib\net45\Telegram.Bot.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="forms\AsyncFormEdit.cs" />
<Compile Include="forms\AsyncFormUpdate.cs" />
<Compile Include="forms\Start.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj">
<Project>{0bd16fb9-7ed4-4ccb-83eb-5cee538e1b6c}</Project>
<Name>TelegramBotBase</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+48
View File
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace AsyncFormUpdates
{
class Program
{
static TelegramBotBase.BotBase<forms.Start> bot = null;
static void Main(string[] args)
{
String apiKey = "APIKey";
bot = new TelegramBotBase.BotBase<forms.Start>(apiKey);
bot.Start();
var timer = new Timer(5000);
timer.Elapsed += Timer_Elapsed;
timer.Start();
Console.ReadLine();
timer.Stop();
bot.Stop();
}
private static async void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
foreach(var s in bot.Sessions.SessionList)
{
//Only for AsyncUpdateForm
if (s.Value.ActiveForm.GetType() != typeof(forms.AsyncFormUpdate) && s.Value.ActiveForm.GetType() != typeof(forms.AsyncFormEdit))
continue;
await bot.InvokeMessageLoop(s.Key);
}
}
}
}
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die einer Assembly zugeordnet sind.
[assembly: AssemblyTitle("AsyncFormUpdates")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AsyncFormUpdates")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("673a56f5-6110-4aed-a68d-562fd6ed3ea6")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
// indem Sie "*" wie unten gezeigt eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace AsyncFormUpdates.forms
{
public class AsyncFormEdit : FormBase
{
[SaveState]
int counter = 0;
int MessageId = 0;
public override async Task Load(MessageResult message)
{
counter++;
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("");
switch (message.RawData ?? "")
{
case "back":
var st = new Start();
await NavigateTo(st);
break;
}
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Back", "back");
if (MessageId != 0)
{
await Device.Edit(MessageId, $"Your current count is at: {counter}", bf);
}
else
{
var m = await Device.Send($"Your current count is at: {counter}", bf, disableNotification: true);
MessageId = m.MessageId;
}
}
}
}
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace AsyncFormUpdates.forms
{
public class AsyncFormUpdate : AutoCleanForm
{
[SaveState]
int counter = 0;
public override async Task Load(MessageResult message)
{
counter++;
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("");
switch (message.RawData ?? "")
{
case "back":
var st = new Start();
await NavigateTo(st);
break;
}
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Back", "back");
await Device.Send($"Your current count is at: {counter}", bf, disableNotification: true);
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace AsyncFormUpdates.forms
{
public class Start : AutoCleanForm
{
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("");
switch (message.RawData ?? "")
{
case "async":
var afe = new AsyncFormEdit();
await NavigateTo(afe);
break;
case "async_del":
var afu = new AsyncFormUpdate();
await NavigateTo(afu);
break;
}
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Open Async Form with AutoCleanupForm", "async_del");
bf.AddButtonRow("Open Async Form with Edit", "async");
await Device.Send("Choose your option", bf);
}
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net472" />
<package id="System.Net.Requests" version="4.3.0" targetFramework="net472" />
<package id="Telegram.Bot" version="15.7.1" targetFramework="net472" />
</packages>
+12
View File
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TelegramBotBase" Version="3.1.1" />
</ItemGroup>
</Project>
+19
View File
@@ -0,0 +1,19 @@
using System;
namespace JoinHiderBot
{
class Program
{
static void Main(string[] args)
{
String apiKey = "";
var bot = new TelegramBotBase.BotBase<forms.Start>(apiKey);
bot.Start();
Console.ReadLine();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Form;
namespace JoinHiderBot.forms
{
public class GroupManageForm : GroupForm
{
public override async Task OnMemberChanges(MemberChangeEventArgs e)
{
if (e.Type != Telegram.Bot.Types.Enums.MessageType.ChatMembersAdded && e.Type != Telegram.Bot.Types.Enums.MessageType.ChatMemberLeft)
return;
var m = e.Result.Message;
await this.Device.DeleteMessage(m);
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace JoinHiderBot.forms
{
public class Start : SplitterForm
{
public override async Task<bool> Open(MessageResult e)
{
await this.Device.Send("This bot works only in groups.");
return true;
}
public override async Task<bool> OpenGroup(MessageResult e)
{
var gmf = new GroupManageForm();
await this.NavigateTo(gmf);
return true;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
namespace SystemCommandsBot
{
class Program
{
public static config.Config BotConfig { get; set; }
static void Main(string[] args)
{
BotConfig = config.Config.load();
if (BotConfig.ApiKey == null || BotConfig.ApiKey.Trim() == "")
{
Console.WriteLine("Config created...");
Console.ReadLine();
return;
}
var bot = new TelegramBotBase.BotBase<forms.StartForm>(BotConfig.ApiKey);
bot.Start();
Console.WriteLine("Bot started");
Console.ReadLine();
bot.Stop();
}
}
}
@@ -7,10 +7,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj" />
<PackageReference Include="TelegramBotBase" Version="3.1.1" />
</ItemGroup>
</Project>
+390 -46
View File
@@ -19,15 +19,17 @@ Download a release: [Releases](https://github.com/MajMcCloud/TelegramBotFramewor
Donations
Bitcoin: 1NqyGWmZg8HLp9qQELbf6bChEoba3mxaFc
Bitcoin: 1GoUJYMwAvBipQTfw2FKydAz12J8RDyeJs / bc1qqwlp0p5ley29lsu6jhe0qv7s7963kfc7d0m53d
ETH: 0x795B70CFC27C69603ce115F2b450cbAC5a5460D0
ETH: 0xAf3835104c2C3E5b3e721FA2c7365955e87DB931
Litecoin: LM5iCN6Nz22wAi8LSFnKgdGGWEtxWfJZXv
Litecoin: LRhF1eB7kneFontcDRDU8YjJhEm2GoYHch
DASH: Xb2qyVefvbKTXusHoxZ4Soja2S6R4vLsSr
DASH: XudiUwWtSmAJj1QDdVW7jocQumJFLsyoGZ
Paypal: https://paypal.me/majmccloud
TRON: TYVZSykaVT1nKZnz9hjDgBRNB9VavU1bpW
BITTORRENT: TYVZSykaVT1nKZnz9hjDgBRNB9VavU1bpW
Thanks !
@@ -37,6 +39,7 @@ Thanks !
## Index
- [Introduction](#introduction)
- [How to Start](#how-to-start)
- [Quick Start](#quick-start)
- [Message Handling](#message-handling)
* [Example #0 - System Calls](#add-some-system-calls-example-0---system-calls)
@@ -70,16 +73,53 @@ Thanks !
* [ToggleButton](#toggle-button)
* [ButtonGrid](#button-grid)
* [Paging and Searching](#paging--searching)
* [TaggedButtonGrid](#tagged-button-grid)
* [CheckedButtonList](#checked-button-list)
* [MultiToggleButton](#multi-toggle-button)
- [Groups](#groups)
* [SplitterForm](#splitter-form)
* [GroupForm](#group-form)
* [GroupForm](#group-form)
- [State Machine and Session Serialization](#statemachine)
- [State Machine and Session Serialization (v3.0.0)](#statemachine-and-sessions)
* In the making, is comming soon.
* [StateMachines](#statemachines)
* [SimpleJSONStateMachine](#simplejsonstatemachine)
* [JSONStateMachine](#jsonstatemachine)
* [XMLStateMachine](#xmlstatemachine)
* [Interfaces](#interfaces)
* [IStateMachine](#istatemachine)
* [IStateForm](#istateform)
* [Attributes](#attributes)
* [SaveState](#savestate)
* [IgnoreState](#ignorestate)
- [Navigation and NavigationController (v4.0.0)](#navigation-and-navigationcontroller)
* [As of Now](#as-of-now)
* [How to use](#how-to-use-)
- [Examples](#examples)
---
@@ -99,18 +139,31 @@ It gives you features which will look/feel like WinForms or have a good way to c
## How to start:
Within your empty App your need to put some initial lines including your APIKey to get things started.
The "BotBase" Class will manage a lot of things for you, like system calls, action events and so on.
The "BotBase" Class will manage a lot of things for you, like bot commands, action events and so on.
"StartForm" is your first Formular which every user will get internally redirected to, like a start page, you could redirect from there later in code, so users won't recognize it.
It needs to be a subclass of "FormBase" you will find in Namespace TelegramBotBase.Base
```
//Prepare the System
BotBase<StartForm> bb = new BotBase<StartForm>("{YOUR API KEY}");
//Prepare the System (New in V5)
var bb = BotBaseBuilder
.Create()
.WithAPIKey("{YOUR API KEY}")
.DefaultMessageLoop()
.WithStartForm<StartForm>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot")
//Add Systemcommands if you like, you could catch them later
bb.SystemCalls.Add("/start");
})
.NoSerialization()
.UseEnglish()
.Build();
//Update bot commands to botfather
bb.UploadBotCommands().Wait();
//Start your Bot
bb.Start();
@@ -129,29 +182,26 @@ public class StartForm : FormBase
}
//Gets invoked during Navigation to this form
public override async Task Init(params object[] param)
{
}
//Gets invoked if the form gets opened (with Form.NavigateTo(NewForm))
public override async Task Opened()
{
//Gets invoked during Navigation to this form
}
//Init() got replaced with event handler
//Gets invoked if the form gets leaved
public override async Task Closed()
{
//Opened() got replaced with event handler
//Closed() got replaced with event handler
}
//Gets invoked on every Message/Action/Data in this context
public override async Task Load(MessageResult message)
{
await this.Device.Send("Hello world!");
}
//Gets invoked on edited messages
public override async Task Edited(MessageResult message)
{
}
//Gets invoked on Button clicks
public override async Task Action(MessageResult message)
@@ -192,14 +242,36 @@ var tf = new TestForm();
await this.NavigateTo(tf);
```
## Quick Start:
When migrating from a previous version or starting completely new, all these options can be a bit overwhelming.
For this I added a QuickStart option, directly after the Create call. It just need basic parameters like in earlier versions.
```
//Prepare the System (New in V5)
var bb = BotBaseBuilder
.Create()
.QuickStart<StartForm>("{YOUR API KEY}")
.Build();
//Start your Bot
bb.Start();
```
## Message Handling
All examples are within the test project, so just try it out on your own.
### Add some system calls (Example #0 - System Calls)
### Add some system calls (Example #0 - Bot Commands)
Inside of the BotFather you are able to add "Commands" to your TelegramBot. The user will see them, depending on the application as options he could choose.
I'm calling them Systemcalls. Before start (and later for sure) you could add them to your BotBase. Every time a message comes in they will get checked if they are one of them.
Before start (and later for sure) you could add them to your BotBase. Every time a message comes in they will get checked if they are one of them.
If so, a special event Handler will get raised where you are easier able to manage the action behind.
Below we have 4 options.
@@ -215,15 +287,26 @@ Below we have 4 options.
```
BotBase<Start> bb = new BotBase<Start>("{YOUR API KEY}");
var bb = BotBaseBuilder
.Create()
.WithAPIKey("{YOUR API KEY}")
.DefaultMessageLoop()
.WithStartForm<Start>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot");
a.Add("form1","Opens test form 1" );
a.Add("form2", "Opens test form 2" );
a.Add("params", "Returns all send parameters as a message." );
bb.SystemCalls.Add("/start");
bb.SystemCalls.Add("/form1");
bb.SystemCalls.Add("/form2");
bb.SystemCalls.Add("/params");
})
.NoSerialization()
.UseEnglish()
.Build();
bb.SystemCall += async (s, en) =>
bb.BotCommand += async (s, en) =>
{
switch (en.Command)
{
@@ -254,6 +337,9 @@ bb.SystemCall += async (s, en) =>
};
//Update Bot commands to botfather
bb.UploadBotCommands().Wait();
bb.Start();
```
@@ -268,19 +354,21 @@ On every input the user is sending back to the bot the Action event gets raised.
```
public class SimpleForm : AutoCleanForm
{
public SimpleForm()
{
this.DeleteSide = eSide.Both;
this.DeleteMode = eDeleteMode.OnLeavingForm;
}
public SimpleForm()
{
this.DeleteSide = TelegramBotBase.Enums.eDeleteSide.Both;
this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm;
this.Opened += SimpleForm_Opened;
}
public override async Task Opened()
{
await this.Device.Send("Hello world! (send 'back' to get back to Start)\r\nOr\r\nhi, hello, maybe, bye and ciao");
}
private async Task SimpleForm_Opened(object sender, EventArgs e)
{
await this.Device.Send("Hello world! (send 'back' to get back to Start)\r\nOr\r\nhi, hello, maybe, bye and ciao");
}
public override async Task Load(MessageResult message)
public override async Task Load(MessageResult message)
{
//message.MessageText will work also, cause it is a string you could manage a lot different scenerios here
@@ -655,7 +743,7 @@ public async override Task Render(MessageResult message)
```
There is also a second example, where every of these 3 inputs gets requested by a different formular (class). Just for imagination of the possiblites.
Cause its to much Text, i didnt have added it here. You will find it under [TelegramBaseTest/Tests/Register/PerStep.cs](TelegramBaseTest/Tests/Register/PerStep.cs)
Cause its to much Text, i didnt have added it here. You will find it under [TelegramBotBaseTest/Tests/Register/PerStep.cs](TelegramBotBaseTest/Tests/Register/PerStep.cs)
Beginn there and navigate your way through these Steps in the subfolder.
@@ -774,6 +862,17 @@ await this.NavigateTo(cd);
### Button Grid
<img src="images/buttongrid.gif?raw=true" />
#### Paging & Searching
<img src="images/buttongrid_pagingfilter.gif?raw=true" />
### Tagged Button Grid
<img src="images/taggedbuttongrid.gif?raw=true" />
### Checked Button List
<img src="images/checkedbuttonlist.gif?raw=true" />
### Multi Toggle Button
<img src="images/multitogglebutton.gif?raw=true" />
## Groups
@@ -856,6 +955,7 @@ public class GroupForm : FormBase
case Telegram.Bot.Types.Enums.MessageType.MessagePinned:
case Telegram.Bot.Types.Enums.MessageType.GroupCreated:
case Telegram.Bot.Types.Enums.MessageType.SupergroupCreated:
case Telegram.Bot.Types.Enums.MessageType.ChannelCreated:
await OnGroupChanged(new GroupChangedEventArgs(message.MessageType, message));
@@ -891,8 +991,252 @@ public class GroupForm : FormBase
```
## Statemachine and Sessions
Depending on the usecases and the overall structure of a Telegram Bot it is essential to have some kind of session serialization or state machine to keep the user context after restarts of the bot (ie. due to updates) or crashes.
For this I have created some easy to implement structures which fits into the current environment.
Below you find all possiblities.
### Statemachines
There are actually 3 types of example state machines you could use. A state machine is a kind of serializer which saves the important session data in a reusable structure like JSON or XML.
You could use one of the following state machines:
#### SimpleJSONStateMachine
Is easy to use and useful for simple structures like basic datatypes. Did not work for complex ones like generics. Use the JSONStateMachine for them.
In general you didn't need to do more then, to keep the actual form:
```
//Prepare the System
var bb = BotBaseBuilder
.Create()
.WithAPIKey("{YOUR API KEY}")
.DefaultMessageLoop()
.WithStartForm<StartForm>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot");
})
.UseSimpleJSON(AppContext.BaseDirectory + "config\\states.json")
.UseEnglish()
.Build();
//Start your Bot
bb.Start();
```
#### JSONStateMachine
Is easy to use too, but works for complex datatypes cause it saves there namespaces and additional type informations into the JSON file too.
In general you didn't need to do more then, to keep the actual form:
```
//Prepare the System
var bb = BotBaseBuilder
.Create()
.WithAPIKey("{YOUR API KEY}")
.DefaultMessageLoop()
.WithStartForm<StartForm>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot");
})
.UseJSON(AppContext.BaseDirectory + "config\\states.json")
.UseEnglish()
.Build();
//Start your Bot
bb.Start();
```
#### XMLStateMachine
The last one, should work like the others.
In general you didn't need to do more then, to keep the actual form:
```
//Prepare the System
var bb = BotBaseBuilder
.Create()
.WithAPIKey("{YOUR API KEY}")
.DefaultMessageLoop()
.WithStartForm<StartForm>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot");
})
.UseXML(AppContext.BaseDirectory + "config\\states.xml")
.UseEnglish()
.Build();
//Start your Bot
bb.Start();
```
### Interfaces
There are two interfaces, one for the StateMachine itself, which is useful to build a custom one for a different datatype and one for implementing into a form which should be invoked with events.
#### IStateMachine
Is the basic StateMachine interface, it has two methods SaveFormStates(SaveStatesEventArgs e) and StateContainer LoadFormStates(), nothing fancy, just simple calls. Implement into both methods your own serialization process.
```
public interface IStateMachine
{
void SaveFormStates(SaveStatesEventArgs e);
StateContainer LoadFormStates();
}
```
#### IStateForm
When implemented, this will invoke one of these two methods: LoadState(LoadStateEventArgs e) or SaveState(SaveStateEventArgs e).
They have methods to load or save data from the statemachine of the current form.
```
public interface IStateForm
{
void LoadState(LoadStateEventArgs e);
void SaveState(SaveStateEventArgs e);
}
```
### Attributes
If you don't want to implement the IStateForm interface, cause there are maybe "just" one or two properties you want to keep and restore, use the following attribute:
#### SaveState
This will let the engine know, that you want too keep and restore this field automatically. Unlike the IStateForm methods, you have no option to manipulate data.
```
[SaveState]
public long UserId { get; set; }
```
#### IgnoreState
Due to the fact that Attribute implementation and interaction is optional, you want to let the engine maybe know, that you don't want to keep a specific form. So it should get "lost". This attribute will help you here, add it to the form class and it will not get serialized, even if it implements IStateForm or the SaveState attributes.
```
[IgnoreState]
public class Registration : STForm
{
}
```
## Navigation and NavigationController
### As of now
As from earlier topics on this readme you already know the default way for (cross) navigation between Forms.
It will look something like this:
```
var f = new FormBase();
await this.NavigateTo(f);
```
Depending on the model and structure of your bot it can make sense, to have more linear navigation instead of "cross" navigation.
In example you have a bot which shows a list of football teams. And when clicking on it you want to open the team details and latest matches.
After the matches you want to maybe switch to a different teams and take a look at their statistics and matches.
At some point, you "just" want to get back to the first team so like on Android your clicking the "back" button multiple times.
This can become really complicated, when not having some controller below which handle these "Push/Pop" calls.
Thats why I introduced a NavigationController class which manages these situations and the stack.
### How to use ?
First you need to create a NavigationController instance at the same position in code, where you want to start the navigation.
You will use the current FormBase instance as a root class within the constructor. So you can later come back to this one.
**Tip**: *You can add also a completely new instance of i.e. a main menu form here to get back to it then. So you are free to choose.*
We are using the same FormBase instance as above.
```
var nc = new NavigationController(this);
var f = new FormBase();
//Replace the current form in the context with the controller.
await this.NavigateTo(nc);
//Push the new from onto the stack to render it
nc.PushAsync(f);
```
Later to open a new form use PushAsync again:
```
await this.NavigationController.PushAsync(newForm);
```
When you want to go back one Form on the stack use PopAsync:
```
await this.NavigationController.PopAsync();
```
**Notice**: *By default the NavigationController has ForceCleanupOnLastPop enabled, which means that when the stack is again at 1 (due to PopAsync or PopToRootAsync calls) it will replace the controller automatically with the root form you have given to the constructor at the beginning.*
## Examples
You find all example projects in the "Examples" subfolder.
- [Examples/SystemCommandsBot/](Examples/SystemCommandsBot/)
Will allow you to run specific system commands or run/kill processes via Bot. Has also a simple authentication mechanism with one password.
- [Examples/JoinHiderBot/](Examples/JoinHiderBot/)
Will delete Join and Leave messages automatically in groups.
- [Examples/AsyncFormUpdates/](Examples/AsyncFormUpdates/)
When you want to update forms async without any user interaction (message/action) before. Use the new InvokeMessageLoop method of BotBase.
---
@@ -0,0 +1,61 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading.Tasks;
using Telegram.Bot.Types.InputFiles;
using Telegram.Bot.Types;
using TelegramBotBase.Sessions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Extensions.Images
{
public static class ImageExtensions
{
public static Stream ToStream(this Image image, ImageFormat format)
{
var stream = new System.IO.MemoryStream();
image.Save(stream, format);
stream.Position = 0;
return stream;
}
/// <summary>
/// Sends an image
/// </summary>
/// <param name="image"></param>
/// <param name="name"></param>
/// <param name="buttons"></param>
/// <param name="replyTo"></param>
/// <param name="disableNotification"></param>
/// <returns></returns>
public static async Task<Message> SendPhoto(this DeviceSession session, Image image, String name, String caption, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false)
{
using (var fileStream = ToStream(image, ImageFormat.Png))
{
InputOnlineFile fts = new InputOnlineFile(fileStream, name);
return await session.SendPhoto(fts, caption: caption, buttons, replyTo, disableNotification);
}
}
/// <summary>
/// Sends an image
/// </summary>
/// <param name="image"></param>
/// <param name="name"></param>
/// <param name="buttons"></param>
/// <param name="replyTo"></param>
/// <param name="disableNotification"></param>
/// <returns></returns>
public static async Task<Message> SendPhoto(this DeviceSession session, Bitmap image, String name, String caption, ButtonForm buttons = null, int replyTo = 0, bool disableNotification = false)
{
using (var fileStream = ToStream(image, ImageFormat.Png))
{
InputOnlineFile fts = new InputOnlineFile(fileStream, name);
return await session.SendPhoto(fts, caption: caption, buttons, replyTo, disableNotification);
}
}
}
}
@@ -0,0 +1,8 @@
# TelegramBotBase.Extensions.Images
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/)
[![telegram chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://www.t.me/tgbotbase)
[![license](https://img.shields.io/github/license/MajMcCloud/telegrambotframework.svg?style=flat-square&maxAge=2592000&label=License)](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
[![downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images)
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net5;netcoreapp3.1;net6</TargetFrameworks>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl>
<Copyright>MIT</Copyright>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="Telegram.Bot" Version="17.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
</Project>
+109
View File
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using TelegramBotBase;
using TelegramBotBase.Form;
using TelegramBotBaseTest.Tests;
using TelegramBotBase.Commands;
using TelegramBotBase.Builder;
namespace TelegramBotBaseTest
{
class Program
{
static void Main(string[] args)
{
String APIKey = "";
var bb = BotBaseBuilder
.Create()
.WithAPIKey(APIKey)
.DefaultMessageLoop()
.WithStartForm<Start>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot");
a.Help("Should show you some help");
a.Settings("Should show you some settings");
a.Add("form1", "Opens test form 1");
a.Add("form2", "Opens test form 2");
a.Add("params", "Returns all send parameters as a message.");
})
.NoSerialization()
.UseEnglish()
.Build();
bb.BotCommand += async (s, en) =>
{
switch (en.Command)
{
case "/start":
var start = new Menu();
await en.Device.ActiveForm.NavigateTo(start);
break;
case "/form1":
var form1 = new TestForm();
await en.Device.ActiveForm.NavigateTo(form1);
break;
case "/form2":
var form2 = new TestForm2();
await en.Device.ActiveForm.NavigateTo(form2);
break;
case "/params":
String m = en.Parameters.DefaultIfEmpty("").Aggregate((a, b) => a + " and " + b);
await en.Device.Send("Your parameters are: " + m, replyTo: en.Device.LastMessageId);
en.Handled = true;
break;
}
};
//Update Bot commands to botfather
bb.UploadBotCommands().Wait();
bb.SetSetting(TelegramBotBase.Enums.eSettings.LogAllMessages, true);
bb.Message += (s, en) =>
{
Console.WriteLine(en.DeviceId + " " + en.Message.MessageText + " " + (en.Message.RawData ?? ""));
};
bb.Start();
Console.WriteLine("Telegram Bot started...");
Console.WriteLine("Press q to quit application.");
Console.ReadLine();
bb.Stop();
}
}
}
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3.0;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net5;net6</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
@@ -27,6 +27,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase.Extensions.Images\TelegramBotBase.Extensions.Images.csproj" />
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
@@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Controls;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls
@@ -44,7 +45,7 @@ namespace TelegramBotBaseTest.Tests.Controls
}
private async void Bg_ButtonClicked(object sender, ButtonClickedEventArgs e)
private async Task Bg_ButtonClicked(object sender, ButtonClickedEventArgs e)
{
if (e.Button == null)
return;
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Controls;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls
{
public class ButtonGridPagingForm : AutoCleanForm
{
ButtonGrid m_Buttons = null;
public ButtonGridPagingForm()
{
this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm;
this.Init += ButtonGridForm_Init;
}
private async Task ButtonGridForm_Init(object sender, InitEventArgs e)
{
m_Buttons = new ButtonGrid();
m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard;
m_Buttons.EnablePaging = true;
m_Buttons.EnableSearch = true;
m_Buttons.HeadLayoutButtonRow = new List<ButtonBase>() { new ButtonBase("Back", "back") };
var countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
ButtonForm bf = new ButtonForm();
foreach (var c in countries)
{
bf.AddButtonRow(new ButtonBase(c.EnglishName, c.EnglishName));
}
m_Buttons.ButtonsForm = bf;
m_Buttons.ButtonClicked += Bg_ButtonClicked;
this.AddControl(m_Buttons);
}
private async Task Bg_ButtonClicked(object sender, ButtonClickedEventArgs e)
{
if (e.Button == null)
return;
if (e.Button.Value == "back")
{
var start = new Menu();
await this.NavigateTo(start);
}
else
{
await this.Device.Send($"Button clicked with Text: {e.Button.Text} and Value {e.Button.Value}");
}
}
}
}
@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Controls;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls
{
public class ButtonGridTagForm : AutoCleanForm
{
TaggedButtonGrid m_Buttons = null;
public ButtonGridTagForm()
{
this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm;
this.Init += ButtonGridTagForm_Init;
}
private async Task ButtonGridTagForm_Init(object sender, InitEventArgs e)
{
m_Buttons = new TaggedButtonGrid();
m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard;
m_Buttons.EnablePaging = true;
m_Buttons.HeadLayoutButtonRow = new List<ButtonBase>() { new ButtonBase("Back", "back") };
var countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
ButtonForm bf = new ButtonForm();
foreach (var c in countries)
{
bf.AddButtonRow(new TagButtonBase(c.EnglishName, c.EnglishName, c.Parent.EnglishName));
}
m_Buttons.Tags = countries.Select(a => a.Parent.EnglishName).Distinct().OrderBy(a => a).ToList();
m_Buttons.SelectedTags = countries.Select(a => a.Parent.EnglishName).Distinct().OrderBy(a => a).ToList();
m_Buttons.EnableCheckAllTools = true;
m_Buttons.DataSource = new TelegramBotBase.Datasources.ButtonFormDataSource(bf);
m_Buttons.ButtonClicked += Bg_ButtonClicked;
this.AddControl(m_Buttons);
}
private async Task Bg_ButtonClicked(object sender, ButtonClickedEventArgs e)
{
if (e.Button == null)
return;
switch (e.Button.Value)
{
case "back":
var start = new Menu();
await this.NavigateTo(start);
return;
}
await this.Device.Send($"Button clicked with Text: {e.Button.Text} and Value {e.Button.Value}");
}
}
}
@@ -8,6 +8,7 @@ using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.Controls;
using TelegramBotBase.Args;
using TelegramBotBase.Controls.Inline;
namespace TelegramBotBaseTest.Tests.Controls
{
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Controls;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls
{
public class CheckedButtonListForm : AutoCleanForm
{
CheckedButtonList m_Buttons = null;
public CheckedButtonListForm()
{
this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm;
this.Init += CheckedButtonListForm_Init;
}
private async Task CheckedButtonListForm_Init(object sender, InitEventArgs e)
{
m_Buttons = new CheckedButtonList();
m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.InlineKeyBoard;
m_Buttons.EnablePaging = true;
m_Buttons.HeadLayoutButtonRow = new List<ButtonBase>() { new ButtonBase("Back", "back"), new ButtonBase("Switch Keyboard", "switch") };
m_Buttons.SubHeadLayoutButtonRow = new List<ButtonBase>() { new ButtonBase("No checked items", "$") };
ButtonForm bf = new ButtonForm();
for (int i = 0; i < 30; i++)
{
bf.AddButtonRow($"{i + 1}. Item", i.ToString());
}
m_Buttons.DataSource = new TelegramBotBase.Datasources.ButtonFormDataSource(bf);
m_Buttons.ButtonClicked += Bg_ButtonClicked;
m_Buttons.CheckedChanged += M_Buttons_CheckedChanged;
this.AddControl(m_Buttons);
}
private async Task M_Buttons_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
m_Buttons.SubHeadLayoutButtonRow = new List<ButtonBase>() { new ButtonBase($"{m_Buttons.CheckedItems.Count} checked items", "$") };
}
private async Task Bg_ButtonClicked(object sender, ButtonClickedEventArgs e)
{
if (e.Button == null)
return;
switch (e.Button.Value)
{
case "back":
var start = new Menu();
await NavigateTo(start);
break;
case "switch":
switch (m_Buttons.KeyboardType)
{
case TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard:
m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.InlineKeyBoard;
break;
case TelegramBotBase.Enums.eKeyboardType.InlineKeyBoard:
m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard;
break;
}
break;
default:
await Device.Send($"Button clicked with Text: {e.Button.Text} and Value {e.Button.Value}");
break;
}
}
}
}
@@ -8,6 +8,7 @@ using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.Controls;
using TelegramBotBase.Args;
using TelegramBotBase.Controls.Inline;
namespace TelegramBotBaseTest.Tests.Controls
{
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Controls;
using TelegramBotBase.Controls.Inline;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls
{
public class MultiToggleButtons : AutoCleanForm
{
public MultiToggleButtons()
{
this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm;
this.Init += ToggleButtons_Init;
}
private async Task ToggleButtons_Init(object sender, InitEventArgs e)
{
var mtb = new MultiToggleButton();
mtb.Options = new List<ButtonBase>() { new ButtonBase("Option 1", "1"), new ButtonBase("Option 2", "2"), new ButtonBase("Option 3", "3") };
mtb.SelectedOption = mtb.Options.FirstOrDefault();
mtb.Toggled += Tb_Toggled;
this.AddControl(mtb);
mtb = new MultiToggleButton();
mtb.Options = new List<ButtonBase>() { new ButtonBase("Option 4", "4"), new ButtonBase("Option 5", "5"), new ButtonBase("Option 6", "6") };
mtb.SelectedOption = mtb.Options.FirstOrDefault();
mtb.AllowEmptySelection = false;
mtb.Toggled += Tb_Toggled;
this.AddControl(mtb);
}
private void Tb_Toggled(object sender, EventArgs e)
{
var tb = sender as MultiToggleButton;
if (tb.SelectedOption != null)
{
Console.WriteLine(tb.ID.ToString() + " was pressed, and toggled to " + tb.SelectedOption.Value);
return;
}
Console.WriteLine("Selection for " + tb.ID.ToString() + " has been removed.");
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls
{
public class MultiViewForm : AutoCleanForm
{
Subclass.MultiViewTest mvt = null;
ButtonGrid bg = null;
public MultiViewForm()
{
this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm;
this.Init += MultiViewForm_Init;
}
private async Task MultiViewForm_Init(object sender, TelegramBotBase.Args.InitEventArgs e)
{
mvt = new Subclass.MultiViewTest();
AddControl(mvt);
bg = new ButtonGrid();
bg.ButtonsForm = new ButtonForm();
bg.ButtonsForm.AddButtonRow("Back", "$back$");
bg.ButtonClicked += Bg_ButtonClicked;
bg.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard;
AddControl(bg);
}
private async Task Bg_ButtonClicked(object sender, TelegramBotBase.Args.ButtonClickedEventArgs e)
{
switch(e.Button.Value)
{
case "$back$":
var mn = new Menu();
await NavigateTo(mn);
break;
}
}
}
}
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls.Subclass
{
public class MultiViewTest : TelegramBotBase.Controls.Hybrid.MultiView
{
public override async Task Action(MessageResult result, string value = null)
{
switch (result.RawData)
{
case "back":
this.SelectedViewIndex--;
break;
case "next":
this.SelectedViewIndex++;
break;
}
}
public override async Task RenderView(RenderViewEventArgs e)
{
ButtonForm bf = new ButtonForm();
bf.AddButtonRow(new ButtonBase("Back", "back"), new ButtonBase("Next", "next"));
switch (e.CurrentView)
{
case 0:
case 1:
case 2:
await Device.Send($"Page {e.CurrentView + 1}", bf);
break;
default:
await Device.Send("Unknown Page", bf);
break;
}
}
}
}
@@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Controls;
using TelegramBotBase.Controls.Inline;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Controls
@@ -7,6 +7,7 @@ using TelegramBotBase.Form;
using TelegramBotBase.Controls;
using TelegramBotBase.Base;
using TelegramBotBase.Args;
using TelegramBotBase.Controls.Inline;
namespace TelegramBotBaseTest.Tests.Controls
{
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.InputFiles;
using Telegram.Bot.Types.ReplyMarkups;
@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Datasources;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Datasources
{
public class CustomDataSource : ButtonFormDataSource
{
public List<String> Countries = new List<string>() { "Country 1", "Country 2", "Country 3" };
public CustomDataSource()
{
loadData();
}
/// <summary>
/// This method has the example purpose of creating and loading some example data.
/// When using a database you do not need this kind of method.
/// </summary>
private void loadData()
{
//Exists data source? Read it
if (File.Exists(AppContext.BaseDirectory + "countries.json"))
{
try
{
var List = Newtonsoft.Json.JsonConvert.DeserializeObject<List<String>>(File.ReadAllText("countries.json"));
Countries = List;
}
catch
{
}
return;
}
//If not, create it
try
{
var countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures).Select(a => a.DisplayName).ToList();
Countries = countries;
var tmp = Newtonsoft.Json.JsonConvert.SerializeObject(countries);
File.WriteAllText( AppContext.BaseDirectory + "countries.json", tmp);
}
catch
{
}
}
public override ButtonRow ItemAt(int index)
{
var item = Countries.ElementAt(index);
if (item == null)
return new ButtonRow();
return Render(item);
}
public override List<ButtonRow> ItemRange(int start, int count)
{
var items = Countries.Skip(start).Take(count);
List<ButtonRow> lst = new List<ButtonRow>();
foreach (var c in items)
{
lst.Add(Render(c));
}
return lst;
}
public override List<ButtonRow> AllItems()
{
List<ButtonRow> lst = new List<ButtonRow>();
foreach (var c in Countries)
{
lst.Add(Render(c));
}
return lst;
}
public override ButtonForm PickItems(int start, int count, string filter = null)
{
List<ButtonRow> rows = ItemRange(start, count);
ButtonForm lst = new ButtonForm();
foreach (var c in rows)
{
lst.AddButtonRow(c);
}
return lst;
}
public override ButtonForm PickAllItems(string filter = null)
{
List<ButtonRow> rows = AllItems();
ButtonForm bf = new ButtonForm();
bf.AddButtonRows(rows);
return bf;
}
public override int CalculateMax(string filter = null)
{
if (filter == null)
return Countries.Count;
return Countries.Where(a => a.IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) != -1).Count();
}
public override ButtonRow Render(object data)
{
var s = data as String;
if (s == null)
return new ButtonRow(new ButtonBase("Empty", "zero"));
return new ButtonRow(new ButtonBase(s, s));
}
public override int Count
{
get
{
return Countries.Count;
}
}
public override int ColumnCount
{
get
{
return 1;
}
}
public override int RowCount
{
get
{
return this.Count;
}
}
}
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Datasources
{
public class List : FormBase
{
ButtonGrid __buttons = null;
public List()
{
this.Init += List_Init;
}
private async Task List_Init(object sender, TelegramBotBase.Args.InitEventArgs e)
{
__buttons = new ButtonGrid();
__buttons.EnablePaging = true;
__buttons.EnableSearch = false;
__buttons.ButtonClicked += __buttons_ButtonClicked;
__buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard;
__buttons.DeleteReplyMessage = true;
__buttons.HeadLayoutButtonRow = new ButtonRow(new ButtonBase("Back", "back"));
var cds = new CustomDataSource();
__buttons.DataSource = cds;
AddControl(__buttons);
}
private async Task __buttons_ButtonClicked(object sender, TelegramBotBase.Args.ButtonClickedEventArgs e)
{
switch(e.Button.Value)
{
case "back":
var mn = new Menu();
await NavigateTo(mn);
break;
}
}
}
}
@@ -12,7 +12,7 @@ namespace TelegramBotBaseTest.Tests.Groups
public class LinkReplaceTest : TelegramBotBase.Form.GroupForm
{
Dictionary<int, int> Counter { get; set; } = new Dictionary<int, int>();
Dictionary<long, int> Counter { get; set; } = new Dictionary<long, int>();
private const int Maximum = 3;
@@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBaseTest.Tests.Controls;
namespace TelegramBotBaseTest.Tests
{
@@ -27,6 +28,7 @@ namespace TelegramBotBaseTest.Tests
}
await Device.HideReplyKeyboard();
}
public override async Task Action(MessageResult message)
@@ -39,7 +41,9 @@ namespace TelegramBotBaseTest.Tests
if (call == null)
return;
switch(call.Value)
message.Handled = true;
switch (call.Value)
{
case "text":
@@ -91,8 +95,6 @@ namespace TelegramBotBaseTest.Tests
case "data":
message.Handled = true;
var data = new DataForm();
await this.NavigateTo(data);
@@ -101,8 +103,6 @@ namespace TelegramBotBaseTest.Tests
case "calendar":
message.Handled = true;
var calendar = new Controls.CalendarPickerForm();
await this.NavigateTo(calendar);
@@ -111,8 +111,6 @@ namespace TelegramBotBaseTest.Tests
case "month":
message.Handled = true;
var month = new Controls.MonthPickerForm();
await this.NavigateTo(month);
@@ -121,8 +119,6 @@ namespace TelegramBotBaseTest.Tests
case "treeview":
message.Handled = true;
var tree = new Controls.TreeViewForms();
await this.NavigateTo(tree);
@@ -131,21 +127,90 @@ namespace TelegramBotBaseTest.Tests
case "togglebuttons":
message.Handled = true;
var tb = new Controls.ToggleButtons();
await this.NavigateTo(tb);
break;
case "buttongrid":
message.Handled = true;
case "multitogglebuttons":
var mtb = new Controls.MultiToggleButtons();
await this.NavigateTo(mtb);
break;
case "buttongrid":
var bg = new Controls.ButtonGridForm();
await this.NavigateTo(bg);
break;
case "buttongridfilter":
var bg2 = new Controls.ButtonGridPagingForm();
await this.NavigateTo(bg2);
break;
case "buttongridtags":
var bg3 = new Controls.ButtonGridTagForm();
await this.NavigateTo(bg3);
break;
case "multiview":
var mvf = new MultiViewForm();
await NavigateTo(mvf);
break;
case "checkedbuttonlist":
var cbl = new CheckedButtonListForm();
await NavigateTo(cbl);
break;
case "navigationcontroller":
var nc = new Navigation.Start();
await NavigateTo(nc);
break;
case "dynamicbuttongrid":
var dg = new Datasources.List();
await NavigateTo(dg);
break;
case "notifications":
var not = new Notifications.Start();
await NavigateTo(not);
break;
default:
message.Handled = false;
break;
}
@@ -173,8 +238,24 @@ namespace TelegramBotBaseTest.Tests
btn.AddButtonRow(new ButtonBase("#11 ToggleButtons", new CallbackData("a", "togglebuttons").Serialize()));
btn.AddButtonRow(new ButtonBase("#11.2 MultiToggleButtons", new CallbackData("a", "multitogglebuttons").Serialize()));
btn.AddButtonRow(new ButtonBase("#12 ButtonGrid", new CallbackData("a", "buttongrid").Serialize()));
btn.AddButtonRow(new ButtonBase("#13 ButtonGrid Paging & Filter", new CallbackData("a", "buttongridfilter").Serialize()));
btn.AddButtonRow(new ButtonBase("#14 ButtonGrid Tags (Filter)", new CallbackData("a", "buttongridtags").Serialize()));
btn.AddButtonRow(new ButtonBase("#15 MultiView", new CallbackData("a", "multiview").Serialize()));
btn.AddButtonRow(new ButtonBase("#16 CheckedButtonList", new CallbackData("a", "checkedbuttonlist").Serialize()));
btn.AddButtonRow(new ButtonBase("#17 NavigationController (Push/Pop)", new CallbackData("a", "navigationcontroller").Serialize()));
btn.AddButtonRow(new ButtonBase("#18 Dynamic ButtonGrid (DataSources)", new CallbackData("a", "dynamicbuttongrid").Serialize()));
btn.AddButtonRow(new ButtonBase("#19 Notifications", new CallbackData("a", "notifications").Serialize()));
await this.Device.Send("Choose your test:", btn);
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Form;
using TelegramBotBase.Form.Navigation;
namespace TelegramBotBaseTest.Tests.Navigation
{
class CustomController : NavigationController
{
public CustomController(FormBase form) : base(form)
{
}
public override Task PushAsync(FormBase form, params object[] args)
{
Console.WriteLine($"Pushes form (Count on stack {this.Index + 1})");
//Device.Send($"Pushes form (Count on stack {this.Index + 1})");
return base.PushAsync(form, args);
}
public override Task PopAsync()
{
Console.WriteLine($"Pops one form (Count on stack {this.Index + 1})");
//Device.Send($"Pops one form (Count on stack {this.Index + 1})");
return base.PopAsync();
}
public override Task PopToRootAsync()
{
Console.WriteLine($"Moved back to root (Count on stack {this.Index + 1})");
//Device.Send($"Moved back to root (Count on stack {this.Index + 1})");
return base.PopToRootAsync();
}
}
}
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Navigation
{
public class Form1 : FormBase
{
Message msg = null;
public Form1()
{
this.Closed += Form1_Closed;
}
private async Task Form1_Closed(object sender, EventArgs e)
{
if (msg == null)
return;
await Device.DeleteMessage(msg);
}
public override async Task Action(MessageResult message)
{
if (message.Handled)
return;
await message.ConfirmAction();
switch (message.RawData)
{
case "next":
message.Handled = true;
var f1 = new Form1();
await NavigationController.PushAsync(f1);
break;
case "previous":
message.Handled = true;
//Pop's the current form and move the previous one. The root form will be the Start class.
await NavigationController.PopAsync();
break;
case "root":
message.Handled = true;
await NavigationController.PopToRootAsync();
break;
}
}
public override async Task Render(MessageResult message)
{
if (msg != null)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Next page", "next");
bf.AddButtonRow("Previous page", "previous");
bf.AddButtonRow("Back to root", "root");
msg = await Device.Send($"Choose your options (Count on stack {NavigationController.Index + 1})", bf);
}
}
}
@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.Form.Navigation;
namespace TelegramBotBaseTest.Tests.Navigation
{
public class Start : FormBase
{
Message msg = null;
public Start()
{
}
public override async Task Load(MessageResult message)
{
}
public override async Task Action(MessageResult message)
{
if (message.Handled)
return;
await message.ConfirmAction();
switch (message.RawData)
{
case "yes":
message.Handled = true;
//Create navigation controller and navigate to it, keep the current form as root form so we can get back to here later
var nc = new CustomController(this);
nc.ForceCleanupOnLastPop = true;
var f1 = new Form1();
await nc.PushAsync(f1);
await NavigateTo(nc);
if (msg == null)
return;
await Device.DeleteMessage(msg);
break;
case "no":
message.Handled = true;
var mn = new Menu();
await NavigateTo(mn);
if (msg == null)
return;
await Device.DeleteMessage(msg);
break;
}
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Yes", "yes");
bf.AddButtonRow("No", "no");
msg = await Device.Send("Open controller?", bf);
}
}
}
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests.Notifications
{
public class Start : AutoCleanForm
{
bool sent = false;
public Start()
{
this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm;
}
public override async Task Action(MessageResult message)
{
if (message.Handled)
return;
switch (message.RawData)
{
case "alert":
await message.ConfirmAction("This is an alert.", true);
break;
case "back":
var mn = new Menu();
await NavigateTo(mn);
break;
default:
await message.ConfirmAction("This is feedback");
break;
}
}
public override async Task Render(MessageResult message)
{
if (sent)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Normal feeback", "normal");
bf.AddButtonRow("Alert Box", "alert");
bf.AddButtonRow("Back", "back");
await Device.Send("Choose your test", bf);
sent = true;
}
}
}
@@ -5,6 +5,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Controls.Inline;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests
@@ -35,41 +36,41 @@ namespace TelegramBotBaseTest.Tests
if (call == null)
return;
TelegramBotBase.Controls.ProgressBar Bar = null;
ProgressBar Bar = null;
switch (call.Value)
{
case "standard":
Bar = new TelegramBotBase.Controls.ProgressBar(0, 100, TelegramBotBase.Controls.ProgressBar.eProgressStyle.standard);
Bar = new ProgressBar(0, 100, ProgressBar.eProgressStyle.standard);
Bar.Device = this.Device;
break;
case "squares":
Bar = new TelegramBotBase.Controls.ProgressBar(0, 100, TelegramBotBase.Controls.ProgressBar.eProgressStyle.squares);
Bar = new ProgressBar(0, 100, ProgressBar.eProgressStyle.squares);
Bar.Device = this.Device;
break;
case "circles":
Bar = new TelegramBotBase.Controls.ProgressBar(0, 100, TelegramBotBase.Controls.ProgressBar.eProgressStyle.circles);
Bar = new ProgressBar(0, 100, ProgressBar.eProgressStyle.circles);
Bar.Device = this.Device;
break;
case "lines":
Bar = new TelegramBotBase.Controls.ProgressBar(0, 100, TelegramBotBase.Controls.ProgressBar.eProgressStyle.lines);
Bar = new ProgressBar(0, 100, ProgressBar.eProgressStyle.lines);
Bar.Device = this.Device;
break;
case "squaredlines":
Bar = new TelegramBotBase.Controls.ProgressBar(0, 100, TelegramBotBase.Controls.ProgressBar.eProgressStyle.squaredLines);
Bar = new ProgressBar(0, 100, ProgressBar.eProgressStyle.squaredLines);
Bar.Device = this.Device;
break;
@@ -38,7 +38,6 @@ namespace TelegramBotBaseTest.Tests
{
case "reply":
break;
@@ -52,10 +51,10 @@ namespace TelegramBotBaseTest.Tests
default:
if (message.RawMessageData == null)
if (message.UpdateData == null)
return;
this.LastMessage = message.RawMessageData.Message.Text;
this.LastMessage = message.Message.Text;
break;
}
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Base;
using TelegramBotBase.Extensions.Images;
using TelegramBotBase.Form;
namespace TelegramBotBaseTest.Tests
@@ -31,7 +32,7 @@ namespace TelegramBotBaseTest.Tests
{
await this.Device.Send("Ciao from Form 2");
}
@@ -118,7 +119,7 @@ namespace TelegramBotBaseTest.Tests
btn.AddButtonRow(new ButtonBase("Request Prompt", CallbackData.Create("navigate", "prompt")));
await this.Device.SendPhoto(bmp, "Test", btn);
await this.Device.SendPhoto(bmp, "Test", "", btn);
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net452" />
<package id="System.Net.Requests" version="4.3.0" targetFramework="net452" />
<package id="Telegram.Bot" version="14.12.0" targetFramework="net452" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net5" />
<package id="System.Net.Requests" version="4.3.0" targetFramework="net5" />
<package id="Telegram.Bot" version="14.12.0" targetFramework="net5" />
</packages>
@@ -9,9 +9,9 @@ using TelegramBotBase.Sessions;
namespace TelegramBotBase.Args
{
/// <summary>
/// Base class for given system call results
/// Base class for given bot command results
/// </summary>
public class SystemCallEventArgs : EventArgs
public class BotCommandEventArgs : EventArgs
{
public String Command { get; set; }
@@ -26,13 +26,13 @@ namespace TelegramBotBase.Args
public Message OriginalMessage { get; set; }
public SystemCallEventArgs()
public BotCommandEventArgs()
{
}
public SystemCallEventArgs(String Command, List<String> Parameters, Message Message, long DeviceId, DeviceSession Device)
public BotCommandEventArgs(String Command, List<String> Parameters, Message Message, long DeviceId, DeviceSession Device)
{
this.Command = Command;
this.Parameters = Parameters;
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBase.Args
@@ -14,6 +15,12 @@ namespace TelegramBotBase.Args
{
public ButtonBase Button { get; set; }
public int Index { get; set; }
public object Tag { get; set; }
public ButtonRow Row { get; set; }
public ButtonClickedEventArgs()
{
@@ -23,7 +30,20 @@ namespace TelegramBotBase.Args
public ButtonClickedEventArgs(ButtonBase button)
{
this.Button = button;
this.Index = -1;
}
public ButtonClickedEventArgs(ButtonBase button, int Index)
{
this.Button = button;
this.Index = Index;
}
public ButtonClickedEventArgs(ButtonBase button, int Index, ButtonRow row)
{
this.Button = button;
this.Index = Index;
this.Row = row;
}
}
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBase.Args
{
public class CheckedChangedEventArgs : EventArgs
{
/// <summary>
/// Contains the index of the row where the button is inside.
/// Contains -1 when it is a layout button or not found.
/// </summary>
public int Index { get; set; }
/// <summary>
/// Contains all buttons within this row, excluding the checkbox.
/// </summary>
public ButtonRow Row { get; set; }
/// <summary>
/// Contains the new checked status of the row.
/// </summary>
public bool Checked { get; set; }
public CheckedChangedEventArgs()
{
}
public CheckedChangedEventArgs(ButtonRow row, int Index, bool Checked)
{
this.Row = row;
this.Index = Index;
this.Checked = Checked;
}
}
}
@@ -54,5 +54,10 @@ namespace TelegramBotBase.Args
return false;
}
public object GetObject(String key)
{
return Values[key];
}
}
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
namespace TelegramBotBase.Args
{
public class MessageDeletedEventArgs
{
public int MessageId
{
get;set;
}
public MessageDeletedEventArgs(int messageId)
{
this.MessageId = messageId;
}
}
}
+8 -1
View File
@@ -19,9 +19,16 @@ namespace TelegramBotBase.Args
public Message Message { get; set; }
public MessageSentEventArgs(Message message)
/// <summary>
/// Contains the element, which has called the method.
/// </summary>
public Type Origin { get; set; }
public MessageSentEventArgs(Message message, Type Origin)
{
this.Message = message;
this.Origin = Origin;
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Args
{
public class PromptDialogCompletedEventArgs
{
public object Tag { get; set; }
public String Value { get; set; }
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Args
{
public class RenderViewEventArgs : EventArgs
{
public int CurrentView { get; set; }
public RenderViewEventArgs(int ViewIndex)
{
CurrentView = ViewIndex;
}
}
}
@@ -32,6 +32,10 @@ namespace TelegramBotBase.Args
{
Values[key] = value;
}
public void SetObject(String key, object value)
{
Values[key] = value;
}
}
}
+17 -5
View File
@@ -28,19 +28,27 @@ namespace TelegramBotBase.Base
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Get invoked when control will be added to a form and invoked.
/// </summary>
/// <returns></returns>
public virtual void Init()
{
}
public virtual async Task Load(MessageResult result)
{
}
public virtual async Task Action(MessageResult result, String value = null)
{
}
@@ -50,18 +58,22 @@ namespace TelegramBotBase.Base
}
public virtual async Task Hidden(bool FormClose)
{
}
/// <summary>
/// Will be called on a cleanup.
/// </summary>
/// <returns></returns>
public virtual async Task Cleanup()
{
}
}
+67 -20
View File
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.InputFiles;
@@ -14,7 +15,11 @@ namespace TelegramBotBase.Base
/// </summary>
public class DataResult : ResultBase
{
public Telegram.Bot.Args.MessageEventArgs RawMessageData { get; set; }
//public Telegram.Bot.Args.MessageEventArgs RawMessageData { get; set; }
public UpdateResult UpdateData { get; set; }
public Contact Contact
{
@@ -64,11 +69,20 @@ namespace TelegramBotBase.Base
}
}
public Telegram.Bot.Types.Enums.MessageType Type
{
get
{
return this.RawMessageData?.Message?.Type ?? Telegram.Bot.Types.Enums.MessageType.Unknown;
return this.Message?.Type ?? Telegram.Bot.Types.Enums.MessageType.Unknown;
}
}
public override Message Message
{
get
{
return this.UpdateData?.Message;
}
}
@@ -86,29 +100,26 @@ namespace TelegramBotBase.Base
}
}
public DataResult(Telegram.Bot.Args.MessageEventArgs rawdata)
public DataResult(UpdateResult update)
{
this.RawMessageData = rawdata;
this.Message = rawdata.Message;
this.UpdateData = update;
}
public DataResult(MessageResult message)
{
this.RawMessageData = message.RawMessageData;
this.Message = message.Message;
this.Client = message.Client;
}
public async Task<InputOnlineFile> DownloadDocument()
{
var encryptedContent = new System.IO.MemoryStream(this.Document.FileSize);
var encryptedContent = new System.IO.MemoryStream(this.Document.FileSize.Value);
var file = await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(this.Document.FileId, encryptedContent);
return new InputOnlineFile(encryptedContent, this.Document.FileName);
}
/// <summary>
/// Downloads a file and saves it to the given path.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public async Task DownloadDocument(String path)
{
var file = await this.Client.TelegramClient.GetFileAsync(this.Document.FileId);
@@ -118,11 +129,47 @@ namespace TelegramBotBase.Base
fs.Dispose();
}
/// <summary>
/// Downloads the document and returns an byte array.
/// </summary>
/// <returns></returns>
public async Task<byte[]> DownloadRawDocument()
{
MemoryStream ms = new MemoryStream();
await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(this.Document.FileId, ms);
return ms.ToArray();
}
/// <summary>
/// Downloads a document and returns it as string. (txt,csv,etc) Default encoding ist UTF8.
/// </summary>
/// <returns></returns>
public async Task<String> DownloadRawTextDocument()
{
return await DownloadRawTextDocument(Encoding.UTF8);
}
/// <summary>
/// Downloads a document and returns it as string. (txt,csv,etc)
/// </summary>
/// <returns></returns>
public async Task<String> DownloadRawTextDocument(Encoding encoding)
{
MemoryStream ms = new MemoryStream();
await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(this.Document.FileId, ms);
ms.Position = 0;
var sr = new StreamReader(ms, encoding);
return sr.ReadToEnd();
}
public async Task<InputOnlineFile> DownloadVideo()
{
var encryptedContent = new System.IO.MemoryStream(this.Video.FileSize);
var encryptedContent = new System.IO.MemoryStream(this.Video.FileSize.Value);
var file = await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(this.Video.FileId, encryptedContent);
return new InputOnlineFile(encryptedContent, "");
}
@@ -137,7 +184,7 @@ namespace TelegramBotBase.Base
public async Task<InputOnlineFile> DownloadAudio()
{
var encryptedContent = new System.IO.MemoryStream(this.Audio.FileSize);
var encryptedContent = new System.IO.MemoryStream(this.Audio.FileSize.Value);
var file = await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(this.Audio.FileId, encryptedContent);
return new InputOnlineFile(encryptedContent, "");
@@ -155,9 +202,9 @@ namespace TelegramBotBase.Base
public async Task<InputOnlineFile> DownloadPhoto(int index)
{
var photo = this.Photos[index];
var encryptedContent = new System.IO.MemoryStream(photo.FileSize);
var encryptedContent = new System.IO.MemoryStream(photo.FileSize.Value);
var file = await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(photo.FileId, encryptedContent);
return new InputOnlineFile(encryptedContent, "");
}
+37 -6
View File
@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Form.Navigation;
using TelegramBotBase.Sessions;
using static TelegramBotBase.Base.Async;
@@ -17,6 +18,8 @@ namespace TelegramBotBase.Form
public class FormBase : IDisposable
{
public NavigationController NavigationController { get; set; }
public DeviceSession Device { get; set; }
public MessageClient Client { get; set; }
@@ -55,7 +58,10 @@ namespace TelegramBotBase.Form
if (this.Events[__evInit] == null)
return;
var handler = this.Events[__evInit].GetInvocationList().Cast<AsyncEventHandler<InitEventArgs>>();
var handler = this.Events[__evInit]?.GetInvocationList().Cast<AsyncEventHandler<InitEventArgs>>();
if (handler == null)
return;
foreach (var h in handler)
{
await Async.InvokeAllAsync<InitEventArgs>(h, this, e);
@@ -84,7 +90,10 @@ namespace TelegramBotBase.Form
if (this.Events[__evOpened] == null)
return;
var handler = this.Events[__evOpened].GetInvocationList().Cast<AsyncEventHandler<EventArgs>>();
var handler = this.Events[__evOpened]?.GetInvocationList().Cast<AsyncEventHandler<EventArgs>>();
if (handler == null)
return;
foreach (var h in handler)
{
await Async.InvokeAllAsync<EventArgs>(h, this, e);
@@ -114,7 +123,10 @@ namespace TelegramBotBase.Form
if (this.Events[__evClosed] == null)
return;
var handler = this.Events[__evClosed].GetInvocationList().Cast<AsyncEventHandler<EventArgs>>();
var handler = this.Events[__evClosed]?.GetInvocationList().Cast<AsyncEventHandler<EventArgs>>();
if (handler == null)
return;
foreach (var h in handler)
{
await Async.InvokeAllAsync<EventArgs>(h, this, e);
@@ -171,7 +183,7 @@ namespace TelegramBotBase.Form
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task LoadControls(MessageResult message)
public virtual async Task LoadControls(MessageResult message)
{
//Looking for the control by id, if not listened, raise event for all
if (message.RawData?.StartsWith("#c") ?? false)
@@ -219,7 +231,7 @@ namespace TelegramBotBase.Form
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ActionControls(MessageResult message)
public virtual async Task ActionControls(MessageResult message)
{
//Looking for the control by id, if not listened, raise event for all
if (message.RawData.StartsWith("#c"))
@@ -269,7 +281,7 @@ namespace TelegramBotBase.Form
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task RenderControls(MessageResult message)
public virtual async Task RenderControls(MessageResult message)
{
foreach (var b in this.Controls)
{
@@ -391,9 +403,15 @@ namespace TelegramBotBase.Form
/// <param name="control"></param>
public void AddControl(ControlBase control)
{
//Duplicate check
if (this.Controls.Contains(control))
throw new ArgumentException("Control has been already added.");
control.ID = this.Controls.Count + 1;
control.Device = this.Device;
this.Controls.Add(control);
control.Init();
}
/// <summary>
@@ -410,6 +428,19 @@ namespace TelegramBotBase.Form
this.Controls.Remove(control);
}
/// <summary>
/// Removes all controls.
/// </summary>
public void RemoveAllControls()
{
foreach(var c in this.Controls)
{
c.Cleanup().Wait();
this.Controls.Remove(c);
}
}
/// <summary>
/// Cleanup
/// </summary>
+88 -86
View File
@@ -5,7 +5,13 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot.Exceptions;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Extensions.Polling;
namespace TelegramBotBase.Base
{
@@ -15,18 +21,23 @@ namespace TelegramBotBase.Base
public class MessageClient
{
public String APIKey { get; set; }
public Telegram.Bot.TelegramBotClient TelegramClient { get; set; }
public ITelegramBotClient TelegramClient { get; set; }
private EventHandlerList __Events { get; set; } = new EventHandlerList();
private static object __evOnMessageLoop = new object();
private static object __evOnMessage = new object();
private static object __evOnMessageEdit = new object();
private static object __evCallbackQuery = new object();
CancellationTokenSource __cancellationTokenSource;
public MessageClient(String APIKey)
{
@@ -40,18 +51,27 @@ namespace TelegramBotBase.Base
{
this.APIKey = APIKey;
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, proxy);
Prepare();
}
public MessageClient(String APIKey, Uri proxyUrl)
public MessageClient(String APIKey, Uri proxyUrl, NetworkCredential credential = null)
{
this.APIKey = APIKey;
var proxy = new WebProxy(proxyUrl);
var proxy = new WebProxy(proxyUrl)
{
Credentials = credential
};
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, proxy);
var httpClient = new HttpClient(
new HttpClientHandler { Proxy = proxy, UseProxy = true }
);
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, httpClient);
Prepare();
}
@@ -68,11 +88,17 @@ namespace TelegramBotBase.Base
var proxy = new WebProxy(proxyHost, proxyPort);
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, proxy);
var httpClient = new HttpClient(
new HttpClientHandler { Proxy = proxy, UseProxy = true }
);
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, httpClient);
Prepare();
}
public MessageClient(String APIKey, Telegram.Bot.TelegramBotClient Client)
{
this.APIKey = APIKey;
@@ -86,114 +112,90 @@ namespace TelegramBotBase.Base
{
this.TelegramClient.Timeout = new TimeSpan(0, 0, 30);
this.TelegramClient.OnMessage += TelegramClient_OnMessage;
this.TelegramClient.OnMessageEdited += TelegramClient_OnMessageEdited;
this.TelegramClient.OnCallbackQuery += TelegramClient_OnCallbackQuery;
}
private async void TelegramClient_OnMessage(object sender, Telegram.Bot.Args.MessageEventArgs e)
public void StartReceiving()
{
//Skip empty messages by default
if (e.Message == null)
return;
__cancellationTokenSource = new CancellationTokenSource();
try
{
var mr = new MessageResult(e);
mr.Client = this;
OnMessage(mr);
}
catch
var receiverOptions = new ReceiverOptions
{
AllowedUpdates = { } // receive all update types
};
}
this.TelegramClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions, __cancellationTokenSource.Token);
}
private async void TelegramClient_OnMessageEdited(object sender, Telegram.Bot.Args.MessageEventArgs e)
public void StopReceiving()
{
//Skip empty messages by default
if (e.Message == null)
return;
try
{
var mr = new MessageResult(e);
mr.Client = this;
OnMessageEdit(mr);
}
catch
{
}
__cancellationTokenSource.Cancel();
}
private async void TelegramClient_OnCallbackQuery(object sender, Telegram.Bot.Args.CallbackQueryEventArgs e)
public Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
try
{
var ar = new MessageResult(e);
ar.Client = this;
OnAction(ar);
}
catch
{
OnMessageLoop(new UpdateResult(update, null));
}
return Task.CompletedTask;
}
public Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
{
if (exception is ApiRequestException exAPI)
{
Console.WriteLine($"Telegram API Error:\n[{exAPI.ErrorCode}]\n{exAPI.Message}");
}
else
{
Console.WriteLine(exception.ToString());
}
return Task.CompletedTask;
}
/// <summary>
/// This will return the current list of bot commands.
/// </summary>
/// <returns></returns>
public async Task<BotCommand[]> GetBotCommands()
{
return await this.TelegramClient.GetMyCommandsAsync();
}
/// <summary>
/// This will set your bot commands to the given list.
/// </summary>
/// <param name="botcommands"></param>
/// <returns></returns>
public async Task SetBotCommands(List<BotCommand> botcommands)
{
await this.TelegramClient.SetMyCommandsAsync(botcommands);
}
#region "Events"
public event EventHandler<MessageResult> Message
public event Async.AsyncEventHandler<UpdateResult> MessageLoop
{
add
{
this.__Events.AddHandler(__evOnMessage, value);
this.__Events.AddHandler(__evOnMessageLoop, value);
}
remove
{
this.__Events.RemoveHandler(__evOnMessage, value);
this.__Events.RemoveHandler(__evOnMessageLoop, value);
}
}
public void OnMessage(MessageResult result)
public void OnMessageLoop(UpdateResult update)
{
(this.__Events[__evOnMessage] as EventHandler<MessageResult>)?.Invoke(this, result);
}
public event EventHandler<MessageResult> MessageEdit
{
add
{
this.__Events.AddHandler(__evOnMessageEdit, value);
}
remove
{
this.__Events.RemoveHandler(__evOnMessageEdit, value);
}
}
public void OnMessageEdit(MessageResult result)
{
(this.__Events[__evOnMessageEdit] as EventHandler<MessageResult>)?.Invoke(this, result);
}
public event EventHandler<MessageResult> Action
{
add
{
this.__Events.AddHandler(__evCallbackQuery, value);
}
remove
{
this.__Events.RemoveHandler(__evCallbackQuery, value);
}
}
public void OnAction(MessageResult result)
{
(this.__Events[__evCallbackQuery] as EventHandler<MessageResult>)?.Invoke(this, result);
(this.__Events[__evOnMessageLoop] as Async.AsyncEventHandler<UpdateResult>)?.Invoke(this, update);
}
+47 -33
View File
@@ -3,15 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Types;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
{
public class MessageResult : ResultBase
{
public Telegram.Bot.Args.MessageEventArgs RawMessageData { get; set; }
public Telegram.Bot.Args.CallbackQueryEventArgs RawCallbackData { get; set; }
public Telegram.Bot.Types.Update UpdateData { get; set; }
/// <summary>
/// Returns the Device/ChatId
@@ -20,12 +21,19 @@ namespace TelegramBotBase.Base
{
get
{
return this.RawMessageData?.Message?.Chat.Id ?? this.RawCallbackData?.CallbackQuery.Message?.Chat.Id ?? 0;
return this.UpdateData?.Message?.Chat?.Id
?? this.UpdateData?.EditedMessage?.Chat.Id
?? this.UpdateData?.CallbackQuery.Message?.Chat.Id
?? Device?.DeviceId
?? 0;
}
}
public DeviceSession Device
{ get; set; }
{
get;
set;
}
/// <summary>
/// The message id
@@ -34,7 +42,10 @@ namespace TelegramBotBase.Base
{
get
{
return this.Message?.MessageId ?? this.RawCallbackData?.CallbackQuery?.Message?.MessageId ?? 0;
return this.UpdateData?.Message?.MessageId
?? this.Message?.MessageId
?? this.UpdateData?.CallbackQuery?.Message?.MessageId
?? 0;
}
}
@@ -42,7 +53,7 @@ namespace TelegramBotBase.Base
{
get
{
return this.RawMessageData?.Message?.Text ?? "";
return this.UpdateData?.Message?.Text ?? "";
}
}
@@ -50,7 +61,7 @@ namespace TelegramBotBase.Base
{
get
{
return this.RawMessageData?.Message?.Text ?? "";
return this.UpdateData?.Message?.Text ?? "";
}
}
@@ -58,7 +69,19 @@ namespace TelegramBotBase.Base
{
get
{
return this.RawMessageData?.Message?.Type ?? Telegram.Bot.Types.Enums.MessageType.Unknown;
return Message?.Type ?? Telegram.Bot.Types.Enums.MessageType.Unknown;
}
}
public Message Message
{
get
{
return this.UpdateData?.Message
?? this.UpdateData?.EditedMessage
?? this.UpdateData?.ChannelPost
?? this.UpdateData?.EditedChannelPost
?? this.UpdateData?.CallbackQuery?.Message;
}
}
@@ -69,14 +92,14 @@ namespace TelegramBotBase.Base
{
get
{
return (this.RawCallbackData != null);
return (this.UpdateData.CallbackQuery != null);
}
}
/// <summary>
/// Is this a system call ? Starts with a slash '/' and a command
/// Is this a command ? Starts with a slash '/' and a command
/// </summary>
public bool IsSystemCall
public bool IsBotCommand
{
get
{
@@ -87,11 +110,11 @@ namespace TelegramBotBase.Base
/// <summary>
/// Returns a List of all parameters which has been sent with the command itself (i.e. /start 123 456 789 => 123,456,789)
/// </summary>
public List<String> SystemCallParameters
public List<String> BotCommandParameters
{
get
{
if (!IsSystemCall)
if (!IsBotCommand)
return new List<string>();
//Split by empty space and skip first entry (command itself), return as list
@@ -100,13 +123,13 @@ namespace TelegramBotBase.Base
}
/// <summary>
/// Returns just the System call command (i.e. /start 1 2 3 => /start)
/// Returns just the command (i.e. /start 1 2 3 => /start)
/// </summary>
public String SystemCommand
public String BotCommand
{
get
{
if (!IsSystemCall)
if (!IsBotCommand)
return null;
return this.MessageText.Split(' ')[0];
@@ -124,7 +147,7 @@ namespace TelegramBotBase.Base
{
get
{
return this.RawCallbackData?.CallbackQuery?.Data;
return this.UpdateData?.CallbackQuery?.Data;
}
}
@@ -151,16 +174,9 @@ namespace TelegramBotBase.Base
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ConfirmAction(String message = "")
public async Task ConfirmAction(String message = "", bool showAlert = false, String urlToOpen = null)
{
try
{
await this.Client.TelegramClient.AnswerCallbackQueryAsync(this.RawCallbackData.CallbackQuery.Id, message);
}
catch
{
}
await this.Device.ConfirmAction(this.UpdateData.CallbackQuery.Id, message, showAlert, urlToOpen);
}
public override async Task DeleteMessage()
@@ -175,17 +191,15 @@ namespace TelegramBotBase.Base
}
}
public MessageResult(Telegram.Bot.Args.MessageEventArgs rawdata)
internal MessageResult()
{
this.RawMessageData = rawdata;
this.Message = rawdata.Message;
}
public MessageResult(Telegram.Bot.Args.CallbackQueryEventArgs callback)
public MessageResult(Telegram.Bot.Types.Update update)
{
this.RawCallbackData = callback;
this.Message = callback.CallbackQuery.Message;
this.UpdateData = update;
}
}
+2 -1
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot;
namespace TelegramBotBase.Base
{
@@ -20,7 +21,7 @@ namespace TelegramBotBase.Base
}
}
public Telegram.Bot.Types.Message Message { get; set; }
public virtual Telegram.Bot.Types.Message Message { get; set; }
/// <summary>
/// Deletes the current message
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Telegram.Bot.Types;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
{
public class UpdateResult : ResultBase
{
public UpdateResult(Update rawData, DeviceSession device)
{
RawData = rawData;
Device = device;
}
/// <summary>
/// Returns the Device/ChatId
/// </summary>
public override long DeviceId
{
get
{
return this.RawData?.Message?.Chat?.Id
?? this.RawData?.CallbackQuery?.Message?.Chat?.Id
?? Device?.DeviceId
?? 0;
}
}
public Update RawData { get; set; }
public override Message Message
{
get
{
return RawData?.Message
?? RawData?.EditedMessage
?? RawData?.ChannelPost
?? RawData?.EditedChannelPost
?? RawData?.CallbackQuery?.Message;
}
}
public DeviceSession Device
{
get;
set;
}
}
}
+173 -344
View File
@@ -3,11 +3,17 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Exceptions;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
using TelegramBotBase.Enums;
using TelegramBotBase.Factories.MessageLoops;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
@@ -18,8 +24,7 @@ namespace TelegramBotBase
/// Bot base class for full Device/Context and Messagehandling
/// </summary>
/// <typeparam name="T"></typeparam>
public class BotBase<T>
where T : FormBase
public class BotBase
{
public MessageClient Client { get; set; }
@@ -36,7 +41,10 @@ namespace TelegramBotBase
/// <summary>
/// Contains System commands which will be available at everytime and didnt get passed to forms, i.e. /start
/// </summary>
public List<String> SystemCalls { get; set; }
public List<BotCommand> BotCommands { get; set; }
#region "Events"
private EventHandlerList __Events = new EventHandlerList();
@@ -46,22 +54,14 @@ namespace TelegramBotBase
private static object __evSystemCall = new object();
public delegate Task SystemCallEventHandler(object sender, SystemCallEventArgs e);
public delegate Task BotCommandEventHandler(object sender, BotCommandEventArgs e);
private static object __evException = new object();
private static object __evUnhandledCall = new object();
#endregion
/// <summary>
/// Skips all messages during running (good for big delay updates)
/// </summary>
public bool SkipAllMessages { get; set; } = false;
/// <summary>
/// Loggs all messages and sent them to the event handler
/// </summary>
public bool LogAllMessages { get; set; } = false;
/// <summary>
/// Enable the SessionState (you need to implement on call forms the IStateForm interface)
@@ -69,97 +69,37 @@ namespace TelegramBotBase
public IStateMachine StateMachine { get; set; }
/// <summary>
/// How often could a form navigate to another (within one user action/call/message)
/// Offers functionality to manage the creation process of the start form.
/// </summary>
private const int NavigationMaximum = 10;
public IStartFormFactory StartFormFactory { get; set; }
/// <summary>
/// Simple start of your Bot with the APIKey
/// Contains the message loop factory, which cares about "message-management."
/// </summary>
/// <param name="apiKey"></param>
public BotBase(String apiKey)
{
this.APIKey = apiKey;
this.Client = new Base.MessageClient(this.APIKey);
this.Client.TelegramClient.Timeout = new TimeSpan(0, 1, 0);
this.SystemCalls = new List<string>();
this.Sessions = new SessionBase();
this.Sessions.Client = this.Client;
}
public IMessageLoopFactory MessageLoopFactory { get; set; }
/// <summary>
/// Simple start of your Bot with the APIKey and a proxyAdress
/// All internal used settings.
/// </summary>
/// <param name="apiKey"></param>
/// <param name="proxyBaseAddress">i.e. https://127.0.0.1:10000</param>
public BotBase(String apiKey, System.Net.Http.HttpClient proxy)
public Dictionary<eSettings, uint> SystemSettings { get; private set; }
public BotBase()
{
this.APIKey = apiKey;
this.SystemSettings = new Dictionary<eSettings, uint>();
this.Client = new Base.MessageClient(this.APIKey, proxy);
SetSetting(eSettings.MaxNumberOfRetries, 5);
SetSetting(eSettings.NavigationMaximum, 10);
SetSetting(eSettings.LogAllMessages, false);
SetSetting(eSettings.SkipAllMessages, false);
SetSetting(eSettings.SaveSessionsOnConsoleExit, false);
this.SystemCalls = new List<string>();
this.BotCommands = new List<BotCommand>();
this.Sessions = new SessionBase();
this.Sessions.Client = this.Client;
this.Sessions.BotBase = this;
}
/// <summary>
/// Simple start of your Bot with the APIKey and a TelegramBotClient instance.
/// </summary>
/// <param name="apiKey"></param>
/// <param name="client"></param>
public BotBase(String apiKey, TelegramBotClient client)
{
this.APIKey = apiKey;
this.Client = new Base.MessageClient(this.APIKey, client);
this.SystemCalls = new List<string>();
this.Sessions = new SessionBase();
this.Sessions.Client = this.Client;
}
/// <summary>
/// Simple start of your Bot with the APIKey and a proxyAdress
/// </summary>
/// <param name="apiKey"></param>
/// <param name="proxyBaseAddress">i.e. https://127.0.0.1:10000</param>
public BotBase(String apiKey, String proxyBaseAddress)
{
this.APIKey = apiKey;
var url = new Uri(proxyBaseAddress);
this.Client = new Base.MessageClient(this.APIKey, url);
this.SystemCalls = new List<string>();
this.Sessions = new SessionBase();
this.Sessions.Client = this.Client;
}
/// <summary>
/// Simple start of your Bot with the APIKey and a proxyAdress
/// </summary>
/// <param name="apiKey"></param>
/// <param name="proxyHost">i.e. 127.0.0.1</param>
/// <param name="proxyPort">i.e. 10000</param>
public BotBase(String apiKey, String proxyHost, int proxyPort)
{
this.APIKey = apiKey;
this.Client = new Base.MessageClient(this.APIKey, proxyHost, proxyPort);
this.SystemCalls = new List<string>();
this.Sessions = new SessionBase();
this.Sessions.Client = this.Client;
}
/// <summary>
/// Start your Bot
@@ -169,16 +109,58 @@ namespace TelegramBotBase
if (this.Client == null)
return;
this.Client.Message += Client_Message;
this.Client.MessageEdit += Client_MessageEdit;
this.Client.Action += Client_Action;
this.Client.MessageLoop += Client_MessageLoop;
if (this.StateMachine != null)
{
this.Sessions.LoadSessionStates(this.StateMachine);
}
this.Client.TelegramClient.StartReceiving();
//Enable auto session saving
if (this.GetSetting(eSettings.SaveSessionsOnConsoleExit, false))
{
TelegramBotBase.Tools.Console.SetHandler(() =>
{
this.Sessions.SaveSessionStates();
});
}
DeviceSession.MaxNumberOfRetries = this.GetSetting(eSettings.MaxNumberOfRetries, 5);
this.Client.StartReceiving();
}
private async Task Client_MessageLoop(object sender, UpdateResult e)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
if (ds == null)
{
ds = this.Sessions.StartSession(e.DeviceId).GetAwaiter().GetResult();
e.Device = ds;
ds.LastMessage = e.RawData.Message;
OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds));
}
var mr = new MessageResult(e.RawData);
int i = 0;
//Should formulars get navigated (allow maximum of 10, to dont get loops)
do
{
i++;
//Reset navigation
ds.FormSwitched = false;
await MessageLoopFactory.MessageLoop(this, ds, e, mr);
mr.IsFirstHandler = false;
} while (ds.FormSwitched && i < this.GetSetting(eSettings.NavigationMaximum, 10));
}
@@ -190,15 +172,12 @@ namespace TelegramBotBase
if (this.Client == null)
return;
this.Client.Message -= Client_Message;
this.Client.Action -= Client_Action;
this.Client.MessageLoop -= Client_MessageLoop;
this.Client.TelegramClient.StopReceiving();
if (this.StateMachine != null)
{
this.Sessions.SaveSessionStates(this.StateMachine);
}
this.Client.StopReceiving();
this.Sessions.SaveSessionStates();
}
/// <summary>
@@ -217,263 +196,110 @@ namespace TelegramBotBase
}
}
private async void Client_Message(object sender, MessageResult e)
/// <summary>
/// This will invoke the full message loop for the device even when no "userevent" like message or action has been raised.
/// </summary>
/// <param name="DeviceId">Contains the device/chat id of the device to update.</param>
public async Task InvokeMessageLoop(long DeviceId)
{
if (this.SkipAllMessages)
return;
var mr = new MessageResult();
try
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
e.Device = ds;
if (LogAllMessages)
{
OnMessage(new MessageIncomeEventArgs(e.DeviceId, ds, e));
}
ds?.OnMessageReceived(new MessageReceivedEventArgs(e.Message));
await Client_TryMessage(sender, e);
}
catch (Telegram.Bot.Exceptions.ApiRequestException ex)
{
}
catch (Exception ex)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, ds?.DeviceId ?? -1, ds, ex));
}
await InvokeMessageLoop(DeviceId, mr);
}
private async Task Client_TryMessage(object sender, MessageResult e)
{
DeviceSession ds = e.Device;
if (ds == null)
{
ds = await this.Sessions.StartSession<T>(e.DeviceId);
e.Device = ds;
ds.LastMessage = e.Message;
OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds));
}
ds.ChatTitle = e.Message.Chat.Username ?? e.Message.Chat.Title;
ds.LastAction = DateTime.Now;
ds.LastMessage = e.Message;
//Is this a systemcall ?
if (e.IsSystemCall && this.SystemCalls.Contains(e.SystemCommand))
{
var sce = new SystemCallEventArgs(e.SystemCommand, e.SystemCallParameters, e.Message, ds.DeviceId, ds);
await OnSystemCall(sce);
if (sce.Handled)
return;
}
FormBase activeForm = null;
int i = 0;
//Should formulars get navigated (allow maximum of 10, to dont get loops)
do
{
i++;
//Reset navigation
ds.FormSwitched = false;
activeForm = ds.ActiveForm;
//Pre Loading Event
await activeForm.PreLoad(e);
//Send Load event to controls
await activeForm.LoadControls(e);
//Loading Event
await activeForm.Load(e);
//Is Attachment ? (Photo, Audio, Video, Contact, Location, Document)
if (e.Message.Type == Telegram.Bot.Types.Enums.MessageType.Contact | e.Message.Type == Telegram.Bot.Types.Enums.MessageType.Document | e.Message.Type == Telegram.Bot.Types.Enums.MessageType.Location |
e.Message.Type == Telegram.Bot.Types.Enums.MessageType.Photo | e.Message.Type == Telegram.Bot.Types.Enums.MessageType.Video | e.Message.Type == Telegram.Bot.Types.Enums.MessageType.Audio)
{
await activeForm.SentData(new DataResult(e));
}
//Render Event
if (!ds.FormSwitched)
{
await activeForm.RenderControls(e);
await activeForm.Render(e);
}
e.IsFirstHandler = false;
} while (ds.FormSwitched && i < NavigationMaximum);
}
private async void Client_MessageEdit(object sender, MessageResult e)
{
if (this.SkipAllMessages)
return;
try
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
e.Device = ds;
if (LogAllMessages)
{
OnMessage(new MessageIncomeEventArgs(e.DeviceId, ds, e));
}
//Call same, to handle received liked edited
ds?.OnMessageReceived(new MessageReceivedEventArgs(e.Message));
await Client_TryMessageEdit(sender, e);
}
catch (Telegram.Bot.Exceptions.ApiRequestException ex)
{
}
catch (Exception ex)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, ds?.DeviceId ?? -1, ds, ex));
}
}
private async Task Client_TryMessageEdit(object sender, MessageResult e)
{
DeviceSession ds = e.Device;
if (ds == null)
{
ds = await this.Sessions.StartSession<T>(e.DeviceId);
e.Device = ds;
}
ds.ChatTitle = e.Message.Chat.Username ?? e.Message.Chat.Title;
ds.LastAction = DateTime.Now;
ds.LastMessage = e.Message;
//Pre Loading Event
await ds.ActiveForm.Edited(e);
//When form has been switched due navigation within the edit method, reopen Client_Message
if (ds.FormSwitched)
{
await Client_TryMessage(sender, e);
}
}
private void Client_Action(object sender, MessageResult e)
/// <summary>
/// This will invoke the full message loop for the device even when no "userevent" like message or action has been raised.
/// </summary>
/// <param name="DeviceId">Contains the device/chat id of the device to update.</param>
/// <param name="e"></param>
public async Task InvokeMessageLoop(long DeviceId, MessageResult e)
{
try
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
DeviceSession ds = this.Sessions.GetSession(DeviceId);
e.Device = ds;
if (LogAllMessages)
{
OnMessage(new MessageIncomeEventArgs(e.DeviceId, ds, e));
}
Client_TryAction(sender, e);
await MessageLoopFactory.MessageLoop(this, ds, new UpdateResult(e.UpdateData, ds), e);
//await Client_Loop(this, e);
}
catch (Exception ex)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, ds?.DeviceId ?? -1, ds, ex));
DeviceSession ds = this.Sessions.GetSession(DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, DeviceId, ds, ex));
}
}
private async void Client_TryAction(object sender, MessageResult e)
/// <summary>
/// Will get invoke on an unhandled call.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void MessageLoopFactory_UnhandledCall(object sender, UnhandledCallEventArgs e)
{
DeviceSession ds = e.Device;
if (ds == null)
{
ds = await this.Sessions.StartSession<T>(e.DeviceId);
e.Device = ds;
}
ds.ChatTitle = e.Message.Chat.Username ?? e.Message.Chat.Title;
ds.LastAction = DateTime.Now;
ds.LastMessage = e.Message;
FormBase activeForm = null;
int i = 0;
//Should formulars get navigated (allow maximum of 10, to dont get loops)
do
{
i++;
//Reset navigation
ds.FormSwitched = false;
activeForm = ds.ActiveForm;
//Pre Loading Event
await activeForm.PreLoad(e);
//Send Load event to controls
await activeForm.LoadControls(e);
//Loading Event
await activeForm.Load(e);
//Action Event
if (!ds.FormSwitched)
{
//Send Action event to controls
await activeForm.ActionControls(e);
//Send Action event to form itself
await activeForm.Action(e);
if (!e.Handled)
{
var uhc = new UnhandledCallEventArgs(e.Message.Text, e.RawData, ds.DeviceId, e.MessageId, e.Message, ds);
OnUnhandledCall(uhc);
if (uhc.Handled)
{
if (ds.FormSwitched)
{
continue;
}
else
{
break;
}
}
}
}
//Render Event
if (!ds.FormSwitched)
{
await activeForm.RenderControls(e);
await activeForm.Render(e);
}
e.IsFirstHandler = false;
} while (ds.FormSwitched && i < NavigationMaximum);
OnUnhandledCall(e);
}
/// <summary>
/// This method will update all local created bot commands to the botfather.
/// </summary>
public async Task UploadBotCommands()
{
await this.Client.SetBotCommands(this.BotCommands);
}
/// <summary>
/// Could set a variety of settings to improve the bot handling.
/// </summary>
/// <param name="set"></param>
/// <param name="Value"></param>
public void SetSetting(eSettings set, uint Value)
{
this.SystemSettings[set] = Value;
}
/// <summary>
/// Could set a variety of settings to improve the bot handling.
/// </summary>
/// <param name="set"></param>
/// <param name="Value"></param>
public void SetSetting(eSettings set, bool Value)
{
this.SystemSettings[set] = (Value ? 1u : 0u);
}
/// <summary>
/// Could get the current value of a setting
/// </summary>
/// <param name="set"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public uint GetSetting(eSettings set, uint defaultValue)
{
if (!this.SystemSettings.ContainsKey(set))
return defaultValue;
return this.SystemSettings[set];
}
/// <summary>
/// Could get the current value of a setting
/// </summary>
/// <param name="set"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public bool GetSetting(eSettings set, bool defaultValue)
{
if (!this.SystemSettings.ContainsKey(set))
return defaultValue;
return this.SystemSettings[set] == 0u ? false : true;
}
#region "Events"
/// <summary>
/// Will be called if a session/context gets started
/// </summary>
@@ -518,15 +344,15 @@ namespace TelegramBotBase
}
/// <summary>
/// Will be called if a system call gets raised
/// Will be called if a bot command gets raised
/// </summary>
public event SystemCallEventHandler SystemCall;
public event BotCommandEventHandler BotCommand;
public async Task OnSystemCall(SystemCallEventArgs e)
public async Task OnBotCommand(BotCommandEventArgs e)
{
if (this.SystemCall != null)
await SystemCall(this, e);
if (this.BotCommand != null)
await BotCommand(this, e);
}
/// <summary>
@@ -570,5 +396,8 @@ namespace TelegramBotBase
(this.__Events[__evUnhandledCall] as EventHandler<UnhandledCallEventArgs>)?.Invoke(this, e);
}
#endregion
}
}
+324
View File
@@ -0,0 +1,324 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using Telegram.Bot;
using Telegram.Bot.Types;
using TelegramBotBase.Base;
using TelegramBotBase.Builder.Interfaces;
using TelegramBotBase.Commands;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Localizations;
using TelegramBotBase.States;
namespace TelegramBotBase.Builder
{
public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, IStartFormSelectionStage, IBuildingStage, INetworkingSelectionStage, IBotCommandsStage, ISessionSerializationStage, ILanguageSelectionStage
{
String _apiKey = null;
IStartFormFactory _factory = null;
MessageClient _client = null;
List<BotCommand> _botcommands = new List<BotCommand>();
IStateMachine _statemachine = null;
IMessageLoopFactory _messageloopfactory = null;
private BotBaseBuilder()
{
}
public static IAPIKeySelectionStage Create()
{
return new BotBaseBuilder();
}
#region "Step 1 (Basic Stuff)"
public IMessageLoopSelectionStage WithAPIKey(string apiKey)
{
this._apiKey = apiKey;
return this;
}
public IBuildingStage QuickStart(string apiKey, Type StartForm)
{
this._apiKey = apiKey;
this._factory = new Factories.DefaultStartFormFactory(StartForm);
DefaultMessageLoop();
NoProxy();
OnlyStart();
NoSerialization();
DefaultLanguage();
return this;
}
public IBuildingStage QuickStart<T>(string apiKey)
where T : FormBase
{
this._apiKey = apiKey;
this._factory = new Factories.DefaultStartFormFactory(typeof(T));
DefaultMessageLoop();
NoProxy();
OnlyStart();
NoSerialization();
DefaultLanguage();
return this;
}
public IBuildingStage QuickStart(string apiKey, IStartFormFactory StartFormFactory)
{
this._apiKey = apiKey;
this._factory = StartFormFactory;
DefaultMessageLoop();
NoProxy();
OnlyStart();
NoSerialization();
DefaultLanguage();
return this;
}
#endregion
#region "Step 2 (Message Loop)"
public IStartFormSelectionStage DefaultMessageLoop()
{
_messageloopfactory = new Factories.MessageLoops.FormBaseMessageLoop();
return this;
}
public IStartFormSelectionStage CustomMessageLoop(IMessageLoopFactory messageLoopClass)
{
_messageloopfactory = messageLoopClass;
return this;
}
public IStartFormSelectionStage CustomMessageLoop<T>()
where T : class, new()
{
_messageloopfactory = typeof(T).GetConstructor(new Type[] { })?.Invoke(new object[] { }) as IMessageLoopFactory;
return this;
}
#endregion
#region "Step 3 (Start Form/Factory)"
public INetworkingSelectionStage WithStartForm(Type startFormClass)
{
this._factory = new Factories.DefaultStartFormFactory(startFormClass);
return this;
}
public INetworkingSelectionStage WithStartForm<T>()
where T : FormBase, new()
{
this._factory = new Factories.DefaultStartFormFactory(typeof(T));
return this;
}
public INetworkingSelectionStage WithStartFormFactory(IStartFormFactory factory)
{
this._factory = factory;
return this;
}
#endregion
#region "Step 4 (Network Settings)"
public IBotCommandsStage WithProxy(string proxyAddress)
{
var url = new Uri(proxyAddress);
_client = new MessageClient(_apiKey, url);
_client.TelegramClient.Timeout = new TimeSpan(0, 1, 0);
return this;
}
public IBotCommandsStage NoProxy()
{
_client = new MessageClient(_apiKey);
_client.TelegramClient.Timeout = new TimeSpan(0, 1, 0);
return this;
}
public IBotCommandsStage WithBotClient(TelegramBotClient tgclient)
{
_client = new MessageClient(_apiKey, tgclient);
_client.TelegramClient.Timeout = new TimeSpan(0, 1, 0);
return this;
}
public IBotCommandsStage WithHostAndPort(string proxyHost, int proxyPort)
{
_client = new MessageClient(_apiKey, proxyHost, proxyPort);
_client.TelegramClient.Timeout = new TimeSpan(0, 1, 0);
return this;
}
public IBotCommandsStage WithHttpClient(HttpClient tgclient)
{
_client = new MessageClient(_apiKey, tgclient);
_client.TelegramClient.Timeout = new TimeSpan(0, 1, 0);
return this;
}
#endregion
#region "Step 5 (Bot Commands)"
public ISessionSerializationStage NoCommands()
{
return this;
}
public ISessionSerializationStage OnlyStart()
{
_botcommands.Start("Starts the bot");
return this;
}
public ISessionSerializationStage DefaultCommands()
{
_botcommands.Start("Starts the bot");
_botcommands.Help("Should show you some help");
_botcommands.Settings("Should show you some settings");
return this;
}
public ISessionSerializationStage CustomCommands(Action<List<BotCommand>> action)
{
action?.Invoke(_botcommands);
return this;
}
#endregion
#region "Step 6 (Serialization)"
public ILanguageSelectionStage NoSerialization()
{
return this;
}
public ILanguageSelectionStage UseSerialization(IStateMachine machine)
{
this._statemachine = machine;
return this;
}
public ILanguageSelectionStage UseJSON(string path)
{
this._statemachine = new JSONStateMachine(path);
return this;
}
public ILanguageSelectionStage UseSimpleJSON(string path)
{
this._statemachine = new SimpleJSONStateMachine(path);
return this;
}
public ILanguageSelectionStage UseXML(string path)
{
this._statemachine = new XMLStateMachine(path);
return this;
}
#endregion
#region "Step 7 (Language)"
public IBuildingStage DefaultLanguage()
{
return this;
}
public IBuildingStage UseEnglish()
{
Localizations.Default.Language = new Localizations.English();
return this;
}
public IBuildingStage UseGerman()
{
Localizations.Default.Language = new Localizations.German();
return this;
}
public IBuildingStage Custom(Localization language)
{
Localizations.Default.Language = language;
return this;
}
#endregion
public BotBase Build()
{
var bb = new BotBase();
bb.APIKey = _apiKey;
bb.StartFormFactory = _factory;
bb.Client = _client;
bb.Sessions.Client = bb.Client;
bb.BotCommands = _botcommands;
bb.StateMachine = _statemachine;
bb.MessageLoopFactory = _messageloopfactory;
bb.MessageLoopFactory.UnhandledCall += bb.MessageLoopFactory_UnhandledCall;
return bb;
}
}
}
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Builder.Interfaces
{
public interface IAPIKeySelectionStage
{
/// <summary>
/// Sets the API Key which will be used by the telegram bot client.
/// </summary>
/// <param name="apiKey"></param>
/// <returns></returns>
IMessageLoopSelectionStage WithAPIKey(String apiKey);
/// <summary>
/// Quick and easy way to create a BotBase instance.
/// Uses: DefaultMessageLoop, NoProxy, OnlyStart, NoSerialization, DefaultLanguage
/// </summary>
/// <param name="apiKey"></param>
/// <param name="StartForm"></param>
/// <returns></returns>
IBuildingStage QuickStart(String apiKey, Type StartForm);
/// <summary>
/// Quick and easy way to create a BotBase instance.
/// Uses: DefaultMessageLoop, NoProxy, OnlyStart, NoSerialization, DefaultLanguage
/// </summary>
/// <param name="apiKey"></param>
/// <returns></returns>
IBuildingStage QuickStart<T>(String apiKey) where T : FormBase;
/// <summary>
/// Quick and easy way to create a BotBase instance.
/// Uses: DefaultMessageLoop, NoProxy, OnlyStart, NoSerialization, DefaultLanguage
/// </summary>
/// <param name="apiKey"></param>
/// <param name="StartFormFactory"></param>
/// <returns></returns>
IBuildingStage QuickStart(String apiKey, IStartFormFactory StartFormFactory);
}
}
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Text;
using Telegram.Bot.Types;
namespace TelegramBotBase.Builder.Interfaces
{
public interface IBotCommandsStage
{
/// <summary>
/// Does not create any commands.
/// </summary>
/// <returns></returns>
ISessionSerializationStage NoCommands();
/// <summary>
/// Creates default commands for start, help and settings.
/// </summary>
/// <returns></returns>
ISessionSerializationStage DefaultCommands();
/// <summary>
/// Only adds the start command.
/// </summary>
/// <returns></returns>
ISessionSerializationStage OnlyStart();
/// <summary>
/// Gives you the ability to add custom commands.
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
ISessionSerializationStage CustomCommands(Action<List<BotCommand>> action);
}
}
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Builder.Interfaces
{
public interface IBuildingStage
{
BotBase Build();
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Localizations;
namespace TelegramBotBase.Builder.Interfaces
{
public interface ILanguageSelectionStage
{
/// <summary>
/// Selects the default language for control usage. (English)
/// </summary>
/// <returns></returns>
IBuildingStage DefaultLanguage();
/// <summary>
/// Selects english as the default language for control labels.
/// </summary>
/// <returns></returns>
IBuildingStage UseEnglish();
/// <summary>
/// Selects german as the default language for control labels.
/// </summary>
/// <returns></returns>
IBuildingStage UseGerman();
/// <summary>
/// Selects a custom language as the default language for control labels.
/// </summary>
/// <returns></returns>
IBuildingStage Custom(Localization language);
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Builder.Interfaces
{
public interface IMessageLoopSelectionStage
{
/// <summary>
/// Chooses a default message loop.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
IStartFormSelectionStage DefaultMessageLoop();
/// <summary>
/// Chooses a custom message loop.
/// </summary>
/// <param name="startFormClass"></param>
/// <returns></returns>
IStartFormSelectionStage CustomMessageLoop(IMessageLoopFactory startFormClass);
/// <summary>
/// Chooses a custom message loop.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
IStartFormSelectionStage CustomMessageLoop<T>() where T : class, new();
}
}
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using Telegram.Bot;
namespace TelegramBotBase.Builder.Interfaces
{
public interface INetworkingSelectionStage
{
/// <summary>
/// Chooses a proxy as network configuration.
/// </summary>
/// <param name="proxyAddress"></param>
/// <returns></returns>
IBotCommandsStage WithProxy(String proxyAddress);
/// <summary>
/// Do not choose a proxy as network configuration.
/// </summary>
/// <returns></returns>
IBotCommandsStage NoProxy();
/// <summary>
/// Chooses a custom instance of TelegramBotClient.
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
IBotCommandsStage WithBotClient(TelegramBotClient client);
/// <summary>
/// Sets the custom proxy host and port.
/// </summary>
/// <param name="proxyHost"></param>
/// <param name="Port"></param>
/// <returns></returns>
IBotCommandsStage WithHostAndPort(String proxyHost, int Port);
/// <summary>
/// Uses a custom http client.
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
IBotCommandsStage WithHttpClient(HttpClient client);
}
}
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Builder.Interfaces
{
public interface ISessionSerializationStage
{
/// <summary>
/// Do not uses serialization.
/// </summary>
/// <returns></returns>
ILanguageSelectionStage NoSerialization();
/// <summary>
/// Sets the state machine for serialization.
/// </summary>
/// <param name="machine"></param>
/// <returns></returns>
ILanguageSelectionStage UseSerialization(IStateMachine machine);
/// <summary>
/// Using the complex version of .Net JSON, which can serialize all objects.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
ILanguageSelectionStage UseJSON(String path);
/// <summary>
/// Use the easy version of .Net JSON, which can serialize basic types, but not generics and others.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
ILanguageSelectionStage UseSimpleJSON(String path);
/// <summary>
/// Uses the XML serializer for session serialization.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
ILanguageSelectionStage UseXML(String path);
}
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Builder.Interfaces
{
public interface IStartFormSelectionStage
{
/// <summary>
/// Chooses a start form type which will be used for new sessions.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
INetworkingSelectionStage WithStartForm(Type startFormClass);
/// <summary>
/// Chooses a generic start form which will be used for new sessions.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
INetworkingSelectionStage WithStartForm<T>() where T : FormBase, new();
/// <summary>
/// Chooses a StartFormFactory which will be use for new sessions.
/// </summary>
/// <param name="factory"></param>
/// <returns></returns>
INetworkingSelectionStage WithStartFormFactory(IStartFormFactory factory);
}
}
+51
View File
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Text;
using Telegram.Bot.Types;
namespace TelegramBotBase.Commands
{
public static class Extensions
{
/// <summary>
/// Adding the default /start command with a description.
/// </summary>
/// <param name="cmds"></param>
/// <param name="description"></param>
public static void Start(this List<BotCommand> cmds, String description)
{
cmds.Add(new BotCommand() { Command = "start", Description = description });
}
/// <summary>
/// Adding the default /help command with a description.
/// </summary>
/// <param name="cmds"></param>
/// <param name="description"></param>
public static void Help(this List<BotCommand> cmds, String description)
{
cmds.Add(new BotCommand() { Command = "help", Description = description });
}
/// <summary>
/// Adding the default /settings command with a description.
/// </summary>
/// <param name="cmds"></param>
/// <param name="description"></param>
public static void Settings(this List<BotCommand> cmds, String description)
{
cmds.Add(new BotCommand() { Command = "settings", Description = description });
}
/// <summary>
/// Adding the command with a description.
/// </summary>
/// <param name="cmds"></param>
/// <param name="command"></param>
/// <param name="description"></param>
public static void Add(this List<BotCommand> cmds, String command, String description)
{
cmds.Add(new BotCommand() { Command = command, Description = description });
}
}
}
+2
View File
@@ -21,5 +21,7 @@ namespace TelegramBotBase.Constants
public const int MaxReplyKeyboardCols = 12;
public const int MessageDeletionsPerSecond = 30;
}
}
-308
View File
@@ -1,308 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls
{
public class ButtonGrid : Base.ControlBase
{
public String Title { get; set; } = "Toggle";
public String ConfirmationText { get; set; } = "";
private bool RenderNecessary = true;
private static readonly object __evButtonClicked = new object();
private readonly EventHandlerList Events = new EventHandlerList();
public ButtonForm ButtonsForm { get; set; }
public int? MessageId { get; set; }
/// <summary>
/// Optional. Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of the same height as the app's standard keyboard.
/// Source: https://core.telegram.org/bots/api#replykeyboardmarkup
/// </summary>
public bool ResizeKeyboard { get; set; } = false;
public bool OneTimeKeyboard { get; set; } = false;
public bool HideKeyboardOnCleanup { get; set; } = true;
public bool DeletePreviousMessage { get; set; } = true;
public ParseMode MessageParseMode { get; set; } = ParseMode.Default;
/// <summary>
/// Defines which type of Button Keyboard should be rendered.
/// </summary>
public eKeyboardType KeyboardType
{
get
{
return m_eKeyboardType;
}
set
{
if (m_eKeyboardType != value)
{
this.RenderNecessary = true;
Cleanup().Wait();
m_eKeyboardType = value;
}
}
}
private eKeyboardType m_eKeyboardType = eKeyboardType.ReplyKeyboard;
public ButtonGrid()
{
this.ButtonsForm = new ButtonForm();
}
public ButtonGrid(eKeyboardType type) : this()
{
m_eKeyboardType = type;
}
public ButtonGrid(ButtonForm form)
{
this.ButtonsForm = form;
}
public event EventHandler<ButtonClickedEventArgs> ButtonClicked
{
add
{
this.Events.AddHandler(__evButtonClicked, value);
}
remove
{
this.Events.RemoveHandler(__evButtonClicked, value);
}
}
public void OnButtonClicked(ButtonClickedEventArgs e)
{
(this.Events[__evButtonClicked] as EventHandler<ButtonClickedEventArgs>)?.Invoke(this, e);
}
public async override Task Load(MessageResult result)
{
if (this.KeyboardType != eKeyboardType.ReplyKeyboard)
return;
if (!result.IsFirstHandler)
return;
var button = ButtonsForm.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText);
if (button == null)
return;
OnButtonClicked(new ButtonClickedEventArgs(button));
//Remove button click message
if (this.DeletePreviousMessage)
await Device.DeleteMessage(result.MessageId);
result.Handled = true;
}
public async override Task Action(MessageResult result, string value = null)
{
if (result.Handled)
return;
if (!result.IsFirstHandler)
return;
await result.ConfirmAction(this.ConfirmationText ?? "");
//Find clicked button depending on Text or Value (depending on markup type)
switch (this.KeyboardType)
{
case eKeyboardType.InlineKeyBoard:
var button = ButtonsForm.ToList().FirstOrDefault(a => a.Value == result.RawData);
if (button == null)
return;
OnButtonClicked(new ButtonClickedEventArgs(button));
result.Handled = true;
break;
}
}
public async override Task Render(MessageResult result)
{
if (!this.RenderNecessary)
return;
switch (m_eKeyboardType)
{
case eKeyboardType.InlineKeyBoard:
if (ButtonsForm.Rows > Constants.Telegram.MaxInlineKeyBoardRows)
{
throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardRows };
}
if (ButtonsForm.Cols > Constants.Telegram.MaxInlineKeyBoardCols)
{
throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardCols };
}
break;
case eKeyboardType.ReplyKeyboard:
if (ButtonsForm.Rows > Constants.Telegram.MaxReplyKeyboardRows)
{
throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardRows };
}
if (ButtonsForm.Cols > Constants.Telegram.MaxReplyKeyboardCols)
{
throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardCols };
}
break;
}
Message m = null;
if (this.MessageId != null)
{
switch (this.KeyboardType)
{
//Reply Keyboard could only be updated with a new keyboard.
case eKeyboardType.ReplyKeyboard:
if (this.ButtonsForm.Count == 0)
{
await this.Device.HideReplyKeyboard();
this.MessageId = null;
}
else
{
if (this.DeletePreviousMessage)
await this.Device.DeleteMessage(this.MessageId.Value);
var rkm = (ReplyKeyboardMarkup)this.ButtonsForm;
rkm.ResizeKeyboard = this.ResizeKeyboard;
rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode);
}
break;
case eKeyboardType.InlineKeyBoard:
m = await this.Device.Edit(this.MessageId.Value, this.Title, (InlineKeyboardMarkup)this.ButtonsForm);
break;
}
}
else
{
switch (this.KeyboardType)
{
case eKeyboardType.ReplyKeyboard:
var rkm = (ReplyKeyboardMarkup)this.ButtonsForm;
rkm.ResizeKeyboard = this.ResizeKeyboard;
rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode);
break;
case eKeyboardType.InlineKeyBoard:
m = await this.Device.Send(this.Title, (InlineKeyboardMarkup)this.ButtonsForm, disableNotification: true, parseMode: MessageParseMode);
break;
}
if (m != null)
{
this.MessageId = m.MessageId;
}
}
this.RenderNecessary = false;
}
public override async Task Hidden(bool FormClose)
{
//Prepare for opening Modal, and comming back
if (!FormClose)
{
this.Updated();
}
}
/// <summary>
/// Tells the control that it has been updated.
/// </summary>
public void Updated()
{
this.RenderNecessary = true;
}
public async override Task Cleanup()
{
if (this.MessageId == null)
return;
switch (this.KeyboardType)
{
case eKeyboardType.InlineKeyBoard:
await this.Device.DeleteMessage(this.MessageId.Value);
this.MessageId = null;
break;
case eKeyboardType.ReplyKeyboard:
if (this.HideKeyboardOnCleanup)
{
await this.Device.HideReplyKeyboard();
}
this.MessageId = null;
break;
}
}
}
}
@@ -0,0 +1,738 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Datasources;
using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions;
using TelegramBotBase.Form;
using static TelegramBotBase.Base.Async;
namespace TelegramBotBase.Controls.Hybrid
{
public class ButtonGrid : Base.ControlBase
{
public String Title { get; set; } = Localizations.Default.Language["ButtonGrid_Title"];
public String ConfirmationText { get; set; } = "";
private bool RenderNecessary = true;
private static readonly object __evButtonClicked = new object();
private readonly EventHandlerList Events = new EventHandlerList();
/// <summary>
///
/// </summary>
[Obsolete("This property is obsolete. Please use the DataSource property instead.")]
public ButtonForm ButtonsForm
{
get
{
return DataSource.ButtonForm;
}
set
{
DataSource = new ButtonFormDataSource(value);
}
}
/// <summary>
/// Data source of the items.
/// </summary>
public ButtonFormDataSource DataSource { get; set; }
public int? MessageId { get; set; }
/// <summary>
/// Optional. Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of the same height as the app's standard keyboard.
/// Source: https://core.telegram.org/bots/api#replykeyboardmarkup
/// </summary>
public bool ResizeKeyboard { get; set; } = false;
public bool OneTimeKeyboard { get; set; } = false;
public bool HideKeyboardOnCleanup { get; set; } = true;
public bool DeletePreviousMessage { get; set; } = true;
/// <summary>
/// Removes the reply message from a user.
/// </summary>
public bool DeleteReplyMessage { get; set; } = true;
/// <summary>
/// Parsemode of the message.
/// </summary>
public ParseMode MessageParseMode { get; set; } = ParseMode.Markdown;
/// <summary>
/// Enables automatic paging of buttons when the amount of rows is exceeding the limits.
/// </summary>
public bool EnablePaging { get; set; } = false;
/// <summary>
/// Enabled a search function.
/// </summary>
public bool EnableSearch { get; set; } = false;
public String SearchQuery { get; set; }
public eNavigationBarVisibility NavigationBarVisibility { get; set; } = eNavigationBarVisibility.always;
/// <summary>
/// Index of the current page
/// </summary>
public int CurrentPageIndex { get; set; } = 0;
public String PreviousPageLabel = Localizations.Default.Language["ButtonGrid_PreviousPage"];
public String NextPageLabel = Localizations.Default.Language["ButtonGrid_NextPage"];
public String NoItemsLabel = Localizations.Default.Language["ButtonGrid_NoItems"];
public String SearchLabel = Localizations.Default.Language["ButtonGrid_SearchFeature"];
/// <summary>
/// Layout of the buttons which should be displayed always on top.
/// </summary>
public ButtonRow HeadLayoutButtonRow { get; set; }
/// <summary>
/// Layout of columns which should be displayed below the header
/// </summary>
public ButtonRow SubHeadLayoutButtonRow { get; set; }
/// <summary>
/// Defines which type of Button Keyboard should be rendered.
/// </summary>
public eKeyboardType KeyboardType
{
get
{
return m_eKeyboardType;
}
set
{
if (m_eKeyboardType != value)
{
this.RenderNecessary = true;
Cleanup().Wait();
m_eKeyboardType = value;
}
}
}
private eKeyboardType m_eKeyboardType = eKeyboardType.ReplyKeyboard;
public ButtonGrid()
{
this.DataSource = new ButtonFormDataSource();
}
public ButtonGrid(eKeyboardType type) : this()
{
m_eKeyboardType = type;
}
public ButtonGrid(ButtonForm form)
{
this.DataSource = new ButtonFormDataSource(form);
}
public event AsyncEventHandler<ButtonClickedEventArgs> ButtonClicked
{
add
{
this.Events.AddHandler(__evButtonClicked, value);
}
remove
{
this.Events.RemoveHandler(__evButtonClicked, value);
}
}
public async Task OnButtonClicked(ButtonClickedEventArgs e)
{
var handler = this.Events[__evButtonClicked]?.GetInvocationList().Cast<AsyncEventHandler<ButtonClickedEventArgs>>();
if (handler == null)
return;
foreach (var h in handler)
{
await Async.InvokeAllAsync<ButtonClickedEventArgs>(h, this, e);
}
}
public override void Init()
{
this.Device.MessageDeleted += Device_MessageDeleted;
}
private void Device_MessageDeleted(object sender, MessageDeletedEventArgs e)
{
if (this.MessageId == null)
return;
if (e.MessageId != this.MessageId)
return;
this.MessageId = null;
}
public async override Task Load(MessageResult result)
{
if (this.KeyboardType != eKeyboardType.ReplyKeyboard)
return;
if (!result.IsFirstHandler)
return;
if (result.MessageText == null || result.MessageText == "")
return;
var matches = new List<ButtonRow>();
ButtonRow match = null;
int index = -1;
if (HeadLayoutButtonRow?.Matches(result.MessageText) ?? false)
{
match = HeadLayoutButtonRow;
goto check;
}
if (SubHeadLayoutButtonRow?.Matches(result.MessageText) ?? false)
{
match = SubHeadLayoutButtonRow;
goto check;
}
var br = DataSource.FindRow(result.MessageText);
if (br != null)
{
match = br.Item1;
index = br.Item2;
}
//var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText);
// bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText)
//var index = bf.FindRowByButton(button);
check:
//Remove button click message
if (this.DeleteReplyMessage)
await Device.DeleteMessage(result.MessageId);
if (match != null)
{
await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match));
result.Handled = true;
return;
}
if (result.MessageText == PreviousPageLabel)
{
if (this.CurrentPageIndex > 0)
this.CurrentPageIndex--;
this.Updated();
}
else if (result.MessageText == NextPageLabel)
{
if (this.CurrentPageIndex < this.PageCount - 1)
this.CurrentPageIndex++;
this.Updated();
}
else if (this.EnableSearch)
{
if (result.MessageText.StartsWith("🔍"))
{
//Sent note about searching
if (this.SearchQuery == null)
{
await this.Device.Send(this.SearchLabel);
}
this.SearchQuery = null;
this.Updated();
return;
}
this.SearchQuery = result.MessageText;
if (this.SearchQuery != null && this.SearchQuery != "")
{
this.CurrentPageIndex = 0;
this.Updated();
}
}
}
public async override Task Action(MessageResult result, string value = null)
{
if (result.Handled)
return;
if (!result.IsFirstHandler)
return;
//Find clicked button depending on Text or Value (depending on markup type)
if (this.KeyboardType != eKeyboardType.InlineKeyBoard)
return;
await result.ConfirmAction(this.ConfirmationText ?? "");
ButtonRow match = null;
int index = -1;
if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false)
{
match = HeadLayoutButtonRow;
goto check;
}
if (SubHeadLayoutButtonRow?.Matches(result.RawData, false) ?? false)
{
match = SubHeadLayoutButtonRow;
goto check;
}
var br = DataSource.FindRow(result.RawData, false);
if (br != null)
{
match = br.Item1;
index = br.Item2;
}
//var bf = DataSource.ButtonForm;
//var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData);
//var index = bf.FindRowByButton(button);
check:
if (match != null)
{
await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index, match));
result.Handled = true;
return;
}
switch (result.RawData)
{
case "$previous$":
if (this.CurrentPageIndex > 0)
this.CurrentPageIndex--;
this.Updated();
break;
case "$next$":
if (this.CurrentPageIndex < this.PageCount - 1)
this.CurrentPageIndex++;
this.Updated();
break;
}
}
/// <summary>
/// This method checks of the amount of buttons
/// </summary>
private void CheckGrid()
{
switch (m_eKeyboardType)
{
case eKeyboardType.InlineKeyBoard:
if (DataSource.RowCount > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging)
{
throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxInlineKeyBoardRows };
}
if (DataSource.ColumnCount > Constants.Telegram.MaxInlineKeyBoardCols)
{
throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxInlineKeyBoardCols };
}
break;
case eKeyboardType.ReplyKeyboard:
if (DataSource.RowCount > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging)
{
throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxReplyKeyboardRows };
}
if (DataSource.ColumnCount > Constants.Telegram.MaxReplyKeyboardCols)
{
throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxReplyKeyboardCols };
}
break;
}
}
public async override Task Render(MessageResult result)
{
if (!this.RenderNecessary)
return;
//Check for rows and column limits
CheckGrid();
this.RenderNecessary = false;
ButtonForm form = this.DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage, (this.EnableSearch ? this.SearchQuery : null));
//if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{
// form = form.FilterDuplicate(this.SearchQuery, true);
//}
//else
//{
// form = form.Duplicate();
//}
if (this.EnablePaging)
{
IntegratePagingView(form);
}
if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0)
{
form.InsertButtonRow(0, this.HeadLayoutButtonRow);
}
if (this.SubHeadLayoutButtonRow != null && SubHeadLayoutButtonRow.Count > 0)
{
if (this.IsNavigationBarVisible)
{
form.InsertButtonRow(2, this.SubHeadLayoutButtonRow);
}
else
{
form.InsertButtonRow(1, this.SubHeadLayoutButtonRow);
}
}
Message m = null;
switch (this.KeyboardType)
{
//Reply Keyboard could only be updated with a new keyboard.
case eKeyboardType.ReplyKeyboard:
if (form.Count == 0)
{
if (this.MessageId != null)
{
await this.Device.HideReplyKeyboard();
this.MessageId = null;
}
return;
}
//if (this.MessageId != null)
//{
// if (form.Count == 0)
// {
// await this.Device.HideReplyKeyboard();
// this.MessageId = null;
// return;
// }
//}
//if (form.Count == 0)
// return;
var rkm = (ReplyKeyboardMarkup)form;
rkm.ResizeKeyboard = this.ResizeKeyboard;
rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false);
//Prevent flicker of keyboard
if (this.DeletePreviousMessage && this.MessageId != null)
await this.Device.DeleteMessage(this.MessageId.Value);
break;
case eKeyboardType.InlineKeyBoard:
//Try to edit message if message id is available
//When the returned message is null then the message has been already deleted, resend it
if (this.MessageId != null)
{
m = await this.Device.Edit(this.MessageId.Value, this.Title, (InlineKeyboardMarkup)form);
if (m != null)
{
this.MessageId = m.MessageId;
return;
}
}
//When no message id is available or it has been deleted due the use of AutoCleanForm re-render automatically
m = await this.Device.Send(this.Title, (InlineKeyboardMarkup)form, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false);
break;
}
if (m != null)
{
this.MessageId = m.MessageId;
}
}
private void IntegratePagingView(ButtonForm dataForm)
{
//No Items
if (dataForm.Rows == 0)
{
dataForm.AddButtonRow(new ButtonBase(NoItemsLabel, "$"));
}
if (this.IsNavigationBarVisible)
{
//🔍
ButtonRow row = new ButtonRow();
row.Add(new ButtonBase(PreviousPageLabel, "$previous$"));
row.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$"));
row.Add(new ButtonBase(NextPageLabel, "$next$"));
if (this.EnableSearch)
{
row.Insert(2, new ButtonBase("🔍 " + (this.SearchQuery ?? ""), "$search$"));
}
dataForm.InsertButtonRow(0, row);
dataForm.AddButtonRow(row);
}
}
public bool PagingNecessary
{
get
{
if (this.KeyboardType == eKeyboardType.InlineKeyBoard && TotalRows > Constants.Telegram.MaxInlineKeyBoardRows)
{
return true;
}
if (this.KeyboardType == eKeyboardType.ReplyKeyboard && TotalRows > Constants.Telegram.MaxReplyKeyboardRows)
{
return true;
}
return false;
}
}
public bool IsNavigationBarVisible
{
get
{
if (this.NavigationBarVisibility == eNavigationBarVisibility.always | (this.NavigationBarVisibility == eNavigationBarVisibility.auto && PagingNecessary))
{
return true;
}
return false;
}
}
/// <summary>
/// Returns the maximum number of rows
/// </summary>
public int MaximumRow
{
get
{
switch (this.KeyboardType)
{
case eKeyboardType.InlineKeyBoard:
return Constants.Telegram.MaxInlineKeyBoardRows;
case eKeyboardType.ReplyKeyboard:
return Constants.Telegram.MaxReplyKeyboardRows;
default:
return 0;
}
}
}
/// <summary>
/// Returns the number of all rows (layout + navigation + content);
/// </summary>
public int TotalRows
{
get
{
return this.LayoutRows + DataSource.RowCount;
}
}
/// <summary>
/// Contains the Number of Rows which are used by the layout.
/// </summary>
private int LayoutRows
{
get
{
int layoutRows = 0;
if (this.NavigationBarVisibility == eNavigationBarVisibility.always | this.NavigationBarVisibility == eNavigationBarVisibility.auto)
layoutRows += 2;
if (this.HeadLayoutButtonRow != null && this.HeadLayoutButtonRow.Count > 0)
layoutRows++;
if (this.SubHeadLayoutButtonRow != null && this.SubHeadLayoutButtonRow.Count > 0)
layoutRows++;
return layoutRows;
}
}
/// <summary>
/// Returns the number of item rows per page.
/// </summary>
public int ItemRowsPerPage
{
get
{
return this.MaximumRow - this.LayoutRows;
}
}
/// <summary>
/// Return the number of pages.
/// </summary>
public int PageCount
{
get
{
if (DataSource.RowCount == 0)
return 1;
//var bf = this.DataSource.PickAllItems(this.EnableSearch ? this.SearchQuery : null);
var max = this.DataSource.CalculateMax(this.EnableSearch ? this.SearchQuery : null);
//if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{
// bf = bf.FilterDuplicate(this.SearchQuery);
//}
if (max == 0)
return 1;
return (int)Math.Ceiling((decimal)((decimal)max / (decimal)ItemRowsPerPage));
}
}
public override async Task Hidden(bool FormClose)
{
//Prepare for opening Modal, and comming back
if (!FormClose)
{
this.Updated();
}
else
{
//Remove event handler
this.Device.MessageDeleted -= Device_MessageDeleted;
}
}
/// <summary>
/// Tells the control that it has been updated.
/// </summary>
public void Updated()
{
this.RenderNecessary = true;
}
public async override Task Cleanup()
{
if (this.MessageId == null)
return;
switch (this.KeyboardType)
{
case eKeyboardType.InlineKeyBoard:
await this.Device.DeleteMessage(this.MessageId.Value);
this.MessageId = null;
break;
case eKeyboardType.ReplyKeyboard:
if (this.HideKeyboardOnCleanup)
{
await this.Device.HideReplyKeyboard();
}
this.MessageId = null;
break;
}
}
}
}
@@ -0,0 +1,112 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls.Hybrid
{
[DebuggerDisplay("{Count} columns")]
public class ButtonRow
{
List<ButtonBase> __buttons = new List<ButtonBase>();
public ButtonRow()
{
}
public ButtonRow(params ButtonBase[] buttons)
{
__buttons = buttons.ToList();
}
public ButtonBase this[int index]
{
get
{
return __buttons[index];
}
}
public int Count
{
get
{
return __buttons.Count;
}
}
public void Add(ButtonBase button)
{
__buttons.Add(button);
}
public void AddRange(ButtonBase button)
{
__buttons.Add(button);
}
public void Insert(int index, ButtonBase button)
{
__buttons.Insert(index, button);
}
public IEnumerator<ButtonBase> GetEnumerator()
{
return __buttons.GetEnumerator();
}
public ButtonBase[] ToArray()
{
return __buttons.ToArray();
}
public List<ButtonBase> ToList()
{
return __buttons.ToList();
}
public bool Matches(String text, bool useText = true)
{
foreach (var b in __buttons)
{
if (useText && b.Text.Trim().Equals(text, StringComparison.InvariantCultureIgnoreCase))
return true;
if (!useText && b.Value.Equals(text, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
/// <summary>
/// Returns the button inside of the row which matches.
/// </summary>
/// <param name="text"></param>
/// <param name="useText"></param>
/// <returns></returns>
public ButtonBase GetButtonMatch(String text, bool useText = true)
{
foreach (var b in __buttons)
{
if (useText && b.Text.Trim().Equals(text, StringComparison.InvariantCultureIgnoreCase))
return b;
if (!useText && b.Value.Equals(text, StringComparison.InvariantCultureIgnoreCase))
return b;
}
return null;
}
public static implicit operator ButtonRow(List<ButtonBase> list)
{
return new ButtonRow() { __buttons = list };
}
}
}
@@ -0,0 +1,874 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Datasources;
using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions;
using TelegramBotBase.Form;
using static TelegramBotBase.Base.Async;
namespace TelegramBotBase.Controls.Hybrid
{
public class CheckedButtonList : Base.ControlBase
{
public String Title { get; set; } = Localizations.Default.Language["ButtonGrid_Title"];
public String ConfirmationText { get; set; } = "";
private bool RenderNecessary = true;
private static readonly object __evButtonClicked = new object();
private static readonly object __evCheckedChanged = new object();
private readonly EventHandlerList Events = new EventHandlerList();
[Obsolete("This property is obsolete. Please use the DataSource property instead.")]
public ButtonForm ButtonsForm
{
get
{
return DataSource.ButtonForm;
}
set
{
DataSource = new ButtonFormDataSource(value);
}
}
/// <summary>
/// Data source of the items.
/// </summary>
public ButtonFormDataSource DataSource { get; set; }
List<int> CheckedRows { get; set; } = new List<int>();
public int? MessageId { get; set; }
/// <summary>
/// Optional. Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of the same height as the app's standard keyboard.
/// Source: https://core.telegram.org/bots/api#replykeyboardmarkup
/// </summary>
public bool ResizeKeyboard { get; set; } = false;
public bool OneTimeKeyboard { get; set; } = false;
public bool HideKeyboardOnCleanup { get; set; } = true;
public bool DeletePreviousMessage { get; set; } = true;
/// <summary>
/// Removes the reply message from a user.
/// </summary>
public bool DeleteReplyMessage { get; set; } = true;
/// <summary>
/// Parsemode of the message.
/// </summary>
public ParseMode MessageParseMode { get; set; } = ParseMode.Markdown;
/// <summary>
/// Enables automatic paging of buttons when the amount of rows is exceeding the limits.
/// </summary>
public bool EnablePaging { get; set; } = false;
///// <summary>
///// Enabled a search function.
///// </summary>
//public bool EnableSearch { get; set; } = false;
//public String SearchQuery { get; set; }
public eNavigationBarVisibility NavigationBarVisibility { get; set; } = eNavigationBarVisibility.always;
/// <summary>
/// Index of the current page
/// </summary>
public int CurrentPageIndex { get; set; } = 0;
public String PreviousPageLabel = Localizations.Default.Language["ButtonGrid_PreviousPage"];
public String NextPageLabel = Localizations.Default.Language["ButtonGrid_NextPage"];
public String NoItemsLabel = Localizations.Default.Language["ButtonGrid_NoItems"];
//public String SearchLabel = Localizations.Default.Language["ButtonGrid_SearchFeature"];
public String CheckedIconLabel { get; set; } = "✅";
public String UncheckedIconLabel { get; set; } = "◻️";
/// <summary>
/// Layout of the buttons which should be displayed always on top.
/// </summary>
public ButtonRow HeadLayoutButtonRow { get; set; }
/// <summary>
/// Layout of columns which should be displayed below the header
/// </summary>
public ButtonRow SubHeadLayoutButtonRow { get; set; }
/// <summary>
/// Defines which type of Button Keyboard should be rendered.
/// </summary>
public eKeyboardType KeyboardType
{
get
{
return m_eKeyboardType;
}
set
{
if (m_eKeyboardType != value)
{
this.RenderNecessary = true;
Cleanup().Wait();
m_eKeyboardType = value;
}
}
}
private eKeyboardType m_eKeyboardType = eKeyboardType.ReplyKeyboard;
public CheckedButtonList()
{
this.DataSource = new ButtonFormDataSource();
}
public CheckedButtonList(eKeyboardType type) : this()
{
m_eKeyboardType = type;
}
public CheckedButtonList(ButtonForm form)
{
this.DataSource = new ButtonFormDataSource(form);
}
public event AsyncEventHandler<ButtonClickedEventArgs> ButtonClicked
{
add
{
this.Events.AddHandler(__evButtonClicked, value);
}
remove
{
this.Events.RemoveHandler(__evButtonClicked, value);
}
}
public async Task OnButtonClicked(ButtonClickedEventArgs e)
{
var handler = this.Events[__evButtonClicked]?.GetInvocationList().Cast<AsyncEventHandler<ButtonClickedEventArgs>>();
if (handler == null)
return;
foreach (var h in handler)
{
await Async.InvokeAllAsync<ButtonClickedEventArgs>(h, this, e);
}
}
public event AsyncEventHandler<CheckedChangedEventArgs> CheckedChanged
{
add
{
this.Events.AddHandler(__evCheckedChanged, value);
}
remove
{
this.Events.RemoveHandler(__evCheckedChanged, value);
}
}
public async Task OnCheckedChanged(CheckedChangedEventArgs e)
{
var handler = this.Events[__evCheckedChanged]?.GetInvocationList().Cast<AsyncEventHandler<CheckedChangedEventArgs>>();
if (handler == null)
return;
foreach (var h in handler)
{
await Async.InvokeAllAsync<CheckedChangedEventArgs>(h, this, e);
}
}
public async override Task Load(MessageResult result)
{
if (this.KeyboardType != eKeyboardType.ReplyKeyboard)
return;
if (!result.IsFirstHandler)
return;
if (result.MessageText == null || result.MessageText == "")
return;
var matches = new List<ButtonRow>();
ButtonRow match = null;
int index = -1;
if (HeadLayoutButtonRow?.Matches(result.MessageText) ?? false)
{
match = HeadLayoutButtonRow;
goto check;
}
if (SubHeadLayoutButtonRow?.Matches(result.MessageText) ?? false)
{
match = SubHeadLayoutButtonRow;
goto check;
}
var br = DataSource.FindRow(result.MessageText);
if (br != null)
{
match = br.Item1;
index = br.Item2;
}
//var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText);
// bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText)
//var index = bf.FindRowByButton(button);
check:
//Remove button click message
if (this.DeleteReplyMessage)
await Device.DeleteMessage(result.MessageId);
if (match == null)
{
if (result.MessageText == PreviousPageLabel)
{
if (this.CurrentPageIndex > 0)
this.CurrentPageIndex--;
this.Updated();
}
else if (result.MessageText == NextPageLabel)
{
if (this.CurrentPageIndex < this.PageCount - 1)
this.CurrentPageIndex++;
this.Updated();
}
else if (result.MessageText.EndsWith(CheckedIconLabel))
{
var s = result.MessageText.Split(' ', '.');
index = int.Parse(s[0]) - 1;
if (!this.CheckedRows.Contains(index))
return;
this.CheckedRows.Remove(index);
this.Updated();
await OnCheckedChanged(new CheckedChangedEventArgs(ButtonsForm[index], index, false));
}
else if (result.MessageText.EndsWith(UncheckedIconLabel))
{
var s = result.MessageText.Split(' ', '.');
index = int.Parse(s[0]) - 1;
if (this.CheckedRows.Contains(index))
return;
this.CheckedRows.Add(index);
this.Updated();
await OnCheckedChanged(new CheckedChangedEventArgs(ButtonsForm[index], index, true));
}
//else if (this.EnableSearch)
//{
// if (result.MessageText.StartsWith("🔍"))
// {
// //Sent note about searching
// if (this.SearchQuery == null)
// {
// await this.Device.Send(this.SearchLabel);
// }
// this.SearchQuery = null;
// this.Updated();
// return;
// }
// this.SearchQuery = result.MessageText;
// if (this.SearchQuery != null && this.SearchQuery != "")
// {
// this.CurrentPageIndex = 0;
// this.Updated();
// }
//}
return;
}
await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match));
result.Handled = true;
//await OnButtonClicked(new ButtonClickedEventArgs(button, index));
////Remove button click message
//if (this.DeletePreviousMessage)
// await Device.DeleteMessage(result.MessageId);
//result.Handled = true;
}
public async override Task Action(MessageResult result, string value = null)
{
if (result.Handled)
return;
if (!result.IsFirstHandler)
return;
//Find clicked button depending on Text or Value (depending on markup type)
if (this.KeyboardType != eKeyboardType.InlineKeyBoard)
return;
await result.ConfirmAction(this.ConfirmationText ?? "");
ButtonRow match = null;
int index = -1;
if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false)
{
match = HeadLayoutButtonRow;
goto check;
}
if (SubHeadLayoutButtonRow?.Matches(result.RawData, false) ?? false)
{
match = SubHeadLayoutButtonRow;
goto check;
}
var br = DataSource.FindRow(result.RawData, false);
if (br != null)
{
match = br.Item1;
index = br.Item2;
}
//var bf = DataSource.ButtonForm;
//var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
// ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData);
//var index = bf.FindRowByButton(button);
check:
if (match != null)
{
await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index, match));
result.Handled = true;
return;
}
//if (button != null)
//{
// await OnButtonClicked(new ButtonClickedEventArgs(button, index));
// result.Handled = true;
// return;
//}
switch (result.RawData)
{
case "$previous$":
if (this.CurrentPageIndex > 0)
this.CurrentPageIndex--;
this.Updated();
break;
case "$next$":
if (this.CurrentPageIndex < this.PageCount - 1)
this.CurrentPageIndex++;
this.Updated();
break;
default:
var s = result.RawData.Split('$');
switch (s[0])
{
case "check":
index = int.Parse(s[1]);
if (!this.CheckedRows.Contains(index))
{
this.CheckedRows.Add(index);
this.Updated();
await OnCheckedChanged(new CheckedChangedEventArgs(ButtonsForm[index], index, true));
}
break;
case "uncheck":
index = int.Parse(s[1]);
if (this.CheckedRows.Contains(index))
{
this.CheckedRows.Remove(index);
this.Updated();
await OnCheckedChanged(new CheckedChangedEventArgs(ButtonsForm[index], index, false));
}
break;
}
break;
}
}
/// <summary>
/// This method checks of the amount of buttons
/// </summary>
private void CheckGrid()
{
switch (m_eKeyboardType)
{
case eKeyboardType.InlineKeyBoard:
if (DataSource.RowCount > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging)
{
throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxInlineKeyBoardRows };
}
if (DataSource.ColumnCount > Constants.Telegram.MaxInlineKeyBoardCols)
{
throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxInlineKeyBoardCols };
}
break;
case eKeyboardType.ReplyKeyboard:
if (DataSource.RowCount > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging)
{
throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxReplyKeyboardRows };
}
if (DataSource.ColumnCount > Constants.Telegram.MaxReplyKeyboardCols)
{
throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxReplyKeyboardCols };
}
break;
}
}
public async override Task Render(MessageResult result)
{
if (!this.RenderNecessary)
return;
//Check for rows and column limits
CheckGrid();
this.RenderNecessary = false;
Message m = null;
ButtonForm form = this.DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage, null);
//if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{
// form = form.FilterDuplicate(this.SearchQuery, true);
//}
//else
//{
//form = form.Duplicate();
//}
if (this.EnablePaging)
{
IntegratePagingView(form);
}
else
{
form = PrepareCheckableLayout(form);
}
if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0)
{
form.InsertButtonRow(0, this.HeadLayoutButtonRow.ToArray());
}
if (this.SubHeadLayoutButtonRow != null && SubHeadLayoutButtonRow.Count > 0)
{
if (this.IsNavigationBarVisible)
{
form.InsertButtonRow(2, this.SubHeadLayoutButtonRow.ToArray());
}
else
{
form.InsertButtonRow(1, this.SubHeadLayoutButtonRow.ToArray());
}
}
switch (this.KeyboardType)
{
//Reply Keyboard could only be updated with a new keyboard.
case eKeyboardType.ReplyKeyboard:
if (form.Count == 0)
{
if (this.MessageId != null)
{
await this.Device.HideReplyKeyboard();
this.MessageId = null;
}
return;
}
//if (form.Count == 0)
// return;
var rkm = (ReplyKeyboardMarkup)form;
rkm.ResizeKeyboard = this.ResizeKeyboard;
rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false);
//Prevent flicker of keyboard
if (this.DeletePreviousMessage && this.MessageId != null)
await this.Device.DeleteMessage(this.MessageId.Value);
break;
case eKeyboardType.InlineKeyBoard:
if (this.MessageId != null)
{
m = await this.Device.Edit(this.MessageId.Value, this.Title, (InlineKeyboardMarkup)form);
}
else
{
m = await this.Device.Send(this.Title, (InlineKeyboardMarkup)form, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false);
}
break;
}
if (m != null)
{
this.MessageId = m.MessageId;
}
}
private void IntegratePagingView(ButtonForm dataForm)
{
//No Items
if (dataForm.Rows == 0)
{
dataForm.AddButtonRow(new ButtonBase(NoItemsLabel, "$"));
}
ButtonForm bf = new ButtonForm();
bf = PrepareCheckableLayout(dataForm);
if (this.IsNavigationBarVisible)
{
//🔍
ButtonRow row = new ButtonRow();
row.Add(new ButtonBase(PreviousPageLabel, "$previous$"));
row.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$"));
row.Add(new ButtonBase(NextPageLabel, "$next$"));
dataForm.InsertButtonRow(0, row);
dataForm.AddButtonRow(row);
}
}
private ButtonForm PrepareCheckableLayout(ButtonForm dataForm)
{
var bf = new ButtonForm();
for (int i = 0; i < dataForm.Rows; i++)
{
int it = (this.CurrentPageIndex * (this.MaximumRow - LayoutRows)) + i;
//if (it > dataForm.Rows - 1)
// break;
var r = dataForm[i];
String s = CheckedRows.Contains(it) ? this.CheckedIconLabel : this.UncheckedIconLabel;
//On reply keyboards we need a unique text.
if (this.KeyboardType == eKeyboardType.ReplyKeyboard)
{
s = $"{it + 1}. " + s;
}
if (CheckedRows.Contains(it))
{
r.Insert(0, new ButtonBase(s, "uncheck$" + it.ToString()));
}
else
{
r.Insert(0, new ButtonBase(s, "check$" + it.ToString()));
}
bf.AddButtonRow(r);
}
return bf;
}
public bool PagingNecessary
{
get
{
if (this.KeyboardType == eKeyboardType.InlineKeyBoard && TotalRows > Constants.Telegram.MaxInlineKeyBoardRows)
{
return true;
}
if (this.KeyboardType == eKeyboardType.ReplyKeyboard && TotalRows > Constants.Telegram.MaxReplyKeyboardRows)
{
return true;
}
return false;
}
}
public bool IsNavigationBarVisible
{
get
{
if (this.NavigationBarVisibility == eNavigationBarVisibility.always | (this.NavigationBarVisibility == eNavigationBarVisibility.auto && PagingNecessary))
{
return true;
}
return false;
}
}
/// <summary>
/// Returns the maximum number of rows
/// </summary>
public int MaximumRow
{
get
{
switch (this.KeyboardType)
{
case eKeyboardType.InlineKeyBoard:
return Constants.Telegram.MaxInlineKeyBoardRows;
case eKeyboardType.ReplyKeyboard:
return Constants.Telegram.MaxReplyKeyboardRows;
default:
return 0;
}
}
}
/// <summary>
/// Returns the number of all rows (layout + navigation + content);
/// </summary>
public int TotalRows
{
get
{
return this.LayoutRows + DataSource.RowCount;
}
}
/// <summary>
/// Contains the Number of Rows which are used by the layout.
/// </summary>
private int LayoutRows
{
get
{
int layoutRows = 0;
if (this.NavigationBarVisibility == eNavigationBarVisibility.always | this.NavigationBarVisibility == eNavigationBarVisibility.auto)
layoutRows += 2;
if (this.HeadLayoutButtonRow != null && this.HeadLayoutButtonRow.Count > 0)
layoutRows++;
if (this.SubHeadLayoutButtonRow != null && this.SubHeadLayoutButtonRow.Count > 0)
layoutRows++;
return layoutRows;
}
}
/// <summary>
/// Returns the number of item rows per page.
/// </summary>
public int ItemRowsPerPage
{
get
{
return this.MaximumRow - this.LayoutRows;
}
}
public int PageCount
{
get
{
if (DataSource.RowCount == 0)
return 1;
//var bf = this.DataSource.PickAllItems(this.EnableSearch ? this.SearchQuery : null);
var max = this.DataSource.RowCount;
//if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{
// bf = bf.FilterDuplicate(this.SearchQuery);
//}
if (max == 0)
return 1;
return (int)Math.Ceiling((decimal)((decimal)max / (decimal)ItemRowsPerPage));
}
}
public override async Task Hidden(bool FormClose)
{
//Prepare for opening Modal, and comming back
if (!FormClose)
{
this.Updated();
}
}
public List<ButtonBase> CheckedItems
{
get
{
List<ButtonBase> lst = new List<ButtonBase>();
foreach (var c in CheckedRows)
{
lst.Add(this.ButtonsForm[c][0]);
}
return lst;
}
}
/// <summary>
/// Tells the control that it has been updated.
/// </summary>
public void Updated()
{
this.RenderNecessary = true;
}
public async override Task Cleanup()
{
if (this.MessageId == null)
return;
switch (this.KeyboardType)
{
case eKeyboardType.InlineKeyBoard:
await this.Device.DeleteMessage(this.MessageId.Value);
this.MessageId = null;
break;
case eKeyboardType.ReplyKeyboard:
if (this.HideKeyboardOnCleanup)
{
await this.Device.HideReplyKeyboard();
}
this.MessageId = null;
break;
}
}
}
}
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
namespace TelegramBotBase.Controls.Hybrid
{
/// <summary>
/// This Control is for having a basic form content switching control.
/// </summary>
public abstract class MultiView : Base.ControlBase
{
/// <summary>
/// Index of the current View.
/// </summary>
public int SelectedViewIndex
{
get
{
return m_iSelectedViewIndex;
}
set
{
m_iSelectedViewIndex = value;
//Already rendered? Re-Render
if (_Rendered)
ForceRender().Wait();
}
}
private int m_iSelectedViewIndex = 0;
/// <summary>
/// Hold if the View has been rendered already.
/// </summary>
private bool _Rendered = false;
private List<int> Messages { get; set; }
public MultiView()
{
Messages = new List<int>();
}
private void Device_MessageSent(object sender, MessageSentEventArgs e)
{
if (e.Origin == null || !e.Origin.IsSubclassOf(typeof(MultiView)))
return;
this.Messages.Add(e.MessageId);
}
public override void Init()
{
Device.MessageSent += Device_MessageSent;
}
public override async Task Load(MessageResult result)
{
_Rendered = false;
}
public override async Task Render(MessageResult result)
{
//When already rendered, skip rendering
if (_Rendered)
return;
await CleanUpView();
await RenderView(new RenderViewEventArgs(this.SelectedViewIndex));
_Rendered = true;
}
/// <summary>
/// Will get invoked on rendering the current controls view.
/// </summary>
/// <param name="e"></param>
public virtual async Task RenderView(RenderViewEventArgs e)
{
}
async Task CleanUpView()
{
var tasks = new List<Task>();
foreach (var msg in this.Messages)
{
tasks.Add(this.Device.DeleteMessage(msg));
}
await Task.WhenAll(tasks);
this.Messages.Clear();
}
/// <summary>
/// Forces render of control contents.
/// </summary>
public async Task ForceRender()
{
await CleanUpView();
await RenderView(new RenderViewEventArgs(this.SelectedViewIndex));
_Rendered = true;
}
public override async Task Cleanup()
{
Device.MessageSent -= Device_MessageSent;
await CleanUpView();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -7,11 +7,11 @@ using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Enums;
using TelegramBotBase.Form;
using TelegramBotBase.Tools;
using static TelegramBotBase.Tools.Arrays;
using static TelegramBotBase.Tools.Time;
namespace TelegramBotBase.Controls
namespace TelegramBotBase.Controls.Inline
{
public class CalendarPicker : Base.ControlBase
{
@@ -27,7 +27,7 @@ namespace TelegramBotBase.Controls
private int? MessageId { get; set; }
public String Title { get; set; } = "Pick Date";
public String Title { get; set; } = Localizations.Default.Language["CalendarPicker_Title"];
public eMonthPickerMode PickerMode { get; set; }
@@ -36,15 +36,17 @@ namespace TelegramBotBase.Controls
public bool EnableMonthView { get; set; } = true;
public bool EnableYearView { get; set; } = true;
public CalendarPicker()
public CalendarPicker(CultureInfo culture)
{
this.SelectedDate = DateTime.Today;
this.VisibleMonth = DateTime.Today;
this.FirstDayOfWeek = DayOfWeek.Monday;
this.Culture = new CultureInfo("de-de");
this.Culture = culture;
this.PickerMode = eMonthPickerMode.day;
}
public CalendarPicker() : this(new CultureInfo("en-en")) { }
@@ -55,7 +57,7 @@ namespace TelegramBotBase.Controls
switch (result.RawData)
{
case "next":
case "$next$":
switch (this.PickerMode)
{
@@ -74,7 +76,7 @@ namespace TelegramBotBase.Controls
break;
case "prev":
case "$prev$":
switch (this.PickerMode)
{
@@ -93,7 +95,7 @@ namespace TelegramBotBase.Controls
break;
case "monthtitle":
case "$monthtitle$":
if (this.EnableMonthView)
{
@@ -102,7 +104,7 @@ namespace TelegramBotBase.Controls
break;
case "yeartitle":
case "$yeartitle$":
if (this.EnableYearView)
{
@@ -110,7 +112,7 @@ namespace TelegramBotBase.Controls
}
break;
case "yearstitle":
case "$yearstitle$":
if (this.EnableMonthView)
{
@@ -124,13 +126,13 @@ namespace TelegramBotBase.Controls
default:
int day = 0;
if (result.RawData.StartsWith("d-") && int.TryParse(result.RawData.Split('-')[1], out day))
if (result.RawData.StartsWith("d-") && TryParseDay(result.RawData.Split('-')[1], this.SelectedDate, out day))
{
this.SelectedDate = new DateTime(this.VisibleMonth.Year, this.VisibleMonth.Month, day);
}
int month = 0;
if (result.RawData.StartsWith("m-") && int.TryParse(result.RawData.Split('-')[1], out month))
if (result.RawData.StartsWith("m-") && TryParseMonth(result.RawData.Split('-')[1], out month))
{
this.SelectedDate = new DateTime(this.VisibleMonth.Year, month, 1);
this.VisibleMonth = this.SelectedDate;
@@ -142,7 +144,7 @@ namespace TelegramBotBase.Controls
}
int year = 0;
if (result.RawData.StartsWith("y-") && int.TryParse(result.RawData.Split('-')[1], out year))
if (result.RawData.StartsWith("y-") && TryParseYear(result.RawData.Split('-')[1], out year))
{
this.SelectedDate = new DateTime(year, SelectedDate.Month, SelectedDate.Day);
this.VisibleMonth = this.SelectedDate;
@@ -179,7 +181,7 @@ namespace TelegramBotBase.Controls
string[] dayNamesNormal = this.Culture.DateTimeFormat.ShortestDayNames;
string[] dayNamesShifted = Shift(dayNamesNormal, (int)this.FirstDayOfWeek);
bf.AddButtonRow(new ButtonBase("<<", "prev"), new ButtonBase(this.Culture.DateTimeFormat.MonthNames[month.Month - 1] + " " + month.Year.ToString(), "monthtitle"), new ButtonBase(">>", "next"));
bf.AddButtonRow(new ButtonBase(Localizations.Default.Language["CalendarPicker_PreviousPage"], "$prev$"), new ButtonBase(this.Culture.DateTimeFormat.MonthNames[month.Month - 1] + " " + month.Year.ToString(), "$monthtitle$"), new ButtonBase(Localizations.Default.Language["CalendarPicker_NextPage"], "$next$"));
bf.AddButtonRow(dayNamesShifted.Select(a => new ButtonBase(a, a)).ToList());
@@ -223,7 +225,7 @@ namespace TelegramBotBase.Controls
case eMonthPickerMode.month:
bf.AddButtonRow(new ButtonBase("<<", "prev"), new ButtonBase(this.VisibleMonth.Year.ToString("0000"), "yeartitle"), new ButtonBase(">>", "next"));
bf.AddButtonRow(new ButtonBase(Localizations.Default.Language["CalendarPicker_PreviousPage"], "$prev$"), new ButtonBase(this.VisibleMonth.Year.ToString("0000"), "$yeartitle$"), new ButtonBase(Localizations.Default.Language["CalendarPicker_NextPage"], "$next$"));
var months = this.Culture.DateTimeFormat.MonthNames;
@@ -235,7 +237,7 @@ namespace TelegramBotBase.Controls
case eMonthPickerMode.year:
bf.AddButtonRow(new ButtonBase("<<", "prev"), new ButtonBase("Year", "yearstitle"), new ButtonBase(">>", "next"));
bf.AddButtonRow(new ButtonBase(Localizations.Default.Language["CalendarPicker_PreviousPage"], "$prev$"), new ButtonBase("Year", "$yearstitle$"), new ButtonBase(Localizations.Default.Language["CalendarPicker_NextPage"], "$next$"));
var starti = Math.Floor(this.VisibleMonth.Year / 10f) * 10;
@@ -8,7 +8,7 @@ using TelegramBotBase.Base;
using TelegramBotBase.Enums;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls
namespace TelegramBotBase.Controls.Inline
{
public class MonthPicker : CalendarPicker
{
@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls.Inline
{
public class MultiToggleButton : ControlBase
{
/// <summary>
/// This contains the selected icon.
/// </summary>
public String SelectedIcon { get; set; } = Localizations.Default.Language["MultiToggleButton_SelectedIcon"];
/// <summary>
/// This will appear on the ConfirmAction message (if not empty)
/// </summary>
public String ChangedString { get; set; } = Localizations.Default.Language["MultiToggleButton_Changed"];
/// <summary>
/// This holds the title of the control.
/// </summary>
public String Title { get; set; } = Localizations.Default.Language["MultiToggleButton_Title"];
public int? MessageId { get; set; }
private bool RenderNecessary = true;
private static readonly object __evToggled = new object();
private readonly EventHandlerList Events = new EventHandlerList();
/// <summary>
/// This will hold all options available.
/// </summary>
public List<ButtonBase> Options { get; set; }
/// <summary>
/// This will set if an empty selection (null) is allowed.
/// </summary>
public bool AllowEmptySelection { get; set; } = true;
public MultiToggleButton()
{
Options = new List<ButtonBase>();
}
public event EventHandler Toggled
{
add
{
this.Events.AddHandler(__evToggled, value);
}
remove
{
this.Events.RemoveHandler(__evToggled, value);
}
}
public void OnToggled(EventArgs e)
{
(this.Events[__evToggled] as EventHandler)?.Invoke(this, e);
}
public override async Task Action(MessageResult result, String value = null)
{
if (result.Handled)
return;
await result.ConfirmAction(this.ChangedString);
switch (value ?? "unknown")
{
default:
var s = value.Split('$');
if (s[0] == "check" && s.Length > 1)
{
int index = 0;
if (!int.TryParse(s[1], out index))
{
return;
}
if(SelectedOption== null || SelectedOption != this.Options[index])
{
this.SelectedOption = this.Options[index];
OnToggled(new EventArgs());
}
else if(this.AllowEmptySelection)
{
this.SelectedOption = null;
OnToggled(new EventArgs());
}
RenderNecessary = true;
return;
}
RenderNecessary = false;
break;
}
result.Handled = true;
}
public override async Task Render(MessageResult result)
{
if (!RenderNecessary)
return;
var bf = new ButtonForm(this);
var lst = new List<ButtonBase>();
foreach (var o in this.Options)
{
var index = this.Options.IndexOf(o);
if (o == this.SelectedOption)
{
lst.Add(new ButtonBase(SelectedIcon + " " + o.Text, "check$" + index));
continue;
}
lst.Add(new ButtonBase(o.Text, "check$" + index));
}
bf.AddButtonRow(lst);
if (this.MessageId != null)
{
var m = await this.Device.Edit(this.MessageId.Value, this.Title, bf);
}
else
{
var m = await this.Device.Send(this.Title, bf, disableNotification: true);
if (m != null)
{
this.MessageId = m.MessageId;
}
}
this.RenderNecessary = false;
}
public ButtonBase SelectedOption
{
get; set;
}
}
}
@@ -5,7 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
namespace TelegramBotBase.Controls
namespace TelegramBotBase.Controls.Inline
{
/// <summary>
/// A simple control for show and managing progress.
@@ -7,20 +7,22 @@ using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls
namespace TelegramBotBase.Controls.Inline
{
public class ToggleButton : ControlBase
{
public String UncheckedIcon { get; set; } = "⚪";
public String UncheckedIcon { get; set; } = Localizations.Default.Language["ToggleButton_OffIcon"];
public String CheckedIcon { get; set; } = "⚫";
public String CheckedIcon { get; set; } = Localizations.Default.Language["ToggleButton_OnIcon"];
public String CheckedString { get; set; } = "On";
public String CheckedString { get; set; } = Localizations.Default.Language["ToggleButton_On"];
public String UncheckedString { get; set; } = "Off";
public String UncheckedString { get; set; } = Localizations.Default.Language["ToggleButton_Off"];
public String Title { get; set; } = "Toggle";
public String ChangedString { get; set; } = Localizations.Default.Language["ToggleButton_Changed"];
public String Title { get; set; } = Localizations.Default.Language["ToggleButton_Title"];
public int? MessageId { get; set; }
@@ -68,7 +70,7 @@ namespace TelegramBotBase.Controls
if (result.Handled)
return;
await result.ConfirmAction();
await result.ConfirmAction(this.ChangedString);
switch (value ?? "unknown")
{
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls
namespace TelegramBotBase.Controls.Inline
{
public class TreeView : ControlBase
{
@@ -20,12 +20,12 @@ namespace TelegramBotBase.Controls
private int? MessageId { get; set; }
public String MoveUpIcon { get; set; } = "🔼 up";
public String MoveUpIcon { get; set; } = Localizations.Default.Language["TreeView_LevelUp"];
public TreeView()
{
this.Nodes = new List<TreeViewNode>();
this.Title = "Select node";
this.Title = Localizations.Default.Language["TreeView_Title"];
}
@@ -52,19 +52,21 @@ namespace TelegramBotBase.Controls
var n = (this.VisibleNode != null ? this.VisibleNode.FindNodeByValue(val) : this.Nodes.FirstOrDefault(a => a.Value == val));
if (n != null)
{
if (n.ChildNodes.Count > 0)
{
this.VisibleNode = n;
}
else
{
this.SelectedNode = (this.SelectedNode != n ? n : null);
}
if (n == null)
return;
result.Handled = true;
if (n.ChildNodes.Count > 0)
{
this.VisibleNode = n;
}
else
{
this.SelectedNode = (this.SelectedNode != n ? n : null);
}
result.Handled = true;
break;
@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Controls
namespace TelegramBotBase.Controls.Inline
{
public class TreeViewNode
{

Some files were not shown because too many files have changed in this diff Show More