Compare commits

..
73 Commits
Author SHA1 Message Date
Florian Zevedei 12622deee8 Removing serialize call due to existing operator 2024-02-05 03:30:39 +01:00
Florian Zevedei c6d5037700 Adding demo for too long callback data 2024-02-03 01:40:07 +01:00
Florian Zevedei 53886fa6bf Adding exception for too long callback data.
- right now only in DEBUG compilation and only for the static operator
2024-02-03 01:39:12 +01:00
Florian Zevedei d572c353f6 Adding CallbackData parameter to action calls 2024-02-03 01:16:56 +01:00
Florian Zevedei b8fd3badd4 Adding Int32, Int64 and StringNavigation 2024-02-02 14:15:26 +01:00
Florian Zevedei 97e04f4162 Update GuidAction.cs 2024-02-02 14:07:50 +01:00
Florian Zevedei 51d9faa807 Adding Int32, Int64 and StringAction 2024-02-02 14:07:41 +01:00
Florian Zevedei 57f2139dba Extracting static extension methods into separate file 2024-02-02 13:42:00 +01:00
Florian Zevedei 9da952aa28 Renaming project 2024-01-25 15:04:36 +01:00
Florian Zevedei 45b05d301b Moving experiment files to external class library for use in production systems 2024-01-25 14:56:18 +01:00
Florian Zevedei d910b42473 Fix 2024-01-24 22:20:37 +01:00
Florian Zevedei 952e0cb979 Simplifications 2024-01-24 22:10:43 +01:00
Florian Zevedei 3c93daed35 Refactoring of Actions
- renaming existing Actions to "Navigation" and adding simple Actions for custom coding
- fitting namespaces to purpose
- cleanup of older test code
2024-01-24 22:05:34 +01:00
Florian Zevedei de93598e6f Simplification of ActionManager and remove uncessary string storage 2024-01-24 21:55:54 +01:00
Florian Zevedei d6961deb8e Update TelegramBotFramework.sln 2024-01-24 21:47:32 +01:00
Florian Zevedei c821a69d57 Changing messageloop behaviour and fix missing Device instance in UpdateResult
- simplified for future changes
2024-01-24 21:47:14 +01:00
Florian Zevedei 07fe8e9074 Renaming project for better namespaces 2024-01-24 21:46:00 +01:00
Florian Zevedei a26c4ed858 Adding EndsWith action and example for RTL community 2024-01-24 17:57:17 +01:00
Florian Zevedei 9aab6bca76 Add instruction for first start 2024-01-24 17:56:43 +01:00
Florian Zevedei fc1e8e84c6 Adding short non generic version 2024-01-22 03:58:00 +01:00
Florian Zevedei aee7562424 Adding Configure method and simplify usability 2024-01-22 03:23:45 +01:00
Florian Zevedei 161f191c43 Adding minimal version of property selector, using reflection 2024-01-20 19:22:26 +01:00
Florian Zevedei d0a6b74244 Update README.md 2024-01-18 19:50:12 +01:00
Florian Zevedei 976f1da602 Update Readme.md 2024-01-18 19:48:44 +01:00
Florian Zevedei 0d3979bbab Update README.md 2024-01-18 19:47:00 +01:00
Florian Zevedei 5713dac312 Update Readme.md 2024-01-18 19:45:14 +01:00
Florian Zevedei 9f41ab352f Initial branch creation with 1. experiment upload. 2024-01-18 19:41:03 +01:00
Florian Zevedei 2d3393aa05 Update README.md 2024-01-17 22:55:11 +01:00
Florian Zevedei fdd814c88e Adding project reference links 2024-01-17 22:54:30 +01:00
Florian Zevedei 2b06fba8ff Adding readme note for IronSoftware extension 2024-01-17 22:51:40 +01:00
Florian Zevedei 8e4bc7a222 Bumps Microsoft.Data.SqlClient from 5.0.0 to 5.1.3. 2024-01-17 22:42:03 +01:00
Florian Zevedei deef8dd086 Replacing project references with nuget reference 2024-01-04 14:17:09 +01:00
Florian Zevedei 3aff0aa94b Updating example projects for use of SingleThread/ThreadPool 2024-01-04 14:16:52 +01:00
Florian Zevedei 75064fd64c Adding nuget references and nuget specs 2024-01-04 14:11:21 +01:00
Florian Zevedei 0cafca9aee Update README.md 2024-01-04 13:46:58 +01:00
Florian Zevedei ad40675b87 First release of IronSoftware drawing extension 2024-01-04 13:44:18 +01:00
Florian Zevedei 26152ee348 Removing System.Drawing.Common dependency 2023-12-29 14:37:19 +01:00
Florian Zevedei d5ffa914f0 Update README.md 2023-12-28 15:17:43 +01:00
Florian Zevedei 7fcaa407ad Adding parallel processing via ThreadPool 2023-12-26 17:40:13 +01:00
Florian Zevedei c1018ac5a0 Adding ThrowPendingUpdates option to builder and Message client. 2023-12-09 14:56:02 +01:00
Florian Zevedei 064399ed77 Replace nuget reference with project for development 2023-12-09 14:54:23 +01:00
Florian Zevedei 40199a1d81 Merge pull request #51 from contributeless/feature/ability-to-log-exceptions-to-any-destination
Added the ReceiveError event to MessageClient to provide the ability to handle exceptions in client code
2023-12-07 17:50:57 +01:00
Florian Zevedei 794dee7f58 improved readability 2023-12-07 17:49:35 +01:00
Florian Zevedei 88309e607b Adding localization notes to Readme file 2023-12-06 15:44:59 +01:00
Florian Zevedei a71eed5054 Merge pull request #54 from MajMcCloud/master
Syncing master and development branch
2023-12-06 15:36:53 +01:00
Florian Zevedei 2a873cc29b Merge pull request #53 from MajMcCloud/development
Integrating development branch into master
2023-12-06 15:23:47 +01:00
Florian Zevedei f401216eff Fixing EditMessage behaviour 2023-12-06 15:19:28 +01:00
Florian Zevedei 0cb4f02a8d Merge pull request #52 from contributeless/fix/get-device-id-from-edited-message
Fix: get chat id from edited message
2023-12-06 15:17:17 +01:00
TCSBANK\e.khudik 62b6bafbd3 Fix: get chat id from edited message 2023-12-05 13:02:53 +03:00
TCSBANK\e.khudik 89332244e3 Added the ReceiveError event to MessageClient to provide the ability to handle exceptions in client code 2023-12-04 15:10:28 +03:00
Florian Zevedei 43f4ab1061 Update README.md 2023-11-26 15:09:42 +01:00
Florian Zevedei 1f438186f4 Merge pull request #50 from MajMcCloud/master
Merging master changes into development branch
2023-11-26 15:08:56 +01:00
Florian Zevedei f447b058d0 Update README.md 2023-11-26 15:08:40 +01:00
Florian Zevedei 40ba9bd8ab Update README.md 2023-11-26 15:07:43 +01:00
Florian Zevedei ca473f78ed Adding /start command handling to test project 2023-11-02 14:52:05 +01:00
Florian Zevedei ab702dbc9f Added missing UsePersian method to interface. 2023-11-01 01:09:35 +01:00
Florian Zevedei 23dc1ff1d6 Merge pull request #48 from mottaghipour/master
[LANGUAGE] implement Persian language class
2023-11-01 01:01:25 +01:00
AmirAbbas e044d725c9 [FEATURE] add UsePersian method to BotBaseBuilder class 2023-10-31 13:25:46 +03:30
AmirAbbas 4825beea7e [FEATURE] implement Persian language class 2023-10-31 13:23:41 +03:30
AmirAbbas 78d65868ff [BASE] Init Persian language class 2023-10-31 13:11:02 +03:30
AmirAbbas d7aca80d59 [REFACTOR] convert language classes to language sealed classes 2023-10-31 13:10:31 +03:30
Florian Zevedei 73863c4154 Adding project files 2023-09-29 18:40:56 +02:00
Florian Zevedei e3b1a32535 Adding missing Program.cs 2023-09-29 18:40:20 +02:00
Florian Zevedei 571049eeb4 Replacing exception text with nameof litteral 2023-09-29 18:34:33 +02:00
Florian Zevedei 30222d640d Adding full DI navigation example for testing purposes.
- adding internal private field into FormBase class
- adding Extension methods into DependencyInjection namespace for use
- adding 2 NavigateTo extension methods for DI using ServiceProvider
- adding example project
- adding to readme as example
2023-09-29 18:34:10 +02:00
Florian Zevedei e6a48115a6 Adding xml comments to BotBase 2023-09-29 18:28:55 +02:00
Florian Zevedei b278015dd4 Replacing static names in exception messages with nameof literal 2023-09-29 18:26:39 +02:00
Florian Zevedei 91e0c54728 Merge pull request #46 from MajMcCloud/development
Migrate development to master
2023-09-16 17:22:01 +02:00
Florian Zevedei 788f935c02 Add latest nuget package instead of local project to extensions 2023-09-16 17:11:40 +02:00
Florian Zevedei d1089fe716 editing Label example project 2023-09-16 17:11:21 +02:00
Florian Zevedei 1d3db0116e Update README.md 2023-09-15 18:52:15 +02:00
Florian Zevedei e95301f727 Update README.md 2023-09-15 18:50:16 +02:00
Florian Zevedei 4ba91803fe Update README.md 2023-09-13 15:13:22 +02:00
83 changed files with 3220 additions and 73 deletions
@@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore;
namespace DependencyInjection.Database;
public class BotDbContext : DbContext
{
public BotDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
@@ -0,0 +1,7 @@
namespace DependencyInjection.Database;
public class User
{
public long Id { get; set; }
public string LastMessage { get; set; }
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,67 @@
using DependencyInjection.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.DependencyInjection;
namespace DependencyInjection.Forms
{
public class ConfirmationForm : FormBase
{
private readonly BotDbContext _dbContext;
public ConfirmationForm(BotDbContext dbContext)
{
_dbContext = dbContext;
}
public override async Task Load(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
{
await this.NavigateTo<StartForm>();
return;
}
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("Go back");
switch (message.RawData)
{
case "back":
await this.NavigateTo<StartForm>();
break;
}
}
public override async Task Render(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Back", "back");
await Device.Send($"ConfirmationForm: Your last message was: {user.LastMessage}. Click \"Back\" to get back.", bf);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using DependencyInjection.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using TelegramBotBase.Builder;
namespace DependencyInjection
{
internal class Program
{
static async Task Main(string[] args)
{
var serviceCollection = new ServiceCollection()
.AddDbContext<BotDbContext>(x => x.UseInMemoryDatabase("TelegramBotBase"));
var serviceProvider = serviceCollection.BuildServiceProvider();
var bot = BotBaseBuilder.Create()
.WithAPIKey(Environment.GetEnvironmentVariable("API_KEY") ??
throw new Exception("API_KEY is not set"))
.DefaultMessageLoop()
.WithServiceProvider<StartForm>(serviceProvider)
.NoProxy()
.NoCommands()
.NoSerialization()
.DefaultLanguage()
.UseSingleThread()
.Build();
await bot.Start();
await Task.Delay(-1);
}
}
}
+79
View File
@@ -0,0 +1,79 @@
using DependencyInjection.Database;
using DependencyInjection.Forms;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
using TelegramBotBase.DependencyInjection;
namespace DependencyInjection;
public class StartForm : FormBase
{
private readonly BotDbContext _dbContext;
public StartForm(BotDbContext dbContext)
{
_dbContext = dbContext;
}
public override async Task Load(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user is null)
{
user = new User
{
Id = Device.DeviceId,
LastMessage = "<unknown>"
};
_dbContext.Users.Add(user);
await _dbContext.SaveChangesAsync();
}
if (message.IsAction)
return;
user.LastMessage = string.IsNullOrWhiteSpace(message.MessageText) ? "<unknown>" : message.MessageText;
await _dbContext.SaveChangesAsync();
}
public override async Task Action(MessageResult message)
{
await message.ConfirmAction("Ok");
switch(message.RawData)
{
case "open":
await this.NavigateTo(typeof(ConfirmationForm));
var new_form = await this.NavigateTo<ConfirmationForm>();
if (new_form == null)
{
await Device.Send("Cant open ConfirmationForm");
}
break;
}
}
public override async Task Render(MessageResult message)
{
var user = await _dbContext.Users.FindAsync(Device.DeviceId);
if (user == null)
return;
var bf = new ButtonForm();
bf.AddButtonRow("Open confirmation", "open");
await Device.Send($"Your last message's text was: `{user.LastMessage}`", bf);
}
}
+1
View File
@@ -18,6 +18,7 @@ var bot = BotBaseBuilder.Create()
.NoCommands()
.NoSerialization()
.DefaultLanguage()
.UseSingleThread()
.Build();
await bot.Start();
@@ -21,12 +21,15 @@ namespace InlineAndReplyCombination
.DefaultCommands()
.UseJSON(Path.Combine(Directory.GetCurrentDirectory(), "states.json"))
.UseEnglish()
.UseSingleThread()
.Build();
await BotBaseInstance.UploadBotCommands();
BotBaseInstance.BotCommand += BotBaseInstance_BotCommand;
await BotBaseInstance.Start();
@@ -37,5 +40,28 @@ namespace InlineAndReplyCombination
await BotBaseInstance.Stop();
}
private static async Task BotBaseInstance_BotCommand(object sender, TelegramBotBase.Args.BotCommandEventArgs e)
{
switch(e.Command)
{
case "/start":
var start = new StartForm();
await e.Device.ActiveForm.NavigateTo(start);
break;
}
}
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\TelegramBotBase\TelegramBotBase.csproj" />
<ProjectReference Include="..\TelegramBotBase.Experiments\TelegramBotBase.Experiments.ActionManager.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
public class HiddenForm : AutoCleanForm
{
public String value { get; set; }
public HiddenForm() {
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
value = value.Replace("_", "\\_");
await Device.Send($"Welcome to Hidden form\n\nThe given value is {value}", bf);
}
}
}
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
public class HiddenForm_EndsWith : AutoCleanForm
{
public String value { get; set; }
public HiddenForm_EndsWith() {
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
value = value.Replace("_", "\\_");
await Device.Send($"Welcome to Hidden ends with form\n\nThe given value is {value}", bf);
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
public class HiddenLetterForm : AutoCleanForm
{
public Guid letterId { get; set; }
public HiddenLetterForm()
{
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
await Device.Send($"Welcome to Hidden letter form\n\nYour letter Id is: {letterId}", bf);
}
}
}
@@ -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 DemoBot.Forms
{
public class HiddenOpenForm : AutoCleanForm
{
public Guid guid { get; set; }
public HiddenOpenForm()
{
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
await Device.Send($"Welcome to Hidden open form\n\nYour guid is: {guid}", bf);
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
public class HiddenTicketForm : AutoCleanForm
{
public Guid ticketId { get; set; }
public HiddenTicketForm()
{
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
}
public override async Task Action(MessageResult message)
{
if (message.RawData != "start")
{
return;
}
await message.ConfirmAction("Lets go");
message.Handled = true;
var st = new StartForm();
await NavigateTo(st);
}
public override async Task Render(MessageResult message)
{
var bf = new ButtonForm();
bf.AddButtonRow("Goto Start", "start");
await Device.Send($"Welcome to Hidden ticket form\n\nYour ticket Id is: {ticketId}", bf);
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace DemoBot.Forms
{
internal class StartForm : AutoCleanForm
{
public StartForm()
{
DeleteMode = TelegramBotBase.Enums.EDeleteMode.OnLeavingForm;
DeleteSide = TelegramBotBase.Enums.EDeleteSide.Both;
Opened += StartForm_Opened;
}
private async Task StartForm_Opened(object sender, EventArgs e)
{
await Device.Send("Hey!\r\n\r\nChoose the /test command to get a message from outside.", disableNotification: true);
}
public override async Task Load(MessageResult message)
{
}
}
}
@@ -0,0 +1,310 @@
using DemoBot.Forms;
using Telegram.Bot;
using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Builder;
using TelegramBotBase.Commands;
using TelegramBotBase.Form;
using TelegramBotBase.Base;
using TelegramBotBase.Experiments.ActionManager;
using TelegramBotBase.Experiments.ActionManager.Actions;
using TelegramBotBase.Experiments.ActionManager.Navigation;
using Telegram.Bot.Types;
namespace DemoBot
{
internal class Program
{
public static String Token = Environment.GetEnvironmentVariable("API_KEY") ?? throw new Exception("API_KEY is not set");
static async Task Main(string[] args)
{
//Using a custom FormBase message loop which is based on the original FormBaseMessageLoop within the framework.
//Would integrate this later into the BotBaseBuilder -> MessageLoop step.
//var cfb = new CustomFormBaseMessageLoop();
//Example 1: Long version
var eam = ExternalActionManager.Configure(config =>
{
//Waiting for input starting with 'n_'
config.AddStartsWithNavigation<HiddenForm>("n_", (a, b) =>
{
a.value = b;
});
//Minimal version, using reflection right now
config.AddStartsWithNavigation<HiddenForm>("a_", (a, b) =>
{
a.value = b;
});
//Waiting for input starting with 't_'
config.AddStartsWithNavigation(typeof(HiddenForm), "t_", (a, b) =>
{
var hf = a as HiddenForm;
if (hf == null)
return;
hf.value = b;
});
//Minimal version, using reflection right now
config.AddEndsWithNavigation<HiddenForm_EndsWith>("_u", (a, b) =>
{
a.value = b;
});
//Deserialize input and waiting for the method property to has value 'tickets'
config.AddGuidNavigation<HiddenTicketForm>("tickets", (a, b) =>
{
a.ticketId = b;
});
//Minimal version, using reflection right now
config.AddGuidNavigation<HiddenLetterForm>("letters", (a, b) =>
{
a.letterId = b;
});
//Deserialize input and waiting for the method property to has value 'open'
config.AddGuidNavigation<HiddenOpenForm>("open", (a, b) =>
{
a.guid = b;
});
//Do custom action
config.AddStartsWithAction("p_", StartsWithActionCallback_Demo);
//Do custom action
config.AddStartsWithAction("p2_", async (value, update, message) =>
{
if (message.UpdateData.CallbackQuery == null)
return;
await update.Device.ConfirmAction(message.UpdateData.CallbackQuery.Id, "Confirmed!");
});
});
//Example 2: Short version
var eam2 = ExternalActionManager.Configure(config =>
{
//Waiting for input starting with 'n_'
config.AddStartsWithNavigation<HiddenForm>("n_", a => a.value);
//Waiting for input starting with 'a_'
config.AddStartsWithNavigation<HiddenForm>("a_", a => a.value);
//Waiting for input starting with 't_' (Non generic version)
config.AddStartsWithNavigation(typeof(HiddenForm), "t_", a => ((HiddenForm)a).value);
//Waiting for input ending with 'n_'
config.AddEndsWithNavigation<HiddenForm_EndsWith>("_u", a => a.value);
//Deserialize input and waiting for the method property to has value 'tickets'
config.AddGuidNavigation<HiddenTicketForm>("tickets", a => a.ticketId);
//Minimal version, using reflection right now
config.AddGuidNavigation<HiddenLetterForm>("letters", a => a.letterId);
//Deserialize input and waiting for the method property to has value 'open'
config.AddGuidNavigation<HiddenOpenForm>("open", a => a.guid);
//Do custom action
config.AddStartsWithAction("p_", StartsWithActionCallback_Demo);
//Do custom action
config.AddStartsWithAction("p2_", async (value, update, message) =>
{
if (message.UpdateData.CallbackQuery == null)
return;
await update.Device.ConfirmAction(message.UpdateData.CallbackQuery.Id, "Confirmed!");
});
config.AddGuidAction("guid.test.too.long", async (g, c, u, m) =>
{
if (m.UpdateData.CallbackQuery == null)
return;
await u.Device.ConfirmAction(m.UpdateData.CallbackQuery.Id, "Confirmed!");
});
});
var bb = BotBaseBuilder.Create()
.WithAPIKey(Token)
.ActionMessageLoop(eam2)
.WithStartForm<Forms.StartForm>()
.NoProxy()
.CustomCommands(a =>
{
a.Start("Starts the bot");
a.Add("test", "Sends a test notification");
a.Add("invalid", "Try to send an invalid data message");
})
.NoSerialization()
.UseGerman()
.UseSingleThread()
.Build();
bb.BotCommand += Bb_BotCommand;
bb.UnhandledCall += Bb_UnhandledCall;
await bb.Start();
await bb.UploadBotCommands();
Console.WriteLine("Bot started.");
Console.ReadLine();
await bb.Stop();
}
private static async Task StartsWithActionCallback_Demo(String value, UpdateResult ur, MessageResult mr)
{
if (mr.UpdateData.CallbackQuery == null)
return;
await ur.Device.ConfirmAction(mr.UpdateData.CallbackQuery.Id, "Confirmed!");
}
private static void Bb_UnhandledCall(object? sender, TelegramBotBase.Args.UnhandledCallEventArgs e)
{
Console.WriteLine($"Unhandled call: {e.RawData}");
}
private static async Task Bb_BotCommand(object sender, TelegramBotBase.Args.BotCommandEventArgs e)
{
var current_form = e.Device.ActiveForm;
switch (e.Command)
{
case "/start":
//Already on start form
if (current_form.GetType() == typeof(Forms.StartForm))
{
return;
}
var st = new Forms.StartForm();
await current_form.NavigateTo(st);
break;
case "/test":
//Send test notification
//Test values
String max_value = "n_".PadRight(32, '1'); //Starts with
String max_value2 = "t_".PadRight(32, '2'); //Starts with
String max_value3 = "a_".PadRight(32, '3'); //Starts with
String max_value4 = "_u".PadLeft(32, '4'); //Ends with
String max_value5 = "p_".PadRight(32, '5'); //Ends with
String max_value6 = "p2_".PadRight(32, '6'); //Ends with
Guid test_value = Guid.NewGuid(); //Unhandled caller
var callback_guid = GuidNavigation.GetCallback("open", Guid.NewGuid()); //HiddenOpenForm
var callback_tickets = GuidNavigation.GetCallback("tickets", Guid.NewGuid()); //HiddenTicketForm
var callback_letters = GuidNavigation.GetCallback("letters", Guid.NewGuid()); //HiddenLetterForm
String message = $"Test notification from 'outside'\n\nTest values are:\n\nTest: {max_value}\nTest2: {max_value2}\nTest3: {max_value3}\nTest (Guid): {test_value.ToString()}\nTest (Callback Guid): {callback_guid.Value}\nTickets (Guid): {callback_tickets.Value}\nLetters (Guid): {callback_letters.Value}\n";
var tb = new TelegramBotClient(Token);
var bf = new ButtonForm();
bf.AddButtonRow(new ButtonBase("Ok", "n_ok"), new ButtonBase("Later", "n_later"));
bf.AddButtonRow("Test (n_)", max_value);
bf.AddButtonRow("Test2 (t_)", max_value2);
bf.AddButtonRow("Test3 (a_)", max_value3);
bf.AddButtonRow("Test4 (_u)", max_value4);
bf.AddButtonRow("Test5 (p_)", max_value5);
bf.AddButtonRow("Test6 (p2_)", max_value6);
bf.AddButtonRow("Test (Guid)", test_value.ToString());
bf.AddButtonRow("Test (Callback Gui)", callback_guid);
bf.AddButtonRow("Tickets", callback_tickets);
bf.AddButtonRow("Letters", callback_letters);
bf.AddButtonRow("Close", "close");
await tb.SendTextMessageAsync(e.DeviceId, message, disableNotification: true, replyMarkup: (InlineKeyboardMarkup)bf);
break;
case "/invalid":
Guid g = Guid.NewGuid();
var tb2 = new TelegramBotClient(Token);
var bf2 = new ButtonForm();
bf2.AddButtonRow("Test test", GuidAction.GetCallback("guid.test.too.long", g));
String message2 = "This is an invalid test message.";
await tb2.SendTextMessageAsync(e.DeviceId, message2, disableNotification: true, replyMarkup: (InlineKeyboardMarkup)bf2);
break;
}
}
}
}
@@ -0,0 +1,29 @@
# ExternalActionManager
Idea of this experiment is to find a good intuitive way for handling "Unhandled calls".
Right now they are "thrown" into an event handler and you have to take care over them on yourself.
Source of them would be in most cases Telegram Bot messages which has an Inlinekeyboard attached and comes from outside of the framework. (via webservice or other remote channels)
And the button press should navigate to a different form, or invoke a different action.
## How does it work ?
- Open the Bot
- Run the "/start" command
- Now you are on the only FormBase where you can get without interaction
- Use the "/test" command to invoke an "outside" message with some different buttons.
- Use them to navigate to different "hidden" forms
## Future ideas
- adding an Action for int and/or long datatypes
-
Begin: 18.01.2024
@@ -0,0 +1,13 @@
using TelegramBotBase.Base;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public static class EndsWithAction_Extensions
{
public static void AddEndsWithAction(this ExternalActionManager manager, string value, Func<String, UpdateResult, MessageResult, Task> action)
{
manager.Add(new EndsWithAction(value, action));
}
}
}
@@ -0,0 +1,29 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public class EndsWithAction : IExternalAction
{
public string SearchForString { get; set; }
public Action<FormBase, string> SetProperty { get; set; }
Func<String, UpdateResult, MessageResult, Task> Action;
public EndsWithAction(string searchFor, Func<String, UpdateResult, MessageResult, Task> action)
{
SearchForString = searchFor;
Action = action;
}
public bool DoesFit(string raw_data) => raw_data.EndsWith(SearchForString);
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(mr.RawData, ur, mr);
}
}
@@ -0,0 +1,14 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public static class GuidAction_Extensions
{
public static void AddGuidAction(this ExternalActionManager manager, string method, Func<Guid, CallbackData, UpdateResult, MessageResult, Task> action)
{
manager.Add(new GuidAction(method, action));
}
}
}
@@ -0,0 +1,85 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public class GuidAction : IExternalAction
{
public string Method { get; set; }
CallbackData? _lastData { get; set; }
Guid? _lastValue { get; set; }
Func<Guid, CallbackData, UpdateResult, MessageResult, Task> Action;
public GuidAction(string method, Func<Guid, CallbackData, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
Guid g;
if (Guid.TryParse(cd.Value, out g))
_lastValue = g;
_lastData = cd;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue.Value, _lastData, ur, mr);
public static CallbackData GetCallback(string method, Guid guid) => new CallbackData(method, guid.ToString());
}
public class GuidAction<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
Guid? _lastValue { get; set; }
Func<Guid, UpdateResult, MessageResult, Task> Action;
public GuidAction(string method, Func<Guid, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
Guid g;
if (Guid.TryParse(cd.Value, out g))
_lastValue = g;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue.Value, ur, mr);
}
}
@@ -0,0 +1,14 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public static class Int32Action_Extensions
{
public static void AddInt32Action(this ExternalActionManager manager, string method, Func<int, CallbackData, UpdateResult, MessageResult, Task> action)
{
manager.Add(new Int32Action(method, action));
}
}
}
@@ -0,0 +1,90 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public class Int32Action : IExternalAction
{
public string Method { get; set; }
CallbackData? _lastData { get; set; }
int? _lastValue { get; set; }
Func<int, CallbackData, UpdateResult, MessageResult, Task> Action;
public Int32Action(string method, Func<int, CallbackData, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
int i;
if (int.TryParse(cd.Value, out i))
_lastValue = i;
_lastData = cd;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue.Value, _lastData, ur, mr);
public static CallbackData GetCallback(string method, long l) => new CallbackData(method, l.ToString());
}
public class Int32Action<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
CallbackData? _lastData { get; set; }
int? _lastValue { get; set; }
Func<int, CallbackData, UpdateResult, MessageResult, Task> Action;
public Int32Action(string method, Func<int, CallbackData, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
int i;
if (int.TryParse(cd.Value, out i))
_lastValue = i;
_lastData = cd;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue.Value, _lastData, ur, mr);
}
}
@@ -0,0 +1,14 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public static class Int64Action_Extensions
{
public static void AddInt64Action(this ExternalActionManager manager, string method, Func<long, CallbackData, UpdateResult, MessageResult, Task> action)
{
manager.Add(new Int64Action(method, action));
}
}
}
@@ -0,0 +1,89 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public class Int64Action : IExternalAction
{
public string Method { get; set; }
CallbackData? _lastData { get; set; }
long? _lastValue { get; set; }
Func<long, CallbackData, UpdateResult, MessageResult, Task> Action;
public Int64Action(string method, Func<long, CallbackData, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
long l;
if (long.TryParse(cd.Value, out l))
_lastValue = l;
_lastData = cd;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue.Value, _lastData, ur, mr);
public static CallbackData GetCallback(string method, long l) => new CallbackData(method, l.ToString());
}
public class Int64Action<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
CallbackData? _lastData { get; set; }
long? _lastValue { get; set; }
Func<long, CallbackData, UpdateResult, MessageResult, Task> Action;
public Int64Action(string method, Func<long, CallbackData, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
long g;
if (long.TryParse(cd.Value, out g))
_lastValue = g;
_lastData = cd;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue.Value, _lastData, ur, mr);
}
}
@@ -0,0 +1,13 @@
using TelegramBotBase.Base;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public static class StartWithAction_Extensions
{
public static void AddStartsWithAction(this ExternalActionManager manager, string value, Func<String, UpdateResult, MessageResult, Task> action)
{
manager.Add(new StartWithAction(value, action));
}
}
}
@@ -0,0 +1,29 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public class StartWithAction : IExternalAction
{
public string SearchForString { get; set; }
public Action<FormBase, string> SetProperty { get; set; }
Func<String, UpdateResult, MessageResult, Task> Action;
public StartWithAction(string searchFor, Func<String, UpdateResult, MessageResult, Task> action)
{
SearchForString = searchFor;
Action = action;
}
public bool DoesFit(string raw_data) => raw_data.StartsWith(SearchForString);
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(mr.RawData, ur, mr);
}
}
@@ -0,0 +1,14 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public static class StringAction_Extensions
{
public static void AddStringAction(this ExternalActionManager manager, string method, Func<String, CallbackData, UpdateResult, MessageResult, Task> action)
{
manager.Add(new StringAction(method, action));
}
}
}
@@ -0,0 +1,85 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Actions
{
public class StringAction : IExternalAction
{
public string Method { get; set; }
CallbackData? _lastData { get; set; }
String? _lastValue { get; set; }
Func<string, CallbackData, UpdateResult, MessageResult, Task> Action;
public StringAction(string method, Func<string, CallbackData, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
_lastValue = cd.Value;
_lastData = cd;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue, _lastData, ur, mr);
public static CallbackData GetCallback(string method, string str) => new CallbackData(method, str);
}
public class StringAction<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
CallbackData? _lastData { get; set; }
String? _lastValue { get; set; }
Func<String, CallbackData, UpdateResult, MessageResult, Task> Action;
public StringAction(string method, Func<String, CallbackData, UpdateResult, MessageResult, Task> action)
{
Method = method;
Action = action;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
_lastValue = cd.Value;
_lastData = cd;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr) => await Action(_lastValue, _lastData, ur, mr);
}
}
@@ -0,0 +1,46 @@
using TelegramBotBase.Base;
namespace TelegramBotBase.Experiments.ActionManager
{
public partial class ExternalActionManager
{
List<IExternalAction> actions = new List<IExternalAction>();
public void Add(IExternalAction action)
{
actions.Add(action);
}
public async Task<bool> ManageCall(UpdateResult ur, MessageResult mr)
{
foreach (var action in actions)
{
if (!action.DoesFit(mr.RawData))
continue;
await action.DoAction(ur, mr);
return true;
}
return false;
}
/// <summary>
/// Creates an instance of the ExternalActionManager for configuration.
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public static ExternalActionManager Configure(Action<ExternalActionManager> action)
{
var eam = new ExternalActionManager();
action(eam);
return eam;
}
}
}
@@ -0,0 +1,13 @@
using TelegramBotBase.Base;
namespace TelegramBotBase.Experiments.ActionManager
{
public interface IExternalAction
{
bool DoesFit(string raw_data);
Task DoAction(UpdateResult ur, MessageResult mr);
}
}
@@ -0,0 +1,63 @@
using System.Linq.Expressions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public static class EndsWithNavigation_Extensions
{
public static void AddEndsWithNavigation<TForm>(this ExternalActionManager manager, string method, Expression<Func<TForm, string>> propertySelector)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<TForm, string>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new EndsWithNavigation<TForm>(method, setter));
}
public static void AddEndsWithNavigation<TForm>(this ExternalActionManager manager, string value, Action<TForm, string> setProperty)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new EndsWithNavigation<TForm>(value, setProperty));
}
public static void AddEndsWithNavigation(this ExternalActionManager manager, Type formType, string value, Expression<Func<FormBase, string>> propertySelector)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<FormBase, string>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new EndsWithNavigation(formType, value, setter));
}
public static void AddEndsWithNavigation(this ExternalActionManager manager, Type formType, string value, Action<FormBase, string> setProperty)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new EndsWithNavigation(formType, value, setProperty));
}
}
}
@@ -0,0 +1,77 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public class EndsWithNavigation : IExternalAction
{
public Type FormType { get; }
public string Value { get; set; }
public Action<FormBase, string> SetProperty { get; set; }
public EndsWithNavigation(Type formType, string value, Action<FormBase, string> setProperty)
{
FormType = formType;
Value = value;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data) => raw_data.EndsWith(Value);
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (mr.RawData != null)
{
SetProperty(new_form, mr.RawData);
}
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
public class EndsWithNavigation<TForm> : IExternalAction
where TForm : FormBase
{
public string Value { get; set; }
public Action<TForm, string> SetProperty { get; set; }
public EndsWithNavigation(string value, Action<TForm, string> setProperty)
{
Value = value;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data) => raw_data.EndsWith(Value);
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (mr.RawData != null)
{
SetProperty(new_form, mr.RawData);
}
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
}
@@ -0,0 +1,63 @@
using System.Linq.Expressions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public static class GuidNavigation_Extensions
{
public static void AddGuidNavigation<TForm>(this ExternalActionManager manager, string method, Expression<Func<TForm, Guid>> propertySelector)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<TForm, Guid>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new GuidNavigation<TForm>(method, setter));
}
public static void AddGuidNavigation<TForm>(this ExternalActionManager manager, string method, Action<TForm, Guid> action)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new GuidNavigation<TForm>(method, action));
}
public static void AddGuidNavigation(this ExternalActionManager manager, Type formType, string value, Expression<Func<FormBase, Guid>> propertySelector)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<FormBase, Guid>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new GuidNavigation(formType, value, setter));
}
public static void AddGuidNavigation(this ExternalActionManager manager, Type formType, string method, Action<FormBase, Guid> action)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new GuidNavigation(formType, method, action));
}
}
}
@@ -0,0 +1,106 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public class GuidNavigation : IExternalAction
{
public Type FormType { get; }
public string Method { get; set; }
public Action<FormBase, Guid> SetProperty { get; set; }
Guid? _lastValue { get; set; }
public GuidNavigation(Type formType, string method, Action<FormBase, Guid> setProperty)
{
FormType = formType;
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
var cd = CallbackData.Deserialize(raw_action);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
Guid g;
if (Guid.TryParse(cd.Value, out g))
_lastValue = g;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
public static CallbackData GetCallback(string method, Guid guid) => new CallbackData(method, guid.ToString());
}
public class GuidNavigation<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
public Action<TForm, Guid> SetProperty { get; set; }
Guid? _lastValue { get; set; }
public GuidNavigation(string method, Action<TForm, Guid> setProperty)
{
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
Guid g;
if (Guid.TryParse(cd.Value, out g))
_lastValue = g;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
}
@@ -0,0 +1,63 @@
using System.Linq.Expressions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public static class Int32Navigation_Extensions
{
public static void AddInt32Navigation<TForm>(this ExternalActionManager manager, string method, Expression<Func<TForm, int>> propertySelector)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<TForm, int>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new Int32Navigation<TForm>(method, setter));
}
public static void AddInt32Navigation<TForm>(this ExternalActionManager manager, string method, Action<TForm, int> action)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new Int32Navigation<TForm>(method, action));
}
public static void AddInt32Navigation(this ExternalActionManager manager, Type formType, string value, Expression<Func<FormBase, int>> propertySelector)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<FormBase, int>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new Int32Navigation(formType, value, setter));
}
public static void AddInt32Navigation(this ExternalActionManager manager, Type formType, string method, Action<FormBase, int> action)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new Int32Navigation(formType, method, action));
}
}
}
@@ -0,0 +1,106 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public class Int32Navigation : IExternalAction
{
public Type FormType { get; }
public string Method { get; set; }
public Action<FormBase, int> SetProperty { get; set; }
int? _lastValue { get; set; }
public Int32Navigation(Type formType, string method, Action<FormBase, int> setProperty)
{
FormType = formType;
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
var cd = CallbackData.Deserialize(raw_action);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
int i;
if (int.TryParse(cd.Value, out i))
_lastValue = i;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
public static CallbackData GetCallback(string method, int i) => new CallbackData(method, i.ToString());
}
public class Int32Navigation<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
public Action<TForm, int> SetProperty { get; set; }
int? _lastValue { get; set; }
public Int32Navigation(string method, Action<TForm, int> setProperty)
{
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
int g;
if (int.TryParse(cd.Value, out g))
_lastValue = g;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
}
@@ -0,0 +1,63 @@
using System.Linq.Expressions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public static class Int64Navigation_Extensions
{
public static void AddInt64Navigation<TForm>(this ExternalActionManager manager, string method, Expression<Func<TForm, long>> propertySelector)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<TForm, long>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new Int64Navigation<TForm>(method, setter));
}
public static void AddInt64Navigation<TForm>(this ExternalActionManager manager, string method, Action<TForm, long> action)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new Int64Navigation<TForm>(method, action));
}
public static void AddInt64Navigation(this ExternalActionManager manager, Type formType, string value, Expression<Func<FormBase, long>> propertySelector)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<FormBase, long>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new Int64Navigation(formType, value, setter));
}
public static void AddInt64Navigation(this ExternalActionManager manager, Type formType, string method, Action<FormBase, long> action)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new Int64Navigation(formType, method, action));
}
}
}
@@ -0,0 +1,106 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public class Int64Navigation : IExternalAction
{
public Type FormType { get; }
public string Method { get; set; }
public Action<FormBase, long> SetProperty { get; set; }
long? _lastValue { get; set; }
public Int64Navigation(Type formType, string method, Action<FormBase, long> setProperty)
{
FormType = formType;
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
var cd = CallbackData.Deserialize(raw_action);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
long l;
if (long.TryParse(cd.Value, out l))
_lastValue = l;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
public static CallbackData GetCallback(string method, long l) => new CallbackData(method, l.ToString());
}
public class Int64Navigation<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
public Action<TForm, long> SetProperty { get; set; }
long? _lastValue { get; set; }
public Int64Navigation(string method, Action<TForm, long> setProperty)
{
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
long l;
if (long.TryParse(cd.Value, out l))
_lastValue = l;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (_lastValue != null)
SetProperty(new_form, _lastValue.Value);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
}
@@ -0,0 +1,63 @@
using System.Linq.Expressions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public static class StartWithNavigation_Extensions
{
public static void AddStartsWithNavigation<TForm>(this ExternalActionManager manager, string method, Expression<Func<TForm, string>> propertySelector)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<TForm, string>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new StartWithNavigation<TForm>(method, setter));
}
public static void AddStartsWithNavigation<TForm>(this ExternalActionManager manager, string value, Action<TForm, string> setProperty)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new StartWithNavigation<TForm>(value, setProperty));
}
public static void AddStartsWithNavigation(this ExternalActionManager manager, Type formType, string value, Expression<Func<FormBase, string>> propertySelector)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<FormBase, string>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new StartWithNavigation(formType, value, setter));
}
public static void AddStartsWithNavigation(this ExternalActionManager manager, Type formType, string value, Action<FormBase, string> setProperty)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new StartWithNavigation(formType, value, setProperty));
}
}
}
@@ -0,0 +1,74 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public class StartWithNavigation : IExternalAction
{
public Type FormType { get; }
public string Value { get; set; }
public Action<FormBase, string> SetProperty { get; set; }
public StartWithNavigation(Type formType, string value, Action<FormBase, string> setProperty)
{
FormType = formType;
Value = value;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data) => raw_data.StartsWith(Value);
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (mr.RawData != null)
{
SetProperty(new_form, mr.RawData);
}
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
public class StartWithNavigation<TForm> : IExternalAction
where TForm : FormBase
{
public string Value { get; set; }
public Action<TForm, string> SetProperty { get; set; }
public StartWithNavigation(string value, Action<TForm, string> setProperty)
{
Value = value;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data) => raw_data.StartsWith(Value);
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (mr.RawData != null)
{
SetProperty(new_form, mr.RawData);
}
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
}
@@ -0,0 +1,63 @@
using System.Linq.Expressions;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public static class StringNavigation_Extensions
{
public static void AddStringNavigation<TForm>(this ExternalActionManager manager, string method, Expression<Func<TForm, String>> propertySelector)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<TForm, String>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new StringNavigation<TForm>(method, setter));
}
public static void AddStringNavigation<TForm>(this ExternalActionManager manager, string method, Action<TForm, String> action)
where TForm : FormBase
{
if (!typeof(FormBase).IsAssignableFrom(typeof(TForm)))
{
throw new ArgumentException($"{nameof(TForm)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new StringNavigation<TForm>(method, action));
}
public static void AddStringNavigation(this ExternalActionManager manager, Type formType, string value, Expression<Func<FormBase, String>> propertySelector)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
var newValue = Expression.Parameter(propertySelector.Body.Type);
var assign = Expression.Lambda<Action<FormBase, String>>(Expression.Assign(propertySelector.Body, newValue), propertySelector.Parameters[0], newValue);
var setter = assign.Compile(true);
manager.Add(new StringNavigation(formType, value, setter));
}
public static void AddStringNavigation(this ExternalActionManager manager, Type formType, string method, Action<FormBase, String> action)
{
if (!typeof(FormBase).IsAssignableFrom(formType))
{
throw new ArgumentException($"{nameof(formType)} argument must be a {nameof(FormBase)} type");
}
manager.Add(new StringNavigation(formType, method, action));
}
}
}
@@ -0,0 +1,100 @@
using TelegramBotBase.Base;
using TelegramBotBase.Form;
namespace TelegramBotBase.Experiments.ActionManager.Navigation
{
public class StringNavigation : IExternalAction
{
public Type FormType { get; }
public string Method { get; set; }
public Action<FormBase, String> SetProperty { get; set; }
String? _lastValue { get; set; }
public StringNavigation(Type formType, string method, Action<FormBase, String> setProperty)
{
FormType = formType;
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_action)
{
var cd = CallbackData.Deserialize(raw_action);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
_lastValue = cd.Value;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var new_form = FormType.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase;
if (_lastValue != null)
SetProperty(new_form, _lastValue);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
public static CallbackData GetCallback(string method, String str) => new CallbackData(method, str);
}
public class StringNavigation<TForm> : IExternalAction
where TForm : FormBase
{
public string Method { get; set; }
public Action<TForm, String> SetProperty { get; set; }
String? _lastValue { get; set; }
public StringNavigation(string method, Action<TForm, String> setProperty)
{
Method = method;
SetProperty = setProperty;
}
public bool DoesFit(string raw_data)
{
var cd = CallbackData.Deserialize(raw_data);
if (cd == null)
return false;
if (cd.Method != Method)
return false;
_lastValue = cd.Value;
return true;
}
public async Task DoAction(UpdateResult ur, MessageResult mr)
{
await mr.ConfirmAction();
var type = typeof(TForm);
TForm new_form = type.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as TForm;
if (_lastValue != null)
SetProperty(new_form, _lastValue);
await ur.Device.ActiveForm.NavigateTo(new_form);
}
}
}
@@ -0,0 +1,146 @@
using System.ComponentModel;
using Telegram.Bot.Types.Enums;
using TelegramBotBase;
using TelegramBotBase.Args;
using TelegramBotBase.Base;
using TelegramBotBase.Interfaces;
using TelegramBotBase.Sessions;
namespace TelegramBotBase.Experiments.ActionManager
{
public class CustomFormBaseMessageLoop : IMessageLoopFactory
{
private static readonly object EvUnhandledCall = new();
private readonly EventHandlerList _events = new();
public ExternalActionManager ExternalActionManager { get; set; }
public async Task MessageLoop(BotBase bot, DeviceSession s, UpdateResult ur, MessageResult mr)
{
var update = ur.RawData;
if (update.Type != UpdateType.Message
&& update.Type != UpdateType.EditedMessage
&& update.Type != UpdateType.CallbackQuery)
{
return;
}
//Remove unecessary parameter from method call in the future
var session = ur.Device;
//Is this a bot command ?
if (mr.IsFirstHandler && mr.IsBotCommand && bot.IsKnownBotCommand(mr.BotCommand))
{
var sce = new BotCommandEventArgs(mr.BotCommand, mr.BotCommandParameters, mr.Message, session.DeviceId,
session);
await bot.OnBotCommand(sce);
if (sce.Handled)
{
return;
}
}
mr.Device = session;
ur.Device = session;
var activeForm = session.ActiveForm;
//Pre Loading Event
await activeForm.PreLoad(mr);
//Send Load event to controls
await activeForm.LoadControls(mr);
//Loading Event
await activeForm.Load(mr);
//Is Attachment ? (Photo, Audio, Video, Contact, Location, Document) (Ignore Callback Queries)
if (update.Type == UpdateType.Message)
{
if ((mr.MessageType == MessageType.Contact)
| (mr.MessageType == MessageType.Document)
| (mr.MessageType == MessageType.Location)
| (mr.MessageType == MessageType.Photo)
| (mr.MessageType == MessageType.Video)
| (mr.MessageType == MessageType.Audio))
{
await activeForm.SentData(new DataResult(ur));
}
}
//Message edited ?
if (update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event
if (!session.FormSwitched && mr.IsAction)
{
//Send Action event to controls
await activeForm.ActionControls(mr);
//Send Action event to form itself
await activeForm.Action(mr);
if (!mr.Handled)
{
var handled = await ExternalActionManager?.ManageCall(ur, mr);
if (handled)
{
mr.Handled = true;
if (!session.FormSwitched)
{
return;
}
}
else
{
var uhc = new UnhandledCallEventArgs(ur.Message.Text, mr.RawData, session.DeviceId, mr.MessageId, ur.Message, session);
OnUnhandledCall(uhc);
if (uhc.Handled)
{
mr.Handled = true;
if (!session.FormSwitched)
{
return;
}
}
}
}
}
if (!session.FormSwitched)
{
//Render Event
await activeForm.RenderControls(mr);
await activeForm.Render(mr);
}
}
/// <summary>
/// Will be called if no form handled this call
/// </summary>
public event EventHandler<UnhandledCallEventArgs> UnhandledCall
{
add => _events.AddHandler(EvUnhandledCall, value);
remove => _events.RemoveHandler(EvUnhandledCall, value);
}
public void OnUnhandledCall(UnhandledCallEventArgs e)
{
(_events[EvUnhandledCall] as EventHandler<UnhandledCallEventArgs>)?.Invoke(this, e);
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Builder.Interfaces;
using TelegramBotBase.Interfaces;
namespace TelegramBotBase.Experiments.ActionManager
{
public static class StaticExtensions
{
public static IStartFormSelectionStage ActionMessageLoop(this IMessageLoopSelectionStage loopSelection, ExternalActionManager managerInstance)
{
var cfb = new CustomFormBaseMessageLoop();
cfb.ExternalActionManager = managerInstance;
return loopSelection.CustomMessageLoop(cfb);
}
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\TelegramBotBase\TelegramBotBase.csproj" />
</ItemGroup>
</Project>
+63 -4
View File
@@ -1,6 +1,27 @@
# Development Lab
Attention: This is a lab for **experimental features**. Here we can try out different ideas and scenarios if they will work and what can be improved.
There is no guarantee that any of these ideas comes in the main project.
Please try everything on your own. Nothing can work, so everything can as well.
You will find all experiments within the ["Experiments"](Experiments) sub folder.
For feedback please join our Telegram group.
**Support group: [@tgbotbase](https://t.me/tgbotbase)**
## current experiments
- [ExternalActionManager](Experiments/ExternalActionManager/DemoBot) => Manager for unhandled actions from "outside"
---
# .NET Telegram Bot Framework - Context based addon
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase/)
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/vpre/TelegramBotBase.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase/)
[![Telegram chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://www.t.me/tgbotbase)
[![License](https://img.shields.io/github/license/MajMcCloud/telegrambotframework.svg?style=flat-square&maxAge=2592000&label=License)](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
@@ -10,12 +31,14 @@
**Support group: [@tgbotbase](https://t.me/tgbotbase)**
**Discord Server: [https://discord.gg/p3PPhYbTf](https://discord.gg/p3PPhYbTf)**
**Discord Server: [https://discord.gg/V3PxreDYfE](https://discord.gg/V3PxreDYfE)**
**Releases: [GitHub](https://github.com/MajMcCloud/TelegramBotFramework/releases)**
## Donate
Paypal: [https://paypal.me/majmccloud](https://paypal.me/majmccloud)
Bitcoin: `1GoUJYMwAvBipQTfw2FKydAz12J8RDyeJs` / `bc1qqwlp0p5ley29lsu6jhe0qv7s7963kfc7d0m53d`
Etherium: `0xAf3835104c2C3E5b3e721FA2c7365955e87DB931`
@@ -57,6 +80,7 @@ BitTorrent: `TYVZSykaVT1nKZnz9hjDgBRNB9VavU1bpW`
* [TaggedButtonGrid](#tagged-button-grid)
* [CheckedButtonList](#checked-button-list)
* [MultiToggleButton](#multi-toggle-button)
- [Localizations](#localizations)
- [Groups](#groups)
* [SplitterForm](#splitter-form)
* [GroupForm](#group-form)
@@ -641,7 +665,7 @@ await this.NavigateTo(cd);
A minimal control which allows to manage a classic "text" message and update its later without having to keep track of the message id.
<img src=".github/images/label.gif?raw=true" />
<img src=".github/images/label.gif?raw=true" style="width:500px" />
Check the example project [TelegramBotBase.Test/Tests/Controls/LabelForm.cs](TelegramBotBase.Test/Tests/Controls/LabelForm.cs)
@@ -712,6 +736,19 @@ Check the example project [TelegramBotBase.Test/Tests/Controls/CheckedButtonList
Check the example project [TelegramBotBase.Test/Tests/Controls/MultiToggleButtonForm.cs](TelegramBotBase.Test/Tests/Controls/MultiToggleButtonForm.cs)
## Localizations
The current available languages for controls are:
- English
- German
- Persian
You can add other languages easily by creating a subclass of the [TelegramBotBase/Localizations/Localization.cs](TelegramBotBase/Localizations/Localization.cs) class.
To set the default language set the *Language* property on the static [TelegramBotBase/Localizations/Default.cs](TelegramBotBase/Localizations/Default.cs) instance.
## Groups
For groups, there are multiple different tools which help to work with and allows bot also to manage
@@ -1034,6 +1071,21 @@ Extends the base package with some additional image methods like SendPhoto (usin
[https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/)
Project: [open source](TelegramBotBase.Extensions.Images/)
### TelegramBotBase.Extensions.Images.IronSoftware
Extends the base package with some additional image methods like SendPhoto (using Bitmap)
Important: This extension uses the IronSoftware drawing library.
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
[![Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware)
[https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
Project: [open source](TelegramBotBase.Extensions.Images.IronSoftware/)
### TelegramBotBase.Extensions.Serializer.Database.MSSQL
A session serializer for Microsoft SQL Server.
@@ -1043,6 +1095,8 @@ A session serializer for Microsoft SQL Server.
[https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
Project: [open source](TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
## Test Project
There is a "TelegramBotBase.Test" project inside the repository which includes minimal examples for all controls available.
@@ -1080,4 +1134,9 @@ Having already a web application and want to add a TelegramBot side-by-side with
Want to use Inline- and ReplyMarkup at the same time ? Here is an example how you can do that:
- [Examples/InlineAndReplyCombination](Examples/InlineAndReplyCombination)
- [Examples/InlineAndReplyCombination](Examples/InlineAndReplyCombination)
Alpha: Full Dependency Injection example within this framework.
- [Examples/DependencyInjection](Examples/DependencyInjection)
@@ -0,0 +1,73 @@
using IronSoftware.Drawing;
using SixLabors.ImageSharp;
using System.IO;
using System.Threading.Tasks;
using Telegram.Bot.Types;
using TelegramBotBase.Form;
using TelegramBotBase.Sessions;
using static IronSoftware.Drawing.AnyBitmap;
using SKImage = SixLabors.ImageSharp.Image;
namespace TelegramBotBase.Extensions.Images.IronSoftware
{
public static class ImageExtensions
{
public static Stream ToStream(this AnyBitmap image, ImageFormat format)
{
var stream = new MemoryStream();
image.ExportStream(stream, format);
stream.Position = 0;
return stream;
}
public static async Task<Stream> ToStream(this SKImage image)
{
var stream = new MemoryStream();
await image.SaveAsPngAsync(stream);
stream.Position = 0;
return stream;
}
/// <summary>
/// Sends an image
/// </summary>
/// <param name="image"></param>
/// <param name="name"></param>
/// <param name="buttons"></param>
/// <param name="replyTo"></param>
/// <param name="disableNotification"></param>
/// <returns></returns>
public static async Task<Message> SendPhoto(this DeviceSession session, AnyBitmap image, string name,
string caption, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false)
{
using (var fileStream = ToStream(image, ImageFormat.Png))
{
var fts = InputFile.FromStream(fileStream, name);
return await session.SendPhoto(fts, caption, buttons, replyTo, disableNotification);
}
}
/// <summary>
/// Sends an image
/// </summary>
/// <param name="image"></param>
/// <param name="name"></param>
/// <param name="buttons"></param>
/// <param name="replyTo"></param>
/// <param name="disableNotification"></param>
/// <returns></returns>
public static async Task<Message> SendPhoto(this DeviceSession session, SKImage image, string name,
string caption, ButtonForm buttons = null, int replyTo = 0,
bool disableNotification = false)
{
using (var fileStream = await ToStream(image))
{
var fts = InputFile.FromStream(fileStream, name);
return await session.SendPhoto(fts, caption, buttons, replyTo, disableNotification);
}
}
}
}
@@ -0,0 +1,12 @@
# TelegramBotBase.Extensions.Images.IronSoftware
[![NuGet version (TelegramBotBase)](https://img.shields.io/nuget/v/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
[![Telegram chat](https://img.shields.io/badge/Support_Chat-Telegram-blue.svg?style=flat-square)](https://www.t.me/tgbotbase)
[![License](https://img.shields.io/github/license/MajMcCloud/telegrambotframework.svg?style=flat-square&maxAge=2592000&label=License)](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
[![Package Downloads](https://img.shields.io/nuget/dt/TelegramBotBase.Extensions.Images.IronSoftware.svg?style=flat-square&label=Package%20Downloads)](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware)
This extension uses the [IronSoftware](https://ironsoftware.com/open-source/csharp/drawing/docs/) drawing library.
Check [https://ironsoftware.com/open-source/csharp/drawing/docs/](https://ironsoftware.com/open-source/csharp/drawing/docs/) for more details on this library.
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6</TargetFrameworks>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images.IronSoftware</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images.IronSoftware</PackageProjectUrl>
<Copyright>MIT</Copyright>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.1</FileVersion>
<Description>This is an extension for sending Bitmap/Images platform independent by using IronSoftware's drawing library via TelegramBotBase.</Description>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IronSoftware.System.Drawing" Version="2024.1.1" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="TelegramBotBase" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>
@@ -2,11 +2,17 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6</TargetFrameworks>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images</RepositoryUrl>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework/tree/development/TelegramBotBase.Extensions.Images</PackageProjectUrl>
<Copyright>MIT</Copyright>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<Authors>$(AssemblyName)</Authors>
<Description>This is an extension for sending Bitmap/Images via TelegramBotBase.</Description>
<FileVersion>1.1.1</FileVersion>
<AssemblyVersion>1.1.1</AssemblyVersion>
<Version>1.1.1</Version>
</PropertyGroup>
<ItemGroup>
@@ -15,10 +21,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
<PackageReference Include="TelegramBotBase" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
@@ -31,7 +31,7 @@ namespace TelegramBotBase.Extensions.Serializer.Database.MSSQL
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
}
@@ -16,15 +16,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.0.0"/>
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.3" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj"/>
<PackageReference Include="TelegramBotBase" Version="6.0.0" />
</ItemGroup>
</Project>
+1
View File
@@ -32,6 +32,7 @@ internal class Program
})
.NoSerialization()
.UseEnglish()
.UseThreadPool()
.Build();
@@ -8,8 +8,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\TelegramBotBase.Extensions.Images\TelegramBotBase.Extensions.Images.csproj" />
<ProjectReference Include="..\TelegramBotBase\TelegramBotBase.csproj" />
<PackageReference Include="TelegramBotBase" Version="6.4.0-preview.1" />
<PackageReference Include="TelegramBotBase.Extensions.Images" Version="1.1.2" />
</ItemGroup>
</Project>
@@ -11,7 +11,7 @@ namespace TelegramBotBase.Example.Tests.Controls;
public class LabelForm : AutoCleanForm
{
TelegramBotBase.Controls.Inline.LabelControl _label;
TelegramBotBase.Controls.Inline.Label _label;
ButtonGrid _buttonGrid;
@@ -27,7 +27,7 @@ public class LabelForm : AutoCleanForm
private Task LabelForm_Init(object sender, InitEventArgs e)
{
//The "label" control
_label = new LabelControl("My test label");
_label = new Label("My test label");
AddControl(_label);
+14
View File
@@ -0,0 +1,14 @@
using System;
namespace TelegramBotBase.Base
{
public class ErrorResult : EventArgs
{
public ErrorResult(Exception exception)
{
Exception = exception;
}
public Exception Exception { get; }
}
}
+2
View File
@@ -32,6 +32,8 @@ public class FormBase : IDisposable
public MessageClient Client { get; set; }
IServiceProvider _serviceProvider = null;
/// <summary>
/// has this formular already been disposed ?
/// </summary>
+97 -16
View File
@@ -18,6 +18,7 @@ namespace TelegramBotBase.Base;
public class MessageClient
{
private static readonly object EvOnMessageLoop = new();
private static readonly object EvOnReceiveError = new();
private static object __evOnMessage = new();
@@ -27,6 +28,26 @@ public class MessageClient
private CancellationTokenSource _cancellationTokenSource;
public string ApiKey { get; }
public ITelegramBotClient TelegramClient { get; set; }
private EventHandlerList Events { get; } = new();
/// <summary>
/// Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before
// start polling. If set to true Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates
// should be set to not null, otherwise Telegram.Bot.Polling.ReceiverOptions.AllowedUpdates
// will effectively be set to receive all Telegram.Bot.Types.Updates.
/// </summary>
public bool ThrowPendingUpdates { get; set; }
public bool UseThreadPool { get; set; } = false;
public int ThreadPool_WorkerThreads { get; set; } = 1;
public int ThreadPool_IOThreads { get; set; } = 1;
public MessageClient(string apiKey)
{
@@ -94,11 +115,6 @@ public class MessageClient
}
public string ApiKey { get; }
public ITelegramBotClient TelegramClient { get; set; }
private EventHandlerList Events { get; } = new();
public void Prepare()
@@ -113,8 +129,21 @@ public class MessageClient
var receiverOptions = new ReceiverOptions();
TelegramClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions,
_cancellationTokenSource.Token);
receiverOptions.ThrowPendingUpdates = ThrowPendingUpdates;
if (UseThreadPool)
{
ThreadPool.SetMaxThreads(ThreadPool_WorkerThreads, ThreadPool_IOThreads);
TelegramClient.StartReceiving(HandleUpdateAsyncThreadPool, HandleErrorAsyncThreadPool, receiverOptions,
_cancellationTokenSource.Token);
}
else
{
TelegramClient.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions,
_cancellationTokenSource.Token);
}
}
public void StopReceiving()
@@ -122,27 +151,45 @@ public class MessageClient
_cancellationTokenSource.Cancel();
}
#region "Single Thread"
public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
await OnMessageLoop(new UpdateResult(update, null));
}
public Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception,
public async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception,
CancellationToken cancellationToken)
{
if (exception is ApiRequestException exApi)
await OnReceiveError(new ErrorResult(exception));
}
#endregion
#region "Thread Pool"
public Task HandleUpdateAsyncThreadPool(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
ThreadPool.QueueUserWorkItem(async a =>
{
Console.WriteLine($"Telegram API Error:\n[{exApi.ErrorCode}]\n{exApi.Message}");
}
else
{
Console.WriteLine(exception.ToString());
}
await OnMessageLoop(new UpdateResult(update, null));
});
return Task.CompletedTask;
}
public Task HandleErrorAsyncThreadPool(ITelegramBotClient botClient, Exception exception,
CancellationToken cancellationToken)
{
ThreadPool.QueueUserWorkItem(async a =>
{
await OnReceiveError(new ErrorResult(exception));
});
return Task.CompletedTask;
}
#endregion
/// <summary>
/// This will return the current list of bot commands.
@@ -186,7 +233,41 @@ public class MessageClient
public async Task OnMessageLoop(UpdateResult update)
{
await (Events[EvOnMessageLoop] as Async.AsyncEventHandler<UpdateResult>)?.Invoke(this, update);
var eventHandlers = (Events[EvOnMessageLoop] as Async.AsyncEventHandler<UpdateResult>)?.Invoke(this, update);
if (eventHandlers != null)
{
await eventHandlers;
}
}
public event Async.AsyncEventHandler<ErrorResult> ReceiveError
{
add => Events.AddHandler(EvOnReceiveError, value);
remove => Events.RemoveHandler(EvOnReceiveError, value);
}
public async Task OnReceiveError(ErrorResult update)
{
var eventHandlers = (Events[EvOnReceiveError] as Async.AsyncEventHandler<ErrorResult>)?.Invoke(this, update);
if (eventHandlers != null)
{
await eventHandlers;
return;
}
//Fallback when no event handler is used.
if (update.Exception is ApiRequestException exApi)
{
Console.WriteLine($"Telegram API Error:\n[{exApi.ErrorCode}]\n{exApi.Message}");
}
else
{
Console.WriteLine(update.Exception.ToString());
}
}
#endregion
+1
View File
@@ -16,6 +16,7 @@ public class UpdateResult : ResultBase
/// </summary>
public override long DeviceId =>
RawData?.Message?.Chat?.Id
?? RawData?.EditedMessage?.Chat?.Id
?? RawData?.CallbackQuery?.Message?.Chat?.Id
?? Device?.DeviceId
?? 0;
+4 -2
View File
@@ -115,12 +115,13 @@ public sealed class BotBase
if (ds == null)
{
ds = await Sessions.StartSession(e.DeviceId);
e.Device = ds;
ds.LastMessage = e.RawData.Message;
OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds));
OnSessionBegins(new SessionBeginEventArgs(e.DeviceId, ds));
}
e.Device = ds;
var mr = new MessageResult(e.RawData);
var i = 0;
@@ -210,6 +211,7 @@ public sealed class BotBase
try
{
var ds = Sessions.GetSession(deviceId);
e.Device = ds;
await MessageLoopFactory.MessageLoop(this, ds, new UpdateResult(e.UpdateData, ds), e);
+55 -11
View File
@@ -18,7 +18,7 @@ namespace TelegramBotBase.Builder;
public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage, IStartFormSelectionStage,
IBuildingStage, INetworkingSelectionStage, IBotCommandsStage, ISessionSerializationStage,
ILanguageSelectionStage
ILanguageSelectionStage, IThreadingStage
{
private string _apiKey;
@@ -39,7 +39,10 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
/// </summary>
private Dictionary<BotCommandScope, List<BotCommand>> BotCommandScopes { get; } = new();
/// <summary>
/// Creates a full BotBase instance with all parameters previously set.
/// </summary>
/// <returns></returns>
public BotBase Build()
{
var bot = new BotBase(_apiKey, _client)
@@ -84,6 +87,8 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
DefaultLanguage();
UseSingleThread();
return this;
}
@@ -104,6 +109,8 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
DefaultLanguage();
UseSingleThread();
return this;
}
@@ -122,6 +129,8 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
DefaultLanguage();
UseSingleThread();
return this;
}
@@ -204,7 +213,7 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
#region "Step 4 (Network Settings)"
public IBotCommandsStage WithProxy(string proxyAddress)
public IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false)
{
var url = new Uri(proxyAddress);
_client = new MessageClient(_apiKey, url)
@@ -214,11 +223,12 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
Timeout = new TimeSpan(0, 1, 0)
},
};
_client.ThrowPendingUpdates = throwPendingUpdates;
return this;
}
public IBotCommandsStage NoProxy()
public IBotCommandsStage NoProxy(bool throwPendingUpdates = false)
{
_client = new MessageClient(_apiKey)
{
@@ -227,11 +237,12 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
Timeout = new TimeSpan(0, 1, 0)
}
};
_client.ThrowPendingUpdates = throwPendingUpdates;
return this;
}
public IBotCommandsStage WithBotClient(TelegramBotClient tgclient)
public IBotCommandsStage WithBotClient(TelegramBotClient tgclient, bool throwPendingUpdates = false)
{
_client = new MessageClient(_apiKey, tgclient)
{
@@ -240,11 +251,12 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
Timeout = new TimeSpan(0, 1, 0)
}
};
_client.ThrowPendingUpdates = throwPendingUpdates;
return this;
}
public IBotCommandsStage WithHostAndPort(string proxyHost, int proxyPort)
public IBotCommandsStage WithHostAndPort(string proxyHost, int proxyPort, bool throwPendingUpdates = false)
{
_client = new MessageClient(_apiKey, proxyHost, proxyPort)
{
@@ -253,10 +265,11 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
Timeout = new TimeSpan(0, 1, 0)
}
};
_client.ThrowPendingUpdates = throwPendingUpdates;
return this;
}
public IBotCommandsStage WithHttpClient(HttpClient tgclient)
public IBotCommandsStage WithHttpClient(HttpClient tgclient, bool throwPendingUpdates = false)
{
_client = new MessageClient(_apiKey, tgclient)
{
@@ -265,6 +278,7 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
Timeout = new TimeSpan(0, 1, 0)
}
};
_client.ThrowPendingUpdates = throwPendingUpdates;
return this;
}
@@ -383,28 +397,58 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
#region "Step 7 (Language)"
public IBuildingStage DefaultLanguage()
public IThreadingStage DefaultLanguage()
{
return this;
}
public IBuildingStage UseEnglish()
public IThreadingStage UseEnglish()
{
Default.Language = new English();
return this;
}
public IBuildingStage UseGerman()
public IThreadingStage UseGerman()
{
Default.Language = new German();
return this;
}
public IBuildingStage Custom(Localization language)
public IThreadingStage UsePersian()
{
Default.Language = new Persian();
return this;
}
public IThreadingStage Custom(Localization language)
{
Default.Language = language;
return this;
}
#endregion
#region "Step 8 (Threading)"
public IBuildingStage UseSingleThread()
{
_client.UseThreadPool = false;
return this;
}
public IBuildingStage UseThreadPool(int workerThreads = 2, int ioThreads = 1)
{
_client.UseThreadPool = true;
_client.ThreadPool_WorkerThreads = workerThreads;
_client.ThreadPool_IOThreads = ioThreads;
return this;
}
#endregion
}
@@ -2,5 +2,9 @@
public interface IBuildingStage
{
/// <summary>
/// Creates a full BotBase instance with all parameters previously set.
/// </summary>
/// <returns></returns>
BotBase Build();
}
@@ -8,23 +8,29 @@ public interface ILanguageSelectionStage
/// Selects the default language for control usage. (English)
/// </summary>
/// <returns></returns>
IBuildingStage DefaultLanguage();
IThreadingStage DefaultLanguage();
/// <summary>
/// Selects english as the default language for control labels.
/// </summary>
/// <returns></returns>
IBuildingStage UseEnglish();
IThreadingStage UseEnglish();
/// <summary>
/// Selects german as the default language for control labels.
/// </summary>
/// <returns></returns>
IBuildingStage UseGerman();
IThreadingStage UseGerman();
/// <summary>
/// Selects persian as the default language for control labels.
/// </summary>
/// <returns></returns>
IThreadingStage UsePersian();
/// <summary>
/// Selects a custom language as the default language for control labels.
/// </summary>
/// <returns></returns>
IBuildingStage Custom(Localization language);
IThreadingStage Custom(Localization language);
}
@@ -9,22 +9,25 @@ public interface INetworkingSelectionStage
/// Chooses a proxy as network configuration.
/// </summary>
/// <param name="proxyAddress"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns>
IBotCommandsStage WithProxy(string proxyAddress);
IBotCommandsStage WithProxy(string proxyAddress, bool throwPendingUpdates = false);
/// <summary>
/// Do not choose a proxy as network configuration.
/// </summary>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns>
IBotCommandsStage NoProxy();
IBotCommandsStage NoProxy(bool throwPendingUpdates = false);
/// <summary>
/// Chooses a custom instance of TelegramBotClient.
/// </summary>
/// <param name="client"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns>
IBotCommandsStage WithBotClient(TelegramBotClient client);
IBotCommandsStage WithBotClient(TelegramBotClient client, bool throwPendingUpdates = false);
/// <summary>
@@ -32,13 +35,15 @@ public interface INetworkingSelectionStage
/// </summary>
/// <param name="proxyHost"></param>
/// <param name="Port"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns>
IBotCommandsStage WithHostAndPort(string proxyHost, int Port);
IBotCommandsStage WithHostAndPort(string proxyHost, int Port, bool throwPendingUpdates = false);
/// <summary>
/// Uses a custom http client.
/// </summary>
/// <param name="client"></param>
/// <param name="throwPendingUpdates">Indicates if all pending Telegram.Bot.Types.Updates should be thrown out before start polling.</param>
/// <returns></returns>
IBotCommandsStage WithHttpClient(HttpClient client);
IBotCommandsStage WithHttpClient(HttpClient client, bool throwPendingUpdates = false);
}
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace TelegramBotBase.Builder.Interfaces
{
public interface IThreadingStage
{
/// <summary>
/// Uses one single thread for message loop. (Default)
/// </summary>
/// <returns></returns>
public IBuildingStage UseSingleThread();
/// <summary>
/// Using the threadpool for managing requests.
/// </summary>
/// <param name="workerThreads">Number of concurrent working threads.</param>
/// <param name="ioThreads">Number of concurrent I/O threads.</param>
/// <returns></returns>
public IBuildingStage UseThreadPool(int workerThreads = 2, int ioThreads = 1);
}
}
+5
View File
@@ -16,4 +16,9 @@ public static class Telegram
public const int MaxReplyKeyboardCols = 12;
public const int MessageDeletionsPerSecond = 30;
/// <summary>
/// The maximum length of callback data. Will raise an exception of it exceeds it.
/// </summary>
public const int MaxCallBackDataBytes = 64;
}
@@ -0,0 +1,86 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TelegramBotBase.Form;
namespace TelegramBotBase.DependencyInjection
{
public static class Extensions
{
internal static FieldInfo _ServiceProviderField = typeof(FormBase).GetField("_serviceProvider", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
/// <summary>
/// Use Dependency Injection to create new form and inject parameters. (Main variant)
/// </summary>
/// <typeparam name="NewForm"></typeparam>
/// <param name="args"></param>
/// <returns></returns>
public static async Task<NewForm> NavigateTo<NewForm>(this FormBase current_form, params object[] args)
where NewForm : FormBase
{
var _serviceProvider = current_form.GetServiceProvider();
var instance = ActivatorUtilities.CreateInstance(_serviceProvider, typeof(NewForm)) as NewForm;
if (instance == null)
return null; //throw new Exception("Could not instantiate new form via DI.");
instance.SetServiceProvider(_serviceProvider);
await current_form.NavigateTo(instance, args);
return instance;
}
/// <summary>
/// Use Dependency Injection to create new form and inject parameters. (Alternative variant)
/// </summary>
/// <param name="current_form"></param>
/// <param name="formBaseType"></param>
/// <param name="args"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static async Task<FormBase> NavigateTo(this FormBase current_form, Type formBaseType, params object[] args)
{
if (!typeof(FormBase).IsAssignableFrom(formBaseType))
throw new ArgumentException($"{nameof(formBaseType)} argument must be a {nameof(FormBase)} type");
var _serviceProvider = current_form.GetServiceProvider();
var instance = ActivatorUtilities.CreateInstance(_serviceProvider, formBaseType) as FormBase;
if (instance == null)
return null; //throw new Exception("Could not instantiate new form via DI.");
instance.SetServiceProvider(_serviceProvider);
await current_form.NavigateTo(instance, args);
return instance;
}
/// <summary>
/// Sets the internal service provider field.
/// </summary>
/// <param name="form"></param>
/// <param name="serviceProvider"></param>
public static void SetServiceProvider(this FormBase form, IServiceProvider serviceProvider)
{
_ServiceProviderField?.SetValue(form, serviceProvider);
}
/// <summary>
/// Gets the internal service provider field value.
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
public static IServiceProvider GetServiceProvider(this FormBase form)
{
var sp = _ServiceProviderField?.GetValue(form) as IServiceProvider;
return sp;
}
}
}
@@ -0,0 +1,21 @@
using System;
using Telegram.Bot.Exceptions;
namespace TelegramBotBase.Exceptions;
public sealed class CallbackDataTooLongException : Exception
{
//public override string Message =>
// $"You have exceeded the maximum {Constants.Telegram.MaxCallBackDataBytes} bytes.";
static ApiRequestException _innerException = new Telegram.Bot.Exceptions.ApiRequestException("Bad Request: BUTTON_DATA_INVALID", 400);
static String _message = $"You have exceeded the maximum {Constants.Telegram.MaxCallBackDataBytes} bytes of callback data.\r\nThis is a pre-sending message from the TelegramBotBase framework.\r\nread more: https://core.telegram.org/bots/api#inlinekeyboardbutton";
public CallbackDataTooLongException() : base(_message, _innerException)
{
}
}
@@ -12,7 +12,7 @@ public class DefaultStartFormFactory : IStartFormFactory
{
if (!typeof(FormBase).IsAssignableFrom(startFormClass))
{
throw new ArgumentException("startFormClass argument must be a FormBase type");
throw new ArgumentException($"{nameof(startFormClass)} argument must be a {nameof(FormBase)} type");
}
_startFormClass = startFormClass;
@@ -1,5 +1,6 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using TelegramBotBase.DependencyInjection;
using TelegramBotBase.Form;
using TelegramBotBase.Interfaces;
@@ -14,16 +15,21 @@ public class ServiceProviderStartFormFactory : IStartFormFactory
{
if (!typeof(FormBase).IsAssignableFrom(startFormClass))
{
throw new ArgumentException("startFormClass argument must be a FormBase type");
throw new ArgumentException($"{nameof(startFormClass)} argument must be a {nameof(FormBase)} type");
}
_startFormClass = startFormClass;
_serviceProvider = serviceProvider;
}
public FormBase CreateForm()
{
return (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass);
var fb = (FormBase)ActivatorUtilities.CreateInstance(_serviceProvider, _startFormClass);
//Sets an internal field for future ServiceProvider navigation
fb.SetServiceProvider(_serviceProvider);
return fb;
}
}
+14 -2
View File
@@ -1,4 +1,6 @@
using Newtonsoft.Json;
using System.Text;
using TelegramBotBase.Exceptions;
namespace TelegramBotBase.Form;
@@ -30,7 +32,7 @@ public class CallbackData
/// Serializes data to json string
/// </summary>
/// <returns></returns>
public string Serialize()
public string Serialize(bool throwExceptionOnOverflow = false)
{
var s = "";
try
@@ -41,6 +43,16 @@ public class CallbackData
{
}
#if DEBUG
//Data is over 64 bytes
if(throwExceptionOnOverflow && Encoding.UTF8.GetByteCount(s) > Constants.Telegram.MaxCallBackDataBytes)
{
throw new CallbackDataTooLongException();
}
#endif
return s;
}
@@ -65,5 +77,5 @@ public class CallbackData
return null;
}
public static implicit operator string(CallbackData callbackData) => callbackData.Serialize();
public static implicit operator string(CallbackData callbackData) => callbackData.Serialize(true);
}
+1 -1
View File
@@ -1,6 +1,6 @@
namespace TelegramBotBase.Localizations;
public class English : Localization
public sealed class English : Localization
{
public English()
{
+1 -1
View File
@@ -1,6 +1,6 @@
namespace TelegramBotBase.Localizations;
public class German : Localization
public sealed class German : Localization
{
public German()
{
+37
View File
@@ -0,0 +1,37 @@
namespace TelegramBotBase.Localizations
{
public sealed class Persian : Localization
{
public Persian()
{
Values["Language"] = "فارسی";
Values["ButtonGrid_Title"] = "منو";
Values["ButtonGrid_NoItems"] = "هیچ آیتمی وجود ندارد.";
Values["ButtonGrid_PreviousPage"] = "◀️";
Values["ButtonGrid_NextPage"] = "▶️";
Values["ButtonGrid_CurrentPage"] = "صفحه ی {0} از {1}";
Values["ButtonGrid_SearchFeature"] = "💡 برای فیلتر کردن لیست پیام ارسال کنید. برای بازنشانی فیلتر روی 🔍 کلیک کنید.";
Values["ButtonGrid_Back"] = "بازگشت";
Values["ButtonGrid_CheckAll"] = "بررسی کردن همه";
Values["ButtonGrid_UncheckAll"] = "بررسی نکردن همه";
Values["CalendarPicker_Title"] = "تاریخ را انتخاب کنید";
Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️";
Values["TreeView_Title"] = "گره را انتخاب کنید";
Values["TreeView_LevelUp"] = "🔼 سطح بالا";
Values["ToggleButton_On"] = "روشن";
Values["ToggleButton_Off"] = "خاموش";
Values["ToggleButton_OnIcon"] = "⚫";
Values["ToggleButton_OffIcon"] = "⚪";
Values["ToggleButton_Title"] = "تغییر وضعیت";
Values["ToggleButton_Changed"] = "انتخاب شده";
Values["MultiToggleButton_SelectedIcon"] = "✅";
Values["MultiToggleButton_Title"] = "چند تعویض";
Values["MultiToggleButton_Changed"] = "انتخاب شده";
Values["PromptDialog_Back"] = "بازگشت";
Values["ToggleButton_Changed"] = "تنظیمات تغییر کرد";
Values["ButtonGrid_SearchIcon"] = "🔍";
Values["ButtonGrid_TagIcon"] = "📁";
}
}
}
@@ -72,6 +72,12 @@ public class FormBaseMessageLoop : IMessageLoopFactory
}
}
//Message edited ?
if(update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event
if (!session.FormSwitched && mr.IsAction)
{
@@ -65,6 +65,12 @@ public class FullMessageLoop : IMessageLoopFactory
}
}
//Message edited ?
if (update.Type == UpdateType.EditedMessage)
{
await activeForm.Edited(mr);
}
//Action Event
if (!session.FormSwitched && mr.IsAction)
{
+1 -1
View File
@@ -29,7 +29,7 @@ public class JsonStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
FilePath = file ?? throw new ArgumentNullException(nameof(file));
@@ -30,7 +30,7 @@ public class SimpleJsonStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
FilePath = file ?? throw new ArgumentNullException(nameof(file));
+1 -1
View File
@@ -27,7 +27,7 @@ public class XmlStateMachine : IStateMachine
if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
{
throw new ArgumentException("FallbackStateForm is not a subclass of FormBase");
throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
}
FilePath = file ?? throw new ArgumentNullException(nameof(file));
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net6;net7</TargetFrameworks>
<LangVersion>10</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
-2
View File
@@ -14,12 +14,10 @@
<dependencies>
<group targetFramework=".NETFramework4.6.1">
<dependency id="Newtonsoft.Json" version="13.0.1" exclude="Build,Analyzers" />
<dependency id="System.Drawing.Common" version="4.6.0" exclude="Build,Analyzers" />
<dependency id="Telegram.Bot" version="19.0.0" exclude="Build,Analyzers" />
</group>
<group targetFramework=".NETStandard2.0">
<dependency id="Newtonsoft.Json" version="13.0.1" exclude="Build,Analyzers" />
<dependency id="System.Drawing.Common" version="4.6.0" exclude="Build,Analyzers" />
<dependency id="Telegram.Bot" version="19.0.0" exclude="Build,Analyzers" />
</group>
</dependencies>
+34 -1
View File
@@ -30,7 +30,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCoreBot", "Examples\EFCor
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotAndWebApplication", "Examples\BotAndWebApplication\BotAndWebApplication.csproj", "{52EA3201-02E8-46F5-87C4-B4752C8A815C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InlineAndReplyCombination", "Examples\InlineAndReplyCombination\InlineAndReplyCombination.csproj", "{067E8EBE-F90A-4AFF-A0FF-20578216486E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjection", "Examples\DependencyInjection\DependencyInjection.csproj", "{689B16BC-200E-4C68-BB2E-8B209070849B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelegramBotBase.Extensions.Images.IronSoftware", "TelegramBotBase.Extensions.Images.IronSoftware\TelegramBotBase.Extensions.Images.IronSoftware.csproj", "{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Experiments", "Experiments", "{2E1FF127-4DC2-40A1-849C-ED1432204E8A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoBot", "Experiments\ExternalActionManager\DemoBot\DemoBot.csproj", "{5184D3F8-8526-413D-8EB0-23636EA7A4EF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TelegramBotBase.Experiments.ActionManager", "Experiments\ExternalActionManager\TelegramBotBase.Experiments\TelegramBotBase.Experiments.ActionManager.csproj", "{625F84FE-6809-4CA4-9C58-3453382D1C65}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExternalActionManager", "ExternalActionManager", "{359F7D60-362C-4F42-9606-A138FB1293E9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -78,6 +90,22 @@ Global
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{067E8EBE-F90A-4AFF-A0FF-20578216486E}.Release|Any CPU.Build.0 = Release|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{689B16BC-200E-4C68-BB2E-8B209070849B}.Release|Any CPU.Build.0 = Release|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6}.Release|Any CPU.Build.0 = Release|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5184D3F8-8526-413D-8EB0-23636EA7A4EF}.Release|Any CPU.Build.0 = Release|Any CPU
{625F84FE-6809-4CA4-9C58-3453382D1C65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{625F84FE-6809-4CA4-9C58-3453382D1C65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{625F84FE-6809-4CA4-9C58-3453382D1C65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{625F84FE-6809-4CA4-9C58-3453382D1C65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -91,6 +119,11 @@ Global
{261BED47-0404-4A9A-86FC-047DE42A7D25} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{52EA3201-02E8-46F5-87C4-B4752C8A815C} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{067E8EBE-F90A-4AFF-A0FF-20578216486E} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{689B16BC-200E-4C68-BB2E-8B209070849B} = {BFA71E3F-31C0-4FC1-A320-4DCF704768C5}
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
{5184D3F8-8526-413D-8EB0-23636EA7A4EF} = {359F7D60-362C-4F42-9606-A138FB1293E9}
{625F84FE-6809-4CA4-9C58-3453382D1C65} = {359F7D60-362C-4F42-9606-A138FB1293E9}
{359F7D60-362C-4F42-9606-A138FB1293E9} = {2E1FF127-4DC2-40A1-849C-ED1432204E8A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {59CB40E1-9FA7-4867-A56F-4F418286F057}