Compare commits

..
297 Commits
Author SHA1 Message Date
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
FlorianDahn eb61d40a38 -Projectfile update 2020-04-09 15:32:48 +02:00
FlorianDahn 2ed030b2fb - adding MarkdownV2 escape function
- adding MarkdownV2 autoescape feature
2020-04-09 11:57:45 +02:00
FlorianDahn fd6f6fef34 - moving LoadSessionStates and SaveSessionStates from BotBase to SessionBase 2020-04-09 11:18:23 +02:00
FlorianDahn 54176f9136 Update SessionBase.cs
- adding method to get User Sessions and Group Sessions
2020-04-05 23:47:27 +07:00
FlorianDahn f80fda8ff9 More Attributes for Serialization
- adding IgnoreState Attribute to make it easier to skip forms on session serialization
2020-04-05 23:46:33 +07:00
FlorianDahn 8b66842d34 Update BotBase.cs
- prevent BotBase from crashing on  loading serialized sessions
2020-04-05 23:31:37 +07:00
FlorianDahn 0cdb8c3a1a Adding StateMachine for Session serialization
- adding multiple classes and interfaces for Session Serialization and recovery after restart
2020-04-05 13:13:11 +07:00
FlorianDahn 8b9929198a Modal and Form updates
- adding ReturnFromModal method to FormBase, will get invoked if context leaves a ModalDialog
- ModalDialog contains ParentForm for better navigation
- ModalDialog invokes ReturnFromModal on parentForm after closing
- adding ShowBackButton to PromptDialog for show a back button
2020-04-04 19:52:15 +07:00
FlorianDahn 7a3e4721bd Update DataResult.cs
- adding DownloadAudio
- adding an optional method for all available ones to save attachments directly to a file
2020-04-03 21:54:32 +07:00
FlorianDahn f914920919 Update LinkReplaceTest.cs
- updating LinkReplaceTest to use Message edits, so links will replace in original messages or edited ones
2020-04-02 12:41:26 +07:00
FlorianDahn 0435c68281 Update DeviceSession.cs
DeviceSession: Removing unused ex variable in Exceptions
2020-04-02 12:39:48 +07:00
FlorianDahn 5a1e050aec Update Generator.cs
- Markup Generator: Updating MentionUser
2020-04-02 12:39:26 +07:00
FlorianDahn 6a62d73fe0 Adding Edited Features
- FormBase: adding Edited method
- MessageClient: adding MessageEdit event
- GroupForm: adding Edited method
- DeviceSession: adding ChatTitle
2020-04-02 12:37:53 +07:00
FlorianDahn 519a6fe41c Update ButtonGrid.cs
- ButtonGrid: adding ConfirmationText
- ButtonGrid: adding ParseMode
2020-04-02 12:30:38 +07:00
FlorianDahn 201dfd10dd - updating Testproject to latest changes in Main project 2020-03-29 11:12:34 +07:00
FlorianDahn 78a412c630 - simplify function returns and improving memory use 2020-03-28 10:58:25 +07:00
FlorianDahn 7a7014cec3 - adding SendTextFile to simplify sending csv or textfiles by code without saving them to harddrive 2020-03-28 10:55:32 +07:00
FlorianDahn 64311d0cd9 - adding link to documentation 2020-03-28 10:29:34 +07:00
FlorianDahn 762fb27294 Markdown Fix
- Updating Markdown generator
- adding more options
2020-03-27 12:41:36 +07:00
FlorianDahn 164194ac66 Update
- adding more Constants to Telegram file
- Buttongrid: fixing check for rows and columns
- Buttongrid: replacingvalues with constants from Telegram file
2020-03-27 12:08:34 +07:00
FlorianDahn 321bd7c0ff - adding GetButtonByValue to ButtonForm 2020-03-27 11:03:15 +07:00
FlorianDahn 37ef30ebee - changing OpenModal and NavigateTo to virtual, so you could override it 2020-03-25 18:10:13 +07:00
FlorianDahn 9b1461ad78 Update README.md 2020-03-25 12:45:07 +07:00
FlorianDahn 38fa0a4e99 Update README.md 2020-03-25 12:44:29 +07:00
FlorianDahn fba922049f Updates and fixes
- adding IsFirstHandler to allow controls to skip Actions which has been invoked already (i.e. due to navigation)
- adding Hidden method to ButtonGrid to fix re-rendering Buttons after opening a Modal form and comming back
- improving and fixing the Cleanup method for ButtonGrid to prevent "Keyboard jumping" for ReplyKeyboard on mobile devices.
- renaming some old *Result objects to *EventArgs for more clear definitions
2020-03-25 12:37:16 +07:00
FlorianDahn d5a9a5f7a4 - adding Hidden method to ControlBase which will be called on leaving form or on opening a modal one 2020-03-25 12:27:01 +07:00
FlorianDahn 5126074419 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2020-03-23 13:06:56 +07:00
FlorianDahn aa38492bde - more refactoring and renaming models *Result to *EventArgs 2020-03-23 13:06:52 +07:00
Florian Dahn 9c68914e9d Delete TelegramBotBase.2.1.0.zip 2020-03-23 13:03:59 +07:00
Florian Dahn 3fe2779b1e Delete TelegramBotBase.2.0.0.zip 2020-03-23 13:03:51 +07:00
FlorianDahn 8e6de4a0e1 - Refactoring and moving all EventArgs structures to folder Args 2020-03-23 13:02:31 +07:00
FlorianDahn 382ae2de2d - adding example project for Bot to start predefined system commands 2020-03-10 01:24:09 +01:00
FlorianDahn 77fd3393de - update spaces 2020-03-10 01:22:52 +01:00
FlorianDahn 0f1da77aca - update on AutoCleanForm for better message deletion on leaving 2020-03-10 01:22:28 +01:00
FlorianDahn 1561c1dd08 -adding HideKeyboardOnCleanup and DeletePreviousMessage on ButtonGrid Control 2020-03-10 01:21:42 +01:00
FlorianDahn 5b38f2e1e2 - adding sendvideo to device for videos or gifs 2019-12-04 01:29:22 +01:00
FlorianDahn 2989456fe5 - change Formbase for better navigation management 2019-12-04 01:28:15 +01:00
FlorianDahn 7e4174a3be Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-12-04 01:27:07 +01:00
FlorianDahn 73f81cc8a2 Revert "- adding new exceptions"
This reverts commit 2071ba2eeb.
2019-12-02 03:54:10 +01:00
FlorianDahn 2071ba2eeb - adding new exceptions 2019-12-02 03:48:43 +01:00
FlorianDahn a08ba8f512 -update on FormBase which should prevent issues on control management 2019-12-02 03:46:40 +01:00
Florian Dahn 649b94dafe Delete dotnetcore.yml 2019-11-28 17:17:19 +01:00
FlorianDahn 7be0f15ca4 - updated readme for groups
- adding readme to project solution
2019-11-28 15:41:44 +01:00
FlorianDahn e29be4f1f4 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-11-23 02:44:59 +01:00
FlorianDahn 176107300b - adding group examples for:
link replacing
   group change notifications
   welcoming users
2019-11-23 02:44:50 +01:00
FlorianDahn 0ceab58059 - updating namespaces in test project
- adding chat splitter to testproject to allow single chat and group chat examples at the same time
2019-11-23 02:44:05 +01:00
FlorianDahn 6ebdd9f208 - additional stuff for group management 2019-11-23 02:41:17 +01:00
FlorianDahn 595efd2276 - adding base class for group management 2019-11-23 02:40:51 +01:00
FlorianDahn 85a479d826 - adding useful tasks for groups 2019-11-23 02:40:25 +01:00
FlorianDahn 150e9f731d - addes SplitterForm to switch between "normal" chats and group chats 2019-11-23 02:39:56 +01:00
FlorianDahn 0eab45bdbb - making Events property of FormBase public, for use in sub classes
- making ResultBase derive from Eventargs
- adding messagetype to messageresult
2019-11-21 21:38:20 +01:00
Florian Dahn a33cf5b841 Create dotnetcore.yml 2019-11-14 22:58:50 +01:00
FlorianDahn 1f9539a23d - updating readme 2019-10-11 22:38:50 +02:00
FlorianDahn 9cbde4830e - adding OpenModal/CloseModal features to leave a form open and "just" show a prompt and get back
- added new "ModalDialog" class to present modal forms
- updating examples for prompt dialog to use the modal feature
2019-10-11 22:37:54 +02:00
FlorianDahn 3bea2783b1 - updated Readme 2019-10-11 21:41:02 +02:00
FlorianDahn 038a941d33 - PromptDialog has been changed to ConfirmDialog
- PromptDialog is now (like in JavaScript) for text inputs
- examples corrected
2019-10-11 21:33:33 +02:00
FlorianDahn 48c62e57f8 - moving TelegramBaseTest to TelegramBotBaseTest 2019-09-29 00:47:18 +02:00
FlorianDahn b0e6a8b981 -migrate from .net 4.7.2 to .Net standard 2.0 for the library and .Net core 3.0 for the example project 2019-09-28 14:06:19 +02:00
FlorianDahn 2e0656dc06 - update readme 2019-09-28 01:19:55 +02:00
FlorianDahn d1456d5bd1 - update readme and formbase 2019-09-28 01:17:38 +02:00
FlorianDahn 058539777f - removing obsolete Property and methods
- updating test project
2019-09-27 23:24:31 +02:00
FlorianDahn 5c69289198 - small additions 2019-09-27 23:18:18 +02:00
FlorianDahn 4c8b085655 - small cleanup 2019-09-27 23:14:28 +02:00
FlorianDahn 94d22318f8 - change SetKeyboardType method to Property 2019-09-27 23:08:55 +02:00
FlorianDahn 2028e2f9e6 - adding ButtonGrid control to Readme 2019-09-27 21:26:43 +02:00
FlorianDahn 394209b303 updating both projects to .Net 4.7.2 2019-09-27 00:01:20 +02:00
FlorianDahn f0fa953819 Updating csproj Files 2019-09-26 23:27:38 +02:00
FlorianDahn 1d44b50883 - small change 2019-09-26 20:57:51 +02:00
FlorianDahn 3c21af8f68 - small change and adding comments 2019-09-26 20:57:16 +02:00
FlorianDahn 1b811e1786 - added ToKeyboardButton to ButtonBase
- added eKeyboardType to switch between Inline and KeykboardButtons
- addes some small functions to ButtonForm
- added new control "ButtonGrid" which manages Button handling with Event Handler for each Button and can switch between InlineMode and KeyboardMode during runtime
- added also Examples to it
2019-09-26 20:54:35 +02:00
FlorianDahn 94195ec5bf - added another Edit Method for InlineKeyboardMarkup
- change Send method parameter ReplyKeyboardMarkup to ReplyMarkupBase to allow also RemoveKeyboard
2019-09-26 20:51:30 +02:00
FlorianDahn 256cac825f - adding Load method for controls 2019-09-26 20:50:33 +02:00
FlorianDahn 69e5a6717b - Splitting enums into single files for more structure
- remove old Enum file
2019-09-26 17:14:12 +02:00
FlorianDahn a6d22cdd82 Update version 2019-09-17 16:44:14 +02:00
FlorianDahn ee4a3d765c Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-09-17 16:43:10 +02:00
FlorianDahn 703c99eb8d - added a custom exception for maximum length messages, saves to time to sent to API
- added a constants file, for have constants to work with
- catching maximum message length
2019-09-17 16:43:02 +02:00
Florian Dahn 138fee9772 Update README.md
adding paypal
2019-09-17 16:33:51 +02:00
Florian Dahn d60399514f Update README.md 2019-09-04 15:13:42 +02:00
FlorianDahn ae0fa3b0e3 Added simple Markdown/HTML Generator
Use with:

"Bold Text".Bold();

"Other text".Italic();

"www.google.de".Link("Google")
2019-08-28 17:33:18 +02:00
FlorianDahn 6a2ac7a309 Added simple Markdown/HTML Generator
Use with:

"Bold Text".Bold();

"Other text".Italic();

"www.google.de".AsLink("Google")
2019-08-28 17:32:08 +02:00
FlorianDahn 1a76b2b3f8 added some forms to obsolete, cause their use is bad for ram management 2019-08-26 01:34:50 +02:00
FlorianDahn 5893108bd3 Update readme 2019-08-23 14:55:54 +02:00
FlorianDahn d668f57711 Update readme 2019-08-23 14:53:24 +02:00
FlorianDahn 56c7754408 - Added ToggleButton as an easy switch control (i.e. for settings page)
- added control id, for easier separating of controls in one form
- added automatic event selection for specific controls, to not raise an event for other controls who has invoked it
- changed Action method
- added example
2019-08-23 14:02:24 +02:00
FlorianDahn 805ffb9ec7 Update version 2019-07-15 13:33:01 +02:00
FlorianDahn aeddd82ea9 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-07-15 13:31:07 +02:00
FlorianDahn 1ec14ce680 Updating nuget packages 2019-07-15 13:31:02 +02:00
Florian Dahn 3a309baeae Update README.md 2019-07-14 23:14:37 +02:00
FlorianDahn e02af0fc76 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-07-14 23:13:19 +02:00
FlorianDahn 530e01f971 new images 2019-07-14 23:13:16 +02:00
Florian Dahn dcf3cf804f Update README.md 2019-07-14 23:00:06 +02:00
Florian Dahn a42d3934b5 Update README.md 2019-07-14 22:52:59 +02:00
Florian Dahn 2126b45fed Update README.md 2019-07-14 22:47:04 +02:00
FlorianDahn b6b9d5b6b1 Update images 2019-07-14 22:46:29 +02:00
FlorianDahn eed5f012dc adding images for example 2019-07-14 22:38:10 +02:00
FlorianDahn de4cc138ae - New control: CalendarPicker
- New control: MonthPicker
- New control TreeView
- adding examples for all 3
- small change on Progress Bar Control
- due latest changes on the base it is now easier to create controls which will be rendered (or not)in several forms depending on user context
2019-07-14 22:27:16 +02:00
FlorianDahn e4da70d37d - Update of FormBase for better async event management
- Update of ControlBase for partial rendering options
- Updating serveral base classes to better asyc event management
- separating Enums into different folder
- some small bug fixes
- removed "CustomEventManagement" cause it doesnt make sense now
2019-07-14 22:22:04 +02:00
FlorianDahn cb4a797b12 Updating ButtonBase to enable Url Buttons 2019-07-14 22:13:23 +02:00
FlorianDahn 94d153e141 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-06-26 12:48:37 +02:00
FlorianDahn e13cb6d4ef - Updating NavigateTo to set PreviousForm now correctly 2019-06-26 12:48:25 +02:00
Florian Dahn c0e285d39e Update README.md 2019-06-24 13:50:55 +02:00
Florian Dahn 8338c5c066 Update README.md 2019-06-24 13:49:37 +02:00
FlorianDahn 52c01b3dfd Update 2019-06-22 15:16:51 +02:00
FlorianDahn fe1aafc9a5 - added IsDisposed to form
- removed FormSwitched from FormBase and added to DeviceSession (more logical message loop)
- added PreviousForm to DeviceSession, contains last opened form (at initial form is null)
-BotBase message loop update
- now navigation is possible from every form, not just the latest ones as before
2019-06-22 15:15:29 +02:00
FlorianDahn 022eee4b57 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-05-04 18:59:08 +02:00
FlorianDahn ea971979e2 - Added comments
- added Edit method
2019-05-04 18:59:05 +02:00
Florian Dahn 9585a231bd Update README.md 2019-05-04 18:43:54 +02:00
Florian Dahn c07aca96a4 Update README.md 2019-05-04 16:56:44 +02:00
Florian Dahn 01de9ec932 Update README.md 2019-05-04 16:56:25 +02:00
FlorianDahn 94fe263676 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-05-04 16:32:50 +02:00
FlorianDahn 4f1eae543d - adding Delete Options for Both sides to AutoCleanForm
- general Performance improvements
- Adding MessageReceived Event to DeviceSession, will be called before "message loop"
- AutoCleanForm optimized and clean process added to public function MessageCleanup (so you could call on your own)
- IsGroup for DeviceSession fixed to correct test
- IsChannel added to DeviceSession

Examples:
- SimpleForm (#1) derived now from AutoCleanForm to show deletion mode for both sides
2019-05-04 16:32:48 +02:00
Florian Dahn 1491fc1795 Update README.md 2019-04-16 13:46:37 +02:00
Florian Dahn b2db44fe4c Update README.md 2019-03-27 14:10:43 +07:00
FlorianDahn 3a78a98b9e Update Readme.md 2019-03-27 14:09:53 +07:00
FlorianDahn b14fa2dc87 - adding new SentData Event in FormBase for User uploads (for Photo, Audio, Video, Contact, Location, Document)
- adding RequestUserLocation and RequestUserContact to Device class
- Update to Device class
- updating examples
- adding example for data upload
- small documentary Updates (Readme.md)
2019-03-27 14:01:48 +07:00
FlorianDahn 5cd54fc746 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-03-20 22:24:42 +07:00
FlorianDahn fb68da6ab1 - added request contact to DeviceSession
- added request location to DeviceSession
2019-03-20 22:24:00 +07:00
FlorianDahn 2f7ded8ce5 - added constructor for adding custom config of TelegramBotClient
- added Log all messages to Action events
2019-03-20 22:21:10 +07:00
Florian Dahn 906177a5f9 Update README.md 2019-03-20 17:54:35 +07:00
FlorianDahn 873c91a165 Update Readme 2019-03-20 17:53:06 +07:00
FlorianDahn 46c1acbf99 Update Readme.md 2019-03-20 17:51:40 +07:00
FlorianDahn acd06a6948 Update Testproject/Readme 2019-03-20 17:50:11 +07:00
Florian Dahn a97305fa35 Update README.md 2019-03-20 17:48:18 +07:00
FlorianDahn 16baef0c45 Update Readme 2019-03-20 17:43:50 +07:00
FlorianDahn 211bc55aff - Cleanup of unecessary init calls
- added form1/form2 option to start menu in test project
- added example for different forms with and without event handlers
- added form examples to Readme.md
2019-03-20 17:42:33 +07:00
FlorianDahn 19f3c9c195 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-03-20 16:57:15 +07:00
FlorianDahn c6666f72bc - Improvements to system calls
- system calls now async with await
- added start command to example
- added "handled" property to system call event args
2019-03-20 16:57:10 +07:00
Florian Dahn bf7f6f851a Update README.md 2019-03-20 14:00:11 +07:00
FlorianDahn 816bf4cbbe Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-03-20 13:44:48 +07:00
FlorianDahn da80af86e3 Small fix in Systemcall management 2019-03-20 13:44:44 +07:00
Florian Dahn 929db3ae88 Update README.md 2019-03-20 11:18:03 +07:00
FlorianDahn 582f005b35 Merge branch 'master' of https://github.com/MajMcCloud/TelegramBotFramework 2019-03-20 11:14:41 +07:00
FlorianDahn bbf90deccc - Added easy to use Proxy functionality into constructor
- improved DeviceSession
- added IsGroup property to DeviceSession
2019-03-20 11:14:38 +07:00
Florian Dahn 1d8b367bea Update README.md 2019-03-18 14:40:16 +07:00
181 changed files with 13113 additions and 1028 deletions
+2
View File
@@ -0,0 +1,2 @@
github: #[MajMcCloud]
custom: ["https://paypal.me/majmccloud", dahn.partners]
+12
View File
@@ -240,3 +240,15 @@ ModelManifest.xml
# FAKE - F# Make
.fake/
MigrationBackup
TelegramBotBase/nuget.exe
Material
TelegramBotBase/cp.ps1
TelegramBotBase/TelegramBotBase.nuspec
Program.cs
TelegramBotBase/Archive
/TelegramBotBase/TelegramBotBase.1.5.0.zip
/TelegramBotBase/TelegramBotBase.1.5.1.zip
/TelegramBotBase/TelegramBotBase.1.5.2.zip
/TelegramBotBase/cpush.ps1
/TelegramBotBase/cpack.ps1
+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>
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<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>{5817184C-0D59-4924-AC6C-6B943967811C}</ProjectGuid>
<ProjectGuid>{673A56F5-6110-4AED-A68D-562FD6ED3EA6}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TelegramBaseTest</RootNamespace>
<AssemblyName>TelegramBaseTest</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<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>
@@ -33,60 +33,37 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<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.Drawing" />
<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=14.10.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Telegram.Bot.14.10.0\lib\net45\Telegram.Bot.dll</HintPath>
<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" />
<Compile Include="Tests\ButtonTestForm.cs" />
<Compile Include="Tests\ProgressTest.cs" />
<Compile Include="Tests\Register\PerForm.cs" />
<Compile Include="Tests\Register\PerStep.cs" />
<Compile Include="Tests\Register\Start.cs" />
<Compile Include="Tests\Register\Steps\Data.cs" />
<Compile Include="Tests\Register\Steps\Step3.cs" />
<Compile Include="Tests\Register\Steps\Step2.cs" />
<Compile Include="Tests\Register\Steps\Step1.cs" />
<Compile Include="Tests\SimpleForm.cs" />
<Compile Include="Tests\Start.cs" />
<Compile Include="Tests\TestForm.cs" />
<Compile Include="Tests\TestForm2.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config">
<SubType>Designer</SubType>
</None>
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj">
<Project>{a61eda27-cfdf-4bcb-b2d6-184f94904f2d}</Project>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj">
<Project>{0bd16fb9-7ed4-4ccb-83eb-5cee538e1b6c}</Project>
<Name>TelegramBotBase</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</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();
}
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="TelegramBotBase" Version="3.1.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace SystemCommandsBot.commands
{
public class Commando
{
public int ID { get; set; }
public String Title { get; set; }
public String ShellCmd { get; set; }
public bool Enabled { get; set; } = true;
public String Action { get; set; }
public bool UseShell { get; set; } = true;
public int? MaxInstances { get; set; }
public String ProcName
{
get;set;
}
}
}
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace SystemCommandsBot.config
{
public class Config
{
public String Password { get; set; }
public String ApiKey { get; set; }
public List<commands.Commando> Commandos { get; set; }
public Config()
{
this.Commandos = new List<commands.Commando>();
}
public void loadDefaultValues()
{
this.ApiKey = "";
this.Commandos.Add(new commands.Commando() { ID = 0, Enabled = true, Title = "Test Befehl", ShellCmd = "explorer.exe", Action = "start", MaxInstances = 2 });
}
public static Config load()
{
try
{
return load(AppContext.BaseDirectory + "config\\default.cfg");
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
public static Config load(String path)
{
try
{
var cfg = Newtonsoft.Json.JsonConvert.DeserializeObject<Config>(File.ReadAllText(path)) as Config;
return cfg;
}
catch (DirectoryNotFoundException ex)
{
DirectoryInfo di = new DirectoryInfo(path);
if (!Directory.Exists(di.Parent.FullName))
{
Directory.CreateDirectory(di.Parent.FullName);
}
var cfg = new Config();
cfg.loadDefaultValues();
cfg.save(path);
return cfg;
}
catch (FileNotFoundException ex)
{
var cfg = new Config();
cfg.loadDefaultValues();
cfg.save(path);
return cfg;
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
public void save(String path)
{
try
{
File.WriteAllText(path, Newtonsoft.Json.JsonConvert.SerializeObject(this));
}
catch
{
}
}
}
}
+172
View File
@@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace SystemCommandsBot.forms
{
public class CmdForm : TelegramBotBase.Form.AutoCleanForm
{
public DateTime ExpiresAt { get; set; }
public int? MessageId { get; set; }
public override async Task Load(MessageResult message)
{
}
public override async Task Action(MessageResult message)
{
var btn = message.RawData;
int id = -1;
if (!int.TryParse(btn, out id))
{
return;
}
var cmd = Program.BotConfig.Commandos.Where(a => a.Enabled && a.ID == id).FirstOrDefault();
if (cmd == null)
{
await this.Device.Send("Cmd nicht verfügbar.");
return;
}
message.Handled = true;
switch (cmd.Action)
{
case "start":
FileInfo fi = new FileInfo(cmd.ShellCmd);
if (cmd.MaxInstances != null && cmd.MaxInstances >= 0)
{
if (Process.GetProcessesByName(cmd.ProcName).Count() >= cmd.MaxInstances)
{
await this.Device.Send("Anwendung läuft bereits.");
await message.ConfirmAction("Anwendung läuft bereits.");
return;
}
}
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = cmd.ShellCmd;
psi.WorkingDirectory = fi.DirectoryName;
psi.UseShellExecute = cmd.UseShell;
Process.Start(psi);
await this.Device.Send(fi.Name + " wurde gestarted.");
await message.ConfirmAction(fi.Name + " wurde gestarted.");
break;
case "kill":
FileInfo fi2 = new FileInfo(cmd.ShellCmd);
String pros = fi2.Name.Replace(fi2.Extension, "");
var proc = Process.GetProcessesByName(pros).ToList();
foreach (var p in proc)
{
try
{
p.Kill();
}
catch
{
}
}
await this.Device.Send(fi2.Name + " wurde beendet.");
await message.ConfirmAction(fi2.Name + " wurde beendet.");
break;
case "restart":
FileInfo fi3 = new FileInfo(cmd.ShellCmd);
String pros2 = fi3.Name.Replace(fi3.Extension, "");
var proc2 = Process.GetProcessesByName(pros2).ToList();
foreach (var p in proc2)
{
try
{
p.Kill();
}
catch
{
}
}
FileInfo fi4 = new FileInfo(cmd.ShellCmd);
ProcessStartInfo psi2 = new ProcessStartInfo();
psi2.FileName = cmd.ShellCmd;
psi2.WorkingDirectory = fi4.DirectoryName;
psi2.FileName = cmd.ShellCmd;
psi2.UseShellExecute = cmd.UseShell;
Process.Start(psi2);
await this.Device.Send(fi3.Name + " wurde neugestarted.");
await message.ConfirmAction(fi3.Name + " wurde neugestarted.");
break;
default:
await message.ConfirmAction();
break;
}
}
public override async Task Render(MessageResult message)
{
if (this.MessageId == null)
{
var buttons = Program.BotConfig.Commandos.Where(a => a.Enabled).Select(a => new ButtonBase(a.Title, a.ID.ToString()));
ButtonForm bf = new ButtonForm();
bf.AddSplitted(buttons, 1);
await this.Device.Send("Deine Optionen", bf);
return;
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
namespace SystemCommandsBot.forms
{
public class StartForm : TelegramBotBase.Form.FormBase
{
public String Password { get; set; }
public override async Task Load(MessageResult message)
{
var inp = message.MessageText;
if (Program.BotConfig.Password == inp)
{
this.Password = inp;
}
}
public override async Task Render(MessageResult message)
{
if (this.Password == null || this.Password.Trim() == "")
{
await this.Device.Send("Bitte gib dein Passwort an.");
return;
}
var cmd = new forms.CmdForm();
cmd.ExpiresAt = DateTime.Now.AddDays(14);
await this.NavigateTo(cmd);
}
}
}
+865 -262
View File
File diff suppressed because it is too large Load Diff
-73
View File
@@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase;
using TelegramBotBase.Form;
using TelegramBaseTest.Tests;
namespace TelegramBaseTest
{
class Program
{
static void Main(string[] args)
{
String APIKey = "{YOUR API KEY}";
BotBase<Start> bb = new BotBase<Start>(APIKey);
bb.SystemCalls.Add("/start");
bb.SystemCalls.Add("/form1");
bb.SystemCalls.Add("/form2");
bb.SystemCalls.Add("/params");
bb.SystemCall += async (s, en) =>
{
switch (en.Command)
{
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.LastMessage);
break;
}
};
bb.Start();
Console.WriteLine("Telegram Bot started...");
Console.WriteLine("Press q to quit application.");
Console.ReadLine();
bb.Stop();
}
}
}
-76
View File
@@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBaseTest.Tests
{
public class Start : FormBase
{
public override async Task Action(MessageResult message)
{
var call = message.GetData<CallbackData>();
await message.ConfirmAction();
if (call == null)
return;
switch(call.Value)
{
case "text":
var sf = new SimpleForm();
await this.NavigateTo(sf);
break;
case "buttons":
var bf = new ButtonTestForm();
await this.NavigateTo(bf);
break;
case "progress":
var pf = new ProgressTest();
await this.NavigateTo(pf);
break;
case "registration":
var reg = new Register.Start();
await this.NavigateTo(reg);
break;
}
}
public override async Task Render(MessageResult message)
{
ButtonForm btn = new ButtonForm();
btn.AddButtonRow(new ButtonBase("#1 Simple Text", new CallbackData("a", "text").Serialize()), new ButtonBase("#2 Button Test", new CallbackData("a", "buttons").Serialize()));
btn.AddButtonRow(new ButtonBase("#3 Progress Bar", new CallbackData("a", "progress").Serialize()));
btn.AddButtonRow(new ButtonBase("#4 Registration Example", new CallbackData("a", "registration").Serialize()));
await this.Device.Send("Choose your test:", btn);
}
}
}
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.1" targetFramework="net452" />
<package id="System.Net.Requests" version="4.3.0" targetFramework="net452" />
<package id="Telegram.Bot" version="14.10.0" targetFramework="net452" />
</packages>
@@ -3,14 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
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; }
@@ -22,17 +23,20 @@ namespace TelegramBotBase.Base
public bool Handled { get; set; } = false;
public Message OriginalMessage { get; set; }
public SystemCallEventArgs()
public BotCommandEventArgs()
{
}
public SystemCallEventArgs(String Command, List<String> Parameters, long DeviceId, DeviceSession Device)
public BotCommandEventArgs(String Command, List<String> Parameters, Message Message, long DeviceId, DeviceSession Device)
{
this.Command = Command;
this.Parameters = Parameters;
this.OriginalMessage = Message;
this.DeviceId = DeviceId;
this.Device = Device;
}
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
namespace TelegramBotBase.Args
{
/// <summary>
/// Button get clicked event
/// </summary>
public class ButtonClickedEventArgs : EventArgs
{
public ButtonBase Button { get; set; }
public int Index { get; set; }
public object Tag { get; set; }
public ButtonRow Row { get; set; }
public ButtonClickedEventArgs()
{
}
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;
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
using Telegram.Bot.Types.Enums;
using TelegramBotBase.Base;
namespace TelegramBotBase.Args
{
public class GroupChangedEventArgs : EventArgs
{
public MessageType Type { get; set; }
public MessageResult OriginalMessage { get; set; }
public GroupChangedEventArgs(MessageType type, MessageResult message)
{
this.Type = type;
this.OriginalMessage = message;
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Args
{
public class InitEventArgs : EventArgs
{
public object[] Args { get; set; }
public InitEventArgs(params object[] args)
{
this.Args = args;
}
}
}
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TelegramBotBase.Args
{
public class LoadStateEventArgs
{
public Dictionary<String,object> Values { get; set; }
public LoadStateEventArgs()
{
Values = new Dictionary<string, object>();
}
public List<String> Keys
{
get
{
return Values.Keys.ToList();
}
}
public String Get(String key)
{
return Values[key].ToString();
}
public int GetInt(String key)
{
int i = 0;
if (int.TryParse(Values[key].ToString(), out i))
return i;
return 0;
}
public double GetDouble(String key)
{
double d = 0;
if (double.TryParse(Values[key].ToString(), out d))
return d;
return 0;
}
public bool GetBool(String key)
{
bool b = false;
if (bool.TryParse(Values[key].ToString(), out b))
return b;
return false;
}
public object GetObject(String key)
{
return Values[key];
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using TelegramBotBase.Base;
namespace TelegramBotBase.Args
{
public class MemberChangeEventArgs : EventArgs
{
public List<User> Members { get; set; }
public MessageType Type { get; set; }
public MessageResult Result { get; set; }
public MemberChangeEventArgs()
{
this.Members = new List<User>();
}
public MemberChangeEventArgs(MessageType type, MessageResult result, params User[] members)
{
this.Type = type;
this.Result = result;
this.Members = members.ToList();
}
}
}
@@ -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;
}
}
}
@@ -7,7 +7,7 @@ using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
{
public class MessageIncomeResult : EventArgs
public class MessageIncomeEventArgs : EventArgs
{
public long DeviceId { get; set; }
@@ -16,7 +16,7 @@ namespace TelegramBotBase.Base
public MessageResult Message { get; set; }
public MessageIncomeResult(long DeviceId, DeviceSession Device, MessageResult message)
public MessageIncomeEventArgs(long DeviceId, DeviceSession Device, MessageResult message)
{
this.DeviceId = DeviceId;
this.Device = Device;
@@ -0,0 +1,28 @@
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 MessageReceivedEventArgs
{
public int MessageId
{
get
{
return this.Message.MessageId;
}
}
public Message Message { get; set; }
public MessageReceivedEventArgs(Message m)
{
this.Message = m;
}
}
}
@@ -0,0 +1,36 @@
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 MessageSentEventArgs
{
public int MessageId
{
get
{
return this.Message.MessageId;
}
}
public Message Message { get; set; }
/// <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;
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Args
{
public class SaveStateEventArgs
{
public Dictionary<String, object> Values { get; set; }
public SaveStateEventArgs()
{
Values = new Dictionary<string, object>();
}
public void Set(String key, String value)
{
Values[key] = value;
}
public void SetInt(String key, int value)
{
Values[key] = value;
}
public void SetBool(String key, bool value)
{
Values[key] = value;
}
public void SetDouble(String key, double value)
{
Values[key] = value;
}
public void SetObject(String key, object value)
{
Values[key] = value;
}
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TelegramBotBase.Base;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Args
{
public class SaveStatesEventArgs
{
public StateContainer States { get; set; }
public SaveStatesEventArgs(StateContainer states)
{
this.States = states;
}
}
}
@@ -7,13 +7,13 @@ using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
{
public class SessionBeginResult : EventArgs
public class SessionBeginEventArgs : EventArgs
{
public long DeviceId { get; set; }
public DeviceSession Device { get; set; }
public SessionBeginResult(long DeviceId, DeviceSession Device)
public SessionBeginEventArgs(long DeviceId, DeviceSession Device)
{
this.DeviceId = DeviceId;
this.Device = Device;
@@ -5,7 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
namespace TelegramBotBase.Args
{
public class SystemExceptionEventArgs : EventArgs
{
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
using Telegram.Bot.Types;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
namespace TelegramBotBase.Args
{
public class UnhandledCallEventArgs : EventArgs
{
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Attributes
{
/// <summary>
/// Declares that this class should not be getting serialized
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreState : Attribute
{
}
}
+15
View File
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Attributes
{
/// <summary>
/// Declares that the field or property should be save and recovered an restart.
/// </summary>
public class SaveState : Attribute
{
public String Key { get; set; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Base
{
public static class Async
{
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs;
public static IEnumerable<AsyncEventHandler<TEventArgs>> GetHandlers<TEventArgs>(
this AsyncEventHandler<TEventArgs> handler)
where TEventArgs : EventArgs
=> handler.GetInvocationList().Cast<AsyncEventHandler<TEventArgs>>();
public static Task InvokeAllAsync<TEventArgs>(this AsyncEventHandler<TEventArgs> handler, object sender, TEventArgs e)
where TEventArgs : EventArgs
=> Task.WhenAll(
handler.GetHandlers()
.Select(handleAsync => handleAsync(sender, e)));
}
}
@@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Form;
namespace TelegramBotBase.Base
{
/// <summary>
/// Button get clicked event
/// </summary>
public class ButtonClickedEventArgs : EventArgs
{
public ButtonBase Button { get; set; }
public ButtonClickedEventArgs()
{
}
public ButtonClickedEventArgs(ButtonBase button)
{
this.Button = button;
}
}
}
+55 -2
View File
@@ -13,14 +13,67 @@ namespace TelegramBotBase.Base
{
public Sessions.DeviceSession Device { get; set; }
public virtual async Task Render()
public int ID { get; set; }
public String ControlID
{
get
{
return "#c" + this.ID.ToString();
}
}
/// <summary>
/// Defines if the control should be rendered and invoked with actions
/// </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 Cleanup()
public virtual async Task Load(MessageResult result)
{
}
public virtual async Task Action(MessageResult result, String value = null)
{
}
public virtual async Task Render(MessageResult result)
{
}
public virtual async Task Hidden(bool FormClose)
{
}
/// <summary>
/// Will be called on a cleanup.
/// </summary>
/// <returns></returns>
public virtual async Task Cleanup()
{
}
}
+217
View File
@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegram.Bot.Types.InputFiles;
namespace TelegramBotBase.Base
{
/// <summary>
/// Returns a class to manage attachments within messages.
/// </summary>
public class DataResult : ResultBase
{
public Telegram.Bot.Args.MessageEventArgs RawMessageData { get; set; }
public Contact Contact
{
get
{
return this.Message.Contact;
}
}
public Location Location
{
get
{
return this.Message.Location;
}
}
public Document Document
{
get
{
return this.Message.Document;
}
}
public Audio Audio
{
get
{
return this.Message.Audio;
}
}
public Video Video
{
get
{
return this.Message.Video;
}
}
public PhotoSize[] Photos
{
get
{
return this.Message.Photo;
}
}
public Telegram.Bot.Types.Enums.MessageType Type
{
get
{
return this.RawMessageData?.Message?.Type ?? Telegram.Bot.Types.Enums.MessageType.Unknown;
}
}
/// <summary>
/// Returns the FileId of the first reachable element.
/// </summary>
public String FileId
{
get
{
return (this.Document?.FileId ??
this.Audio?.FileId ??
this.Video?.FileId ??
this.Photos.FirstOrDefault()?.FileId);
}
}
public DataResult(Telegram.Bot.Args.MessageEventArgs rawdata)
{
this.RawMessageData = rawdata;
this.Message = rawdata.Message;
}
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 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);
FileStream fs = new FileStream(path, FileMode.Create);
await this.Client.TelegramClient.DownloadFileAsync(file.FilePath, fs);
fs.Close();
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 file = await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(this.Video.FileId, encryptedContent);
return new InputOnlineFile(encryptedContent, "");
}
public async Task DownloadVideo(String path)
{
var file = await this.Client.TelegramClient.GetFileAsync(this.Video.FileId);
FileStream fs = new FileStream(path, FileMode.Create);
await this.Client.TelegramClient.DownloadFileAsync(file.FilePath, fs);
fs.Close();
fs.Dispose();
}
public async Task<InputOnlineFile> DownloadAudio()
{
var encryptedContent = new System.IO.MemoryStream(this.Audio.FileSize);
var file = await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(this.Audio.FileId, encryptedContent);
return new InputOnlineFile(encryptedContent, "");
}
public async Task DownloadAudio(String path)
{
var file = await this.Client.TelegramClient.GetFileAsync(this.Audio.FileId);
FileStream fs = new FileStream(path, FileMode.Create);
await this.Client.TelegramClient.DownloadFileAsync(file.FilePath, fs);
fs.Close();
fs.Dispose();
}
public async Task<InputOnlineFile> DownloadPhoto(int index)
{
var photo = this.Photos[index];
var encryptedContent = new System.IO.MemoryStream(photo.FileSize);
var file = await this.Client.TelegramClient.GetInfoAndDownloadFileAsync(photo.FileId, encryptedContent);
return new InputOnlineFile(encryptedContent, "");
}
public async Task DownloadPhoto(int index, String path)
{
var photo = this.Photos[index];
var file = await this.Client.TelegramClient.GetFileAsync(photo.FileId);
FileStream fs = new FileStream(path, FileMode.Create);
await this.Client.TelegramClient.DownloadFileAsync(file.FilePath, fs);
fs.Close();
fs.Dispose();
}
}
}
+356 -17
View File
@@ -1,10 +1,14 @@
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;
using TelegramBotBase.Form.Navigation;
using TelegramBotBase.Sessions;
using static TelegramBotBase.Base.Async;
namespace TelegramBotBase.Form
{
@@ -13,47 +17,159 @@ namespace TelegramBotBase.Form
/// </summary>
public class FormBase : IDisposable
{
public NavigationController NavigationController { get; set; }
public DeviceSession Device { get; set; }
public MessageClient Client { get; set; }
public bool CustomEventManagement { get; set; } = false;
/// <summary>
/// contains if the form has been switched (navigated)
/// has this formular already been disposed ?
/// </summary>
public bool FormSwitched { get; set; } = false;
public bool IsDisposed { get; set; } = false;
public List<ControlBase> Controls { get; set; }
public EventHandlerList Events = new EventHandlerList();
private static object __evInit = new object();
private static object __evOpened = new object();
private static object __evClosed = new object();
public FormBase()
{
this.Controls = new List<Base.ControlBase>();
}
public FormBase(MessageClient Client): this()
public FormBase(MessageClient Client) : this()
{
this.Client = Client;
}
public async Task OnInit(InitEventArgs e)
{
if (this.Events[__evInit] == null)
return;
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);
}
}
///// <summary>
///// Will get called at the initialization (once per context)
///// </summary>
public event AsyncEventHandler<InitEventArgs> Init
{
add
{
this.Events.AddHandler(__evInit, value);
}
remove
{
this.Events.RemoveHandler(__evInit, value);
}
}
public async Task OnOpened(EventArgs e)
{
if (this.Events[__evOpened] == null)
return;
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);
}
}
/// <summary>
/// Will get called at the initialization (once per context)
/// Gets invoked if gets navigated to this form
/// </summary>
public virtual async Task Init(params object[] args)
/// <returns></returns>
public event AsyncEventHandler<EventArgs> Opened
{
add
{
this.Events.AddHandler(__evOpened, value);
}
remove
{
this.Events.RemoveHandler(__evOpened, value);
}
}
public virtual async Task Opened()
public async Task OnClosed(EventArgs e)
{
if (this.Events[__evClosed] == null)
return;
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);
}
}
/// <summary>
/// Form has been closed (left)
/// </summary>
/// <returns></returns>
public event AsyncEventHandler<EventArgs> Closed
{
add
{
this.Events.AddHandler(__evClosed, value);
}
remove
{
this.Events.RemoveHandler(__evClosed, value);
}
}
/// <summary>
/// Get invoked when a modal child from has been closed.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public virtual async Task ReturnFromModal(ModalDialog modal)
{
}
public virtual async Task Closed()
/// <summary>
/// Pre to form close, cleanup all controls
/// </summary>
/// <returns></returns>
public async Task CloseControls()
{
foreach (var b in this.Controls)
{
await b.Cleanup();
b.Cleanup().Wait();
}
}
@@ -62,44 +178,267 @@ namespace TelegramBotBase.Form
}
/// <summary>
/// Gets invoked if a message was sent or an action triggered
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
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)
{
var c = this.Controls.FirstOrDefault(a => a.ControlID == message.RawData.Split('_')[0]);
if (c != null)
{
await c.Load(message);
return;
}
}
foreach (var b in this.Controls)
{
if (!b.Enabled)
continue;
await b.Load(message);
}
}
/// <summary>
/// Gets invoked if the form gets loaded and on every message belongs to this context
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public virtual async Task Load(MessageResult message)
{
}
/// <summary>
/// Gets invoked, when a messages has been edited.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public virtual async Task Edited(MessageResult message)
{
}
/// <summary>
/// Gets invoked if the user clicked a button.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
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"))
{
var c = this.Controls.FirstOrDefault(a => a.ControlID == message.RawData.Split('_')[0]);
if (c != null)
{
await c.Action(message, message.RawData.Split('_')[1]);
return;
}
}
foreach (var b in this.Controls)
{
if (!b.Enabled)
continue;
await b.Action(message);
if (message.Handled)
return;
}
}
/// <summary>
/// Gets invoked if the user has clicked a button.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public virtual async Task Action(MessageResult message)
{
}
/// <summary>
/// Gets invoked if the user has sent some media (Photo, Audio, Video, Contact, Location, Document)
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public virtual async Task SentData(DataResult message)
{
}
/// <summary>
/// Gets invoked at the end of the cycle to "Render" text, images, buttons, etc...
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public virtual async Task RenderControls(MessageResult message)
{
foreach (var b in this.Controls)
{
if (!b.Enabled)
continue;
await b.Render(message);
}
}
/// <summary>
/// Gets invoked at the end of the cycle to "Render" text, images, buttons, etc...
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public virtual async Task Render(MessageResult message)
{
}
/// <summary>
/// Navigates to a new form
/// </summary>
/// <param name="newForm"></param>
/// <returns></returns>
public async Task NavigateTo(FormBase newForm, params object[] args)
public virtual async Task NavigateTo(FormBase newForm, params object[] args)
{
DeviceSession ds = this.Device;
if (ds == null)
return;
this.FormSwitched = true;
ds.FormSwitched = true;
ds.PreviousForm = ds.ActiveForm;
ds.ActiveForm = newForm;
newForm.Client = this.Client;
newForm.Device = ds;
await newForm.Init(args);
//Notify prior to close
foreach (var b in this.Controls)
{
if (!b.Enabled)
continue;
await this.Closed();
await b.Hidden(true);
}
await newForm.Opened();
this.CloseControls().Wait();
await this.OnClosed(new EventArgs());
await newForm.OnInit(new InitEventArgs(args));
await newForm.OnOpened(new EventArgs());
}
/// <summary>
/// Opens this form modal, but don't closes the original ones
/// </summary>
/// <param name="newForm"></param>
/// <returns></returns>
public virtual async Task OpenModal(ModalDialog newForm, params object[] args)
{
DeviceSession ds = this.Device;
if (ds == null)
return;
var parentForm = this;
ds.FormSwitched = true;
ds.PreviousForm = ds.ActiveForm;
ds.ActiveForm = newForm;
newForm.Client = parentForm.Client;
newForm.Device = ds;
newForm.ParentForm = parentForm;
newForm.Closed += async (s, en) =>
{
await CloseModal(newForm, parentForm);
};
foreach (var b in this.Controls)
{
if (!b.Enabled)
continue;
await b.Hidden(false);
}
await newForm.OnInit(new InitEventArgs(args));
await newForm.OnOpened(new EventArgs());
}
public async Task CloseModal(ModalDialog modalForm, FormBase oldForm)
{
DeviceSession ds = this.Device;
if (ds == null)
return;
if (modalForm == null)
throw new Exception("No modal form");
ds.FormSwitched = true;
ds.PreviousForm = ds.ActiveForm;
ds.ActiveForm = oldForm;
}
/// <summary>
/// Adds a control to the formular and sets its ID and Device.
/// </summary>
/// <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>
/// Removes control from the formular and runs a cleanup on it.
/// </summary>
/// <param name="control"></param>
public void RemoveControl(ControlBase control)
{
if (!this.Controls.Contains(control))
return;
control.Cleanup().Wait();
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>
@@ -109,7 +448,7 @@ namespace TelegramBotBase.Form
{
this.Client = null;
this.Device = null;
this.FormSwitched = false;
this.IsDisposed = true;
}
}
}
+101 -3
View File
@@ -2,8 +2,11 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
namespace TelegramBotBase.Base
{
@@ -13,6 +16,7 @@ namespace TelegramBotBase.Base
public class MessageClient
{
public String APIKey { get; set; }
public Telegram.Bot.TelegramBotClient TelegramClient { get; set; }
@@ -21,6 +25,8 @@ namespace TelegramBotBase.Base
private static object __evOnMessage = new object();
private static object __evOnMessageEdit = new object();
private static object __evCallbackQuery = new object();
@@ -32,6 +38,43 @@ namespace TelegramBotBase.Base
Prepare();
}
public MessageClient(String APIKey, HttpClient proxy)
{
this.APIKey = APIKey;
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, proxy);
Prepare();
}
public MessageClient(String APIKey, Uri proxyUrl)
{
this.APIKey = APIKey;
var proxy = new WebProxy(proxyUrl);
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, proxy);
Prepare();
}
/// <summary>
/// Initializes the client with a proxy
/// </summary>
/// <param name="APIKey"></param>
/// <param name="proxyHost">i.e. 127.0.0.1</param>
/// <param name="proxyPort">i.e. 10000</param>
public MessageClient(String APIKey, String proxyHost, int proxyPort)
{
this.APIKey = APIKey;
var proxy = new WebProxy(proxyHost, proxyPort);
this.TelegramClient = new Telegram.Bot.TelegramBotClient(APIKey, proxy);
Prepare();
}
public MessageClient(String APIKey, Telegram.Bot.TelegramBotClient Client)
{
this.APIKey = APIKey;
@@ -45,13 +88,11 @@ 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)
{
//Skip empty messages by default
@@ -70,6 +111,25 @@ namespace TelegramBotBase.Base
}
}
private async void TelegramClient_OnMessageEdited(object sender, Telegram.Bot.Args.MessageEventArgs e)
{
//Skip empty messages by default
if (e.Message == null)
return;
try
{
var mr = new MessageResult(e);
mr.Client = this;
OnMessageEdit(mr);
}
catch
{
}
}
private async void TelegramClient_OnCallbackQuery(object sender, Telegram.Bot.Args.CallbackQueryEventArgs e)
{
try
@@ -84,6 +144,27 @@ namespace TelegramBotBase.Base
}
}
/// <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"
@@ -104,6 +185,23 @@ namespace TelegramBotBase.Base
(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
+41 -11
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Base
{
@@ -19,10 +20,19 @@ namespace TelegramBotBase.Base
{
get
{
return this.RawMessageData?.Message?.Chat.Id ?? this.RawCallbackData?.CallbackQuery.Message?.Chat.Id ?? 0;
return this.RawMessageData?.Message?.Chat.Id
?? this.RawCallbackData?.CallbackQuery.Message?.Chat.Id
?? Device?.DeviceId
?? 0;
}
}
public DeviceSession Device
{
get;
set;
}
/// <summary>
/// The message id
/// </summary>
@@ -30,7 +40,9 @@ namespace TelegramBotBase.Base
{
get
{
return this.Message?.MessageId ?? this.RawCallbackData?.CallbackQuery?.Message?.MessageId ?? 0;
return this.Message?.MessageId
?? this.RawCallbackData?.CallbackQuery?.Message?.MessageId
?? 0;
}
}
@@ -50,6 +62,15 @@ namespace TelegramBotBase.Base
}
}
public Telegram.Bot.Types.Enums.MessageType MessageType
{
get
{
return this.RawMessageData?.Message?.Type
?? Telegram.Bot.Types.Enums.MessageType.Unknown;
}
}
/// <summary>
/// Is this an action ? (i.e. button click)
/// </summary>
@@ -62,9 +83,9 @@ namespace TelegramBotBase.Base
}
/// <summary>
/// Is this a system call ? Starts with a slash and command
/// Is this a command ? Starts with a slash '/' and a command
/// </summary>
public bool IsSystemCall
public bool IsBotCommand
{
get
{
@@ -75,11 +96,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
@@ -88,19 +109,24 @@ 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];
}
}
/// <summary>
/// Returns if this message will be used on the first form or not.
/// </summary>
public bool IsFirstHandler { get; set; } = true;
public bool Handled { get; set; } = false;
public String RawData
@@ -134,11 +160,11 @@ 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);
await this.Client.TelegramClient.AnswerCallbackQueryAsync(this.RawCallbackData.CallbackQuery.Id, message, showAlert, urlToOpen);
}
catch
{
@@ -158,6 +184,10 @@ namespace TelegramBotBase.Base
}
}
internal MessageResult()
{
}
public MessageResult(Telegram.Bot.Args.MessageEventArgs rawdata)
{
@@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
namespace TelegramBotBase.Base
{
public class MessageSentEventArgs
{
public int MessageId { get; set; }
public Message Message { get; set; }
public MessageSentEventArgs()
{
}
public MessageSentEventArgs(int MessageId, Message message)
{
this.MessageId = MessageId;
this.Message = message;
}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace TelegramBotBase.Base
{
public class ResultBase
public class ResultBase : EventArgs
{
public MessageClient Client { get; set; }
+34
View File
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TelegramBotBase.Base
{
public partial class StateContainer
{
public List<StateEntry> States { get; set; }
public List<long> ChatIds
{
get
{
return States.Where(a => a.DeviceId > 0).Select(a => a.DeviceId).ToList();
}
}
public List<long> GroupIds
{
get
{
return States.Where(a => a.DeviceId < 0).Select(a => a.DeviceId).ToList();
}
}
public StateContainer()
{
this.States = new List<StateEntry>();
}
}
}
+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Text;
namespace TelegramBotBase.Base
{
[DebuggerDisplay("Device: {DeviceId}, {FormUri}")]
public class StateEntry
{
/// <summary>
/// Contains the DeviceId of the entry.
/// </summary>
public long DeviceId { get; set; }
/// <summary>
/// Contains the Username (on privat chats) or Group title on groups/channels.
/// </summary>
public String ChatTitle { get; set; }
/// <summary>
/// Contains additional values to save.
/// </summary>
public Dictionary<String, object> Values { get; set; }
/// <summary>
/// Contains the full qualified namespace of the form to used for reload it via reflection.
/// </summary>
public String FormUri {get;set;}
/// <summary>
/// Contains the assembly, where to find that form.
/// </summary>
public String QualifiedName { get; set; }
public StateEntry()
{
this.Values = new Dictionary<string, object>();
}
}
}
+490 -119
View File
@@ -4,8 +4,14 @@ using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Types;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
using TelegramBotBase.Enums;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace TelegramBotBase
@@ -27,12 +33,15 @@ namespace TelegramBotBase
/// <summary>
/// List of all running/active sessions
/// </summary>
public SessionBase Sessions { get; set; }
public SessionBase<T> Sessions { get; set; }
/// <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();
@@ -42,39 +51,104 @@ namespace TelegramBotBase
private static object __evSystemCall = new object();
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)
/// Enable the SessionState (you need to implement on call forms the IStateForm interface)
/// </summary>
public bool SkipAllMessages { get; set; } = false;
public IStateMachine StateMachine { get; set; }
/// <summary>
/// Loggs all messages and sent them to the event handler
/// </summary>
public bool LogAllMessages { get; set; } = false;
/// <summary>
/// How often could a form navigate to another (within one user action/call/message)
/// </summary>
private const int NavigationMaximum = 10;
public Dictionary<eSettings, uint> SystemSettings { get; private set; }
private BotBase()
{
this.SystemSettings = new Dictionary<eSettings, uint>();
SetSetting(eSettings.MaxNumberOfRetries, 5);
SetSetting(eSettings.NavigationMaximum, 10);
SetSetting(eSettings.LogAllMessages, false);
SetSetting(eSettings.SkipAllMessages, false);
SetSetting(eSettings.SaveSessionsOnConsoleExit, false);
this.BotCommands = new List<BotCommand>();
this.Sessions = new SessionBase<T>();
this.Sessions.BotBase = this;
}
/// <summary>
/// Simple start of your Bot with the APIKey
/// </summary>
/// <param name="apiKey"></param>
public BotBase(String apiKey)
public BotBase(String apiKey, bool initClient = true) : this()
{
this.APIKey = apiKey;
if (!initClient)
return;
this.Client = new Base.MessageClient(this.APIKey);
this.Client.TelegramClient.Timeout = new TimeSpan(0, 1, 0);
this.SystemCalls = new List<string>();
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, System.Net.Http.HttpClient proxy) : this(apiKey, false)
{
this.Client = new Base.MessageClient(this.APIKey, proxy);
this.Sessions.Client = this.Client;
}
/// <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, false)
{
this.Client = new Base.MessageClient(this.APIKey, client);
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, false)
{
var url = new Uri(proxyBaseAddress);
this.Client = new Base.MessageClient(this.APIKey, url);
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, false)
{
this.Client = new Base.MessageClient(this.APIKey, proxyHost, proxyPort);
this.Sessions = new SessionBase();
this.Sessions.Client = this.Client;
}
@@ -87,12 +161,29 @@ namespace TelegramBotBase
return;
this.Client.Message += Client_Message;
this.Client.MessageEdit += Client_MessageEdit;
this.Client.Action += Client_Action;
if (this.StateMachine != null)
{
this.Sessions.LoadSessionStates(this.StateMachine);
}
//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.TelegramClient.StartReceiving();
}
/// <summary>
/// Stop your Bot
/// </summary>
@@ -101,7 +192,12 @@ namespace TelegramBotBase
if (this.Client == null)
return;
this.Client.Message -= Client_Message;
this.Client.Action -= Client_Action;
this.Client.TelegramClient.StopReceiving();
this.Sessions.SaveSessionStates();
}
/// <summary>
@@ -120,21 +216,24 @@ namespace TelegramBotBase
}
}
private void Client_Message(object sender, MessageResult e)
private async void Client_Message(object sender, MessageResult e)
{
if (this.SkipAllMessages)
if (this.GetSetting(eSettings.SkipAllMessages, false))
return;
try
{
if (LogAllMessages)
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
e.Device = ds;
if (this.GetSetting(eSettings.LogAllMessages, false))
{
DeviceSession ds2 = this.Sessions.GetSession(e.DeviceId);
OnMessage(new MessageIncomeResult(e.DeviceId, ds2, e));
OnMessage(new MessageIncomeEventArgs(e.DeviceId, ds, e));
}
ds?.OnMessageReceived(new MessageReceivedEventArgs(e.Message));
Client_TryMessage(sender, e);
await Client_Loop(sender, e);
}
catch (Telegram.Bot.Exceptions.ApiRequestException ex)
{
@@ -147,28 +246,102 @@ namespace TelegramBotBase
}
}
private async void Client_TryMessage(object sender, MessageResult e)
//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.LastAction = DateTime.Now;
// ds.LastMessage = e.Message;
// //Is this a bot command ?
// if (e.IsBotCommand && this.BotCommands.Count(a => "/" + a.Command == e.BotCommand) > 0)
// {
// var sce = new BotCommandEventArgs(e.BotCommand, e.BotCommandParameters, e.Message, ds.DeviceId, ds);
// await OnBotCommand(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 < this.GetSetting(eSettings.NavigationMaximum, 10));
//}
private async Task Client_Loop(object sender, MessageResult e)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
DeviceSession ds = e.Device;
if (ds == null)
{
ds = await this.Sessions.StartSession<T>(e.DeviceId);
e.Device = ds;
ds.LastMessage = e.Message;
ds.LastMessage = e.MessageId;
OnSessionBegins(new SessionBeginResult(e.DeviceId, ds));
OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds));
}
ds.LastAction = DateTime.Now;
ds.LastMessage = e.MessageId;
ds.LastMessage = e.Message;
//Is this a systemcall ?
if (e.IsSystemCall && this.SystemCalls.Contains(e.SystemCommand))
//Is this a bot command ?
if (e.IsBotCommand && this.BotCommands.Count(a => "/" + a.Command == e.BotCommand) > 0)
{
var sce = new SystemCallEventArgs(e.SystemCommand, e.SystemCallParameters, ds.DeviceId, ds);
OnSystemCall(sce);
var sce = new BotCommandEventArgs(e.BotCommand, e.BotCommandParameters, e.Message, ds.DeviceId, ds);
await OnBotCommand(sce);
return;
if (sce.Handled)
return;
}
FormBase activeForm = null;
@@ -180,75 +353,34 @@ namespace TelegramBotBase
{
i++;
activeForm = ds.ActiveForm;
//Reset navigation
ds.FormSwitched = false;
//If the form manages the events by itselfs, skip here
if (activeForm.CustomEventManagement)
return;
activeForm = ds.ActiveForm;
//Pre Loading Event
await activeForm.PreLoad(e);
//Loading Event
await activeForm.Load(e);
//Render Event
if (!activeForm.FormSwitched)
await activeForm.Render(e);
} while (activeForm.FormSwitched && i < NavigationMaximum);
}
private void Client_Action(object sender, MessageResult e)
{
try
{
Client_TryAction(sender, e);
}
catch (Exception ex)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, ds?.DeviceId ?? -1, ds, ex));
}
}
private async void Client_TryAction(object sender, MessageResult e)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
if (ds == null)
{
ds = await this.Sessions.StartSession<T>(e.DeviceId);
}
ds.LastAction = DateTime.Now;
ds.LastMessage = e.MessageId;
FormBase activeForm = null;
int i = 0;
//Should formulars get navigated (allow maximum of 10, to dont get loops)
do
{
i++;
activeForm = ds.ActiveForm;
//If the form manages the events by itselfs, skip here
if (activeForm.CustomEventManagement)
return;
//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.MessageType == Telegram.Bot.Types.Enums.MessageType.Contact | e.MessageType == Telegram.Bot.Types.Enums.MessageType.Document | e.MessageType == Telegram.Bot.Types.Enums.MessageType.Location |
e.MessageType == Telegram.Bot.Types.Enums.MessageType.Photo | e.MessageType == Telegram.Bot.Types.Enums.MessageType.Video | e.MessageType == Telegram.Bot.Types.Enums.MessageType.Audio)
{
await activeForm.SentData(new DataResult(e));
}
//Action Event
if (!activeForm.FormSwitched)
if (!ds.FormSwitched && e.IsAction)
{
//Send Action event to controls
await activeForm.ActionControls(e);
//Send Action event to form itself
await activeForm.Action(e);
if (!e.Handled)
@@ -258,11 +390,8 @@ namespace TelegramBotBase
if (uhc.Handled)
{
if (activeForm.FormSwitched)
{
continue;
}
else
e.Handled = true;
if (!ds.FormSwitched)
{
break;
}
@@ -271,19 +400,266 @@ namespace TelegramBotBase
}
//Render Event
if (!activeForm.FormSwitched)
await activeForm.Render(e);
if (!ds.FormSwitched)
{
//Render Event
await activeForm.RenderControls(e);
await activeForm.Render(e);
}
e.IsFirstHandler = false;
} while (ds.FormSwitched && i < this.GetSetting(eSettings.NavigationMaximum, 10));
} while (activeForm.FormSwitched && i < NavigationMaximum);
}
/// <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)
{
var mr = new MessageResult();
await InvokeMessageLoop(DeviceId, mr);
}
/// <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(DeviceId);
e.Device = ds;
await Client_Loop(this, e);
}
catch (Exception ex)
{
DeviceSession ds = this.Sessions.GetSession(DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, DeviceId, ds, ex));
}
}
private async void Client_MessageEdit(object sender, MessageResult e)
{
if (this.GetSetting(eSettings.SkipAllMessages, false))
return;
try
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
e.Device = ds;
if (this.GetSetting(eSettings.LogAllMessages, false))
{
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.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_Loop(sender, e);
}
}
private async void Client_Action(object sender, MessageResult e)
{
try
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
e.Device = ds;
if (this.GetSetting(eSettings.LogAllMessages, false))
{
OnMessage(new MessageIncomeEventArgs(e.DeviceId, ds, e));
}
await Client_Loop(sender, e);
}
catch (Exception ex)
{
DeviceSession ds = this.Sessions.GetSession(e.DeviceId);
OnException(new SystemExceptionEventArgs(e.Message.Text, ds?.DeviceId ?? -1, ds, ex));
}
}
//private async void Client_TryAction(object sender, MessageResult e)
//{
// DeviceSession ds = e.Device;
// if (ds == null)
// {
// ds = await this.Sessions.StartSession<T>(e.DeviceId);
// e.Device = ds;
// }
// 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)
// {
// e.Handled = true;
// if (!ds.FormSwitched)
// {
// break;
// }
// }
// }
// }
// //Render Event
// if (!ds.FormSwitched)
// {
// await activeForm.RenderControls(e);
// await activeForm.Render(e);
// }
// e.IsFirstHandler = false;
// } while (ds.FormSwitched && i < this.GetSetting(eSettings.NavigationMaximum, 10));
//}
/// <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>
public event EventHandler<SessionBeginResult> SessionBegins
public event EventHandler<SessionBeginEventArgs> SessionBegins
{
add
{
@@ -295,16 +671,16 @@ namespace TelegramBotBase
}
}
public void OnSessionBegins(SessionBeginResult e)
public void OnSessionBegins(SessionBeginEventArgs e)
{
(this.__Events[__evSessionBegins] as EventHandler<SessionBeginResult>)?.Invoke(this, e);
(this.__Events[__evSessionBegins] as EventHandler<SessionBeginEventArgs>)?.Invoke(this, e);
}
/// <summary>
/// Will be called on incomming message
/// </summary>
public event EventHandler<MessageIncomeResult> Message
public event EventHandler<MessageIncomeEventArgs> Message
{
add
{
@@ -316,30 +692,22 @@ namespace TelegramBotBase
}
}
public void OnMessage(MessageIncomeResult e)
public void OnMessage(MessageIncomeEventArgs e)
{
(this.__Events[__evMessage] as EventHandler<MessageIncomeResult>)?.Invoke(this, e);
(this.__Events[__evMessage] as EventHandler<MessageIncomeEventArgs>)?.Invoke(this, e);
}
/// <summary>
/// Will be called if a system call gets raised
/// Will be called if a bot command gets raised
/// </summary>
public event EventHandler<SystemCallEventArgs> SystemCall
{
add
{
this.__Events.AddHandler(__evSystemCall, value);
}
remove
{
this.__Events.RemoveHandler(__evSystemCall, value);
}
}
public event BotCommandEventHandler BotCommand;
public void OnSystemCall(SystemCallEventArgs e)
public async Task OnBotCommand(BotCommandEventArgs e)
{
(this.__Events[__evSystemCall] as EventHandler<SystemCallEventArgs>)?.Invoke(this, e);
if (this.BotCommand != null)
await BotCommand(this, e);
}
/// <summary>
@@ -383,5 +751,8 @@ namespace TelegramBotBase
(this.__Events[__evUnhandledCall] as EventHandler<UnhandledCallEventArgs>)?.Invoke(this, e);
}
#endregion
}
}
+40
View File
@@ -0,0 +1,40 @@
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 AddStartCommand(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 AddHelpCommand(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 AddSettingsCommand(this List<BotCommand> cmds, String description)
{
cmds.Add(new BotCommand() { Command = "settings", Description = description });
}
}
}
+27
View File
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Constants
{
public static class Telegram
{
/// <summary>
/// The maximum length of message text before the API throws an exception. (We will catch it before)
/// </summary>
public const int MaxMessageLength = 4096;
public const int MaxInlineKeyBoardRows = 13;
public const int MaxInlineKeyBoardCols = 8;
public const int MaxReplyKeyboardRows = 25;
public const int MaxReplyKeyboardCols = 12;
public const int MessageDeletionsPerSecond = 30;
}
}
@@ -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.Default;
/// <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.Default;
/// <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
@@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
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.Inline
{
public class CalendarPicker : Base.ControlBase
{
public DateTime SelectedDate { get; set; }
public DateTime VisibleMonth { get; set; }
public DayOfWeek FirstDayOfWeek { get; set; }
public CultureInfo Culture { get; set; }
private int? MessageId { get; set; }
public String Title { get; set; } = Localizations.Default.Language["CalendarPicker_Title"];
public eMonthPickerMode PickerMode { get; set; }
public bool EnableDayView { get; set; } = true;
public bool EnableMonthView { get; set; } = true;
public bool EnableYearView { get; set; } = true;
public CalendarPicker(CultureInfo culture)
{
this.SelectedDate = DateTime.Today;
this.VisibleMonth = DateTime.Today;
this.FirstDayOfWeek = DayOfWeek.Monday;
this.Culture = culture;
this.PickerMode = eMonthPickerMode.day;
}
public CalendarPicker() : this(new CultureInfo("en-en")) { }
public override async Task Action(MessageResult result, String value = null)
{
await result.ConfirmAction();
switch (result.RawData)
{
case "$next$":
switch (this.PickerMode)
{
case eMonthPickerMode.day:
this.VisibleMonth = this.VisibleMonth.AddMonths(1);
break;
case eMonthPickerMode.month:
this.VisibleMonth = this.VisibleMonth.AddYears(1);
break;
case eMonthPickerMode.year:
this.VisibleMonth = this.VisibleMonth.AddYears(10);
break;
}
break;
case "$prev$":
switch (this.PickerMode)
{
case eMonthPickerMode.day:
this.VisibleMonth = this.VisibleMonth.AddMonths(-1);
break;
case eMonthPickerMode.month:
this.VisibleMonth = this.VisibleMonth.AddYears(-1);
break;
case eMonthPickerMode.year:
this.VisibleMonth = this.VisibleMonth.AddYears(-10);
break;
}
break;
case "$monthtitle$":
if (this.EnableMonthView)
{
this.PickerMode = eMonthPickerMode.month;
}
break;
case "$yeartitle$":
if (this.EnableYearView)
{
this.PickerMode = eMonthPickerMode.year;
}
break;
case "$yearstitle$":
if (this.EnableMonthView)
{
this.PickerMode = eMonthPickerMode.month;
}
this.VisibleMonth = this.SelectedDate;
break;
default:
int day = 0;
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-") && TryParseMonth(result.RawData.Split('-')[1], out month))
{
this.SelectedDate = new DateTime(this.VisibleMonth.Year, month, 1);
this.VisibleMonth = this.SelectedDate;
if (this.EnableDayView)
{
this.PickerMode = eMonthPickerMode.day;
}
}
int year = 0;
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;
if (this.EnableMonthView)
{
this.PickerMode = eMonthPickerMode.month;
}
}
break;
}
}
public override async Task Render(MessageResult result)
{
ButtonForm bf = new ButtonForm();
switch (this.PickerMode)
{
case eMonthPickerMode.day:
var month = this.VisibleMonth;
string[] dayNamesNormal = this.Culture.DateTimeFormat.ShortestDayNames;
string[] dayNamesShifted = Shift(dayNamesNormal, (int)this.FirstDayOfWeek);
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());
//First Day of month
var firstDay = new DateTime(month.Year, month.Month, 1);
//Last Day of month
var lastDay = firstDay.LastDayOfMonth();
//Start of Week where first day of month is (left border)
var start = firstDay.StartOfWeek(this.FirstDayOfWeek);
//End of week where last day of month is (right border)
var end = lastDay.EndOfWeek(this.FirstDayOfWeek);
for (int i = 0; i <= ((end - start).Days / 7); i++)
{
var lst = new List<ButtonBase>();
for (int id = 0; id < 7; id++)
{
var d = start.AddDays((i * 7) + id);
if (d < firstDay | d > lastDay)
{
lst.Add(new ButtonBase("-", "m-" + d.Day.ToString()));
continue;
}
var day = d.Day.ToString();
if (d == DateTime.Today)
{
day = "(" + day + ")";
}
lst.Add(new ButtonBase((this.SelectedDate == d ? "[" + day + "]" : day), "d-" + d.Day.ToString()));
}
bf.AddButtonRow(lst);
}
break;
case eMonthPickerMode.month:
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;
var buttons = months.Select((a, b) => new ButtonBase((b == this.SelectedDate.Month - 1 && this.SelectedDate.Year == this.VisibleMonth.Year ? "[ " + a + " ]" : a), "m-" + (b + 1).ToString()));
bf.AddSplitted(buttons, 2);
break;
case eMonthPickerMode.year:
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;
for (int i = 0; i < 10; i++)
{
var m = starti + (i * 2);
bf.AddButtonRow(new ButtonBase((this.SelectedDate.Year == m ? "[ " + m.ToString() + " ]" : m.ToString()), "y-" + m.ToString()), new ButtonBase((this.SelectedDate.Year == (m + 1) ? "[ " + (m + 1).ToString() + " ]" : (m + 1).ToString()), "y-" + (m + 1).ToString()));
}
break;
}
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);
this.MessageId = m.MessageId;
}
}
public override async Task Cleanup()
{
if (this.MessageId != null)
{
await this.Device.DeleteMessage(this.MessageId.Value);
}
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Enums;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls.Inline
{
public class MonthPicker : CalendarPicker
{
public MonthPicker()
{
this.PickerMode = eMonthPickerMode.month;
this.EnableDayView = false;
}
}
}
@@ -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;
}
}
}
@@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.Linq;
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.
@@ -220,7 +221,7 @@ namespace TelegramBotBase.Controls
}
public async override Task Render()
public async override Task Render(MessageResult result)
{
if (!this.RenderNecessary)
{
@@ -0,0 +1,147 @@
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 ToggleButton : ControlBase
{
public String UncheckedIcon { get; set; } = Localizations.Default.Language["ToggleButton_OffIcon"];
public String CheckedIcon { get; set; } = Localizations.Default.Language["ToggleButton_OnIcon"];
public String CheckedString { get; set; } = Localizations.Default.Language["ToggleButton_On"];
public String UncheckedString { get; set; } = Localizations.Default.Language["ToggleButton_Off"];
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; }
public bool Checked { get; set; }
private bool RenderNecessary = true;
private static readonly object __evToggled = new object();
private readonly EventHandlerList Events = new EventHandlerList();
public ToggleButton()
{
}
public ToggleButton(String CheckedString, String UncheckedString)
{
this.CheckedString = CheckedString;
this.UncheckedString = UncheckedString;
}
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")
{
case "on":
if (this.Checked)
return;
RenderNecessary = true;
this.Checked = true;
OnToggled(new EventArgs());
break;
case "off":
if (!this.Checked)
return;
RenderNecessary = true;
this.Checked = false;
OnToggled(new EventArgs());
break;
default:
RenderNecessary = false;
break;
}
result.Handled = true;
}
public override async Task Render(MessageResult result)
{
if (!RenderNecessary)
return;
var bf = new ButtonForm(this);
ButtonBase bOn = new ButtonBase((this.Checked ? CheckedIcon : UncheckedIcon) + " " + CheckedString, "on");
ButtonBase bOff = new ButtonBase((!this.Checked ? CheckedIcon : UncheckedIcon) + " " + UncheckedString, "off");
bf.AddButtonRow(bOn, bOff);
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;
}
}
}
+122
View File
@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Controls.Inline
{
public class TreeView : ControlBase
{
public List<TreeViewNode> Nodes { get; set; }
public TreeViewNode SelectedNode { get; set; }
public TreeViewNode VisibleNode { get; set; }
public String Title { get; set; }
private int? MessageId { get; set; }
public String MoveUpIcon { get; set; } = Localizations.Default.Language["TreeView_LevelUp"];
public TreeView()
{
this.Nodes = new List<TreeViewNode>();
this.Title = Localizations.Default.Language["TreeView_Title"];
}
public override async Task Action(MessageResult result, String value = null)
{
await result.ConfirmAction();
if (result.Handled)
return;
var val = result.RawData;
switch (val)
{
case "up":
case "parent":
this.VisibleNode = (this.VisibleNode?.ParentNode);
result.Handled = true;
break;
default:
var n = (this.VisibleNode != null ? this.VisibleNode.FindNodeByValue(val) : this.Nodes.FirstOrDefault(a => a.Value == val));
if (n == null)
return;
if (n.ChildNodes.Count > 0)
{
this.VisibleNode = n;
}
else
{
this.SelectedNode = (this.SelectedNode != n ? n : null);
}
result.Handled = true;
break;
}
}
public override async Task Render(MessageResult result)
{
var startnode = this.VisibleNode;
var nodes = (startnode?.ChildNodes ?? this.Nodes);
ButtonForm bf = new ButtonForm();
if (startnode != null)
{
bf.AddButtonRow(new ButtonBase(this.MoveUpIcon, "up"), new ButtonBase(startnode.Text, "parent"));
}
foreach (var n in nodes)
{
var s = n.Text;
if (this.SelectedNode == n)
{
s = "[ " + s + " ]";
}
bf.AddButtonRow(new ButtonBase(s, n.Value, n.Url));
}
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);
this.MessageId = m.MessageId;
}
}
public String GetPath()
{
return (this.VisibleNode?.GetPath() ?? "\\");
}
}
}
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Controls.Inline
{
public class TreeViewNode
{
public String Text { get; set; }
public String Value { get; set; }
public String Url { get; set; }
public List<TreeViewNode> ChildNodes { get; set; } = new List<TreeViewNode>();
public TreeViewNode ParentNode { get; set; }
public TreeViewNode(String Text, String Value)
{
this.Text = Text;
this.Value = Value;
}
public TreeViewNode(String Text, String Value, String Url) : this(Text, Value)
{
this.Url = Url;
}
public TreeViewNode(String Text, String Value, params TreeViewNode[] childnodes) : this(Text, Value)
{
foreach(var c in childnodes)
{
AddNode(c);
}
}
public void AddNode(TreeViewNode node)
{
node.ParentNode = this;
ChildNodes.Add(node);
}
public TreeViewNode FindNodeByValue(String Value)
{
return this.ChildNodes.FirstOrDefault(a => a.Value == Value);
}
public String GetPath()
{
String s = "\\" + this.Value;
var p = this;
while (p.ParentNode != null)
{
s = "\\" + p.ParentNode.Value + s;
p = p.ParentNode;
}
return s;
}
}
}
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Datasources
{
public class ButtonFormDataSource : Interfaces.IDataSource<ButtonRow>
{
public virtual ButtonForm ButtonForm
{
get
{
return __buttonform;
}
set
{
__buttonform = value;
}
}
private ButtonForm __buttonform = null;
public ButtonFormDataSource()
{
__buttonform = new ButtonForm();
}
public ButtonFormDataSource(ButtonForm bf)
{
__buttonform = bf;
}
/// <summary>
/// Returns the amount of rows exisiting.
/// </summary>
/// <returns></returns>
public virtual int Count => ButtonForm.Count;
/// <summary>
/// Returns the amount of rows.
/// </summary>
public virtual int RowCount => ButtonForm.Rows;
/// <summary>
/// Returns the maximum amount of columns.
/// </summary>
public virtual int ColumnCount => ButtonForm.Cols;
/// <summary>
/// Returns the row with the specific index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public virtual ButtonRow ItemAt(int index)
{
return ButtonForm[index];
}
public virtual List<ButtonRow> ItemRange(int start, int count)
{
return ButtonForm.GetRange(start, count);
}
public virtual List<ButtonRow> AllItems()
{
return ButtonForm.ToArray();
}
public virtual ButtonForm PickItems(int start, int count, String filter = null)
{
ButtonForm bf = new ButtonForm();
ButtonForm dataForm = null;
if (filter == null)
{
dataForm = ButtonForm.Duplicate();
}
else
{
dataForm = ButtonForm.FilterDuplicate(filter, true);
}
for (int i = 0; i < count; i++)
{
int it = start + i;
if (it > dataForm.Rows - 1)
break;
bf.AddButtonRow(dataForm[it]);
}
return bf;
}
public virtual ButtonForm PickAllItems(String filter = null)
{
if (filter == null)
return ButtonForm.Duplicate();
return ButtonForm.FilterDuplicate(filter, true);
}
public virtual Tuple<ButtonRow, int> FindRow(String text, bool useText = true)
{
return ButtonForm.FindRow(text, useText);
}
/// <summary>
/// Returns the maximum items of this data source.
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
public virtual int CalculateMax(String filter = null)
{
return PickAllItems(filter).Rows;
}
public virtual ButtonRow Render(object data)
{
return data as ButtonRow;
}
public static implicit operator ButtonFormDataSource(ButtonForm bf)
{
return new ButtonFormDataSource(bf);
}
public static implicit operator ButtonForm(ButtonFormDataSource ds)
{
return ds.ButtonForm;
}
}
}
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TelegramBotBase.Datasources
{
public class StaticDataSource<T> : Interfaces.IDataSource<T>
{
List<T> Data { get; set; }
public StaticDataSource()
{
}
public StaticDataSource(List<T> data)
{
this.Data = data;
}
public int Count
{
get
{
return Data.Count;
}
}
public T ItemAt(int index)
{
return Data[index];
}
public List<T> ItemRange(int start, int count)
{
return Data.Skip(start).Take(count).ToList();
}
public List<T> AllItems()
{
return Data;
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Enums
{
public enum eDeleteMode
{
/// <summary>
/// Don't delete any message.
/// </summary>
None = 0,
/// <summary>
/// Delete messages on every callback/action.
/// </summary>
OnEveryCall = 1,
/// <summary>
/// Delete on leaving this form.
/// </summary>
OnLeavingForm = 2
}
}
+24
View File
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Enums
{
public enum eDeleteSide
{
/// <summary>
/// Delete only messages from this bot.
/// </summary>
BotOnly = 0,
/// <summary>
/// Delete only user messages.
/// </summary>
UserOnly = 1,
/// <summary>
/// Delete all messages in this context.
/// </summary>
Both = 2
}
}
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Enums
{
public enum eKeyboardType
{
/// <summary>
/// Uses a ReplyKeyboardMarkup
/// </summary>
ReplyKeyboard = 0,
/// <summary>
/// Uses a InlineKeyboardMakup
/// </summary>
InlineKeyBoard = 1
}
}
+24
View File
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Enums
{
public enum eMonthPickerMode
{
/// <summary>
/// Shows the calendar with day picker mode
/// </summary>
day = 0,
/// <summary>
/// Shows the calendar with month overview
/// </summary>
month = 1,
/// <summary>
/// Shows the calendar with year overview
/// </summary>
year = 2
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Enums
{
public enum eNavigationBarVisibility
{
/// <summary>
/// Shows it depending on the amount of items.
/// </summary>
auto = 0,
/// <summary>
/// Will not show it at any time.
/// </summary>
never = 1,
/// <summary>
/// Will show it at any time.
/// </summary>
always = 2
}
}
+40
View File
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Enums
{
public enum eSettings
{
/// <summary>
/// How often could a form navigate to another (within one user action/call/message)
/// </summary>
NavigationMaximum = 1,
/// <summary>
/// Loggs all messages and sent them to the event handler
/// </summary>
LogAllMessages = 2,
/// <summary>
/// Skips all messages during running (good for big delay updates)
/// </summary>
SkipAllMessages = 3,
/// <summary>
/// Does stick to the console event handler and saves all sessions on exit.
/// </summary>
SaveSessionsOnConsoleExit = 4,
/// <summary>
/// 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.
/// </summary>
MaxNumberOfRetries = 5,
}
}
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Exceptions
{
public class MaxLengthException : Exception
{
public MaxLengthException(int length) : base($"Your messages with a length of {length} is too long for telegram. Actually is {Constants.Telegram.MaxMessageLength} characters allowed. Please split it.")
{
}
}
}
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Exceptions
{
public class MaximumColsException : Exception
{
public int Value { get; set; }
public int Maximum { get; set; }
public override string Message
{
get
{
return $"You have exceeded the maximum of columns by {Value.ToString()} / {Maximum.ToString()}";
}
}
}
}
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Exceptions
{
public class MaximumRowsReachedException : Exception
{
public int Value { get; set; }
public int Maximum { get; set; }
public override string Message
{
get
{
return $"You have exceeded the maximum of rows by {Value.ToString()} / {Maximum.ToString()}";
}
}
}
}
+4 -4
View File
@@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
namespace TelegramBotBase.Form
@@ -11,17 +12,16 @@ namespace TelegramBotBase.Form
/// <summary>
/// A simple prompt dialog with one ok Button
/// </summary>
public class AlertDialog : PromptDialog
[IgnoreState]
public class AlertDialog : ConfirmDialog
{
public String ButtonText { get; set; }
public AlertDialog(String Message, String ButtonText, FormBase FormToOpen = null) : base(Message)
public AlertDialog(String Message, String ButtonText) : base(Message)
{
this.Buttons.Add(new ButtonBase(ButtonText, "ok"));
this.ButtonText = ButtonText;
if (FormToOpen != null)
this.ButtonForms.Add("ok", FormToOpen);
}
}
+12 -1
View File
@@ -4,6 +4,8 @@ using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
namespace TelegramBotBase.Form
@@ -11,13 +13,22 @@ namespace TelegramBotBase.Form
/// <summary>
/// A prompt with a lot of buttons
/// </summary>
[IgnoreState]
public class ArrayPromptDialog : FormBase
{
/// <summary>
/// The message the users sees.
/// </summary>
public String Message { get; set; }
/// <summary>
/// An additional optional value.
/// </summary>
public object Tag { get; set; }
public ButtonBase[][] Buttons { get; set; }
[Obsolete]
public Dictionary<String, FormBase> ButtonForms { get; set; } = new Dictionary<string, FormBase>();
private EventHandlerList __Events { get; set; } = new EventHandlerList();
@@ -67,7 +78,7 @@ namespace TelegramBotBase.Form
return;
}
OnButtonClicked(new ButtonClickedEventArgs(button));
OnButtonClicked(new ButtonClickedEventArgs(button) { Tag = this.Tag });
FormBase fb = ButtonForms.ContainsKey(call.Value) ? ButtonForms[call.Value] : null;
+132 -32
View File
@@ -1,10 +1,15 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot.Exceptions;
using Telegram.Bot.Types;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
using TelegramBotBase.Enums;
namespace TelegramBotBase.Form
{
@@ -13,35 +18,54 @@ namespace TelegramBotBase.Form
/// </summary>
public class AutoCleanForm : FormBase
{
List<Message> OldMessages { get; set; }
[SaveState]
public List<int> OldMessages { get; set; }
[SaveState]
public eDeleteMode DeleteMode { get; set; }
public enum eDeleteMode
{
None = 0,
OnEveryCall = 1,
OnLeavingForm = 2
}
[SaveState]
public eDeleteSide DeleteSide { get; set; }
public AutoCleanForm()
{
this.OldMessages = new List<Message>();
this.OldMessages = new List<int>();
this.DeleteMode = eDeleteMode.OnEveryCall;
this.DeleteSide = eDeleteSide.BotOnly;
this.Init += AutoCleanForm_Init;
this.Closed += AutoCleanForm_Closed;
}
public override async Task Init(params object[] args)
private async Task AutoCleanForm_Init(object sender, InitEventArgs e)
{
if (this.Device == null)
return;
this.Device.MessageSent += Device_MessageSent;
this.Device.MessageReceived += Device_MessageReceived;
}
private void Device_MessageReceived(object sender, MessageReceivedEventArgs e)
{
if (this.DeleteSide == eDeleteSide.BotOnly)
return;
this.OldMessages.Add(e.Message.MessageId);
}
private void Device_MessageSent(object sender, MessageSentEventArgs e)
{
this.OldMessages.Add(e.Message);
if (this.DeleteSide == eDeleteSide.UserOnly)
return;
this.OldMessages.Add(e.Message.MessageId);
}
public override async Task PreLoad(MessageResult message)
@@ -49,41 +73,39 @@ namespace TelegramBotBase.Form
if (this.DeleteMode != eDeleteMode.OnEveryCall)
return;
while (this.OldMessages.Count > 0)
{
if (!await this.Device.DeleteMessage(this.OldMessages[0].MessageId))
{
//Nachricht konnte nicht gelöscht werden, vermutlich existiert diese nicht mehr
if (this.OldMessages.Count > 0)
this.OldMessages.RemoveAt(0);
}
}
await MessageCleanup();
}
/// <summary>
/// Fügt eine Nachricht zu der Liste der löschenden hinzu.
/// Adds a message to this of removable ones
/// </summary>
/// <param name="Id"></param>
public void AddMessage(Message m)
{
this.OldMessages.Add(m);
this.OldMessages.Add(m.MessageId);
}
/// <summary>
/// Adds a message to this of removable ones
/// </summary>
/// <param name="Id"></param>
public void AddMessage(int messageId)
{
this.OldMessages.Add(messageId);
}
/// <summary>
/// Behält die Nachricht mit der angegebenen Id.
/// Keeps the message by removing it from the list
/// </summary>
/// <param name="Id"></param>
public void LeaveMessage(int Id)
{
var m = this.OldMessages.FirstOrDefault(a => a.MessageId == Id);
if (m == null)
return;
this.OldMessages.Remove(m);
this.OldMessages.Remove(Id);
}
/// <summary>
/// Behält die zuletzt gesendete Nachricht.
/// Keeps the last sent message
/// </summary>
public void LeaveLastMessage()
{
@@ -93,15 +115,93 @@ namespace TelegramBotBase.Form
this.OldMessages.RemoveAt(this.OldMessages.Count - 1);
}
public async override Task Closed()
private async Task AutoCleanForm_Closed(object sender, EventArgs e)
{
if (this.DeleteMode != eDeleteMode.OnLeavingForm)
return;
foreach (var m in this.OldMessages)
MessageCleanup().Wait();
}
/// <summary>
/// Cleans up all remembered messages.
/// </summary>
/// <returns></returns>
public async Task MessageCleanup()
{
var oldMessages = OldMessages.AsEnumerable();
#if !NET472
while (oldMessages.Any())
{
await this.Device.DeleteMessage(m.MessageId);
using var cts = new CancellationTokenSource();
var deletedMessages = new ConcurrentBag<int>();
var parallelQuery = OldMessages.AsParallel()
.WithCancellation(cts.Token);
Task retryAfterTask = null;
try
{
parallelQuery.ForAll(i =>
{
Device.DeleteMessage(i).GetAwaiter().GetResult();
deletedMessages.Add(i);
});
}
catch (AggregateException ex)
{
cts.Cancel();
var retryAfterSeconds = ex.InnerExceptions
.Where(e => e is ApiRequestException apiEx && apiEx.ErrorCode == 429)
.Max(e =>(int?) ((ApiRequestException)e).Parameters.RetryAfter) ?? 0;
retryAfterTask = Task.Delay(retryAfterSeconds * 1000);
}
deletedMessages.AsParallel().ForAll(i => Device.OnMessageDeleted(new MessageDeletedEventArgs(i)));
oldMessages = oldMessages.Where(x => !deletedMessages.Contains(x));
if (retryAfterTask != null)
await retryAfterTask;
}
#else
while (oldMessages.Any())
{
using (var cts = new CancellationTokenSource())
{
var deletedMessages = new ConcurrentBag<int>();
var parallelQuery = OldMessages.AsParallel()
.WithCancellation(cts.Token);
Task retryAfterTask = null;
try
{
parallelQuery.ForAll(i =>
{
Device.DeleteMessage(i).GetAwaiter().GetResult();
deletedMessages.Add(i);
});
}
catch (AggregateException ex)
{
cts.Cancel();
var retryAfterSeconds = ex.InnerExceptions
.Where(e => e is ApiRequestException apiEx && apiEx.ErrorCode == 429)
.Max(e => (int?)((ApiRequestException)e).Parameters.RetryAfter) ?? 0;
retryAfterTask = Task.Delay(retryAfterSeconds * 1000);
}
deletedMessages.AsParallel().ForAll(i => Device.OnMessageDeleted(new MessageDeletedEventArgs(i)));
oldMessages = oldMessages.Where(x => !deletedMessages.Contains(x));
if (retryAfterTask != null)
await retryAfterTask;
}
}
#endif
OldMessages.Clear();
}
}
}
+42 -2
View File
@@ -1,29 +1,69 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types.ReplyMarkups;
namespace TelegramBotBase.Form
{
[DebuggerDisplay("{Text}, {Value}")]
/// <summary>
/// Base class for button handling
/// </summary>
public class ButtonBase
{
public String Text { get; set; }
public virtual String Text { get; set; }
public String Value { get; set; }
public String Url { get; set; }
public ButtonBase()
{
}
public ButtonBase(String Text, String Value)
public ButtonBase(String Text, String Value, String Url = null)
{
this.Text = Text;
this.Value = Value;
this.Url = Url;
}
/// <summary>
/// Returns an inline Button
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public virtual InlineKeyboardButton ToInlineButton(ButtonForm form)
{
String id = (form.DependencyControl != null ? form.DependencyControl.ControlID + "_" : "");
if (this.Url == null)
{
return InlineKeyboardButton.WithCallbackData(this.Text, id + this.Value);
}
var ikb = new InlineKeyboardButton();
ikb.Text = this.Text;
ikb.Url = this.Url;
return ikb;
}
/// <summary>
/// Returns a KeyBoardButton
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public virtual KeyboardButton ToKeyboardButton(ButtonForm form)
{
return new KeyboardButton(this.Text);
}
}
+268 -6
View File
@@ -4,6 +4,8 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Base;
using TelegramBotBase.Controls.Hybrid;
namespace TelegramBotBase.Form
{
@@ -12,14 +14,65 @@ namespace TelegramBotBase.Form
/// </summary>
public class ButtonForm
{
List<List<ButtonBase>> Buttons = new List<List<ButtonBase>>();
List<ButtonRow> Buttons = new List<ButtonRow>();
public IReplyMarkup Markup { get; set; }
public ControlBase DependencyControl { get; set; }
/// <summary>
/// Contains the number of rows.
/// </summary>
public int Rows
{
get
{
return Buttons.Count;
}
}
/// <summary>
/// Contains the highest number of columns in an row.
/// </summary>
public int Cols
{
get
{
return Buttons.Select(a => a.Count).OrderByDescending(a => a).FirstOrDefault();
}
}
public ButtonRow this[int row]
{
get
{
return Buttons[row];
}
}
public ButtonForm()
{
}
public void AddButtonRow(List<ButtonBase> row)
public ButtonForm(ControlBase control)
{
this.DependencyControl = control;
}
public void AddButtonRow(String Text, String Value, String Url = null)
{
Buttons.Add(new List<ButtonBase>() { new ButtonBase(Text, Value, Url) });
}
//public void AddButtonRow(ButtonRow row)
//{
// Buttons.Add(row.ToList());
//}
public void AddButtonRow(ButtonRow row)
{
Buttons.Add(row);
}
@@ -29,6 +82,26 @@ namespace TelegramBotBase.Form
AddButtonRow(row.ToList());
}
public void AddButtonRows(IEnumerable<ButtonRow> rows)
{
Buttons.AddRange(rows);
}
public void InsertButtonRow(int index, IEnumerable<ButtonBase> row)
{
Buttons.Insert(index, row.ToList());
}
public void InsertButtonRow(int index, ButtonRow row)
{
Buttons.Insert(index, row);
}
//public void InsertButtonRow(int index, params ButtonBase[] row)
//{
// InsertButtonRow(index, row.ToList());
//}
public static T[][] SplitTo<T>(IEnumerable<T> items, int itemsPerRow = 2)
{
T[][] splitted = default(T[][]);
@@ -49,6 +122,17 @@ namespace TelegramBotBase.Form
return splitted;
}
public int Count
{
get
{
if (this.Buttons.Count == 0)
return 0;
return this.Buttons.Select(a => a.ToArray()).ToList().Aggregate((a, b) => a.Union(b).ToArray()).Length;
}
}
/// <summary>
/// Add buttons splitted in the amount of columns (i.e. 2 per row...)
/// </summary>
@@ -58,24 +142,202 @@ namespace TelegramBotBase.Form
{
var sp = SplitTo<ButtonBase>(buttons, buttonsPerRow);
foreach(var bl in sp)
foreach (var bl in sp)
{
AddButtonRow(bl);
}
}
public InlineKeyboardButton[][] ToArray()
/// <summary>
/// Returns a range of rows from the buttons.
/// </summary>
/// <param name="start"></param>
/// <param name="count"></param>
/// <returns></returns>
public List<ButtonRow> GetRange(int start, int count)
{
var ikb = this.Buttons.Select(a => a.Select(b => InlineKeyboardButton.WithCallbackData(b.Text, b.Value)).ToArray()).ToArray();
return Buttons.Skip(start).Take(count).ToList();
}
public List<ButtonBase> ToList()
{
return this.Buttons.DefaultIfEmpty(new List<ButtonBase>()).Select(a => a.ToList()).Aggregate((a, b) => a.Union(b).ToList());
}
public InlineKeyboardButton[][] ToInlineButtonArray()
{
var ikb = this.Buttons.Select(a => a.ToArray().Select(b => b.ToInlineButton(this)).ToArray()).ToArray();
return ikb;
}
public KeyboardButton[][] ToReplyButtonArray()
{
var ikb = this.Buttons.Select(a => a.ToArray().Select(b => b.ToKeyboardButton(this)).ToArray()).ToArray();
return ikb;
}
public List<ButtonRow> ToArray()
{
return Buttons;
}
public int FindRowByButton(ButtonBase button)
{
var row = this.Buttons.FirstOrDefault(a => a.ToArray().Count(b => b == button) > 0);
if (row == null)
return -1;
return this.Buttons.IndexOf(row);
}
public Tuple<ButtonRow, int> FindRow(String text, bool useText = true)
{
var r = this.Buttons.FirstOrDefault(a => a.Matches(text, useText));
if (r == null)
return null;
var i = this.Buttons.IndexOf(r);
return new Tuple<ButtonRow, int>(r, i);
}
/// <summary>
/// Returns the first Button with the given value.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public ButtonBase GetButtonByValue(String value)
{
return this.ToList().Where(a => a.Value == value).FirstOrDefault();
}
public static implicit operator InlineKeyboardMarkup(ButtonForm form)
{
InlineKeyboardMarkup ikm = new InlineKeyboardMarkup(form.ToArray());
if (form == null)
return null;
InlineKeyboardMarkup ikm = new InlineKeyboardMarkup(form.ToInlineButtonArray());
return ikm;
}
public static implicit operator ReplyKeyboardMarkup(ButtonForm form)
{
if (form == null)
return null;
ReplyKeyboardMarkup ikm = new ReplyKeyboardMarkup(form.ToReplyButtonArray());
return ikm;
}
/// <summary>
/// Creates a copy of this form.
/// </summary>
/// <returns></returns>
public ButtonForm Duplicate()
{
var bf = new ButtonForm()
{
Markup = this.Markup,
DependencyControl = this.DependencyControl
};
foreach (var b in Buttons)
{
var lst = new ButtonRow();
foreach (var b2 in b)
{
lst.Add(b2);
}
bf.Buttons.Add(lst);
}
return bf;
}
/// <summary>
/// Creates a copy of this form and filters by the parameter.
/// </summary>
/// <returns></returns>
public ButtonForm FilterDuplicate(String filter, bool ByRow = false)
{
var bf = new ButtonForm()
{
Markup = this.Markup,
DependencyControl = this.DependencyControl
};
foreach (var b in Buttons)
{
var lst = new ButtonRow();
foreach (var b2 in b)
{
if (b2.Text.IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) == -1)
continue;
//Copy full row, when at least one match has found.
if (ByRow)
{
lst = b;
break;
}
else
{
lst.Add(b2);
}
}
if (lst.Count > 0)
bf.Buttons.Add(lst);
}
return bf;
}
/// <summary>
/// Creates a copy of this form and filters by the parameter.
/// </summary>
/// <returns></returns>
public ButtonForm TagDuplicate(List<String> tags, bool ByRow = false)
{
var bf = new ButtonForm()
{
Markup = this.Markup,
DependencyControl = this.DependencyControl
};
foreach (var b in Buttons)
{
var lst = new ButtonRow();
foreach (var b2 in b)
{
if (!(b2 is TagButtonBase tb))
continue;
if (!tags.Contains(tb.Tag))
continue;
//Copy full row, when at least one match has found.
if (ByRow)
{
lst = b;
break;
}
else
{
lst.Add(b2);
}
}
if (lst.Count > 0)
bf.Buttons.Add(lst);
}
return bf;
}
}
}
+124
View File
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
namespace TelegramBotBase.Form
{
[IgnoreState]
public class ConfirmDialog : ModalDialog
{
/// <summary>
/// The message the users sees.
/// </summary>
public String Message { get; set; }
/// <summary>
/// An additional optional value.
/// </summary>
public object Tag { get; set; }
/// <summary>
/// Automatically close form on button click
/// </summary>
public bool AutoCloseOnClick { get; set; } = true;
public List<ButtonBase> Buttons { get; set; }
private EventHandlerList __Events { get; set; } = new EventHandlerList();
private static object __evButtonClicked { get; } = new object();
public ConfirmDialog()
{
}
public ConfirmDialog(String Message)
{
this.Message = Message;
this.Buttons = new List<Form.ButtonBase>();
}
public ConfirmDialog(String Message, params ButtonBase[] Buttons)
{
this.Message = Message;
this.Buttons = Buttons.ToList();
}
/// <summary>
/// Adds one Button
/// </summary>
/// <param name="button"></param>
public void AddButton(ButtonBase button)
{
this.Buttons.Add(button);
}
public override async Task Action(MessageResult message)
{
if (message.Handled)
return;
if (!message.IsFirstHandler)
return;
var call = message.GetData<CallbackData>();
if (call == null)
return;
message.Handled = true;
await message.ConfirmAction();
await message.DeleteMessage();
ButtonBase button = this.Buttons.FirstOrDefault(a => a.Value == call.Value);
if (button == null)
{
return;
}
OnButtonClicked(new ButtonClickedEventArgs(button) { Tag = this.Tag });
if (AutoCloseOnClick)
await CloseForm();
}
public override async Task Render(MessageResult message)
{
ButtonForm btn = new ButtonForm();
var buttons = this.Buttons.Select(a => new ButtonBase(a.Text, CallbackData.Create("action", a.Value))).ToList();
btn.AddButtonRow(buttons);
await this.Device.Send(this.Message, btn);
}
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);
}
}
}
+41
View File
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Form
{
public class DynamicButton : ButtonBase
{
public override string Text
{
get
{
return GetText?.Invoke() ?? m_text;
}
set
{
m_text = value;
}
}
private String m_text = "";
private Func<String> GetText;
public DynamicButton(String Text, String Value, String Url = null)
{
this.Text = Text;
this.Value = Value;
this.Url = Url;
}
public DynamicButton(Func<String> GetText, String Value, String Url = null)
{
this.GetText = GetText;
this.Value = Value;
this.Url = Url;
}
}
}
+86
View File
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
namespace TelegramBotBase.Form
{
public class GroupForm : FormBase
{
public GroupForm()
{
}
public override async Task Load(MessageResult message)
{
switch (message.MessageType)
{
case Telegram.Bot.Types.Enums.MessageType.ChatMembersAdded:
await OnMemberChanges(new MemberChangeEventArgs(Telegram.Bot.Types.Enums.MessageType.ChatMembersAdded, message, message.RawMessageData.Message.NewChatMembers));
break;
case Telegram.Bot.Types.Enums.MessageType.ChatMemberLeft:
await OnMemberChanges(new MemberChangeEventArgs(Telegram.Bot.Types.Enums.MessageType.ChatMemberLeft, message, message.RawMessageData.Message.LeftChatMember));
break;
case Telegram.Bot.Types.Enums.MessageType.ChatPhotoChanged:
case Telegram.Bot.Types.Enums.MessageType.ChatPhotoDeleted:
case Telegram.Bot.Types.Enums.MessageType.ChatTitleChanged:
case Telegram.Bot.Types.Enums.MessageType.MigratedFromGroup:
case Telegram.Bot.Types.Enums.MessageType.MigratedToSupergroup:
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));
break;
default:
await OnMessage(message);
break;
}
}
public override async Task Edited(MessageResult message)
{
await OnMessageEdit(message);
}
public virtual async Task OnMemberChanges(MemberChangeEventArgs e)
{
}
public virtual async Task OnGroupChanged(GroupChangedEventArgs e)
{
}
public virtual async Task OnMessage(MessageResult e)
{
}
public virtual async Task OnMessageEdit(MessageResult e)
{
}
}
}
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace TelegramBotBase.Form
{
public class ModalDialog : FormBase
{
/// <summary>
/// Contains the parent from where the modal dialog has been opened.
/// </summary>
public FormBase ParentForm { get; set; }
/// <summary>
/// This is a modal only function and does everything to close this form.
/// </summary>
public async Task CloseForm()
{
await this.CloseControls();
await this.OnClosed(new EventArgs());
await this.ParentForm?.ReturnFromModal(this);
}
}
}
@@ -0,0 +1,358 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Form.Navigation
{
[DebuggerDisplay("{Index+1} Forms")]
public class NavigationController : FormBase, IStateForm
{
[SaveState]
private List<FormBase> History { get; set; }
[SaveState]
public int Index { get; set; }
/// <summary>
/// Will replace the controller when poping a form to the root form.
/// </summary>
[SaveState]
public bool ForceCleanupOnLastPop { get; set; }
public NavigationController()
{
History = new List<FormBase>();
Index = -1;
ForceCleanupOnLastPop = true;
this.Init += NavigationController_Init;
this.Opened += NavigationController_Opened;
this.Closed += NavigationController_Closed;
}
public NavigationController(FormBase startForm, params FormBase[] forms) : this()
{
this.Client = startForm.Client;
this.Device = startForm.Device;
startForm.NavigationController = this;
History.Add(startForm);
Index = 0;
if (forms.Length > 0)
{
History.AddRange(forms);
Index = History.Count - 1;
}
}
private async Task NavigationController_Init(object sender, Args.InitEventArgs e)
{
if (CurrentForm == null)
return;
await CurrentForm.OnInit(e);
}
private async Task NavigationController_Opened(object sender, EventArgs e)
{
if (CurrentForm == null)
return;
await CurrentForm.OnOpened(e);
}
private async Task NavigationController_Closed(object sender, EventArgs e)
{
if (CurrentForm == null)
return;
await CurrentForm.OnClosed(e);
}
/// <summary>
/// Remove the current active form on the stack.
/// </summary>
/// <returns></returns>
public virtual async Task PopAsync()
{
if (History.Count == 0)
return;
var form = History[Index];
form.NavigationController = null;
History.Remove(form);
Index--;
Device.FormSwitched = true;
await form.OnClosed(new EventArgs());
//Leave NavigationController and move to the last one
if (ForceCleanupOnLastPop && History.Count == 1)
{
var last_form = History[0];
last_form.NavigationController = null;
await this.NavigateTo(last_form);
return;
}
if (History.Count > 0)
{
form = History[Index];
await form.OnOpened(new EventArgs());
}
}
/// <summary>
/// Pop's through all forms back to the root form.
/// </summary>
/// <returns></returns>
public virtual async Task PopToRootAsync()
{
while (Index > 0)
{
await PopAsync();
}
}
/// <summary>
/// Pushing the given form to the stack and renders it.
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public virtual async Task PushAsync(FormBase form, params object[] args)
{
form.Client = this.Client;
form.Device = this.Device;
form.NavigationController = this;
this.History.Add(form);
Index++;
Device.FormSwitched = true;
if (Index < 2)
return;
await form.OnInit(new InitEventArgs(args));
await form.OnOpened(new EventArgs());
}
/// <summary>
/// Pops the current form and pushes a new one.
/// Will help to remove forms so you can not navigate back to them.
/// </summary>
/// <param name="form"></param>
/// <param name="args"></param>
/// <returns></returns>
public virtual async Task PushAndReplaceAsync(FormBase form, params object[] args)
{
await PopAsync();
await PushAsync(form, args);
}
/// <summary>
/// Returns the current form from the stack.
/// </summary>
public FormBase CurrentForm
{
get
{
if (this.History.Count == 0)
return null;
return this.History[Index];
}
}
public List<FormBase> GetAllForms()
{
return History.ToList();
}
public void LoadState(LoadStateEventArgs e)
{
if (e.Get("$controller.history.count") == null)
return;
int historyCount = e.GetInt("$controller.history.count");
for (int i = 0; i < historyCount; i++)
{
var c = e.GetObject($"$controller.history[{i}]") as Dictionary<String, object>;
var qname = e.Get($"$controller.history[{i}].type");
if (qname == null)
continue;
Type t = Type.GetType(qname.ToString());
if (t == null || !t.IsSubclassOf(typeof(FormBase)))
{
continue;
}
var form = t.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
//No default constructor, fallback
if (form == null)
{
continue;
}
var properties = c.Where(a => a.Key.StartsWith("$"));
var fields = form.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Where(a => a.GetCustomAttributes(typeof(SaveState), true).Length != 0).ToList();
foreach (var p in properties)
{
var f = fields.FirstOrDefault(a => a.Name == p.Key.Substring(1));
if (f == null)
continue;
try
{
if (f.PropertyType.IsEnum)
{
var ent = Enum.Parse(f.PropertyType, p.Value.ToString());
f.SetValue(form, ent);
continue;
}
f.SetValue(form, p.Value);
}
catch (ArgumentException ex)
{
Tools.Conversion.CustomConversionChecks(form, p, f);
}
catch
{
}
}
form.Device = this.Device;
form.Client = this.Client;
form.NavigationController = this;
form.OnInit(new InitEventArgs());
this.History.Add(form);
}
}
public void SaveState(SaveStateEventArgs e)
{
e.Set("$controller.history.count", History.Count.ToString());
int i = 0;
foreach (var form in History)
{
var fields = form.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Where(a => a.GetCustomAttributes(typeof(SaveState), true).Length != 0).ToList();
var dt = new Dictionary<String, object>();
foreach (var f in fields)
{
var val = f.GetValue(form);
dt.Add("$" + f.Name, val);
}
e.Set($"$controller.history[{i}].type", form.GetType().AssemblyQualifiedName);
e.SetObject($"$controller.history[{i}]", dt);
i++;
}
}
#region "Methods passthrough"
public override async Task NavigateTo(FormBase newForm, params object[] args)
{
await CurrentForm.NavigateTo(newForm, args);
}
public override async Task LoadControls(MessageResult message)
{
await CurrentForm.LoadControls(message);
}
public override async Task Load(MessageResult message)
{
await CurrentForm.Load(message);
}
public override async Task ActionControls(MessageResult message)
{
await CurrentForm.ActionControls(message);
}
public override async Task Action(MessageResult message)
{
await CurrentForm.Action(message);
}
public override async Task Edited(MessageResult message)
{
await CurrentForm.Edited(message);
}
public override async Task Render(MessageResult message)
{
await CurrentForm.Render(message);
}
public override async Task RenderControls(MessageResult message)
{
await CurrentForm.RenderControls(message);
}
public override async Task PreLoad(MessageResult message)
{
await CurrentForm.PreLoad(message);
}
public override async Task ReturnFromModal(ModalDialog modal)
{
await CurrentForm.ReturnFromModal(modal);
}
public override async Task SentData(DataResult message)
{
await CurrentForm.SentData(message);
}
#endregion
}
}
+64 -41
View File
@@ -4,22 +4,44 @@ using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args;
using TelegramBotBase.Attributes;
using TelegramBotBase.Base;
namespace TelegramBotBase.Form
{
public class PromptDialog : FormBase
[IgnoreState]
public class PromptDialog : ModalDialog
{
/// <summary>
/// The message the users sees.
/// </summary>
public String Message { get; set; }
/// <summary>
/// The returned text value by the user.
/// </summary>
public String Value { get; set; }
public List<ButtonBase> Buttons { get; set; }
public Dictionary<String, FormBase> ButtonForms { get; set; } = new Dictionary<string, FormBase>();
/// <summary>
/// An additional optional value.
/// </summary>
public object Tag { get; set; }
private EventHandlerList __Events { get; set; } = new EventHandlerList();
private static object __evButtonClicked { get; } = new object();
private static object __evCompleted { get; } = new object();
public bool ShowBackButton { get; set; } = false;
public String BackLabel { get; set; } = Localizations.Default.Language["PromptDialog_Back"];
/// <summary>
/// Contains the RAW received message.
/// </summary>
public Message ReceivedMessage { get; set; }
public PromptDialog()
{
@@ -29,73 +51,74 @@ namespace TelegramBotBase.Form
public PromptDialog(String Message)
{
this.Message = Message;
this.Buttons = new List<Form.ButtonBase>();
}
public PromptDialog(String Message, params ButtonBase[] Buttons)
public async override Task Load(MessageResult message)
{
this.Message = Message;
this.Buttons = Buttons.ToList();
}
public override async Task Action(MessageResult message)
{
var call = message.GetData<CallbackData>();
if (call == null)
if (message.Handled)
return;
message.Handled = true;
if (!message.IsFirstHandler)
return;
await message.ConfirmAction();
await message.DeleteMessage();
ButtonBase button = this.Buttons.FirstOrDefault(a => a.Value == call.Value);
if (button == null)
if (this.ShowBackButton && message.MessageText == BackLabel)
{
await this.CloseForm();
return;
}
OnButtonClicked(new ButtonClickedEventArgs(button));
FormBase fb = ButtonForms.ContainsKey(call.Value) ? ButtonForms[call.Value] : null;
if (fb != null)
if (this.Value == null)
{
await this.NavigateTo(fb);
}
}
this.Value = message.MessageText;
ReceivedMessage = message.Message;
}
}
public override async Task Render(MessageResult message)
{
ButtonForm btn = new ButtonForm();
if (this.Value == null)
{
if (this.ShowBackButton)
{
ButtonForm bf = new ButtonForm();
bf.AddButtonRow(new ButtonBase(BackLabel, "back"));
await this.Device.Send(this.Message, (ReplyMarkupBase)bf);
return;
}
await this.Device.Send(this.Message);
return;
}
var buttons = this.Buttons.Select(a => new ButtonBase(a.Text, CallbackData.Create("action", a.Value))).ToList();
btn.AddButtonRow(buttons);
message.Handled = true;
await this.Device.Send(this.Message, btn);
OnCompleted(new PromptDialogCompletedEventArgs() { Tag = this.Tag, Value = this.Value });
await this.CloseForm();
}
public event EventHandler<ButtonClickedEventArgs> ButtonClicked
public event EventHandler<PromptDialogCompletedEventArgs> Completed
{
add
{
this.__Events.AddHandler(__evButtonClicked, value);
this.__Events.AddHandler(__evCompleted, value);
}
remove
{
this.__Events.RemoveHandler(__evButtonClicked, value);
this.__Events.RemoveHandler(__evCompleted, value);
}
}
public void OnButtonClicked(ButtonClickedEventArgs e)
public void OnCompleted(PromptDialogCompletedEventArgs e)
{
(this.__Events[__evButtonClicked] as EventHandler<ButtonClickedEventArgs>)?.Invoke(this, e);
(this.__Events[__evCompleted] as EventHandler<PromptDialogCompletedEventArgs>)?.Invoke(this, e);
}
}
+100
View File
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
namespace TelegramBotBase.Form
{
/// <summary>
/// This is used to split incomming requests depending on the chat type.
/// </summary>
public class SplitterForm : FormBase
{
private static object __evOpenSupergroup = new object();
private static object __evOpenGroup = new object();
private static object __evOpenChannel = new object();
private static object __evOpen = new object();
public override async Task Load(MessageResult message)
{
if (message.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Channel)
{
if (await OpenChannel(message))
{
return;
}
}
if (message.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Supergroup)
{
if (await OpenSupergroup(message))
{
return;
}
if (await OpenGroup(message))
{
return;
}
}
if (message.Message.Chat.Type == Telegram.Bot.Types.Enums.ChatType.Group)
{
if (await OpenGroup(message))
{
return;
}
}
await Open(message);
}
public virtual async Task<bool> OpenSupergroup(MessageResult e)
{
return false;
}
public virtual async Task<bool> OpenChannel(MessageResult e)
{
return false;
}
public virtual async Task<bool> Open(MessageResult e)
{
return false;
}
public virtual async Task<bool> OpenGroup(MessageResult e)
{
return false;
}
public override Task Action(MessageResult message)
{
return base.Action(message);
}
public override Task PreLoad(MessageResult message)
{
return base.PreLoad(message);
}
public override Task Render(MessageResult message)
{
return base.Render(message);
}
public override Task SentData(DataResult message)
{
return base.SentData(message);
}
}
}
+55
View File
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Telegram.Bot.Types.ReplyMarkups;
namespace TelegramBotBase.Form
{
/// <summary>
/// Base class for button handling
/// </summary>
public class TagButtonBase : ButtonBase
{
public String Tag { get; set; }
public TagButtonBase()
{
}
public TagButtonBase(String Text, String Value, String Tag)
{
this.Text = Text;
this.Value = Value;
this.Tag = Tag;
}
/// <summary>
/// Returns an inline Button
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public override InlineKeyboardButton ToInlineButton(ButtonForm form)
{
String id = (form.DependencyControl != null ? form.DependencyControl.ControlID + "_" : "");
return InlineKeyboardButton.WithCallbackData(this.Text, id + this.Value);
}
/// <summary>
/// Returns a KeyBoardButton
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public override KeyboardButton ToKeyboardButton(ButtonForm form)
{
return new KeyboardButton(this.Text);
}
}
}
+43
View File
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Controls.Hybrid;
namespace TelegramBotBase.Interfaces
{
public interface IDataSource<T>
{
/// <summary>
/// Returns the amount of items within this source.
/// </summary>
/// <returns></returns>
int Count { get; }
/// <summary>
/// Returns the item at the specific index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
T ItemAt(int index);
/// <summary>
/// Get all items from this source within this range.
/// </summary>
/// <param name="start"></param>
/// <param name="count"></param>
/// <returns></returns>
List<T> ItemRange(int start, int count);
/// <summary>
/// Gets a list of all items of this datasource.
/// </summary>
/// <returns></returns>
List<T> AllItems();
}
}
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Form;
namespace TelegramBotBase.Interfaces
{
interface IDeviceSession
{
/// <summary>
/// Device or chat id
/// </summary>
long DeviceId { get; set; }
/// <summary>
/// Username of user or group
/// </summary>
String ChatTitle { get; set; }
/// <summary>
/// When did any last action happend (message received or button clicked)
/// </summary>
DateTime LastAction { get; set; }
/// <summary>
/// Returns the form where the user/group is at the moment.
/// </summary>
FormBase ActiveForm { get; set; }
/// <summary>
/// Returns the previous shown form
/// </summary>
FormBase PreviousForm { get; set; }
/// <summary>
/// contains if the form has been switched (navigated)
/// </summary>
bool FormSwitched { get; set; }
}
}
+20
View File
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
namespace TelegramBotBase.Interfaces
{
/// <summary>
/// Is used to save specific fields into a session state to survive restarts or unhandled exceptions and crashes.
/// </summary>
public interface IStateForm
{
void LoadState(LoadStateEventArgs e);
void SaveState(SaveStateEventArgs e);
}
}
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Interfaces
{
public interface IStateMachine
{
Type FallbackStateForm { get; }
void SaveFormStates(SaveStatesEventArgs e);
StateContainer LoadFormStates();
}
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Localizations
{
public static class Default
{
public static Localization Language = new English();
}
}
+41
View File
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Localizations
{
public class English : Localization
{
public English() : base()
{
Values["Language"] = "English";
Values["ButtonGrid_Title"] = "Menu";
Values["ButtonGrid_NoItems"] = "There are no items.";
Values["ButtonGrid_PreviousPage"] = "◀️";
Values["ButtonGrid_NextPage"] = "▶️";
Values["ButtonGrid_CurrentPage"] = "Page {0} of {1}";
Values["ButtonGrid_SearchFeature"] = "💡 Send a message to filter the list. Click the 🔍 to reset the filter.";
Values["ButtonGrid_Back"] = "Back";
Values["ButtonGrid_CheckAll"] = "Check all";
Values["ButtonGrid_UncheckAll"] = "Uncheck all";
Values["CalendarPicker_Title"] = "Pick date";
Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️";
Values["TreeView_Title"] = "Select node";
Values["TreeView_LevelUp"] = "🔼 level up";
Values["ToggleButton_On"] = "On";
Values["ToggleButton_Off"] = "Off";
Values["ToggleButton_OnIcon"] = "⚫";
Values["ToggleButton_OffIcon"] = "⚪";
Values["ToggleButton_Title"] = "Toggle";
Values["ToggleButton_Changed"] = "Choosen";
Values["MultiToggleButton_SelectedIcon"] = "✅";
Values["MultiToggleButton_Title"] = "Multi-Toggle";
Values["MultiToggleButton_Changed"] = "Choosen";
Values["PromptDialog_Back"] = "Back";
Values["ToggleButton_Changed"] = "Setting changed";
}
}
}

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