diff --git a/README.md b/README.md
index a4ec1b1..903a138 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# .NET Telegram Bot Framework - Context based addon
-[](https://www.nuget.org/packages/TelegramBotBase/)
+[](https://www.nuget.org/packages/TelegramBotBase/)
[](https://www.t.me/tgbotbase)
[](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
@@ -723,6 +723,7 @@ The current available languages for controls are:
- English
- German
- Persian
+- Russian
You can add other languages easily by creating a subclass of the [TelegramBotBase/Localizations/Localization.cs](TelegramBotBase/Localizations/Localization.cs) class.
@@ -1039,6 +1040,8 @@ again at 1 (due to `PopAsync` or `PopToRootAsync` calls) it will replace the con
you
have given to the constructor at the beginning.*
+---
+
## Extensions
### TelegramBotBase.Extensions.Images
@@ -1048,7 +1051,28 @@ 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)
-[https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/)
+Source code: [TelegramBotBase.Extensions.Images/](/TelegramBotBase.Extensions.Images/)
+
+Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.Images/](https://www.nuget.org/packages/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.
+
+[](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/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Images.IronSoftware/)
+
+Source code: [TelegramBotBase.Extensions.Images.IronSoftware/](TelegramBotBase.Extensions.Images.IronSoftware/)
+
+Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.IronSoftware/](https://www.nuget.org/packages/TelegramBotBase.Extensions.IronSoftware/)
+
+---
Project: [open source](TelegramBotBase.Extensions.Images/)
@@ -1072,7 +1096,26 @@ 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)
-[https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
+Source code: [TelegramBotBase.Extensions.Serializer.Database.MSSQL/](/TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
+
+Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
+
+---
+
+### TelegramBotBase.Extensions.Serializer.Database.PostgreSql
+
+A session serializer for PostgreSql Server.
+
+[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/)
+[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql)
+
+Source code: [TelegramBotBase.Extensions.Serializer.Database.PostgreSql/](/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/)
+
+Nuget package: [https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/)
+
+Credits: [@Kataane](https://github.com/Kataane)
+
+---
Project: [open source](TelegramBotBase.Extensions.Serializer.Database.MSSQL/)
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/BotBaseBuilderExtensions.cs b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/BotBaseBuilderExtensions.cs
new file mode 100644
index 0000000..7b79d06
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/BotBaseBuilderExtensions.cs
@@ -0,0 +1,92 @@
+using System;
+using TelegramBotBase.Builder;
+using TelegramBotBase.Builder.Interfaces;
+
+namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
+{
+ ///
+ /// Provides extension methods for configuring the use of PostgreSQL Server Database for session serialization.
+ ///
+ public static class BotBaseBuilderExtensions
+ {
+ ///
+ /// Uses an PostgreSQL Server Database to save and restore sessions.
+ ///
+ /// The session serialization stage builder.
+ /// The connection string to the PostgreSQL database.
+ /// The fallback form type.
+ /// The prefix for database table names (default is "tgb_").
+ /// The language selection stage builder.
+ public static ILanguageSelectionStage UsePostgreSqlDatabase(
+ this ISessionSerializationStage builder,
+ string connectionString, Type fallbackForm = null,
+ string tablePrefix = "tgb_")
+ {
+ var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
+
+ builder.UseSerialization(serializer);
+
+ return builder as BotBaseBuilder;
+ }
+
+
+ ///
+ /// Uses an PostgreSQL Server Database to save and restore sessions.
+ ///
+ /// The session serialization stage builder.
+ /// The host or IP address of the PostgreSQL server.
+ /// The port number for the PostgreSQL server.
+ /// The name of the PostgreSQL database.
+ /// The user ID for connecting to the PostgreSQL server.
+ /// The password for connecting to the PostgreSQL server.
+ /// The fallback form type.
+ /// The prefix for database table names (default is "tgb_").
+ /// The language selection stage builder.
+ public static ILanguageSelectionStage UsePostgreSqlDatabase(
+ this ISessionSerializationStage builder,
+ string hostOrIp, int port,
+ string databaseName, string userId,
+ string password, Type fallbackForm = null,
+ string tablePrefix = "tgb_")
+ {
+ var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Username={userId};Password={password}";
+
+ var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
+
+ builder.UseSerialization(serializer);
+
+ return builder as BotBaseBuilder;
+ }
+
+ ///
+ /// Uses an PostgreSQL Server Database with Windows Authentication to save and restore sessions.
+ ///
+ /// The session serialization stage builder.
+ /// The host or IP address of the PostgreSQL server.
+ /// The port number for the PostgreSQL server.
+ /// The name of the PostgreSQL database.
+ /// A flag indicating whether to use Windows Authentication (true) or not (false).
+ /// The fallback form type.
+ /// The prefix for database table names (default is "tgb_").
+ /// The language selection stage builder.
+ public static ILanguageSelectionStage UsePostgreSqlDatabase(
+ this ISessionSerializationStage builder,
+ string hostOrIp, int port,
+ string databaseName, bool integratedSecurity = true,
+ Type fallbackForm = null, string tablePrefix = "tgb_")
+ {
+ if (!integratedSecurity)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Integrated Security=true;";
+
+ var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
+
+ builder.UseSerialization(serializer);
+
+ return builder as BotBaseBuilder;
+ }
+ }
+}
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/PostgreSqlSerializer.cs b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/PostgreSqlSerializer.cs
new file mode 100644
index 0000000..7875b70
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/PostgreSqlSerializer.cs
@@ -0,0 +1,252 @@
+using Npgsql;
+using System;
+using System.Data;
+using NpgsqlTypes;
+using TelegramBotBase.Args;
+using TelegramBotBase.Base;
+using TelegramBotBase.Form;
+using TelegramBotBase.Interfaces;
+
+namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
+{
+ ///
+ /// Represents a PostgreSQL implementation of the for saving and loading form states.
+ ///
+ public class PostgreSqlSerializer : IStateMachine
+ {
+ private readonly string insertIntoSessionSql;
+ private readonly string insertIntoSessionsDataSql;
+ private readonly string selectAllDevicesSessionsSql;
+ private readonly string selectAllDevicesSessionsDataSql;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The connection string to the PostgreSQL database.
+ /// The prefix for database table names (default is "tgb_").
+ /// The fallback state form type.
+ /// Thrown when is null.
+ /// Thrown when is not a subclass of .
+ public PostgreSqlSerializer(string connectionString, string tablePrefix = "tgb_", Type fallbackStateForm = null)
+ {
+ ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
+
+ TablePrefix = tablePrefix;
+
+ FallbackStateForm = fallbackStateForm;
+
+ if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
+ {
+ throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
+ }
+
+ insertIntoSessionSql = "INSERT INTO " + TablePrefix +
+ "devices_sessions (deviceId, deviceTitle, \"FormUri\", \"QualifiedName\") VALUES (@deviceId, @deviceTitle, @FormUri, @QualifiedName)";
+ insertIntoSessionsDataSql = "INSERT INTO " + TablePrefix + "devices_sessions_data (deviceId, key, value, type) VALUES (@deviceId, @key, @value, @type)";
+
+ selectAllDevicesSessionsSql = "SELECT * FROM " + TablePrefix + "devices_sessions";
+ selectAllDevicesSessionsDataSql = "SELECT * FROM " + TablePrefix + "devices_sessions_data WHERE deviceId = @deviceId";
+ }
+
+ ///
+ /// Gets the connection string to the PostgreSQL database.
+ ///
+ public string ConnectionString { get; }
+
+ ///
+ /// Gets or sets the table name prefix for database tables.
+ ///
+ public string TablePrefix { get; set; }
+
+ ///
+ /// Gets or sets the fallback state form type.
+ ///
+ public Type FallbackStateForm { get; set; }
+
+ ///
+ ///
+ /// Saves form states to the PostgreSQL database.
+ ///
+ /// The containing the states to be saved.
+ public void SaveFormStates(SaveStatesEventArgs e)
+ {
+ var container = e.States;
+
+ //Cleanup old Session data
+ Cleanup();
+
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ //Store session data in database
+ foreach (var state in container.States)
+ {
+ using (var sessionCommand = connection.CreateCommand())
+ {
+ sessionCommand.CommandText = insertIntoSessionSql;
+
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint){Value = state.DeviceId });
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceTitle", DbType.StringFixedLength){Value = state.ChatTitle ?? string.Empty});
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@FormUri", DbType.StringFixedLength) {Value = state.FormUri});
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@QualifiedName", DbType.StringFixedLength){Value = state.QualifiedName });
+
+ sessionCommand.ExecuteNonQuery();
+ }
+ }
+ }
+
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ foreach (var state in container.States)
+ {
+ SaveSessionsData(state, connection);
+ }
+ }
+ }
+
+ ///
+ ///
+ /// Loads form states from the PostgreSQL database.
+ ///
+ /// A containing the loaded form states.
+ public StateContainer LoadFormStates()
+ {
+ var stateContainer = new StateContainer();
+
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ using (var sessionCommand = connection.CreateCommand())
+ {
+ sessionCommand.CommandText = selectAllDevicesSessionsSql;
+
+ var sessionTable = new DataTable();
+ using (var dataAdapter = new NpgsqlDataAdapter(sessionCommand))
+ {
+ dataAdapter.Fill(sessionTable);
+
+ foreach (DataRow row in sessionTable.Rows)
+ {
+ var stateEntry = new StateEntry
+ {
+ DeviceId = (long)row["deviceId"],
+ ChatTitle = row["deviceTitle"].ToString(),
+ FormUri = row["FormUri"].ToString(),
+ QualifiedName = row["QualifiedName"].ToString()
+ };
+
+ stateContainer.States.Add(stateEntry);
+
+ if (stateEntry.DeviceId > 0)
+ {
+ stateContainer.ChatIds.Add(stateEntry.DeviceId);
+ }
+ else
+ {
+ stateContainer.GroupIds.Add(stateEntry.DeviceId);
+ }
+
+ LoadDataTable(connection, row, stateEntry);
+ }
+ }
+ }
+ }
+
+ return stateContainer;
+ }
+
+ ///
+ /// Cleans up old session data in the PostgreSQL database.
+ ///
+ private void Cleanup()
+ {
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ using (var clearCommand = connection.CreateCommand())
+ {
+ clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions_data";
+ clearCommand.ExecuteNonQuery();
+ }
+
+ using (var clearCommand = connection.CreateCommand())
+ {
+ clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions";
+ clearCommand.ExecuteNonQuery();
+ }
+ }
+ }
+
+ ///
+ /// Saves session data to the PostgreSQL database.
+ ///
+ /// The state entry containing session data to be saved.
+ /// The NpgsqlConnection used for the database interaction.
+ private void SaveSessionsData(StateEntry state, NpgsqlConnection connection)
+ {
+ foreach (var data in state.Values)
+ {
+ using (var dataCommand = connection.CreateCommand())
+ {
+ dataCommand.CommandText = insertIntoSessionsDataSql;
+
+ dataCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint) { Value = state.DeviceId });
+ dataCommand.Parameters.Add(new NpgsqlParameter("@key", DbType.StringFixedLength) { Value = data.Key });
+
+ var type = data.Value.GetType();
+
+ if (type.IsPrimitive || type == typeof(string))
+ {
+ dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = data.Value });
+ }
+ else
+ {
+ var json = System.Text.Json.JsonSerializer.Serialize(data.Value);
+ dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = json });
+ }
+
+ dataCommand.Parameters.Add(new NpgsqlParameter("@type", DbType.StringFixedLength) { Value = type.AssemblyQualifiedName });
+
+ dataCommand.ExecuteNonQuery();
+ }
+ }
+ }
+
+ ///
+ /// Loads session data from the PostgreSQL database.
+ ///
+ /// The NpgsqlConnection used for the database interaction.
+ /// The DataRow representing a session entry in the main sessions table.
+ /// The StateEntry object to which session data will be loaded.
+ private void LoadDataTable(NpgsqlConnection connection, DataRow row, StateEntry stateEntry)
+ {
+ using (var sessionCommand = connection.CreateCommand())
+ {
+ sessionCommand.CommandText = selectAllDevicesSessionsDataSql;
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", row["deviceId"]));
+
+ var dataCommandTable = new DataTable();
+ using (var npgSqlDataAdapter = new NpgsqlDataAdapter(sessionCommand))
+ {
+ npgSqlDataAdapter.Fill(dataCommandTable);
+
+ foreach (DataRow dataRow in dataCommandTable.Rows)
+ {
+ var key = dataRow["key"].ToString();
+ var type = Type.GetType(dataRow["type"].ToString());
+
+ var value = System.Text.Json.JsonSerializer.Deserialize(dataRow["value"].ToString(), type);
+
+ stateEntry.Values.Add(key, value);
+ }
+ }
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/README.md b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/README.md
new file mode 100644
index 0000000..fb985d8
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/README.md
@@ -0,0 +1,27 @@
+# TelegramBotBase.Extensions.Serializer.Database.PostgreSQL
+
+[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL/)
+[](https://www.t.me/tgbotbase)
+
+[](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
+[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL)
+
+## How to use
+
+```csharp
+using TelegramBotBase.Extensions.Serializer.Database.PostgreSQL;
+
+
+var bot = BotBaseBuilder
+ .Create()
+ .WithAPIKey(APIKey)
+ .DefaultMessageLoop()
+ .WithStartForm()
+ .NoProxy()
+ .OnlyStart()
+ .UsePostgreSqlDatabase("localhost", "8181", "telegram_bot")
+ .UseEnglish()
+ .Build();
+
+bot.Start();
+```
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj
new file mode 100644
index 0000000..ce2b6ef
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj
@@ -0,0 +1,28 @@
+
+
+
+ netstandard2.0;netcoreapp3.1;net6
+ True
+ https://github.com/MajMcCloud/TelegramBotFramework
+ https://github.com/MajMcCloud/TelegramBotFramework
+ MIT
+ true
+ snupkg
+ 1.0.1
+ 1.0.1
+ 1.0.1
+
+ A session serializer for PostgreSQL Server.
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/create_tables.sql b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/create_tables.sql
new file mode 100644
index 0000000..d83bd38
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/create_tables.sql
@@ -0,0 +1,25 @@
+-- Enable uuid-ossp extension
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+-- Create table tgb_devices_sessions
+CREATE TABLE tgb_devices_sessions (
+ deviceId bigint NOT NULL,
+ deviceTitle character varying(512) NOT NULL,
+ "FormUri" character varying(512) NOT NULL,
+ "QualifiedName" character varying(512) NOT NULL,
+ CONSTRAINT PK_tgb_devices_sessions_1 PRIMARY KEY (deviceId)
+);
+
+-- Create table tgb_devices_sessions_data
+CREATE TABLE tgb_devices_sessions_data (
+ Id uuid DEFAULT uuid_generate_v4() NOT NULL,
+ deviceId bigint NOT NULL,
+ key character varying(512) NOT NULL,
+ "value" text NOT NULL,
+ "type" character varying(512) NOT NULL,
+ CONSTRAINT PK_tgb_devices_session_data PRIMARY KEY (Id)
+);
+
+-- Add default constraint for Id column in tgb_devices_sessions_data
+ALTER TABLE tgb_devices_sessions_data
+ ALTER COLUMN Id SET DEFAULT uuid_generate_v4();
diff --git a/TelegramBotBase/Builder/BotBaseBuilder.cs b/TelegramBotBase/Builder/BotBaseBuilder.cs
index 6dd7462..64596b9 100644
--- a/TelegramBotBase/Builder/BotBaseBuilder.cs
+++ b/TelegramBotBase/Builder/BotBaseBuilder.cs
@@ -398,30 +398,42 @@ public class BotBaseBuilder : IAPIKeySelectionStage, IMessageLoopSelectionStage,
#region "Step 7 (Language)"
- public IThreadingStage DefaultLanguage()
+ ///
+ public IBuildingStage DefaultLanguage()
{
return this;
}
- public IThreadingStage UseEnglish()
+ ///
+ public IBuildingStage UseEnglish()
{
Default.Language = new English();
return this;
}
- public IThreadingStage UseGerman()
+ ///
+ public IBuildingStage UseGerman()
{
Default.Language = new German();
return this;
}
- public IThreadingStage UsePersian()
+ ///
+ public IBuildingStage UsePersian()
{
Default.Language = new Persian();
return this;
}
- public IThreadingStage Custom(Localization language)
+ ///
+ public IBuildingStage UseRussian()
+ {
+ Default.Language = new Russian();
+ return this;
+ }
+
+ ///
+ public IBuildingStage Custom(Localization language)
{
Default.Language = language;
return this;
diff --git a/TelegramBotBase/Builder/Interfaces/ILanguageSelectionStage.cs b/TelegramBotBase/Builder/Interfaces/ILanguageSelectionStage.cs
index 1a43a52..b06addd 100644
--- a/TelegramBotBase/Builder/Interfaces/ILanguageSelectionStage.cs
+++ b/TelegramBotBase/Builder/Interfaces/ILanguageSelectionStage.cs
@@ -2,35 +2,44 @@
namespace TelegramBotBase.Builder.Interfaces;
+///
+/// Represents the language selection stage in the localization process.
+///
public interface ILanguageSelectionStage
{
///
/// Selects the default language for control usage. (English)
///
- ///
- IThreadingStage DefaultLanguage();
+ /// The next stage in the building process.
+ IBuildingStage DefaultLanguage();
///
/// Selects english as the default language for control labels.
///
- ///
- IThreadingStage UseEnglish();
+ /// The next stage in the building process.
+ IBuildingStage UseEnglish();
///
/// Selects german as the default language for control labels.
///
- ///
- IThreadingStage UseGerman();
+ /// The next stage in the building process.
+ IBuildingStage UseGerman();
///
/// Selects persian as the default language for control labels.
///
- ///
- IThreadingStage UsePersian();
+ /// The next stage in the building process.
+ IBuildingStage UsePersian();
+
+ ///
+ /// Selects russian as the default language for control labels.
+ ///
+ /// The next stage in the building process.
+ IBuildingStage UseRussian();
///
/// Selects a custom language as the default language for control labels.
///
- ///
- IThreadingStage Custom(Localization language);
+ /// The next stage in the building process.
+ IBuildingStage Custom(Localization language);
}
\ No newline at end of file
diff --git a/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs
index ca0a895..bc06b06 100644
--- a/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs
+++ b/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs
@@ -426,8 +426,6 @@ public class ButtonGrid : ControlBase
return;
}
- await result.ConfirmAction(ConfirmationText ?? "");
-
ButtonRow match = null;
var index = -1;
@@ -462,6 +460,8 @@ public class ButtonGrid : ControlBase
check:
if (match != null)
{
+ await result.ConfirmAction(ConfirmationText ?? "");
+
await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index,
match));
diff --git a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs
index c2fec7e..94266b7 100644
--- a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs
+++ b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs
@@ -29,8 +29,6 @@ public class TaggedButtonGrid : MultiView
public string BackLabel = Default.Language["ButtonGrid_Back"];
- public string CheckAllLabel = Default.Language["ButtonGrid_CheckAll"];
-
public string NextPageLabel = Default.Language["ButtonGrid_NextPage"];
public string NoItemsLabel = Default.Language["ButtonGrid_NoItems"];
@@ -39,11 +37,17 @@ public class TaggedButtonGrid : MultiView
public string SearchLabel = Default.Language["ButtonGrid_SearchFeature"];
- public string UncheckAllLabel = Default.Language["ButtonGrid_UncheckAll"];
+ public string TotalTagsLabel = Default.Language["TaggedButtonGrid_TotalTags"];
+
+ public string CheckedTagsLabel = Default.Language["TaggedButtonGrid_CheckedTags"];
+
+ public string CheckAllLabel = Default.Language["TaggedButtonGrid_CheckAll"];
+
+ public string UncheckAllLabel = Default.Language["TaggedButtonGrid_UncheckAll"];
public string SearchIcon = Default.Language["ButtonGrid_SearchIcon"];
- public string TagIcon = Default.Language["ButtonGrid_TagIcon"];
+ public string TagIcon = Default.Language["TaggedButtonGrid_TagIcon"];
public TaggedButtonGrid()
{
@@ -379,14 +383,6 @@ public class TaggedButtonGrid : MultiView
index = br.Item2;
}
-
- //var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText)
- // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText);
-
- // bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText)
-
- //var index = bf.FindRowByButton(button);
-
check:
@@ -490,10 +486,16 @@ public class TaggedButtonGrid : MultiView
if (result.MessageText == CheckAllLabel)
{
CheckAllTags();
+ Updated();
+ result.Handled = true;
+ return;
}
else if (result.MessageText == UncheckAllLabel)
{
UncheckAllTags();
+ Updated();
+ result.Handled = true;
+ return;
}
var i = result.MessageText.LastIndexOf(" ");
@@ -569,15 +571,6 @@ public class TaggedButtonGrid : MultiView
index = br.Item2;
}
-
- //var bf = DataSource.ButtonForm;
-
- //var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
- // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData)
- // ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData);
-
- //var index = bf.FindRowByButton(button);
-
check:
if (match != null)
{
@@ -619,22 +612,47 @@ public class TaggedButtonGrid : MultiView
break;
- case "$back$":
- SelectedViewIndex = 0;
+ default:
+
+ if (SelectedViewIndex != 1)
+ return;
+
+
+ switch (result.RawData)
+ {
+ case "$back$":
+
+ SelectedViewIndex = 0;
+ Updated();
+
+ return;
+
+ case "$checkall$":
+
+ CheckAllTags();
+
+ return;
+
+ case "$uncheckall$":
+
+ UncheckAllTags();
+
+ return;
+
+ }
+
+ if (SelectedTags.Contains(result.RawData))
+ {
+ SelectedTags.Remove(result.RawData);
+ }
+ else
+ {
+ SelectedTags.Add(result.RawData);
+ }
+
Updated();
- break;
-
- case "$checkall$":
-
- CheckAllTags();
-
- break;
-
- case "$uncheckall$":
-
- UncheckAllTags();
break;
}
@@ -723,8 +741,10 @@ public class TaggedButtonGrid : MultiView
if (EnableCheckAllTools)
{
- TagsSubHeadLayoutButtonRow = new ButtonRow(new ButtonBase(CheckAllLabel, "$checkall$"),
- new ButtonBase(UncheckAllLabel, "$uncheckall$"));
+ TagsSubHeadLayoutButtonRow = new ButtonRow(new ButtonBase(string.Format(TotalTagsLabel, Tags.Count), "$total$"),
+ new ButtonBase(CheckAllLabel, "$checkall$"),
+ new ButtonBase(UncheckAllLabel, "$uncheckall$"),
+ new ButtonBase(string.Format(CheckedTagsLabel, SelectedTags.Count), "checked$"));
bf.AddButtonRow(TagsSubHeadLayoutButtonRow);
}
@@ -756,10 +776,6 @@ public class TaggedButtonGrid : MultiView
return;
}
- //if (bf.Count == 0)
- // return;
-
-
var rkm = (ReplyKeyboardMarkup)bf;
rkm.ResizeKeyboard = ResizeKeyboard;
rkm.OneTimeKeyboard = OneTimeKeyboard;
@@ -881,21 +897,20 @@ public class TaggedButtonGrid : MultiView
{
Message m = null;
- var form = DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage,
- EnableSearch ? SearchQuery : null);
-
- //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
- //{
- // form = form.FilterDuplicate(this.SearchQuery, true);
- //}
- //else
- //{
- // form = form.Duplicate();
- //}
+ ButtonForm form = null;
if (Tags != null && SelectedTags != null)
{
+ form = DataSource.PickAllItems(EnableSearch ? SearchQuery : null); //CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage,
+
form = form.TagDuplicate(SelectedTags);
+
+ form = new ButtonForm(form.ToRowList().Skip(CurrentPageIndex * ItemRowsPerPage).Take(ItemRowsPerPage));
+ }
+ else
+ {
+ form = DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage,
+ EnableSearch ? SearchQuery : null);
}
if (EnablePaging)
diff --git a/TelegramBotBase/Form/ButtonForm.cs b/TelegramBotBase/Form/ButtonForm.cs
index 9591ddf..c5f745a 100644
--- a/TelegramBotBase/Form/ButtonForm.cs
+++ b/TelegramBotBase/Form/ButtonForm.cs
@@ -23,6 +23,11 @@ public class ButtonForm
DependencyControl = control;
}
+ public ButtonForm(IEnumerable rows)
+ {
+ _buttons = rows.ToList();
+ }
+
public IReplyMarkup Markup { get; set; }
@@ -149,6 +154,11 @@ public class ButtonForm
.Aggregate((a, b) => a.Union(b).ToList());
}
+ public List ToRowList()
+ {
+ return _buttons;
+ }
+
public InlineKeyboardButton[][] ToInlineButtonArray()
{
var ikb = _buttons.Select(a => a.ToArray().Select(b => b.ToInlineButton(this)).ToArray()).ToArray();
diff --git a/TelegramBotBase/Localizations/English.cs b/TelegramBotBase/Localizations/English.cs
index d69bbe2..974143b 100644
--- a/TelegramBotBase/Localizations/English.cs
+++ b/TelegramBotBase/Localizations/English.cs
@@ -12,8 +12,10 @@ public sealed class English : Localization
Values["ButtonGrid_CurrentPage"] = "Page {0} of {1}";
Values["ButtonGrid_SearchFeature"] = "💡 Send a message to filter the list. Click the 🔍 to reset the filter.";
Values["ButtonGrid_Back"] = "Back";
- Values["ButtonGrid_CheckAll"] = "Check all";
- Values["ButtonGrid_UncheckAll"] = "Uncheck all";
+ Values["TaggedButtonGrid_TotalTags"] = "Total: {0}";
+ Values["TaggedButtonGrid_CheckedTags"] = "Checked: {0}";
+ Values["TaggedButtonGrid_CheckAll"] = "Check all";
+ Values["TaggedButtonGrid_UncheckAll"] = "Uncheck all";
Values["CalendarPicker_Title"] = "Pick date";
Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️";
@@ -31,6 +33,6 @@ public sealed class English : Localization
Values["PromptDialog_Back"] = "Back";
Values["ToggleButton_Changed"] = "Setting changed";
Values["ButtonGrid_SearchIcon"] = "🔍";
- Values["ButtonGrid_TagIcon"] = "📁";
+ Values["TaggedButtonGrid_TagIcon"] = "📁";
}
}
diff --git a/TelegramBotBase/Localizations/German.cs b/TelegramBotBase/Localizations/German.cs
index 6380635..ffe99c4 100644
--- a/TelegramBotBase/Localizations/German.cs
+++ b/TelegramBotBase/Localizations/German.cs
@@ -10,11 +10,12 @@ public sealed class German : Localization
Values["ButtonGrid_PreviousPage"] = "◀️";
Values["ButtonGrid_NextPage"] = "▶️";
Values["ButtonGrid_CurrentPage"] = "Seite {0} von {1}";
- Values["ButtonGrid_SearchFeature"] =
- "💡 Sende eine Nachricht um die Liste zu filtern. Klicke die 🔍 um den Filter zurückzusetzen.";
+ Values["ButtonGrid_SearchFeature"] = "💡 Sende eine Nachricht um die Liste zu filtern. Klicke die 🔍 um den Filter zurückzusetzen.";
Values["ButtonGrid_Back"] = "Zurück";
- Values["ButtonGrid_CheckAll"] = "Alle auswählen";
- Values["ButtonGrid_UncheckAll"] = "Keine auswählen";
+ Values["TaggedButtonGrid_TotalTags"] = "Gesamt: {0}";
+ Values["TaggedButtonGrid_CheckedTags"] = "Ausgewählt: {0}";
+ Values["TaggedButtonGrid_CheckAll"] = "Alle auswählen";
+ Values["TaggedButtonGrid_UncheckAll"] = "Keine auswählen";
Values["CalendarPicker_Title"] = "Datum auswählen";
Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️";
@@ -31,5 +32,7 @@ public sealed class German : Localization
Values["MultiToggleButton_Changed"] = "Ausgewählt";
Values["PromptDialog_Back"] = "Zurück";
Values["ToggleButton_Changed"] = "Einstellung geändert";
+ Values["ButtonGrid_SearchIcon"] = "🔍";
+ Values["TaggedButtonGrid_TagIcon"] = "📁";
}
}
\ No newline at end of file
diff --git a/TelegramBotBase/Localizations/Persian.cs b/TelegramBotBase/Localizations/Persian.cs
index 919e355..72c6b40 100644
--- a/TelegramBotBase/Localizations/Persian.cs
+++ b/TelegramBotBase/Localizations/Persian.cs
@@ -1,37 +1,39 @@
-namespace TelegramBotBase.Localizations
+namespace TelegramBotBase.Localizations;
+
+public sealed class Persian : Localization
{
- public sealed class Persian : Localization
+ public Persian()
{
- 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"] = "📁";
- }
+ 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["TaggedButtonGrid_TotalTags"] = "Total: {0}";
+ Values["TaggedButtonGrid_CheckedTags"] = "Checked: {0}";
+ Values["TaggedButtonGrid_CheckAll"] = "بررسی کردن همه";
+ Values["TaggedButtonGrid_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["TaggedButtonGrid_TagIcon"] = "📁";
}
}
+
diff --git a/TelegramBotBase/Localizations/Russian.cs b/TelegramBotBase/Localizations/Russian.cs
new file mode 100644
index 0000000..08315e6
--- /dev/null
+++ b/TelegramBotBase/Localizations/Russian.cs
@@ -0,0 +1,38 @@
+namespace TelegramBotBase.Localizations;
+
+public sealed class Russian : Localization
+{
+ public Russian()
+ {
+ Values["Language"] = "Русский (Russian)";
+ Values["ButtonGrid_Title"] = "Меню";
+ Values["ButtonGrid_NoItems"] = "Нет доступных элементов.";
+ Values["ButtonGrid_PreviousPage"] = "◀️";
+ Values["ButtonGrid_NextPage"] = "▶️";
+ Values["ButtonGrid_CurrentPage"] = "Страница {0} из {1}";
+ Values["ButtonGrid_SearchFeature"] = "💡 Отправьте сообщение, чтобы отфильтровать список. Нажмите на 🔍, чтобы сбросить фильтр.";
+ Values["ButtonGrid_Back"] = "Назад";
+ Values["TaggedButtonGrid_TotalTags"] = "Всего: {0}";
+ Values["TaggedButtonGrid_CheckedTags"] = "Отмечено: {0}";
+ Values["TaggedButtonGrid_CheckAll"] = "Выделить все";
+ Values["TaggedButtonGrid_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["TaggedButtonGrid_TagIcon"] = "📁";
+ }
+}
\ No newline at end of file
diff --git a/TelegramBotFramework.sln b/TelegramBotFramework.sln
index 84dbf82..409d950 100644
--- a/TelegramBotFramework.sln
+++ b/TelegramBotFramework.sln
@@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InlineAndReplyCombination",
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.Serializer.Database.PostgreSql", "TelegramBotBase.Extensions.Serializer.Database.PostgreSql\TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj", "{7C55D9FF-7DC1-41D0-809C-469EBFA20992}"
+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
Global
@@ -86,6 +88,10 @@ Global
{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
+ {7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C55D9FF-7DC1-41D0-809C-469EBFA20992}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C55D9FF-7DC1-41D0-809C-469EBFA20992}.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
@@ -104,6 +110,7 @@ Global
{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}
+ {7C55D9FF-7DC1-41D0-809C-469EBFA20992} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
{DC521A4C-7446-46F7-845B-AAF10EDCF8C6} = {E3193182-6FDA-4FA3-AD26-A487291E7681}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution