From a8a7047af59a919ba5d1f104ba1db0f6807c9345 Mon Sep 17 00:00:00 2001
From: Ronny Gunawan <3048897+ronnygunawan@users.noreply.github.com>
Date: Sat, 7 Dec 2024 12:25:48 +0700
Subject: [PATCH] Cleanup linter resharper warnings
---
.editorconfig | 21 +-
.../inspectionProfiles/Project_Default.xml | 6 +
.../AI/Gemini/GeminiTextPromptHandler.cs | 184 +++---
.../AI/OpenAI/AskCommandHandler.cs | 34 +-
.../OpenAIImageGenerationPromptHandler.cs | 30 +-
.../AI/OpenAI/OpenAIImagePromptHandler.cs | 67 +-
.../AI/OpenAI/OpenAITextPromptHandler.cs | 61 +-
.../AI/RateLimit/AIRateLimiters.cs | 4 +-
.../StabilityTextToImagePromptHandler.cs | 21 +-
.../Art/ArtCommandHandler.cs | 23 +-
.../BMKG/BMKGCommandHandler.cs | 19 +-
.../CallbackQueryUpdateHandler.cs | 4 +-
.../InlineQuery/InlineQueryUpdateHandler.cs | 12 +-
.../BotUpdate/Message/AICallCommandHandler.cs | 38 +-
.../Message/AIFollowUpMessageHandler.cs | 33 +-
.../BotUpdate/Message/MessageUpdateHandler.cs | 293 ++++-----
.../BotUpdate/Message/SlashCommandHandler.cs | 38 +-
.../Eval/EvalCommandHandler.cs | 24 +-
.../Exec/ExecCommandHandler.cs | 22 +-
.../FlipFlop/FlipFlopCommandHandler.cs | 6 +-
.../Fuck/FuckCommandHandler.cs | 10 +-
.../GoogleMaps/MapCommandHandler.cs | 19 +-
.../Humor/HumorCommandHandler.cs | 13 +-
.../Khodam/KhodamCommandHandler.cs | 4 +-
.../Pop/BubbleWrapCallbackHandler.cs | 7 +-
.../Pop/PopCommandHandler.cs | 6 +-
.../Primbon/PrimbonCommandHandler.cs | 37 +-
.../Privilege/PrivilegeCommandHandler.cs | 29 +-
.../SQL/SQLCommandHandler.cs | 62 +-
.../TelegramMessageCache.cs | 19 +-
.../Weather/WeatherCommandHandler.cs | 18 +-
BotNet.Commands/AI/Gemini/GeminiTextPrompt.cs | 58 +-
.../AI/OpenAI/OpenAIImageGenerationPrompt.cs | 4 +-
.../AI/OpenAI/OpenAIImagePrompt.cs | 39 +-
BotNet.Commands/AI/OpenAI/OpenAITextPrompt.cs | 60 +-
BotNet.Commands/BMKG/BMKGCommand.cs | 6 +-
.../BotUpdate/Message/AICallCommand.cs | 8 +-
.../BotUpdate/Message/AIFollowUpMessage.cs | 32 +-
.../BotUpdate/Message/AIResponseMessage.cs | 6 +-
BotNet.Commands/ChatAggregate/Chat.cs | 20 +-
.../CommandPrioritizationOptions.cs | 4 +-
.../CommandPriorityCategorizer.cs | 4 +-
BotNet.Commands/SQL/SQLCommand.cs | 12 +-
BotNet.Commands/SenderAggregate/Sender.cs | 25 +-
BotNet.Services/BMKG/BMKG.cs | 10 +-
BotNet.Services/BMKG/EarthQuake.cs | 9 +-
BotNet.Services/BMKG/LatestEarthQuake.cs | 34 +-
.../BMKG/ServiceCollectionExtensions.cs | 2 +-
BotNet.Services/BotCommands/OpenAI.cs | 576 ------------------
BotNet.Services/BotCommands/Preview.cs | 88 ---
.../BotProfile/BotProfileAccessor.cs | 4 +-
.../Brainfuck/BrainfuckInterpreter.cs | 5 +-
.../Brainfuck/BrainfuckTranspiler.cs | 28 +-
.../BubbleWrap/BubbleWrapKeyboardGenerator.cs | 21 +-
BotNet.Services/BubbleWrap/BubbleWrapSheet.cs | 4 +-
.../ChineseCalendar/ChineseCalendarScraper.cs | 7 +-
.../JsonConverters/ScriptObjectConverter.cs | 2 +-
.../JsonConverters/UndefinedConverter.cs | 2 +-
BotNet.Services/ClearScript/V8Evaluator.cs | 25 +-
BotNet.Services/ClearScript/V8Options.cs | 4 +-
.../ColorCard/ColorCardRenderer.cs | 27 +-
BotNet.Services/CopyPasta/CopyPastaLookup.cs | 6 +-
BotNet.Services/Craiyon/CraiyonClient.cs | 46 +-
.../DynamicExpresso/CSharpEvaluator.cs | 14 +-
.../ServiceCollectionExtensions.cs | 24 +-
.../FancyText/FancyTextGenerator.cs | 21 +-
BotNet.Services/Gemini/GeminiClient.cs | 23 +-
BotNet.Services/Giphy/GiphyClient.cs | 6 +-
BotNet.Services/GoogleMap/GeoCode.cs | 23 +-
BotNet.Services/GoogleMap/Models/Geometry.cs | 1 +
.../GoogleMap/Models/LocationType.cs | 3 +-
BotNet.Services/GoogleMap/Models/Result.cs | 1 +
.../GoogleMap/Models/StatusCode.cs | 3 +-
BotNet.Services/GoogleMap/StaticMap.cs | 12 +-
.../GoogleSheets/FromColumnAttribute.cs | 12 +-
.../GoogleSheets/GoogleSheetsClient.cs | 16 +-
.../ImageConverter/IcoToPngConverter.cs | 25 +-
BotNet.Services/Json/SnakeCaseNamingPolicy.cs | 18 +-
BotNet.Services/Khodam/KhodamCalculator.cs | 10 +-
.../ServiceCollectionExtensions.cs | 4 +-
.../KokizzuVPSBenchmark/VPSBenchmark.cs | 2 +-
.../VPSBenchmarkDataSource.cs | 13 +-
.../MarkdownV2/MarkdownV2Sanitizer.cs | 16 +-
BotNet.Services/Meme/MemeGenerator.cs | 31 +-
BotNet.Services/Meme/Templates.cs | 2 +-
BotNet.Services/OpenAI/AttachmentGenerator.cs | 4 +-
BotNet.Services/OpenAI/IntentDetector.cs | 8 +-
BotNet.Services/OpenAI/OpenAIClient.cs | 153 +++--
BotNet.Services/OpenAI/OpenAIOptions.cs | 4 +-
.../OpenAI/OpenAIStreamingClient.cs | 22 +-
.../OpenAI/ServiceCollectionExtensions.cs | 6 +-
BotNet.Services/OpenAI/Skills/AssistantBot.cs | 43 +-
BotNet.Services/OpenAI/Skills/FriendlyBot.cs | 75 ++-
.../OpenAI/Skills/ImageGenerationBot.cs | 11 +-
BotNet.Services/OpenAI/Skills/SarcasticBot.cs | 10 +-
.../OpenAI/Skills/TldrGenerator.cs | 6 +-
BotNet.Services/OpenAI/Skills/Translator.cs | 8 +-
BotNet.Services/OpenAI/Skills/VisionBot.cs | 8 +-
BotNet.Services/OpenAI/ThreadTracker.cs | 6 +-
.../Pemilu2024/PilegDPRDapilDataSource.cs | 118 ++--
.../Pemilu2024/PilegDPRPerDapilDataSource.cs | 126 ++--
.../PilegDPRPerProvinsiDataSource.cs | 122 ++--
.../Pemilu2024/PilpresDataSource.cs | 22 +-
.../Pemilu2024/ServiceCollectionExtensions.cs | 180 +++---
BotNet.Services/Pemilu2024/SirekapClient.cs | 36 +-
BotNet.Services/Pemilu2024/Types.cs | 18 +-
.../Pesto/Exceptions/PestoAPIException.cs | 7 +-
.../Exceptions/PestoEmptyCodeException.cs | 4 +-
.../PestoMonthlyLimitExceededException.cs | 4 +-
.../PestoRuntimeNotFoundException.cs | 7 +-
.../PestoServerRateLimitedException.cs | 4 +-
BotNet.Services/Pesto/Models/Language.cs | 8 +-
BotNet.Services/Pesto/PestoClient.cs | 73 +--
BotNet.Services/Piston/PistonClient.cs | 2 +-
BotNet.Services/Preview/YoutubePreview.cs | 60 +-
BotNet.Services/Primbon/PrimbonScraper.cs | 21 +-
.../ProgrammerHumor/ProgrammerHumorScraper.cs | 6 +-
.../RateLimit/Internal/PerChatRateLimiter.cs | 21 +-
.../Internal/PerUserPerChatRateLimiter.cs | 21 +-
.../RateLimit/Internal/PerUserRateLimiter.cs | 21 +-
.../RateLimit/RateLimitExceededException.cs | 10 +-
BotNet.Services/RateLimit/RateLimiter.cs | 2 -
BotNet.Services/Sqlite/ScopedDatabase.cs | 1 -
.../Models/ContentFilteredException.cs | 8 +-
.../Stability/Skills/ImageGenerationBot.cs | 4 +-
.../Stability/Skills/ImageVariationBot.cs | 4 +-
BotNet.Services/Stability/StabilityClient.cs | 41 +-
.../TelegramBotClientResilienceExtensions.cs | 8 +-
.../ThisArtworkDoesNotExist.cs | 14 +-
.../ThisXDoesNotExist/ThisCatDoesNotExist.cs | 14 +-
.../ThisXDoesNotExist/ThisIdeaDoesNotExist.cs | 4 +-
BotNet.Services/Tiktok/TiktokLinkSanitizer.cs | 5 +-
.../Tokopedia/TokopediaLinkSanitizer.cs | 7 +-
.../Typography/BotNetFontService.cs | 12 +-
BotNet.Services/Typography/FontStyle.cs | 2 +-
BotNet.Services/Weather/CurrentWeather.cs | 46 +-
BotNet.Services/Weather/Models/Current.cs | 3 +-
BotNet.Services/Weather/Weather.cs | 21 +-
BotNet.Tests/BotNet.Tests.csproj | 1 -
.../SocialLink/SocialLinkEmbedFixerTests.cs | 5 +-
BotNet.Tests/TestUtilities/HttpClientMock.cs | 7 +-
BotNet.sln.DotSettings | 254 ++++++++
BotNet.sln.DotSettings.user | 7 +
BotNet/Bot/BotService.cs | 32 +-
BotNet/Bot/CommandConsumer.cs | 16 +-
BotNet/Bot/UpdateHandler.cs | 15 +-
BotNet/Program.cs | 8 +-
.../DecimalClock/DecimalClockSvgBuilder.cs | 8 +-
BotNet/Views/DecimalClock/Svg.cshtml | 10 +-
README.md | 16 +-
pehape | 2 +-
151 files changed, 1920 insertions(+), 2534 deletions(-)
create mode 100644 .idea/.idea.BotNet/.idea/inspectionProfiles/Project_Default.xml
delete mode 100644 BotNet.Services/BotCommands/OpenAI.cs
delete mode 100644 BotNet.Services/BotCommands/Preview.cs
create mode 100644 BotNet.sln.DotSettings
create mode 100644 BotNet.sln.DotSettings.user
diff --git a/.editorconfig b/.editorconfig
index 467392d..3ecfbf2 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -85,30 +85,24 @@ dotnet_naming_symbols.method_symbols.required_modifiers = async
dotnet_naming_rule.public_members_must_be_capitalized.severity = error
dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols
dotnet_naming_rule.public_members_must_be_capitalized.style = pascal_case_style
-dotnet_naming_symbols.public_symbols.applicable_kinds = property,method,field,event,delegate
+dotnet_naming_symbols.public_symbols.applicable_kinds = property,method,field,event,delegate,tuples
dotnet_naming_symbols.public_symbols.applicable_accessibilities = public,internal,protected,protected_internal
-# Named tuples must be pascal case
-dotnet_naming_rule.public_members_must_be_capitalized.severity = warning
-dotnet_naming_rule.public_members_must_be_capitalized.symbols = named_tuples
-dotnet_naming_rule.public_members_must_be_capitalized.style = pascal_case_style
-dotnet_naming_symbols.named_tuples.applicable_kinds = tuples
-dotnet_naming_symbols.named_tuples.applicable_accessibilities = public,internal,protected,protected_internal
# Fields must be camel case prefixed with an underscore
dotnet_naming_rule.non_public_members_must_be_underscored_camel_case.severity = warning
dotnet_naming_rule.non_public_members_must_be_underscored_camel_case.symbols = fields
dotnet_naming_rule.non_public_members_must_be_underscored_camel_case.style = underscore_camel_case_style
dotnet_naming_symbols.fields.applicable_kinds = field
dotnet_naming_symbols.fields.applicable_accessibilities = private
-# Constants must be macro case
+# Constants must be pascal case
dotnet_naming_rule.constant_fields_should_be_upper_case.severity = error
dotnet_naming_rule.constant_fields_should_be_upper_case.symbols = constant_fields
-dotnet_naming_rule.constant_fields_should_be_upper_case.style = macro_case_style
+dotnet_naming_rule.constant_fields_should_be_upper_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
-# Static readonly fields must be macro case
+# Static readonly fields must be pascal case
dotnet_naming_rule.static_readonly_fields_should_be_upper_case.severity = error
dotnet_naming_rule.static_readonly_fields_should_be_upper_case.symbols = static_readonly_fields
-dotnet_naming_rule.static_readonly_fields_should_be_upper_case.style = macro_case_style
+dotnet_naming_rule.static_readonly_fields_should_be_upper_case.style = pascal_case_style
dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.static_readonly_fields.required_modifiers = static,readonly
# Locals must be camel case
@@ -153,7 +147,7 @@ csharp_style_inlined_variable_declaration = true:suggestion
# C# Formatting Rules #
###############################
# New line preferences
-csharp_new_line_before_open_brace = false
+csharp_new_line_before_open_brace = none
csharp_new_line_before_else = false
csharp_new_line_before_catch = false
csharp_new_line_before_finally = false
@@ -214,9 +208,6 @@ dotnet_diagnostic.RCS1118.severity = warning
# RCS1169: Make field read-only.
dotnet_diagnostic.RCS1169.severity = warning
-# IDE0011: Add braces
-csharp_prefer_braces = when_multiline:warning
-
# RCS1001: Add braces (when expression spans over multiple lines).
dotnet_diagnostic.RCS1001.severity = warning
diff --git a/.idea/.idea.BotNet/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.BotNet/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..5cb71ef
--- /dev/null
+++ b/.idea/.idea.BotNet/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BotNet.CommandHandlers/AI/Gemini/GeminiTextPromptHandler.cs b/BotNet.CommandHandlers/AI/Gemini/GeminiTextPromptHandler.cs
index f16d604..b917b41 100644
--- a/BotNet.CommandHandlers/AI/Gemini/GeminiTextPromptHandler.cs
+++ b/BotNet.CommandHandlers/AI/Gemini/GeminiTextPromptHandler.cs
@@ -21,23 +21,20 @@ public sealed class GeminiTextPromptHandler(
GeminiClient geminiClient,
ITelegramMessageCache telegramMessageCache,
CommandPriorityCategorizer commandPriorityCategorizer,
- ICommandQueue commandQueue,
ILogger logger
) : ICommandHandler {
- internal static readonly RateLimiter CHAT_RATE_LIMITER = RateLimiter.PerUserPerChat(5, TimeSpan.FromMinutes(5));
- internal static readonly RateLimiter VIP_CHAT_RATE_LIMITER = RateLimiter.PerUserPerChat(5, TimeSpan.FromMinutes(2));
+ private static readonly RateLimiter ChatRateLimiter = RateLimiter.PerUserPerChat(5, TimeSpan.FromMinutes(5));
+ private static readonly RateLimiter VipChatRateLimiter = RateLimiter.PerUserPerChat(5, TimeSpan.FromMinutes(2));
private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly GeminiClient _geminiClient = geminiClient;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
- private readonly ICommandQueue _commandQueue = commandQueue;
- private readonly ILogger _logger = logger;
- public Task Handle(GeminiTextPrompt textPrompt, CancellationToken cancellationToken) {
+ public Task Handle(
+ GeminiTextPrompt textPrompt,
+ CancellationToken cancellationToken
+ ) {
if (textPrompt.Command.Chat is GroupChat) {
try {
- AIRateLimiters.GROUP_CHAT_RATE_LIMITER.ValidateActionRate(
+ AiRateLimiters.GroupChatRateLimiter.ValidateActionRate(
chatId: textPrompt.Command.Chat.Id,
userId: textPrompt.Command.Sender.Id
);
@@ -46,9 +43,7 @@ public Task Handle(GeminiTextPrompt textPrompt, CancellationToken cancellationTo
chatId: textPrompt.Command.Chat.Id,
text: $"Anda terlalu banyak memanggil AI. Coba lagi {exc.Cooldown} atau lanjutkan di private chat.
",
parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = textPrompt.Command.MessageId
- },
+ replyParameters: new ReplyParameters { MessageId = textPrompt.Command.MessageId },
replyMarkup: new InlineKeyboardMarkup(
InlineKeyboardButton.WithUrl("Private chat 💬", "t.me/TeknumBot")
),
@@ -57,13 +52,13 @@ public Task Handle(GeminiTextPrompt textPrompt, CancellationToken cancellationTo
}
} else {
try {
- if (textPrompt.Command.Sender is VIPSender) {
- VIP_CHAT_RATE_LIMITER.ValidateActionRate(
+ if (textPrompt.Command.Sender is VipSender) {
+ VipChatRateLimiter.ValidateActionRate(
chatId: textPrompt.Command.Chat.Id,
userId: textPrompt.Command.Sender.Id
);
} else {
- CHAT_RATE_LIMITER.ValidateActionRate(
+ ChatRateLimiter.ValidateActionRate(
chatId: textPrompt.Command.Chat.Id,
userId: textPrompt.Command.Sender.Id
);
@@ -73,101 +68,102 @@ public Task Handle(GeminiTextPrompt textPrompt, CancellationToken cancellationTo
chatId: textPrompt.Command.Chat.Id,
text: $"Anda terlalu banyak memanggil AI. Coba lagi {exc.Cooldown}.
",
parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = textPrompt.Command.MessageId
- },
+ replyParameters: new ReplyParameters { MessageId = textPrompt.Command.MessageId },
cancellationToken: cancellationToken
);
}
}
// Fire and forget
- Task.Run(async () => {
- List messages = [
- Content.FromText("user", "Act as an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point."),
- Content.FromText("model", "Sure.")
- ];
+ // ReSharper disable once MethodSupportsCancellation
+ Task.Run(
+ async () => {
+ List messages = [
+ Content.FromText("user", "Act as an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point."),
+ Content.FromText("model", "Sure.")
+ ];
- // Merge adjacent messages from same role
- foreach (MessageBase message in textPrompt.Thread.Reverse()) {
- Content content = Content.FromText(
- role: message.Sender.GeminiRole,
- text: message.Text
- );
+ // Merge adjacent messages from same role
+ foreach (MessageBase message in textPrompt.Thread.Reverse()) {
+ Content content = Content.FromText(
+ role: message.Sender.GeminiRole,
+ text: message.Text
+ );
- if (messages.Count > 0
- && messages[^1].Role == message.Sender.GeminiRole) {
- messages[^1].Add(content);
- } else {
- messages.Add(content);
+ if (messages.Count > 0 &&
+ messages[^1].Role == message.Sender.GeminiRole) {
+ messages[^1]
+ .Add(content);
+ } else {
+ messages.Add(content);
+ }
}
- }
- // Trim thread longer than 10 messages
- while (messages.Count > 10) {
- messages.RemoveAt(0);
- }
-
- // Thread must start with user message
- while (messages.Count > 0
- && messages[0].Role != "user") {
- messages.RemoveAt(0);
- }
+ // Trim thread longer than 10 messages
+ while (messages.Count > 10) {
+ messages.RemoveAt(0);
+ }
- // Merge user message with replied to message if thread is initiated by replying to another user
- if (messages.Count > 0
- && messages[^1].Role == "user") {
- messages[^1].Add(Content.FromText("user", textPrompt.Prompt));
- } else {
- messages.Add(Content.FromText("user", textPrompt.Prompt));
- }
+ // Thread must start with user message
+ while (messages.Count > 0 &&
+ messages[0].Role != "user") {
+ messages.RemoveAt(0);
+ }
- string response = await _geminiClient.ChatAsync(
- messages: messages,
- maxTokens: 512,
- cancellationToken: cancellationToken
- );
+ // Merge user message with replied to message if thread is initiated by replying to another user
+ if (messages.Count > 0 &&
+ messages[^1].Role == "user") {
+ messages[^1]
+ .Add(Content.FromText("user", textPrompt.Prompt));
+ } else {
+ messages.Add(Content.FromText("user", textPrompt.Prompt));
+ }
- // Send response
- Message responseMessage;
- try {
- responseMessage = await telegramBotClient.SendTextMessageAsync(
- chatId: textPrompt.Command.Chat.Id,
- text: response,
- parseModes: [ParseMode.MarkdownV2, ParseMode.Markdown, ParseMode.Html],
- replyToMessageId: textPrompt.Command.MessageId,
- replyMarkup: new InlineKeyboardMarkup(
- InlineKeyboardButton.WithUrl(
- text: "Generated by Google Gemini 1.5 Flash",
- url: "https://deepmind.google/technologies/gemini/"
- )
- ),
+ string response = await geminiClient.ChatAsync(
+ messages: messages,
+ maxTokens: 512,
cancellationToken: cancellationToken
);
- } catch (Exception exc) {
- _logger.LogError(exc, null);
- await telegramBotClient.SendMessage(
- chatId: textPrompt.Command.Chat.Id,
- text: "😵",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = textPrompt.Command.MessageId
- },
- cancellationToken: cancellationToken
+
+ // Send response
+ Message responseMessage;
+ try {
+ responseMessage = await telegramBotClient.SendTextMessageAsync(
+ chatId: textPrompt.Command.Chat.Id,
+ text: response,
+ parseModes: [ParseMode.MarkdownV2, ParseMode.Markdown, ParseMode.Html],
+ replyToMessageId: textPrompt.Command.MessageId,
+ replyMarkup: new InlineKeyboardMarkup(
+ InlineKeyboardButton.WithUrl(
+ text: "Generated by Google Gemini 1.5 Flash",
+ url: "https://deepmind.google/technologies/gemini/"
+ )
+ ),
+ cancellationToken: cancellationToken
+ );
+ } catch (Exception exc) {
+ logger.LogError(exc, null);
+ await telegramBotClient.SendMessage(
+ chatId: textPrompt.Command.Chat.Id,
+ text: "😵",
+ parseMode: ParseMode.Html,
+ replyParameters: new ReplyParameters { MessageId = textPrompt.Command.MessageId },
+ cancellationToken: cancellationToken
+ );
+ return;
+ }
+
+ // Track thread
+ telegramMessageCache.Add(
+ message: AiResponseMessage.FromMessage(
+ message: responseMessage,
+ replyToMessage: textPrompt.Command,
+ callSign: "Gemini",
+ commandPriorityCategorizer: commandPriorityCategorizer
+ )
);
- return;
}
-
- // Track thread
- _telegramMessageCache.Add(
- message: AIResponseMessage.FromMessage(
- message: responseMessage,
- replyToMessage: textPrompt.Command,
- callSign: "Gemini",
- commandPriorityCategorizer: _commandPriorityCategorizer
- )
- );
- });
+ );
return Task.CompletedTask;
}
diff --git a/BotNet.CommandHandlers/AI/OpenAI/AskCommandHandler.cs b/BotNet.CommandHandlers/AI/OpenAI/AskCommandHandler.cs
index 291f497..70a3938 100644
--- a/BotNet.CommandHandlers/AI/OpenAI/AskCommandHandler.cs
+++ b/BotNet.CommandHandlers/AI/OpenAI/AskCommandHandler.cs
@@ -19,26 +19,20 @@
namespace BotNet.CommandHandlers.AI.OpenAI {
public sealed class AskCommandHandler(
ITelegramBotClient telegramBotClient,
- OpenAIClient openAIClient,
+ OpenAiClient openAiClient,
ITelegramMessageCache telegramMessageCache,
CommandPriorityCategorizer commandPriorityCategorizer,
ILogger logger
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly OpenAIClient _openAIClient = openAIClient;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
- private readonly ILogger _logger = logger;
-
public async Task Handle(AskCommand askCommand, CancellationToken cancellationToken) {
if (askCommand.Command.Chat is GroupChat) {
try {
- AIRateLimiters.GROUP_CHAT_RATE_LIMITER.ValidateActionRate(
+ AiRateLimiters.GroupChatRateLimiter.ValidateActionRate(
chatId: askCommand.Command.Chat.Id,
userId: askCommand.Command.Sender.Id
);
} catch (RateLimitExceededException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: askCommand.Command.Chat.Id,
text: $"Anda terlalu banyak memanggil AI. Coba lagi {exc.Cooldown} atau lanjutkan di private chat.
",
parseMode: ParseMode.Html,
@@ -53,12 +47,12 @@ await _telegramBotClient.SendMessage(
}
} else {
try {
- OpenAITextPromptHandler.CHAT_RATE_LIMITER.ValidateActionRate(
+ OpenAiTextPromptHandler.ChatRateLimiter.ValidateActionRate(
chatId: askCommand.Command.Chat.Id,
userId: askCommand.Command.Sender.Id
);
} catch (RateLimitExceededException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: askCommand.Command.Chat.Id,
text: $"Anda terlalu banyak memanggil AI. Coba lagi {exc.Cooldown}.
",
parseMode: ParseMode.Html,
@@ -81,12 +75,12 @@ await _telegramBotClient.SendMessage(
messages.AddRange(
from message in askCommand.Thread.Take(10).Reverse()
select ChatMessage.FromText(
- role: message.Sender.ChatGPTRole,
+ role: message.Sender.ChatGptRole,
text: message.Text
)
);
- Message responseMessage = await _telegramBotClient.SendMessage(
+ Message responseMessage = await telegramBotClient.SendMessage(
chatId: askCommand.Command.Chat.Id,
text: MarkdownV2Sanitizer.Sanitize("… ⏳"),
parseMode: ParseMode.MarkdownV2,
@@ -95,9 +89,9 @@ select ChatMessage.FromText(
}
);
- string response = await _openAIClient.ChatAsync(
+ string response = await openAiClient.ChatAsync(
model: askCommand switch {
- ({ Command: { Sender: VIPSender } or { Chat: HomeGroupChat } }) => "gpt-4-1106-preview",
+ ({ Command: { Sender: VipSender } or { Chat: HomeGroupChat } }) => "gpt-4-1106-preview",
_ => "gpt-3.5-turbo"
},
messages: messages,
@@ -115,7 +109,7 @@ select ChatMessage.FromText(
replyMarkup: new InlineKeyboardMarkup(
InlineKeyboardButton.WithUrl(
text: askCommand switch {
- ({ Command: { Sender: VIPSender } or { Chat: HomeGroupChat } }) => "Generated by OpenAI GPT-4",
+ ({ Command: { Sender: VipSender } or { Chat: HomeGroupChat } }) => "Generated by OpenAI GPT-4",
_ => "Generated by OpenAI GPT-3.5 Turbo"
},
url: "https://openai.com/gpt-4"
@@ -124,7 +118,7 @@ select ChatMessage.FromText(
cancellationToken: cancellationToken
);
} catch (Exception exc) {
- _logger.LogError(exc, null);
+ logger.LogError(exc, null);
await telegramBotClient.EditMessageText(
chatId: askCommand.Command.Chat.Id,
messageId: responseMessage.MessageId,
@@ -136,12 +130,12 @@ await telegramBotClient.EditMessageText(
}
// Track thread
- _telegramMessageCache.Add(
- message: AIResponseMessage.FromMessage(
+ telegramMessageCache.Add(
+ message: AiResponseMessage.FromMessage(
message: responseMessage,
replyToMessage: askCommand.Command,
callSign: "GPT",
- commandPriorityCategorizer: _commandPriorityCategorizer
+ commandPriorityCategorizer: commandPriorityCategorizer
)
);
});
diff --git a/BotNet.CommandHandlers/AI/OpenAI/OpenAIImageGenerationPromptHandler.cs b/BotNet.CommandHandlers/AI/OpenAI/OpenAIImageGenerationPromptHandler.cs
index 4602c92..aacd96b 100644
--- a/BotNet.CommandHandlers/AI/OpenAI/OpenAIImageGenerationPromptHandler.cs
+++ b/BotNet.CommandHandlers/AI/OpenAI/OpenAIImageGenerationPromptHandler.cs
@@ -10,32 +10,26 @@
using Telegram.Bot.Types.ReplyMarkups;
namespace BotNet.CommandHandlers.AI.OpenAI {
- public sealed class OpenAIImageGenerationPromptHandler(
+ public sealed class OpenAiImageGenerationPromptHandler(
ITelegramBotClient telegramBotClient,
ImageGenerationBot imageGenerationBot,
ITelegramMessageCache telegramMessageCache,
CommandPriorityCategorizer commandPriorityCategorizer,
- ILogger logger
- ) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ImageGenerationBot _imageGenerationBot = imageGenerationBot;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
- private readonly ILogger _logger = logger;
-
- public Task Handle(OpenAIImageGenerationPrompt command, CancellationToken cancellationToken) {
+ ILogger logger
+ ) : ICommandHandler {
+ public Task Handle(OpenAiImageGenerationPrompt command, CancellationToken cancellationToken) {
// Fire and forget
Task.Run(async () => {
try {
Uri generatedImageUrl;
try {
- generatedImageUrl = await _imageGenerationBot.GenerateImageAsync(
+ generatedImageUrl = await imageGenerationBot.GenerateImageAsync(
prompt: command.Prompt,
cancellationToken: cancellationToken
);
} catch (Exception exc) {
- _logger.LogError(exc, "Could not generate image");
- await _telegramBotClient.EditMessageText(
+ logger.LogError(exc, "Could not generate image");
+ await telegramBotClient.EditMessageText(
chatId: command.Chat.Id,
messageId: command.ResponseMessageId,
text: "Failed to generate image.
",
@@ -47,7 +41,7 @@ await _telegramBotClient.EditMessageText(
// Delete busy message
try {
- await _telegramBotClient.DeleteMessage(
+ await telegramBotClient.DeleteMessage(
chatId: command.Chat.Id,
messageId: command.ResponseMessageId,
cancellationToken: cancellationToken
@@ -57,7 +51,7 @@ await _telegramBotClient.DeleteMessage(
}
// Send generated image
- Message responseMessage = await _telegramBotClient.SendPhoto(
+ Message responseMessage = await telegramBotClient.SendPhoto(
chatId: command.Chat.Id,
photo: new InputFileUrl(generatedImageUrl),
replyMarkup: new InlineKeyboardMarkup(
@@ -73,14 +67,14 @@ await _telegramBotClient.DeleteMessage(
);
// Track thread
- _telegramMessageCache.Add(
- NormalMessage.FromMessage(responseMessage, _commandPriorityCategorizer)
+ telegramMessageCache.Add(
+ NormalMessage.FromMessage(responseMessage, commandPriorityCategorizer)
);
} catch (OperationCanceledException) {
// Terminate gracefully
// TODO: tie up loose ends
} catch (Exception exc) {
- _logger.LogError(exc, "Could not handle command");
+ logger.LogError(exc, "Could not handle command");
}
});
diff --git a/BotNet.CommandHandlers/AI/OpenAI/OpenAIImagePromptHandler.cs b/BotNet.CommandHandlers/AI/OpenAI/OpenAIImagePromptHandler.cs
index bdbfbc3..aabff6e 100644
--- a/BotNet.CommandHandlers/AI/OpenAI/OpenAIImagePromptHandler.cs
+++ b/BotNet.CommandHandlers/AI/OpenAI/OpenAIImagePromptHandler.cs
@@ -19,28 +19,21 @@
using Telegram.Bot.Types.ReplyMarkups;
namespace BotNet.CommandHandlers.AI.OpenAI {
- public sealed class OpenAIImagePromptHandler(
+ public sealed class OpenAiImagePromptHandler(
ITelegramBotClient telegramBotClient,
ICommandQueue commandQueue,
ITelegramMessageCache telegramMessageCache,
- OpenAIClient openAIClient,
+ OpenAiClient openAiClient,
CommandPriorityCategorizer commandPriorityCategorizer,
- ILogger logger
- ) : ICommandHandler {
- internal static readonly RateLimiter VISION_RATE_LIMITER = RateLimiter.PerUserPerChat(1, TimeSpan.FromMinutes(15));
- internal static readonly RateLimiter VIP_VISION_RATE_LIMITER = RateLimiter.PerUserPerChat(2, TimeSpan.FromMinutes(2));
+ ILogger logger
+ ) : ICommandHandler {
+ private static readonly RateLimiter VisionRateLimiter = RateLimiter.PerUserPerChat(1, TimeSpan.FromMinutes(15));
+ private static readonly RateLimiter VipVisionRateLimiter = RateLimiter.PerUserPerChat(2, TimeSpan.FromMinutes(2));
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ICommandQueue _commandQueue = commandQueue;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly OpenAIClient _openAIClient = openAIClient;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
- private readonly ILogger _logger = logger;
-
- public Task Handle(OpenAIImagePrompt imagePrompt, CancellationToken cancellationToken) {
- if (imagePrompt.Command.Sender is not VIPSender
+ public Task Handle(OpenAiImagePrompt imagePrompt, CancellationToken cancellationToken) {
+ if (imagePrompt.Command.Sender is not VipSender
&& imagePrompt.Command.Chat is not HomeGroupChat) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: imagePrompt.Command.Chat.Id,
text: MarkdownV2Sanitizer.Sanitize("Vision tidak bisa dipakai di sini."),
parseMode: ParseMode.MarkdownV2,
@@ -52,19 +45,19 @@ public Task Handle(OpenAIImagePrompt imagePrompt, CancellationToken cancellation
}
try {
- if (imagePrompt.Command.Sender is VIPSender) {
- VIP_VISION_RATE_LIMITER.ValidateActionRate(
+ if (imagePrompt.Command.Sender is VipSender) {
+ VipVisionRateLimiter.ValidateActionRate(
chatId: imagePrompt.Command.Chat.Id,
userId: imagePrompt.Command.Sender.Id
);
} else {
- VISION_RATE_LIMITER.ValidateActionRate(
+ VisionRateLimiter.ValidateActionRate(
chatId: imagePrompt.Command.Chat.Id,
userId: imagePrompt.Command.Sender.Id
);
}
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: imagePrompt.Command.Chat.Id,
text: $"Anda terlalu banyak menggunakan vision. Coba lagi {exc.Cooldown}.
",
parseMode: ParseMode.Html,
@@ -78,13 +71,13 @@ public Task Handle(OpenAIImagePrompt imagePrompt, CancellationToken cancellation
// Fire and forget
Task.Run(async () => {
(string? imageBase64, string? error) = await GetImageBase64Async(
- botClient: _telegramBotClient,
+ botClient: telegramBotClient,
fileId: imagePrompt.ImageFileId,
cancellationToken: cancellationToken
);
if (error is not null) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: imagePrompt.Command.Chat.Id,
text: $"{error}
",
parseMode: ParseMode.Html,
@@ -103,7 +96,7 @@ await _telegramBotClient.SendMessage(
messages.AddRange(
from message in imagePrompt.Thread.Take(10).Reverse()
select ChatMessage.FromText(
- role: message.Sender.ChatGPTRole,
+ role: message.Sender.ChatGptRole,
text: message.Text
)
);
@@ -112,7 +105,7 @@ select ChatMessage.FromText(
ChatMessage.FromTextWithImageBase64("user", imagePrompt.Prompt, imageBase64!)
);
- Message responseMessage = await _telegramBotClient.SendMessage(
+ Message responseMessage = await telegramBotClient.SendMessage(
chatId: imagePrompt.Command.Chat.Id,
text: MarkdownV2Sanitizer.Sanitize("… ⏳"),
parseMode: ParseMode.MarkdownV2,
@@ -121,7 +114,7 @@ select ChatMessage.FromText(
}
);
- string response = await _openAIClient.ChatAsync(
+ string response = await openAiClient.ChatAsync(
model: "gpt-4-vision-preview",
messages: messages,
maxTokens: 512,
@@ -130,11 +123,11 @@ select ChatMessage.FromText(
// Handle image generation intent
if (response.StartsWith("ImageGeneration:")) {
- if (imagePrompt.Command.Sender is not VIPSender) {
+ if (imagePrompt.Command.Sender is not VipSender) {
try {
- ArtCommandHandler.IMAGE_GENERATION_RATE_LIMITER.ValidateActionRate(imagePrompt.Command.Chat.Id, imagePrompt.Command.Sender.Id);
+ ArtCommandHandler.ImageGenerationRateLimiter.ValidateActionRate(imagePrompt.Command.Chat.Id, imagePrompt.Command.Sender.Id);
} catch (RateLimitExceededException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: imagePrompt.Command.Chat.Id,
text: $"Anda belum mendapat giliran. Coba lagi {exc.Cooldown}.",
parseMode: ParseMode.Html,
@@ -149,9 +142,9 @@ await _telegramBotClient.SendMessage(
string imageGenerationPrompt = response.Substring(response.IndexOf(':') + 1).Trim();
switch (imagePrompt.Command) {
- case { Sender: VIPSender }:
- await _commandQueue.DispatchAsync(
- command: new OpenAIImageGenerationPrompt(
+ case { Sender: VipSender }:
+ await commandQueue.DispatchAsync(
+ command: new OpenAiImageGenerationPrompt(
callSign: imagePrompt.CallSign,
prompt: imageGenerationPrompt,
promptMessageId: imagePrompt.Command.MessageId,
@@ -162,7 +155,7 @@ await _commandQueue.DispatchAsync(
);
break;
case { Chat: HomeGroupChat }:
- await _commandQueue.DispatchAsync(
+ await commandQueue.DispatchAsync(
command: new StabilityTextToImagePrompt(
callSign: imagePrompt.CallSign,
prompt: imageGenerationPrompt,
@@ -174,7 +167,7 @@ await _commandQueue.DispatchAsync(
);
break;
default:
- await _telegramBotClient.EditMessageText(
+ await telegramBotClient.EditMessageText(
chatId: imagePrompt.Command.Chat.Id,
messageId: responseMessage.MessageId,
text: MarkdownV2Sanitizer.Sanitize("Image generation tidak bisa dipakai di sini."),
@@ -202,7 +195,7 @@ await _telegramBotClient.EditMessageText(
cancellationToken: cancellationToken
);
} catch (Exception exc) {
- _logger.LogError(exc, null);
+ logger.LogError(exc, null);
await telegramBotClient.EditMessageText(
chatId: imagePrompt.Command.Chat.Id,
messageId: responseMessage.MessageId,
@@ -214,12 +207,12 @@ await telegramBotClient.EditMessageText(
}
// Track thread
- _telegramMessageCache.Add(
- message: AIResponseMessage.FromMessage(
+ telegramMessageCache.Add(
+ message: AiResponseMessage.FromMessage(
message: responseMessage,
replyToMessage: imagePrompt.Command,
callSign: imagePrompt.CallSign,
- commandPriorityCategorizer: _commandPriorityCategorizer
+ commandPriorityCategorizer: commandPriorityCategorizer
)
);
});
diff --git a/BotNet.CommandHandlers/AI/OpenAI/OpenAITextPromptHandler.cs b/BotNet.CommandHandlers/AI/OpenAI/OpenAITextPromptHandler.cs
index 8a64659..995edf5 100644
--- a/BotNet.CommandHandlers/AI/OpenAI/OpenAITextPromptHandler.cs
+++ b/BotNet.CommandHandlers/AI/OpenAI/OpenAITextPromptHandler.cs
@@ -19,32 +19,25 @@
using Telegram.Bot.Types.ReplyMarkups;
namespace BotNet.CommandHandlers.AI.OpenAI {
- public sealed class OpenAITextPromptHandler(
+ public sealed class OpenAiTextPromptHandler(
ITelegramBotClient telegramBotClient,
ICommandQueue commandQueue,
ITelegramMessageCache telegramMessageCache,
- OpenAIClient openAIClient,
+ OpenAiClient openAiClient,
CommandPriorityCategorizer commandPriorityCategorizer,
- ILogger logger
- ) : ICommandHandler {
- internal static readonly RateLimiter CHAT_RATE_LIMITER = RateLimiter.PerUserPerChat(5, TimeSpan.FromMinutes(15));
+ ILogger logger
+ ) : ICommandHandler {
+ internal static readonly RateLimiter ChatRateLimiter = RateLimiter.PerUserPerChat(5, TimeSpan.FromMinutes(15));
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ICommandQueue _commandQueue = commandQueue;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly OpenAIClient _openAIClient = openAIClient;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
- private readonly ILogger _logger = logger;
-
- public Task Handle(OpenAITextPrompt textPrompt, CancellationToken cancellationToken) {
+ public Task Handle(OpenAiTextPrompt textPrompt, CancellationToken cancellationToken) {
if (textPrompt.Command.Chat is GroupChat) {
try {
- AIRateLimiters.GROUP_CHAT_RATE_LIMITER.ValidateActionRate(
+ AiRateLimiters.GroupChatRateLimiter.ValidateActionRate(
chatId: textPrompt.Command.Chat.Id,
userId: textPrompt.Command.Sender.Id
);
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: textPrompt.Command.Chat.Id,
text: $"Anda terlalu banyak memanggil AI. Coba lagi {exc.Cooldown} atau lanjutkan di private chat.
",
parseMode: ParseMode.Html,
@@ -59,12 +52,12 @@ public Task Handle(OpenAITextPrompt textPrompt, CancellationToken cancellationTo
}
} else {
try {
- CHAT_RATE_LIMITER.ValidateActionRate(
+ ChatRateLimiter.ValidateActionRate(
chatId: textPrompt.Command.Chat.Id,
userId: textPrompt.Command.Sender.Id
);
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: textPrompt.Command.Chat.Id,
text: $"Anda terlalu banyak memanggil AI. Coba lagi {exc.Cooldown}.
",
parseMode: ParseMode.Html,
@@ -85,7 +78,7 @@ public Task Handle(OpenAITextPrompt textPrompt, CancellationToken cancellationTo
messages.AddRange(
from message in textPrompt.Thread.Take(10).Reverse()
select ChatMessage.FromText(
- role: message.Sender.ChatGPTRole,
+ role: message.Sender.ChatGptRole,
text: message.Text
)
);
@@ -94,7 +87,7 @@ select ChatMessage.FromText(
ChatMessage.FromText("user", textPrompt.Prompt)
);
- Message responseMessage = await _telegramBotClient.SendMessage(
+ Message responseMessage = await telegramBotClient.SendMessage(
chatId: textPrompt.Command.Chat.Id,
text: MarkdownV2Sanitizer.Sanitize("… ⏳"),
parseMode: ParseMode.MarkdownV2,
@@ -103,9 +96,9 @@ select ChatMessage.FromText(
}
);
- string response = await _openAIClient.ChatAsync(
+ string response = await openAiClient.ChatAsync(
model: textPrompt switch {
- ({ Command: { Sender: VIPSender } or { Chat: HomeGroupChat } }) => "gpt-4-1106-preview",
+ { Command: { Sender: VipSender } or { Chat: HomeGroupChat } } => "gpt-4-1106-preview",
_ => "gpt-3.5-turbo"
},
messages: messages,
@@ -115,11 +108,11 @@ select ChatMessage.FromText(
// Handle image generation intent
if (response.StartsWith("ImageGeneration:")) {
- if (textPrompt.Command.Sender is not VIPSender) {
+ if (textPrompt.Command.Sender is not VipSender) {
try {
- ArtCommandHandler.IMAGE_GENERATION_RATE_LIMITER.ValidateActionRate(textPrompt.Command.Chat.Id, textPrompt.Command.Sender.Id);
+ ArtCommandHandler.ImageGenerationRateLimiter.ValidateActionRate(textPrompt.Command.Chat.Id, textPrompt.Command.Sender.Id);
} catch (RateLimitExceededException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: textPrompt.Command.Chat.Id,
text: $"Anda belum mendapat giliran. Coba lagi {exc.Cooldown}.",
parseMode: ParseMode.Html,
@@ -134,9 +127,9 @@ await _telegramBotClient.SendMessage(
string imageGenerationPrompt = response.Substring(response.IndexOf(':') + 1).Trim();
switch (textPrompt.Command) {
- case { Sender: VIPSender }:
- await _commandQueue.DispatchAsync(
- command: new OpenAIImageGenerationPrompt(
+ case { Sender: VipSender }:
+ await commandQueue.DispatchAsync(
+ command: new OpenAiImageGenerationPrompt(
callSign: textPrompt.CallSign,
prompt: imageGenerationPrompt,
promptMessageId: textPrompt.Command.MessageId,
@@ -147,7 +140,7 @@ await _commandQueue.DispatchAsync(
);
break;
case { Chat: HomeGroupChat }:
- await _commandQueue.DispatchAsync(
+ await commandQueue.DispatchAsync(
command: new StabilityTextToImagePrompt(
callSign: textPrompt.CallSign,
prompt: imageGenerationPrompt,
@@ -159,7 +152,7 @@ await _commandQueue.DispatchAsync(
);
break;
default:
- await _telegramBotClient.EditMessageText(
+ await telegramBotClient.EditMessageText(
chatId: textPrompt.Command.Chat.Id,
messageId: responseMessage.MessageId,
text: MarkdownV2Sanitizer.Sanitize("Image generation tidak bisa dipakai di sini."),
@@ -181,7 +174,7 @@ await _telegramBotClient.EditMessageText(
replyMarkup: new InlineKeyboardMarkup(
InlineKeyboardButton.WithUrl(
text: textPrompt switch {
- ({ Command: { Sender: VIPSender } or { Chat: HomeGroupChat } }) => "Generated by OpenAI GPT-4",
+ { Command: { Sender: VipSender } or { Chat: HomeGroupChat } } => "Generated by OpenAI GPT-4",
_ => "Generated by OpenAI GPT-3.5 Turbo"
},
url: "https://openai.com/gpt-4"
@@ -190,7 +183,7 @@ await _telegramBotClient.EditMessageText(
cancellationToken: cancellationToken
);
} catch (Exception exc) {
- _logger.LogError(exc, null);
+ logger.LogError(exc, null);
await telegramBotClient.EditMessageText(
chatId: textPrompt.Command.Chat.Id,
messageId: responseMessage.MessageId,
@@ -202,12 +195,12 @@ await telegramBotClient.EditMessageText(
}
// Track thread
- _telegramMessageCache.Add(
- message: AIResponseMessage.FromMessage(
+ telegramMessageCache.Add(
+ message: AiResponseMessage.FromMessage(
message: responseMessage,
replyToMessage: textPrompt.Command,
callSign: textPrompt.CallSign,
- commandPriorityCategorizer: _commandPriorityCategorizer
+ commandPriorityCategorizer: commandPriorityCategorizer
)
);
});
diff --git a/BotNet.CommandHandlers/AI/RateLimit/AIRateLimiters.cs b/BotNet.CommandHandlers/AI/RateLimit/AIRateLimiters.cs
index 22e6f7c..f8c1358 100644
--- a/BotNet.CommandHandlers/AI/RateLimit/AIRateLimiters.cs
+++ b/BotNet.CommandHandlers/AI/RateLimit/AIRateLimiters.cs
@@ -1,7 +1,7 @@
using BotNet.Services.RateLimit;
namespace BotNet.CommandHandlers.AI.RateLimit {
- internal static class AIRateLimiters {
- internal static readonly RateLimiter GROUP_CHAT_RATE_LIMITER = RateLimiter.PerUserPerChat(4, TimeSpan.FromMinutes(60));
+ internal static class AiRateLimiters {
+ internal static readonly RateLimiter GroupChatRateLimiter = RateLimiter.PerUserPerChat(4, TimeSpan.FromMinutes(60));
}
}
diff --git a/BotNet.CommandHandlers/AI/Stability/StabilityTextToImagePromptHandler.cs b/BotNet.CommandHandlers/AI/Stability/StabilityTextToImagePromptHandler.cs
index 002446d..7a1bd57 100644
--- a/BotNet.CommandHandlers/AI/Stability/StabilityTextToImagePromptHandler.cs
+++ b/BotNet.CommandHandlers/AI/Stability/StabilityTextToImagePromptHandler.cs
@@ -16,32 +16,27 @@ public sealed class StabilityTextToImagePromptHandler(
ITelegramMessageCache telegramMessageCache,
CommandPriorityCategorizer commandPriorityCategorizer
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ImageGenerationBot _imageGenerationBot = imageGenerationBot;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
-
public Task Handle(StabilityTextToImagePrompt command, CancellationToken cancellationToken) {
// Fire and forget
Task.Run(async () => {
try {
byte[] generatedImage;
try {
- generatedImage = await _imageGenerationBot.GenerateImageAsync(
+ generatedImage = await imageGenerationBot.GenerateImageAsync(
prompt: command.Prompt,
cancellationToken: cancellationToken
);
} catch (ContentFilteredException exc) {
- await _telegramBotClient.EditMessageText(
+ await telegramBotClient.EditMessageText(
chatId: command.Chat.Id,
messageId: command.ResponseMessageId,
- text: $"{exc.Message ?? "Content filtered."}
",
+ text: $"{exc.Message}
",
parseMode: ParseMode.Html,
cancellationToken: cancellationToken
);
return;
} catch {
- await _telegramBotClient.EditMessageText(
+ await telegramBotClient.EditMessageText(
chatId: command.Chat.Id,
messageId: command.ResponseMessageId,
text: "Failed to generate image.
",
@@ -53,7 +48,7 @@ await _telegramBotClient.EditMessageText(
// Delete busy message
try {
- await _telegramBotClient.DeleteMessage(
+ await telegramBotClient.DeleteMessage(
chatId: command.Chat.Id,
messageId: command.ResponseMessageId,
cancellationToken: cancellationToken
@@ -64,7 +59,7 @@ await _telegramBotClient.DeleteMessage(
// Send generated image
using MemoryStream generatedImageStream = new(generatedImage);
- Message responseMessage = await _telegramBotClient.SendPhoto(
+ Message responseMessage = await telegramBotClient.SendPhoto(
chatId: command.Chat.Id,
photo: new InputFileStream(generatedImageStream, "art.png"),
replyMarkup: new InlineKeyboardMarkup(
@@ -80,8 +75,8 @@ await _telegramBotClient.DeleteMessage(
);
// Track thread
- _telegramMessageCache.Add(
- NormalMessage.FromMessage(responseMessage, _commandPriorityCategorizer)
+ telegramMessageCache.Add(
+ NormalMessage.FromMessage(responseMessage, commandPriorityCategorizer)
);
} catch (OperationCanceledException) {
// Terminate gracefully
diff --git a/BotNet.CommandHandlers/Art/ArtCommandHandler.cs b/BotNet.CommandHandlers/Art/ArtCommandHandler.cs
index cd96deb..2e8ddc2 100644
--- a/BotNet.CommandHandlers/Art/ArtCommandHandler.cs
+++ b/BotNet.CommandHandlers/Art/ArtCommandHandler.cs
@@ -15,16 +15,13 @@ public sealed class ArtCommandHandler(
ITelegramBotClient telegramBotClient,
ICommandQueue commandQueue
) : ICommandHandler {
- internal static readonly RateLimiter IMAGE_GENERATION_RATE_LIMITER = RateLimiter.PerUser(2, TimeSpan.FromMinutes(3));
-
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ICommandQueue _commandQueue = commandQueue;
+ internal static readonly RateLimiter ImageGenerationRateLimiter = RateLimiter.PerUser(2, TimeSpan.FromMinutes(3));
public Task Handle(ArtCommand command, CancellationToken cancellationToken) {
try {
- IMAGE_GENERATION_RATE_LIMITER.ValidateActionRate(command.Chat.Id, command.Sender.Id);
+ ImageGenerationRateLimiter.ValidateActionRate(command.Chat.Id, command.Sender.Id);
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"Anda belum mendapat giliran. Coba lagi {exc.Cooldown}.",
parseMode: ParseMode.Html,
@@ -39,8 +36,8 @@ public Task Handle(ArtCommand command, CancellationToken cancellationToken) {
Task.Run(async () => {
try {
switch (command) {
- case { Sender: VIPSender }: {
- Message busyMessage = await _telegramBotClient.SendMessage(
+ case { Sender: VipSender }: {
+ Message busyMessage = await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Generating image… ⏳",
parseMode: ParseMode.Markdown,
@@ -50,8 +47,8 @@ public Task Handle(ArtCommand command, CancellationToken cancellationToken) {
cancellationToken: cancellationToken
);
- await _commandQueue.DispatchAsync(
- new OpenAIImageGenerationPrompt(
+ await commandQueue.DispatchAsync(
+ new OpenAiImageGenerationPrompt(
callSign: "GPT",
prompt: command.Prompt,
promptMessageId: command.PromptMessageId,
@@ -63,7 +60,7 @@ await _commandQueue.DispatchAsync(
}
break;
case { Chat: HomeGroupChat }: {
- Message busyMessage = await _telegramBotClient.SendMessage(
+ Message busyMessage = await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Generating image… ⏳",
parseMode: ParseMode.Markdown,
@@ -73,7 +70,7 @@ await _commandQueue.DispatchAsync(
cancellationToken: cancellationToken
);
- await _commandQueue.DispatchAsync(
+ await commandQueue.DispatchAsync(
new StabilityTextToImagePrompt(
callSign: "GPT",
prompt: command.Prompt,
@@ -86,7 +83,7 @@ await _commandQueue.DispatchAsync(
}
break;
default:
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: MarkdownV2Sanitizer.Sanitize("Image generation tidak bisa dipakai di sini."),
parseMode: ParseMode.MarkdownV2,
diff --git a/BotNet.CommandHandlers/BMKG/BMKGCommandHandler.cs b/BotNet.CommandHandlers/BMKG/BMKGCommandHandler.cs
index 9b9ad30..0f78dcc 100644
--- a/BotNet.CommandHandlers/BMKG/BMKGCommandHandler.cs
+++ b/BotNet.CommandHandlers/BMKG/BMKGCommandHandler.cs
@@ -6,20 +6,17 @@
using Telegram.Bot.Types.Enums;
namespace BotNet.CommandHandlers.BMKG {
- public sealed class BMKGCommandHandler(
+ public sealed class BmkgCommandHandler(
ITelegramBotClient telegramBotClient,
LatestEarthQuake latestEarthQuake
- ) : ICommandHandler {
- private static readonly RateLimiter RATE_LIMITER = RateLimiter.PerChat(3, TimeSpan.FromMinutes(2));
+ ) : ICommandHandler {
+ private static readonly RateLimiter RateLimiter = RateLimiter.PerChat(3, TimeSpan.FromMinutes(2));
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly LatestEarthQuake _latestEarthQuake = latestEarthQuake;
-
- public Task Handle(BMKGCommand command, CancellationToken cancellationToken) {
+ public Task Handle(BmkgCommand command, CancellationToken cancellationToken) {
try {
- RATE_LIMITER.ValidateActionRate(command.Chat.Id, command.Sender.Id);
+ RateLimiter.ValidateActionRate(command.Chat.Id, command.Sender.Id);
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"Sabar dulu ya, tunggu giliran yang lain. Coba lagi {exc.Cooldown}.",
parseMode: ParseMode.Html,
@@ -33,9 +30,9 @@ public Task Handle(BMKGCommand command, CancellationToken cancellationToken) {
// Fire and forget
Task.Run(async () => {
try {
- (string text, string shakemapUrl) = await _latestEarthQuake.GetLatestAsync();
+ (string text, string shakemapUrl) = await latestEarthQuake.GetLatestAsync();
- await _telegramBotClient.SendPhoto(
+ await telegramBotClient.SendPhoto(
chatId: command.Chat.Id,
photo: new InputFileUrl(shakemapUrl),
caption: text,
diff --git a/BotNet.CommandHandlers/BotUpdate/CallbackQuery/CallbackQueryUpdateHandler.cs b/BotNet.CommandHandlers/BotUpdate/CallbackQuery/CallbackQueryUpdateHandler.cs
index 90af78b..c4602f5 100644
--- a/BotNet.CommandHandlers/BotUpdate/CallbackQuery/CallbackQueryUpdateHandler.cs
+++ b/BotNet.CommandHandlers/BotUpdate/CallbackQuery/CallbackQueryUpdateHandler.cs
@@ -6,8 +6,6 @@ namespace BotNet.CommandHandlers.BotUpdate.CallbackQuery {
public sealed class CallbackQueryUpdateHandler(
ICommandQueue commandQueue
) : ICommandHandler {
- private readonly ICommandQueue _commandQueue = commandQueue;
-
public async Task Handle(CallbackQueryUpdate command, CancellationToken cancellationToken) {
// Only handle callback queries with data
if (command.CallbackQuery.Data is not { } data) {
@@ -27,7 +25,7 @@ public async Task Handle(CallbackQueryUpdate command, CancellationToken cancella
callbackQuery: command.CallbackQuery,
out BubbleWrapCallback? bubbleWrapCallback
)) {
- await _commandQueue.DispatchAsync(bubbleWrapCallback);
+ await commandQueue.DispatchAsync(bubbleWrapCallback);
}
break;
}
diff --git a/BotNet.CommandHandlers/BotUpdate/InlineQuery/InlineQueryUpdateHandler.cs b/BotNet.CommandHandlers/BotUpdate/InlineQuery/InlineQueryUpdateHandler.cs
index 87bae9c..55c538f 100644
--- a/BotNet.CommandHandlers/BotUpdate/InlineQuery/InlineQueryUpdateHandler.cs
+++ b/BotNet.CommandHandlers/BotUpdate/InlineQuery/InlineQueryUpdateHandler.cs
@@ -13,16 +13,12 @@ public sealed class InlineQueryUpdateHandler(
BrainfuckTranspiler brainfuckTranspiler,
ILogger logger
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly BrainfuckTranspiler _brainfuckTranspiler = brainfuckTranspiler;
- private readonly ILogger _logger = logger;
-
public Task Handle(InlineQueryUpdate command, CancellationToken cancellationToken) {
// Fire and forget
Task.Run(async () => {
try {
// Query must not be empty
- if (command.InlineQuery?.Query.Trim() is not { Length: > 0 } query) {
+ if (command.InlineQuery.Query.Trim() is not { Length: > 0 } query) {
return;
}
@@ -53,7 +49,7 @@ public Task Handle(InlineQueryUpdate command, CancellationToken cancellationToke
}
// Generate brainfuck code
- string brainfuckCode = _brainfuckTranspiler.TranspileBrainfuck(query);
+ string brainfuckCode = brainfuckTranspiler.TranspileBrainfuck(query);
results.Add(new InlineQueryResultArticle(
id: Guid.NewGuid().ToString("N"),
title: brainfuckCode,
@@ -61,7 +57,7 @@ public Task Handle(InlineQueryUpdate command, CancellationToken cancellationToke
));
// Send results
- await _telegramBotClient.AnswerInlineQuery(
+ await telegramBotClient.AnswerInlineQuery(
inlineQueryId: command.InlineQuery.Id,
results: results,
cancellationToken: cancellationToken
@@ -69,7 +65,7 @@ await _telegramBotClient.AnswerInlineQuery(
} catch (OperationCanceledException) {
// Terminate gracefully
} catch (Exception exc) {
- _logger.LogError(exc, "Could not handle inline query");
+ logger.LogError(exc, "Could not handle inline query");
}
});
diff --git a/BotNet.CommandHandlers/BotUpdate/Message/AICallCommandHandler.cs b/BotNet.CommandHandlers/BotUpdate/Message/AICallCommandHandler.cs
index 78577fa..5b9bb96 100644
--- a/BotNet.CommandHandlers/BotUpdate/Message/AICallCommandHandler.cs
+++ b/BotNet.CommandHandlers/BotUpdate/Message/AICallCommandHandler.cs
@@ -2,49 +2,43 @@
using BotNet.Commands.AI.Gemini;
using BotNet.Commands.AI.OpenAI;
using BotNet.Commands.BotUpdate.Message;
-using BotNet.Services.OpenAI;
namespace BotNet.CommandHandlers.BotUpdate.Message {
- public sealed class AICallCommandHandler(
+ public sealed class AiCallCommandHandler(
ICommandQueue commandQueue,
- ITelegramMessageCache telegramMessageCache,
- IntentDetector intentDetector
- ) : ICommandHandler {
- private readonly ICommandQueue _commandQueue = commandQueue;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly IntentDetector _intentDetector = intentDetector;
-
- public async Task Handle(AICallCommand command, CancellationToken cancellationToken) {
+ ITelegramMessageCache telegramMessageCache
+ ) : ICommandHandler {
+ public async Task Handle(AiCallCommand command, CancellationToken cancellationToken) {
switch (command.CallSign) {
case "GPT" when command.ImageFileId is null && command.ReplyToMessage?.ImageFileId is null: {
- await _commandQueue.DispatchAsync(
- command: OpenAITextPrompt.FromAICallCommand(
+ await commandQueue.DispatchAsync(
+ command: OpenAiTextPrompt.FromAiCallCommand(
aiCallCommand: command,
thread: command.ReplyToMessage is { } replyToMessage
- ? _telegramMessageCache.GetThread(replyToMessage)
- : Enumerable.Empty()
+ ? telegramMessageCache.GetThread(replyToMessage)
+ : []
)
);
break;
}
case "GPT" when command.ImageFileId is not null || command.ReplyToMessage?.ImageFileId is not null: {
- await _commandQueue.DispatchAsync(
- command: OpenAIImagePrompt.FromAICallCommand(
+ await commandQueue.DispatchAsync(
+ command: OpenAiImagePrompt.FromAiCallCommand(
aiCallCommand: command,
thread: command.ReplyToMessage is { } replyToMessage
- ? _telegramMessageCache.GetThread(replyToMessage)
- : Enumerable.Empty()
+ ? telegramMessageCache.GetThread(replyToMessage)
+ : []
)
);
break;
}
case "AI" or "Bot" or "Gemini" when command.ImageFileId is null && command.ReplyToMessage?.ImageFileId is null: {
- await _commandQueue.DispatchAsync(
- command: GeminiTextPrompt.FromAICallCommand(
+ await commandQueue.DispatchAsync(
+ command: GeminiTextPrompt.FromAiCallCommand(
aiCallCommand: command,
thread: command.ReplyToMessage is { } replyToMessage
- ? _telegramMessageCache.GetThread(replyToMessage)
- : Enumerable.Empty()
+ ? telegramMessageCache.GetThread(replyToMessage)
+ : []
)
);
break;
diff --git a/BotNet.CommandHandlers/BotUpdate/Message/AIFollowUpMessageHandler.cs b/BotNet.CommandHandlers/BotUpdate/Message/AIFollowUpMessageHandler.cs
index b05e37c..6cc1017 100644
--- a/BotNet.CommandHandlers/BotUpdate/Message/AIFollowUpMessageHandler.cs
+++ b/BotNet.CommandHandlers/BotUpdate/Message/AIFollowUpMessageHandler.cs
@@ -4,39 +4,32 @@
using BotNet.Commands.BotUpdate.Message;
namespace BotNet.CommandHandlers.BotUpdate.Message {
- public sealed class AIFollowUpMessageHandler(
+ public sealed class AiFollowUpMessageHandler(
ICommandQueue commandQueue,
ITelegramMessageCache telegramMessageCache
- ) : ICommandHandler {
- private readonly ICommandQueue _commandQueue = commandQueue;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
-
- public async Task Handle(AIFollowUpMessage command, CancellationToken cancellationToken) {
+ ) : ICommandHandler {
+ public async Task Handle(AiFollowUpMessage command, CancellationToken cancellationToken) {
switch (command.CallSign) {
// OpenAI GPT-4 Chat
case "GPT":
- await _commandQueue.DispatchAsync(
- command: OpenAITextPrompt.FromAIFollowUpMessage(
+ await commandQueue.DispatchAsync(
+ command: OpenAiTextPrompt.FromAiFollowUpMessage(
aiFollowUpMessage: command,
- thread: command.ReplyToMessage is null
- ? Enumerable.Empty()
- : _telegramMessageCache.GetThread(
- firstMessage: command.ReplyToMessage
- )
+ thread: telegramMessageCache.GetThread(
+ firstMessage: command.ReplyToMessage
+ )
)
);
break;
// Google Gemini Chat
case "AI" or "Bot" or "Gemini":
- await _commandQueue.DispatchAsync(
- command: GeminiTextPrompt.FromAIFollowUpMessage(
+ await commandQueue.DispatchAsync(
+ command: GeminiTextPrompt.FromAiFollowUpMessage(
aIFollowUpMessage: command,
- thread: command.ReplyToMessage is null
- ? Enumerable.Empty()
- : _telegramMessageCache.GetThread(
- firstMessage: command.ReplyToMessage
- )
+ thread: telegramMessageCache.GetThread(
+ firstMessage: command.ReplyToMessage
+ )
)
);
break;
diff --git a/BotNet.CommandHandlers/BotUpdate/Message/MessageUpdateHandler.cs b/BotNet.CommandHandlers/BotUpdate/Message/MessageUpdateHandler.cs
index 8f3f95d..359fa2a 100644
--- a/BotNet.CommandHandlers/BotUpdate/Message/MessageUpdateHandler.cs
+++ b/BotNet.CommandHandlers/BotUpdate/Message/MessageUpdateHandler.cs
@@ -19,127 +19,129 @@ public sealed class MessageUpdateHandler(
BotProfileAccessor botProfileAccessor,
CommandPriorityCategorizer commandPriorityCategorizer
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ICommandQueue _commandQueue = commandQueue;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
- private readonly BotProfileAccessor _botProfileAccessor = botProfileAccessor;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
-
- public async Task Handle(MessageUpdate update, CancellationToken cancellationToken) {
+ public async Task Handle(
+ MessageUpdate update,
+ CancellationToken cancellationToken
+ ) {
// Handle slash commands
if (update.Message.Entities?.FirstOrDefault() is {
- Type: MessageEntityType.BotCommand,
- Offset: 0
- }) {
+ Type: MessageEntityType.BotCommand,
+ Offset: 0
+ }) {
if (SlashCommand.TryCreate(
- message: update.Message,
- botUsername: (await _botProfileAccessor.GetBotProfileAsync(cancellationToken)).Username!,
- commandPriorityCategorizer: _commandPriorityCategorizer,
- out SlashCommand? slashCommand
- )) {
- await _commandQueue.DispatchAsync(
+ message: update.Message,
+ botUsername: (await botProfileAccessor.GetBotProfileAsync(cancellationToken)).Username!,
+ commandPriorityCategorizer: commandPriorityCategorizer,
+ out SlashCommand? slashCommand
+ )) {
+ await commandQueue.DispatchAsync(
command: slashCommand
);
}
+
return;
}
// Handle Social Link (better preview)
if ((update.Message.Text ?? update.Message.Caption) is { } textOrCaption) {
- IEnumerable possibleUrls = SocialLinkEmbedFixer.GetPossibleUrls(textOrCaption);
+ List possibleUrls = SocialLinkEmbedFixer.GetPossibleUrls(textOrCaption)
+ .ToList();
if (possibleUrls.Any()) {
// Fire and forget
- Task _ = Task.Run(async () => {
- try {
- foreach (Uri url in possibleUrls) {
- Uri fixedUrl = SocialLinkEmbedFixer.Fix(url);
- await _telegramBotClient.SendMessage(
- chatId: update.Message.Chat.Id,
- text: $"Preview: {fixedUrl.OriginalString}",
- replyParameters: new ReplyParameters {
- MessageId = update.Message.MessageId
- },
- cancellationToken: cancellationToken
- );
+ Task _ = Task.Run(
+ async () => {
+ try {
+ foreach (Uri url in possibleUrls) {
+ Uri fixedUrl = SocialLinkEmbedFixer.Fix(url);
+ await telegramBotClient.SendMessage(
+ chatId: update.Message.Chat.Id,
+ text: $"Preview: {fixedUrl.OriginalString}",
+ replyParameters: new ReplyParameters { MessageId = update.Message.MessageId },
+ cancellationToken: cancellationToken
+ );
+ }
+ } catch (OperationCanceledException) {
+ // Terminate gracefully
}
- } catch (OperationCanceledException) {
- // Terminate gracefully
}
- });
+ );
return;
}
}
// Handle reddit mirroring
- if (update.Message?.Entities?.FirstOrDefault(entity => entity is {
- Type: MessageEntityType.Url
- }) is {
- Offset: var offset,
- Length: var length
- } && update.Message.Text?.Substring(offset, length) is { } url
- && url.StartsWith("https://www.reddit.com/", out string? remainingUrl)) {
+ if (update.Message.Entities?.FirstOrDefault(
+ entity => entity is {
+ Type: MessageEntityType.Url
+ }
+ ) is {
+ Offset: var offset,
+ Length: var length
+ } &&
+ update.Message.Text?.Substring(offset, length) is { } url &&
+ url.StartsWith("https://www.reddit.com/", out string? remainingUrl)) {
// Fire and forget
- Task _ = Task.Run(async () => {
- try {
- await _telegramBotClient.SendMessage(
- chatId: update.Message.Chat.Id,
- text: $"Mirror: https://libreddit.teknologiumum.com/{remainingUrl}",
- replyParameters: new ReplyParameters {
- MessageId = update.Message.MessageId
- },
- linkPreviewOptions: new LinkPreviewOptions {
- IsDisabled = true
- },
- cancellationToken: cancellationToken
- );
- } catch (OperationCanceledException) {
- // Terminate gracefully
+ Task _ = Task.Run(
+ async () => {
+ try {
+ await telegramBotClient.SendMessage(
+ chatId: update.Message.Chat.Id,
+ text: $"Mirror: https://libreddit.teknologiumum.com/{remainingUrl}",
+ replyParameters: new ReplyParameters { MessageId = update.Message.MessageId },
+ linkPreviewOptions: new LinkPreviewOptions { IsDisabled = true },
+ cancellationToken: cancellationToken
+ );
+ } catch (OperationCanceledException) {
+ // Terminate gracefully
+ }
}
- });
+ );
return;
- } else if (update.Message?.Entities?.FirstOrDefault(entity => entity is {
- Type: MessageEntityType.TextLink
- }) is { Url: { } textUrl }
- && textUrl.StartsWith("https://www.reddit.com/", out string? remainingTextUrl)) {
+ }
+
+ if (update.Message.Entities?.FirstOrDefault(
+ entity => entity is {
+ Type: MessageEntityType.TextLink
+ }
+ ) is { Url: { } textUrl } &&
+ textUrl.StartsWith("https://www.reddit.com/", out string? remainingTextUrl)) {
// Fire and forget
- Task _ = Task.Run(async () => {
- try {
- await _telegramBotClient.SendMessage(
- chatId: update.Message.Chat.Id,
- text: $"Mirror: https://libreddit.teknologiumum.com/{remainingTextUrl}",
- replyParameters: new ReplyParameters {
- MessageId = update.Message.MessageId
- },
- linkPreviewOptions: new LinkPreviewOptions {
- IsDisabled = true
- },
- cancellationToken: cancellationToken
- );
- } catch (OperationCanceledException) {
- // Terminate gracefully
+ Task _ = Task.Run(
+ async () => {
+ try {
+ await telegramBotClient.SendMessage(
+ chatId: update.Message.Chat.Id,
+ text: $"Mirror: https://libreddit.teknologiumum.com/{remainingTextUrl}",
+ replyParameters: new ReplyParameters { MessageId = update.Message.MessageId },
+ linkPreviewOptions: new LinkPreviewOptions { IsDisabled = true },
+ cancellationToken: cancellationToken
+ );
+ } catch (OperationCanceledException) {
+ // Terminate gracefully
+ }
}
- });
+ );
return;
}
// Handle AI calls
- if (AICallCommand.TryCreate(
- message: update.Message!,
- commandPriorityCategorizer: _commandPriorityCategorizer,
- out AICallCommand? aiCallCommand
- )) {
+ if (AiCallCommand.TryCreate(
+ message: update.Message,
+ commandPriorityCategorizer: commandPriorityCategorizer,
+ out AiCallCommand? aiCallCommand
+ )) {
// Cache both message and reply to message
- _telegramMessageCache.Add(
+ telegramMessageCache.Add(
message: aiCallCommand
);
if (aiCallCommand.ReplyToMessage is { } replyToMessage) {
- _telegramMessageCache.Add(
+ telegramMessageCache.Add(
message: replyToMessage
);
}
- await _commandQueue.DispatchAsync(
+ await commandQueue.DispatchAsync(
command: aiCallCommand
);
return;
@@ -147,96 +149,97 @@ await _commandQueue.DispatchAsync(
// Handle AI follow up message
if (update.Message is {
- ReplyToMessage.MessageId: int replyToMessageId,
- Chat.Id: long chatId
- } && _telegramMessageCache.GetOrDefault(
- messageId: new(replyToMessageId),
- chatId: new(chatId)
- ) is AIResponseMessage) {
- if (!AIFollowUpMessage.TryCreate(
- message: update.Message,
- thread: _telegramMessageCache.GetThread(
- messageId: new(replyToMessageId),
- chatId: new(chatId)
- ),
- commandPriorityCategorizer: _commandPriorityCategorizer,
- out AIFollowUpMessage? aiFollowUpMessage
- )) {
+ ReplyToMessage.MessageId: int replyToMessageId,
+ Chat.Id: long chatId
+ } &&
+ telegramMessageCache.GetOrDefault(
+ messageId: new(replyToMessageId),
+ chatId: new(chatId)
+ ) is AiResponseMessage) {
+ if (!AiFollowUpMessage.TryCreate(
+ message: update.Message,
+ thread: telegramMessageCache.GetThread(
+ messageId: new(replyToMessageId),
+ chatId: new(chatId)
+ ),
+ commandPriorityCategorizer: commandPriorityCategorizer,
+ out AiFollowUpMessage? aiFollowUpMessage
+ )) {
return;
}
// Cache follow up message
- _telegramMessageCache.Add(
+ telegramMessageCache.Add(
message: aiFollowUpMessage
);
- await _commandQueue.DispatchAsync(aiFollowUpMessage);
+ await commandQueue.DispatchAsync(aiFollowUpMessage);
return;
}
-
+
// Handle SQL
if (update.Message is {
- ReplyToMessage: null,
- Text: { } text
- } && text.StartsWith("select", StringComparison.OrdinalIgnoreCase)) {
+ ReplyToMessage: null,
+ Text: { } text
+ } &&
+ text.StartsWith("select", StringComparison.OrdinalIgnoreCase)) {
try {
- Sequence ast = new SqlParser.Parser().ParseSql(text);
+ Sequence ast = new Parser().ParseSql(text);
if (ast.Count > 1) {
// Fire and forget
- Task _ = Task.Run(async () => {
- try {
- await _telegramBotClient.SendMessage(
- chatId: update.Message.Chat.Id,
- text: $"Your SQL contains more than one statement.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = update.Message.MessageId
- },
- cancellationToken: cancellationToken
- );
- } catch (OperationCanceledException) {
- // Terminate gracefully
+ Task _ = Task.Run(
+ async () => {
+ try {
+ await telegramBotClient.SendMessage(
+ chatId: update.Message.Chat.Id,
+ text: $"Your SQL contains more than one statement.
",
+ parseMode: ParseMode.Html,
+ replyParameters: new ReplyParameters { MessageId = update.Message.MessageId },
+ cancellationToken: cancellationToken
+ );
+ } catch (OperationCanceledException) {
+ // Terminate gracefully
+ }
}
- });
+ );
return;
}
- if (ast[0] is not Statement.Select selectStatement) {
+
+ if (ast[0] is not Statement.Select) {
// Fire and forget
- Task _ = Task.Run(async () => {
- try {
- await _telegramBotClient.SendMessage(
- chatId: update.Message.Chat.Id,
- text: $"Your SQL is not a SELECT statement.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = update.Message.MessageId
- },
- cancellationToken: cancellationToken
- );
- } catch (OperationCanceledException) {
- // Terminate gracefully
+ Task _ = Task.Run(
+ async () => {
+ try {
+ await telegramBotClient.SendMessage(
+ chatId: update.Message.Chat.Id,
+ text: $"Your SQL is not a SELECT statement.
",
+ parseMode: ParseMode.Html,
+ replyParameters: new ReplyParameters { MessageId = update.Message.MessageId },
+ cancellationToken: cancellationToken
+ );
+ } catch (OperationCanceledException) {
+ // Terminate gracefully
+ }
}
- });
+ );
return;
}
- if (SQLCommand.TryCreate(
- message: update.Message,
- commandPriorityCategorizer: _commandPriorityCategorizer,
- sqlCommand: out SQLCommand? sqlCommand
- )) {
- await _commandQueue.DispatchAsync(
+
+ if (SqlCommand.TryCreate(
+ message: update.Message,
+ commandPriorityCategorizer: commandPriorityCategorizer,
+ sqlCommand: out SqlCommand? sqlCommand
+ )) {
+ await commandQueue.DispatchAsync(
command: sqlCommand
);
- return;
}
} catch (ParserException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: update.Message.Chat.Id,
text: $"{exc.Message}
",
parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = update.Message.MessageId
- },
+ replyParameters: new ReplyParameters { MessageId = update.Message.MessageId },
cancellationToken: cancellationToken
);
} catch {
diff --git a/BotNet.CommandHandlers/BotUpdate/Message/SlashCommandHandler.cs b/BotNet.CommandHandlers/BotUpdate/Message/SlashCommandHandler.cs
index 342b53c..92cf33d 100644
--- a/BotNet.CommandHandlers/BotUpdate/Message/SlashCommandHandler.cs
+++ b/BotNet.CommandHandlers/BotUpdate/Message/SlashCommandHandler.cs
@@ -24,10 +24,6 @@ public sealed class SlashCommandHandler(
ICommandQueue commandQueue,
ITelegramMessageCache telegramMessageCache
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ICommandQueue _commandQueue = commandQueue;
- private readonly ITelegramMessageCache _telegramMessageCache = telegramMessageCache;
-
public async Task Handle(SlashCommand command, CancellationToken cancellationToken) {
try {
switch (command.Command) {
@@ -35,14 +31,14 @@ public async Task Handle(SlashCommand command, CancellationToken cancellationTok
case "/flop":
case "/flep":
case "/flap":
- await _commandQueue.DispatchAsync(FlipFlopCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(FlipFlopCommand.FromSlashCommand(command));
break;
case "/evaljs":
case "/evalcs":
- await _commandQueue.DispatchAsync(EvalCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(EvalCommand.FromSlashCommand(command));
break;
case "/fuck":
- await _commandQueue.DispatchAsync(FuckCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(FuckCommand.FromSlashCommand(command));
break;
case "/c":
case "/clojure":
@@ -69,51 +65,51 @@ public async Task Handle(SlashCommand command, CancellationToken cancellationTok
case "/js":
case "/ts":
case "/vb":
- await _commandQueue.DispatchAsync(ExecCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(ExecCommand.FromSlashCommand(command));
break;
case "/pop":
- await _commandQueue.DispatchAsync(PopCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(PopCommand.FromSlashCommand(command));
break;
case "/ask":
- await _commandQueue.DispatchAsync(
+ await commandQueue.DispatchAsync(
command: AskCommand.FromSlashCommand(
command: command,
thread: command.ReplyToMessage is null
- ? Enumerable.Empty()
- : _telegramMessageCache.GetThread(
+ ? []
+ : telegramMessageCache.GetThread(
firstMessage: command.ReplyToMessage
)
)
);
break;
case "/humor":
- await _commandQueue.DispatchAsync(HumorCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(HumorCommand.FromSlashCommand(command));
break;
case "/primbon":
- await _commandQueue.DispatchAsync(PrimbonCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(PrimbonCommand.FromSlashCommand(command));
break;
case "/art":
- await _commandQueue.DispatchAsync(ArtCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(ArtCommand.FromSlashCommand(command));
break;
case "/bmkg":
- await _commandQueue.DispatchAsync(BMKGCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(BmkgCommand.FromSlashCommand(command));
break;
case "/map":
- await _commandQueue.DispatchAsync(MapCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(MapCommand.FromSlashCommand(command));
break;
case "/weather":
- await _commandQueue.DispatchAsync(WeatherCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(WeatherCommand.FromSlashCommand(command));
break;
case "/privilege":
case "/start":
- await _commandQueue.DispatchAsync(PrivilegeCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(PrivilegeCommand.FromSlashCommand(command));
break;
case "/khodam":
- await _commandQueue.DispatchAsync(KhodamCommand.FromSlashCommand(command));
+ await commandQueue.DispatchAsync(KhodamCommand.FromSlashCommand(command));
break;
}
} catch (UsageException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: exc.Message,
parseMode: exc.ParseMode,
diff --git a/BotNet.CommandHandlers/Eval/EvalCommandHandler.cs b/BotNet.CommandHandlers/Eval/EvalCommandHandler.cs
index ce62072..201c3d9 100644
--- a/BotNet.CommandHandlers/Eval/EvalCommandHandler.cs
+++ b/BotNet.CommandHandlers/Eval/EvalCommandHandler.cs
@@ -14,23 +14,19 @@ public sealed class EvalCommandHandler(
V8Evaluator v8Evaluator,
CSharpEvaluator cSharpEvaluator
) : ICommandHandler {
- private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() { WriteIndented = true };
-
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly V8Evaluator _v8Evaluator = v8Evaluator;
- private readonly CSharpEvaluator _cSharpEvaluator = cSharpEvaluator;
+ private static readonly JsonSerializerOptions JsonSerializerOptions = new() { WriteIndented = true };
public async Task Handle(EvalCommand command, CancellationToken cancellationToken) {
string result;
switch (command.Command) {
case "/evaljs":
try {
- result = await _v8Evaluator.EvaluateAsync(
+ result = await v8Evaluator.EvaluateAsync(
script: command.Code,
cancellationToken: cancellationToken
);
} catch (ScriptEngineException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "" + WebUtility.HtmlEncode(exc.Message) + "
",
parseMode: ParseMode.Html,
@@ -39,7 +35,7 @@ await _telegramBotClient.SendMessage(
);
return;
} catch (OperationCanceledException) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Timeout exceeded.
",
parseMode: ParseMode.Html,
@@ -48,7 +44,7 @@ await _telegramBotClient.SendMessage(
);
return;
} catch (JsonException exc) when (exc.Message.Contains("A possible object cycle was detected.")) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "A possible object cycle was detected.
",
parseMode: ParseMode.Html,
@@ -60,14 +56,14 @@ await _telegramBotClient.SendMessage(
break;
case "/evalcs":
try {
- object resultObject = _cSharpEvaluator.Evaluate(
+ object resultObject = cSharpEvaluator.Evaluate(
expression: command.Code
);
// Prettify result
- result = JsonSerializer.Serialize(resultObject, JSON_SERIALIZER_OPTIONS);
+ result = JsonSerializer.Serialize(resultObject, JsonSerializerOptions);
} catch (Exception exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "" + WebUtility.HtmlEncode(exc.Message) + "
",
parseMode: ParseMode.Html,
@@ -82,7 +78,7 @@ await _telegramBotClient.SendMessage(
}
if (result.Length > 1000) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Result is too long.
",
parseMode: ParseMode.Html,
@@ -90,7 +86,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} else {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: result.Length >= 2 && result[0] == '"' && result[^1] == '"'
? $"Expression:\n{WebUtility.HtmlEncode(command.Code)}
\n\nString Result:\n{WebUtility.HtmlEncode(result[1..^1].Replace("\\n", "\n"))}
"
diff --git a/BotNet.CommandHandlers/Exec/ExecCommandHandler.cs b/BotNet.CommandHandlers/Exec/ExecCommandHandler.cs
index 5f4925e..59aaff0 100644
--- a/BotNet.CommandHandlers/Exec/ExecCommandHandler.cs
+++ b/BotNet.CommandHandlers/Exec/ExecCommandHandler.cs
@@ -15,10 +15,6 @@ public sealed class ExecCommandHandler(
PistonClient pistonClient,
ILogger logger
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly PistonClient _pistonClient = pistonClient;
- private readonly ILogger _logger = logger;
-
public Task Handle(ExecCommand command, CancellationToken cancellationToken) {
// Ignore non-mentioned commands in home group
if (command.Chat is HomeGroupChat
@@ -29,14 +25,14 @@ public Task Handle(ExecCommand command, CancellationToken cancellationToken) {
// Fire and forget
Task.Run(async () => {
try {
- ExecuteResult result = await _pistonClient.ExecuteAsync(
+ ExecuteResult result = await pistonClient.ExecuteAsync(
language: command.PistonLanguageIdentifier,
code: command.Code,
cancellationToken: cancellationToken
);
if (result.Compile is { Code: not 0 }) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"{WebUtility.HtmlEncode(result.Compile.Stderr)}
",
parseMode: ParseMode.Html,
@@ -44,7 +40,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} else if (result.Run.Code != 0) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"{WebUtility.HtmlEncode(result.Run.Stderr)}
",
parseMode: ParseMode.Html,
@@ -52,7 +48,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} else if (result.Run.Output.Length > 1000 || result.Run.Output.Count(c => c == '\n') > 20) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Output is too long.
",
parseMode: ParseMode.Html,
@@ -60,7 +56,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} else {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"Code:\n```{command.HighlightLanguageIdentifier}\n{MarkdownV2Sanitizer.Sanitize(command.Code)}\n```\nOutput:\n```\n{MarkdownV2Sanitizer.Sanitize(result.Run.Output)}\n```",
parseMode: ParseMode.MarkdownV2,
@@ -71,15 +67,15 @@ await _telegramBotClient.SendMessage(
#pragma warning disable CS0618 // Type or member is obsolete
} catch (ExecutionEngineException exc) {
#pragma warning restore CS0618 // Type or member is obsolete
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
- text: "" + WebUtility.HtmlEncode(exc.Message ?? "Unknown error") + "
",
+ text: "" + WebUtility.HtmlEncode(exc.Message) + "
",
parseMode: ParseMode.Html,
replyParameters: new ReplyParameters { MessageId = command.CodeMessageId },
cancellationToken: cancellationToken
);
} catch (OperationCanceledException) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Timeout exceeded.
",
parseMode: ParseMode.Html,
@@ -87,7 +83,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} catch (Exception exc) {
- _logger.LogError(exc, "Unhandled exception while executing code.");
+ logger.LogError(exc, "Unhandled exception while executing code.");
}
});
diff --git a/BotNet.CommandHandlers/FlipFlop/FlipFlopCommandHandler.cs b/BotNet.CommandHandlers/FlipFlop/FlipFlopCommandHandler.cs
index 2929931..fac8416 100644
--- a/BotNet.CommandHandlers/FlipFlop/FlipFlopCommandHandler.cs
+++ b/BotNet.CommandHandlers/FlipFlop/FlipFlopCommandHandler.cs
@@ -8,12 +8,10 @@ namespace BotNet.CommandHandlers.FlipFlop {
internal sealed class FlipFlopCommandHandler(
ITelegramBotClient telegramBotClient
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
-
public async Task Handle(FlipFlopCommand command, CancellationToken cancellationToken) {
// Download original image
using MemoryStream originalImageStream = new();
- File fileInfo = await _telegramBotClient.GetInfoAndDownloadFile(
+ File fileInfo = await telegramBotClient.GetInfoAndDownloadFile(
fileId: command.ImageFileId,
destination: originalImageStream,
cancellationToken: cancellationToken
@@ -40,7 +38,7 @@ public async Task Handle(FlipFlopCommand command, CancellationToken cancellation
// Send result image
using MemoryStream resultImageStream = new(resultImage);
- await _telegramBotClient.SendPhoto(
+ await telegramBotClient.SendPhoto(
chatId: command.Chat.Id,
photo: new InputFileStream(resultImageStream, new string(fileInfo.FileId.Reverse().ToArray()) + ".png"),
replyParameters: new ReplyParameters { MessageId = command.ImageMessageId },
diff --git a/BotNet.CommandHandlers/Fuck/FuckCommandHandler.cs b/BotNet.CommandHandlers/Fuck/FuckCommandHandler.cs
index 9074463..06d870c 100644
--- a/BotNet.CommandHandlers/Fuck/FuckCommandHandler.cs
+++ b/BotNet.CommandHandlers/Fuck/FuckCommandHandler.cs
@@ -9,14 +9,12 @@ namespace BotNet.CommandHandlers.Fuck {
public sealed class FuckCommandHandler(
ITelegramBotClient telegramBotClient
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
-
public async Task Handle(FuckCommand command, CancellationToken cancellationToken) {
try {
string stdout = BrainfuckInterpreter.RunBrainfuck(
code: command.Code
);
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: WebUtility.HtmlEncode(stdout),
parseMode: ParseMode.Html,
@@ -24,7 +22,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} catch (InvalidProgramException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "" + WebUtility.HtmlEncode(exc.Message) + "
",
parseMode: ParseMode.Html,
@@ -32,7 +30,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} catch (IndexOutOfRangeException) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Memory access violation
",
parseMode: ParseMode.Html,
@@ -40,7 +38,7 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
} catch (TimeoutException) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Operation timed out
",
parseMode: ParseMode.Html,
diff --git a/BotNet.CommandHandlers/GoogleMaps/MapCommandHandler.cs b/BotNet.CommandHandlers/GoogleMaps/MapCommandHandler.cs
index eab9b7f..032fb31 100644
--- a/BotNet.CommandHandlers/GoogleMaps/MapCommandHandler.cs
+++ b/BotNet.CommandHandlers/GoogleMaps/MapCommandHandler.cs
@@ -13,18 +13,13 @@ public sealed class MapCommandHandler(
StaticMap staticMap,
ILogger logger
) : ICommandHandler {
- private static readonly RateLimiter SEARCH_PLACE_RATE_LIMITER = RateLimiter.PerUserPerChat(1, TimeSpan.FromMinutes(2));
-
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly GeoCode _geoCode = geoCode;
- private readonly StaticMap _staticMap = staticMap;
- private readonly ILogger _logger = logger;
+ private static readonly RateLimiter SearchPlaceRateLimiter = RateLimiter.PerUserPerChat(1, TimeSpan.FromMinutes(2));
public Task Handle(MapCommand command, CancellationToken cancellationToken) {
try {
- SEARCH_PLACE_RATE_LIMITER.ValidateActionRate(command.Chat.Id, command.Sender.Id);
+ SearchPlaceRateLimiter.ValidateActionRate(command.Chat.Id, command.Sender.Id);
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"Anda belum mendapat giliran. Coba lagi {exc.Cooldown}.",
parseMode: ParseMode.Html,
@@ -36,10 +31,10 @@ public Task Handle(MapCommand command, CancellationToken cancellationToken) {
// Fire and forget
Task.Run(async () => {
try {
- (double lat, double lng) = await _geoCode.SearchPlaceAsync(command.PlaceName);
- string staticMapUrl = _staticMap.SearchPlace(command.PlaceName);
+ (double lat, double lng) = await geoCode.SearchPlaceAsync(command.PlaceName);
+ string staticMapUrl = staticMap.SearchPlace(command.PlaceName);
- await _telegramBotClient.SendPhoto(
+ await telegramBotClient.SendPhoto(
chatId: command.Chat.Id,
photo: new InputFileUrl(staticMapUrl),
caption: $"View in 🗺️ Google Maps",
@@ -50,7 +45,7 @@ await _telegramBotClient.SendPhoto(
} catch (OperationCanceledException) {
// Terminate gracefully
} catch (Exception exc) {
- _logger.LogError(exc, "Could not find place");
+ logger.LogError(exc, "Could not find place");
await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Lokasi tidak dapat ditemukan
",
diff --git a/BotNet.CommandHandlers/Humor/HumorCommandHandler.cs b/BotNet.CommandHandlers/Humor/HumorCommandHandler.cs
index a30da62..ebb624a 100644
--- a/BotNet.CommandHandlers/Humor/HumorCommandHandler.cs
+++ b/BotNet.CommandHandlers/Humor/HumorCommandHandler.cs
@@ -10,16 +10,13 @@ public sealed class HumorCommandHandler(
ITelegramBotClient telegramBotClient,
ProgrammerHumorScraper programmerHumorScraper
) : ICommandHandler {
- private static readonly RateLimiter RATE_LIMITER = RateLimiter.PerChat(2, TimeSpan.FromMinutes(2));
-
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly ProgrammerHumorScraper _programmerHumorScraper = programmerHumorScraper;
+ private static readonly RateLimiter RateLimiter = RateLimiter.PerChat(2, TimeSpan.FromMinutes(2));
public Task Handle(HumorCommand command, CancellationToken cancellationToken) {
try {
- RATE_LIMITER.ValidateActionRate(command.Chat.Id, command.Sender.Id);
+ RateLimiter.ValidateActionRate(command.Chat.Id, command.Sender.Id);
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"Bentar ya saya mikir dulu jokenya. Coba lagi {exc.Cooldown}.",
parseMode: ParseMode.Html,
@@ -31,10 +28,10 @@ public Task Handle(HumorCommand command, CancellationToken cancellationToken) {
// Fire and forget
Task.Run(async () => {
try {
- (string title, byte[] image) = await _programmerHumorScraper.GetRandomJokeAsync(cancellationToken);
+ (string title, byte[] image) = await programmerHumorScraper.GetRandomJokeAsync(cancellationToken);
using MemoryStream imageStream = new(image);
- await _telegramBotClient.SendPhoto(
+ await telegramBotClient.SendPhoto(
chatId: command.Chat.Id,
photo: new InputFileStream(imageStream, "joke.webp"),
caption: title,
diff --git a/BotNet.CommandHandlers/Khodam/KhodamCommandHandler.cs b/BotNet.CommandHandlers/Khodam/KhodamCommandHandler.cs
index fd250cb..a049713 100644
--- a/BotNet.CommandHandlers/Khodam/KhodamCommandHandler.cs
+++ b/BotNet.CommandHandlers/Khodam/KhodamCommandHandler.cs
@@ -9,15 +9,13 @@ namespace BotNet.CommandHandlers.Khodam {
public sealed class KhodamCommandHandler(
ITelegramBotClient telegramBotClient
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
-
public async Task Handle(KhodamCommand command, CancellationToken cancellationToken) {
string khodam = KhodamCalculator.CalculateKhodam(
name: command.Name,
userId: command.UserId
);
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $$"""
Khodam {{WebUtility.HtmlEncode(command.Name)}} hari ini adalah...
diff --git a/BotNet.CommandHandlers/Pop/BubbleWrapCallbackHandler.cs b/BotNet.CommandHandlers/Pop/BubbleWrapCallbackHandler.cs
index 7fdfc51..a187436 100644
--- a/BotNet.CommandHandlers/Pop/BubbleWrapCallbackHandler.cs
+++ b/BotNet.CommandHandlers/Pop/BubbleWrapCallbackHandler.cs
@@ -8,11 +8,8 @@ public sealed class BubbleWrapCallbackHandler(
ITelegramBotClient telegramBotClient,
BubbleWrapKeyboardGenerator bubbleWrapKeyboardGenerator
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly BubbleWrapKeyboardGenerator _bubbleWrapKeyboardGenerator = bubbleWrapKeyboardGenerator;
-
public Task Handle(BubbleWrapCallback command, CancellationToken cancellationToken) {
- InlineKeyboardMarkup poppedKeyboardMarkup = _bubbleWrapKeyboardGenerator.HandleCallback(
+ InlineKeyboardMarkup poppedKeyboardMarkup = bubbleWrapKeyboardGenerator.HandleCallback(
chatId: command.ChatId,
messageId: command.MessageId,
sheetData: command.SheetData
@@ -20,7 +17,7 @@ public Task Handle(BubbleWrapCallback command, CancellationToken cancellationTok
// Fire and forget
Task.Run(async () => {
- await _telegramBotClient.EditMessageReplyMarkup(
+ await telegramBotClient.EditMessageReplyMarkup(
chatId: command.ChatId,
messageId: command.MessageId,
replyMarkup: poppedKeyboardMarkup,
diff --git a/BotNet.CommandHandlers/Pop/PopCommandHandler.cs b/BotNet.CommandHandlers/Pop/PopCommandHandler.cs
index f235ce7..12adaaa 100644
--- a/BotNet.CommandHandlers/Pop/PopCommandHandler.cs
+++ b/BotNet.CommandHandlers/Pop/PopCommandHandler.cs
@@ -7,14 +7,12 @@ namespace BotNet.CommandHandlers.Pop {
public sealed class PopCommandHandler(
ITelegramBotClient telegramBotClient
) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
-
public async Task Handle(PopCommand command, CancellationToken cancellationToken) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Here's a bubble wrap. Enjoy!",
parseMode: ParseMode.Html,
- replyMarkup: BubbleWrapKeyboardGenerator.EMPTY_KEYBOARD,
+ replyMarkup: BubbleWrapKeyboardGenerator.EmptyKeyboard,
cancellationToken: cancellationToken
);
}
diff --git a/BotNet.CommandHandlers/Primbon/PrimbonCommandHandler.cs b/BotNet.CommandHandlers/Primbon/PrimbonCommandHandler.cs
index 34e23a0..3b35e8b 100644
--- a/BotNet.CommandHandlers/Primbon/PrimbonCommandHandler.cs
+++ b/BotNet.CommandHandlers/Primbon/PrimbonCommandHandler.cs
@@ -12,20 +12,17 @@ public sealed class PrimbonCommandHandler(
PrimbonScraper primbonScraper,
ChineseCalendarScraper chineseCalendarScraper
) : ICommandHandler {
- private static readonly RateLimiter RATE_LIMITER = RateLimiter.PerChat(2, TimeSpan.FromMinutes(2));
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly PrimbonScraper _primbonScraper = primbonScraper;
- private readonly ChineseCalendarScraper _chineseCalendarScraper = chineseCalendarScraper;
+ private static readonly RateLimiter RateLimiter = RateLimiter.PerChat(2, TimeSpan.FromMinutes(2));
public async Task Handle(PrimbonCommand command, CancellationToken cancellationToken) {
try {
- RATE_LIMITER.ValidateActionRate(command.Chat.Id, command.Sender.Id);
+ RateLimiter.ValidateActionRate(command.Chat.Id, command.Sender.Id);
- (string javaneseDate, string sangar, string restriction) = await _primbonScraper.GetTaliwangkeAsync(
+ (string javaneseDate, string sangar, string restriction) = await primbonScraper.GetTaliwangkeAsync(
date: command.Date,
cancellationToken: cancellationToken
);
- (string title, string[] traits) = await _primbonScraper.GetKamarokamAsync(
+ (string title, string[] traits) = await primbonScraper.GetKamarokamAsync(
date: command.Date,
cancellationToken: cancellationToken
);
@@ -37,36 +34,36 @@ public async Task Handle(PrimbonCommand command, CancellationToken cancellationT
string godOfWealth,
string[] auspiciousActivities,
string[] inauspiciousActivities
- ) = await _chineseCalendarScraper.GetYellowCalendarAsync(
+ ) = await chineseCalendarScraper.GetYellowCalendarAsync(
date: command.Date,
cancellationToken: cancellationToken
);
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
- text: $$"""
- {{javaneseDate}}
+ text: $"""
+ {javaneseDate}
Petung Hari Baik
- {{title}}: {{string.Join(", ", traits)}}
+ {title}: {string.Join(", ", traits)}
Hari Larangan
- {{sangar}}! {{restriction}}
+ {sangar}! {restriction}
Chinese Calendar
- Clash: {{clash}} Evil: {{evil}}
- God of Joy: {{godOfJoy}}
- God of Happiness: {{godOfHappiness}}
- God of Wealth: {{godOfWealth}}
- Auspicious Activities: {{string.Join(", ", auspiciousActivities)}}
- Inauspicious Activities: {{string.Join(", ", inauspiciousActivities)}}
+ Clash: {clash} Evil: {evil}
+ God of Joy: {godOfJoy}
+ God of Happiness: {godOfHappiness}
+ God of Wealth: {godOfWealth}
+ Auspicious Activities: {string.Join(", ", auspiciousActivities)}
+ Inauspicious Activities: {string.Join(", ", inauspiciousActivities)}
""",
parseMode: ParseMode.Html,
replyParameters: new ReplyParameters { MessageId = command.CommandMessageId },
cancellationToken: cancellationToken
);
} catch (RateLimitExceededException exc) when (exc is { Cooldown: var cooldown }) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"Coba lagi {cooldown}.",
parseMode: ParseMode.Html,
diff --git a/BotNet.CommandHandlers/Privilege/PrivilegeCommandHandler.cs b/BotNet.CommandHandlers/Privilege/PrivilegeCommandHandler.cs
index 7588bdc..6af3355 100644
--- a/BotNet.CommandHandlers/Privilege/PrivilegeCommandHandler.cs
+++ b/BotNet.CommandHandlers/Privilege/PrivilegeCommandHandler.cs
@@ -1,5 +1,4 @@
using BotNet.Commands.ChatAggregate;
-using BotNet.Commands.CommandPrioritization;
using BotNet.Commands.Privilege;
using BotNet.Commands.SenderAggregate;
using BotNet.Services.RateLimit;
@@ -9,17 +8,13 @@
namespace BotNet.CommandHandlers.Privilege {
public sealed class PrivilegeCommandHandler(
- ITelegramBotClient telegramBotClient,
- CommandPriorityCategorizer commandPriorityCategorizer
+ ITelegramBotClient telegramBotClient
) : ICommandHandler {
- private static readonly RateLimiter RATE_LIMITER = RateLimiter.PerChat(1, TimeSpan.FromMinutes(1));
-
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly CommandPriorityCategorizer _commandPriorityCategorizer = commandPriorityCategorizer;
+ private static readonly RateLimiter RateLimiter = RateLimiter.PerChat(1, TimeSpan.FromMinutes(1));
public Task Handle(PrivilegeCommand command, CancellationToken cancellationToken) {
try {
- RATE_LIMITER.ValidateActionRate(command.Chat.Id, command.Sender.Id);
+ RateLimiter.ValidateActionRate(command.Chat.Id, command.Sender.Id);
} catch (RateLimitExceededException) {
// Silently reject commands after rate limit exceeded
return Task.CompletedTask;
@@ -29,8 +24,8 @@ public Task Handle(PrivilegeCommand command, CancellationToken cancellationToken
Task.Run(async () => {
try {
switch (command) {
- case { Chat: PrivateChat, Sender: VIPSender }:
- await _telegramBotClient.SendMessage(
+ case { Chat: PrivateChat, Sender: VipSender }:
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $$"""
👑 Anda adalah user VIP (ID: {{command.Sender.Id}})
@@ -45,7 +40,7 @@ await _telegramBotClient.SendMessage(
);
break;
case { Chat: PrivateChat }:
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $$"""
❌ Feature bot dibatasi di dalam private chat (ID: {{command.Sender.Id}})
@@ -59,8 +54,8 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
break;
- case { Chat: HomeGroupChat, Sender: VIPSender }:
- await _telegramBotClient.SendMessage(
+ case { Chat: HomeGroupChat, Sender: VipSender }:
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $$"""
👑 Group {{command.Chat.Title}} (ID: {{command.Chat.Id}}) adalah home group
@@ -78,7 +73,7 @@ await _telegramBotClient.SendMessage(
);
break;
case { Chat: HomeGroupChat }:
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $$"""
👑 Group {{command.Chat.Title}} (ID: {{command.Chat.Id}}) adalah home group
@@ -92,8 +87,8 @@ await _telegramBotClient.SendMessage(
cancellationToken: cancellationToken
);
break;
- case { Chat: GroupChat, Sender: VIPSender }:
- await _telegramBotClient.SendMessage(
+ case { Chat: GroupChat, Sender: VipSender }:
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $$"""
⚠️ Bot dipakai di group selain home group (ID: {{command.Chat.Id}})
@@ -113,7 +108,7 @@ await _telegramBotClient.SendMessage(
);
break;
case { Chat: GroupChat }:
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $$"""
⚠️ Bot dipakai di group selain home group (ID: {{command.Chat.Id}})
diff --git a/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs b/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs
index 2edb3dc..74a237c 100644
--- a/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs
+++ b/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System.Globalization;
+using System.Text;
using BotNet.Commands.SQL;
using BotNet.Services.SQL;
using BotNet.Services.Sqlite;
@@ -10,21 +11,18 @@
using Telegram.Bot.Types.Enums;
namespace BotNet.CommandHandlers.SQL {
- public sealed class SQLCommandHandler(
+ public sealed class SqlCommandHandler(
ITelegramBotClient telegramBotClient,
IServiceProvider serviceProvider
- ) : ICommandHandler {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly IServiceProvider _serviceProvider = serviceProvider;
-
- public async Task Handle(SQLCommand command, CancellationToken cancellationToken) {
+ ) : ICommandHandler {
+ public async Task Handle(SqlCommand command, CancellationToken cancellationToken) {
if (command.SelectStatement.Query.Body.AsSelectExpression().Select.From is not { } froms
|| froms.Count == 0) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "No FROM clause found.
",
parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters { MessageId = command.SQLMessageId },
+ replyParameters: new ReplyParameters { MessageId = command.SqlMessageId },
cancellationToken: cancellationToken
);
return;
@@ -47,16 +45,16 @@ await _telegramBotClient.SendMessage(
}
// Create scoped for scoped database
- using IServiceScope serviceScope = _serviceProvider.CreateScope();
+ using IServiceScope serviceScope = serviceProvider.CreateScope();
// Load tables into memory
foreach (string table in tables) {
IScopedDataSource? dataSource = serviceScope.ServiceProvider.GetKeyedService(table);
if (dataSource == null) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
- text: $$"""
- Table '{{table}}' not found. Available tables are:
+ text: $"""
+ Table '{table}' not found. Available tables are:
- pileg_dpr_dapil
- pileg_dpr_<kodedapil>
- pileg_dpr_provinsi
@@ -65,7 +63,7 @@ await _telegramBotClient.SendMessage(
""",
parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters { MessageId = command.SQLMessageId },
+ replyParameters: new ReplyParameters { MessageId = command.SqlMessageId },
cancellationToken: cancellationToken
);
return;
@@ -101,25 +99,25 @@ await _telegramBotClient.SendMessage(
Type fieldType = reader.GetFieldType(i);
if (fieldType == typeof(string)) {
- values[i] = '"' + reader.GetString(i).Replace("\"", "\"\"") + '"';
+ values[i] = $"\"{reader.GetString(i).Replace("\"", "\"\"")}\"";
} else if (fieldType == typeof(int)) {
values[i] = reader.GetInt32(i).ToString();
} else if (fieldType == typeof(long)) {
values[i] = reader.GetInt64(i).ToString();
} else if (fieldType == typeof(float)) {
- values[i] = reader.GetFloat(i).ToString();
+ values[i] = reader.GetFloat(i).ToString(CultureInfo.InvariantCulture);
} else if (fieldType == typeof(double)) {
- values[i] = reader.GetDouble(i).ToString();
+ values[i] = reader.GetDouble(i).ToString(CultureInfo.InvariantCulture);
} else if (fieldType == typeof(decimal)) {
- values[i] = reader.GetDecimal(i).ToString();
+ values[i] = reader.GetDecimal(i).ToString(CultureInfo.InvariantCulture);
} else if (fieldType == typeof(bool)) {
values[i] = reader.GetBoolean(i).ToString();
} else if (fieldType == typeof(DateTime)) {
- values[i] = reader.GetDateTime(i).ToString();
+ values[i] = reader.GetDateTime(i).ToString(CultureInfo.InvariantCulture);
} else if (fieldType == typeof(byte[])) {
- values[i] = BitConverter.ToString(reader.GetFieldValue(i)).Replace("-", "");
+ values[i] = Convert.ToHexString(reader.GetFieldValue(i));
} else {
- values[i] = reader[i]?.ToString() ?? "";
+ values[i] = reader[i].ToString() ?? "";
}
}
resultBuilder.AppendLine(string.Join(',', values));
@@ -128,11 +126,11 @@ await _telegramBotClient.SendMessage(
}
);
} catch (SqliteException exc) {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "" + exc.Message.Replace("SQLite Error", "Error") + "
",
parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters { MessageId = command.SQLMessageId },
+ replyParameters: new ReplyParameters { MessageId = command.SqlMessageId },
cancellationToken: cancellationToken
);
return;
@@ -141,19 +139,19 @@ await _telegramBotClient.SendMessage(
// Send result
string csvResult = resultBuilder.ToString();
if (csvResult.Length > 4000) {
- await _telegramBotClient.SendDocument(
+ await telegramBotClient.SendDocument(
chatId: command.Chat.Id,
caption: $"{rows} rows affected",
document: new InputFileStream(new MemoryStream(Encoding.UTF8.GetBytes(csvResult)), "result.csv"),
- replyParameters: new ReplyParameters { MessageId = command.SQLMessageId },
+ replyParameters: new ReplyParameters { MessageId = command.SqlMessageId },
cancellationToken: cancellationToken
);
} else {
- await _telegramBotClient.SendMessage(
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
- text: "```csv\n" + resultBuilder.ToString() + $"```\n{rows} rows affected",
+ text: $"```csv\n{resultBuilder}```\n{rows} rows affected",
parseMode: ParseMode.MarkdownV2,
- replyParameters: new ReplyParameters { MessageId = command.SQLMessageId },
+ replyParameters: new ReplyParameters { MessageId = command.SqlMessageId },
cancellationToken: cancellationToken
);
}
@@ -178,9 +176,9 @@ private static void CollectTableNames(ref HashSet tables, TableFactor ta
}
}
break;
- case TableFactor.Function function:
+ case TableFactor.Function:
break;
- case TableFactor.JsonTable jsonTable:
+ case TableFactor.JsonTable:
break;
case TableFactor.NestedJoin nestedJoin:
if (nestedJoin.TableWithJoins != null) {
@@ -203,9 +201,9 @@ private static void CollectTableNames(ref HashSet tables, TableFactor ta
case TableFactor.Table table:
tables.Add(table.Name.ToString());
break;
- case TableFactor.TableFunction tableFunction:
+ case TableFactor.TableFunction:
break;
- case TableFactor.UnNest unNest:
+ case TableFactor.UnNest:
break;
case TableFactor.Unpivot unpivot:
tables.Add(unpivot.Name.ToString());
diff --git a/BotNet.CommandHandlers/TelegramMessageCache.cs b/BotNet.CommandHandlers/TelegramMessageCache.cs
index 8eecf39..acbec33 100644
--- a/BotNet.CommandHandlers/TelegramMessageCache.cs
+++ b/BotNet.CommandHandlers/TelegramMessageCache.cs
@@ -7,19 +7,18 @@ namespace BotNet.CommandHandlers {
internal sealed class TelegramMessageCache(
IMemoryCache memoryCache
) : ITelegramMessageCache {
- private static readonly TimeSpan CACHE_TTL = TimeSpan.FromHours(1);
- private readonly IMemoryCache _memoryCache = memoryCache;
+ private static readonly TimeSpan CacheTtl = TimeSpan.FromHours(1);
public void Add(MessageBase message) {
- _memoryCache.Set(
+ memoryCache.Set(
key: new Key(message.MessageId, message.Chat.Id),
value: message,
- absoluteExpirationRelativeToNow: CACHE_TTL
+ absoluteExpirationRelativeToNow: CacheTtl
);
}
public MessageBase? GetOrDefault(MessageId messageId, ChatId chatId) {
- if (_memoryCache.TryGetValue(
+ if (memoryCache.TryGetValue(
key: new Key(messageId, chatId),
value: out MessageBase? message
)) {
@@ -42,10 +41,12 @@ public IEnumerable GetThread(MessageId messageId, ChatId chatId) {
public IEnumerable GetThread(MessageBase firstMessage) {
yield return firstMessage;
Add(firstMessage);
- if (firstMessage.ReplyToMessage is not null) {
- foreach (MessageBase reply in GetThread(firstMessage.ReplyToMessage.MessageId, firstMessage.Chat.Id)) {
- yield return reply;
- }
+ if (firstMessage.ReplyToMessage is null) {
+ yield break;
+ }
+
+ foreach (MessageBase reply in GetThread(firstMessage.ReplyToMessage.MessageId, firstMessage.Chat.Id)) {
+ yield return reply;
}
}
diff --git a/BotNet.CommandHandlers/Weather/WeatherCommandHandler.cs b/BotNet.CommandHandlers/Weather/WeatherCommandHandler.cs
index 776547e..d886e85 100644
--- a/BotNet.CommandHandlers/Weather/WeatherCommandHandler.cs
+++ b/BotNet.CommandHandlers/Weather/WeatherCommandHandler.cs
@@ -12,17 +12,13 @@ public sealed class WeatherCommandHandler(
CurrentWeather currentWeather,
ILogger logger
) : ICommandHandler {
- private static readonly RateLimiter GET_WEATHER_RATE_LIMITER = RateLimiter.PerUserPerChat(3, TimeSpan.FromMinutes(2));
-
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
- private readonly CurrentWeather _currentWeather = currentWeather;
- private readonly ILogger _logger = logger;
+ private static readonly RateLimiter GetWeatherRateLimiter = RateLimiter.PerUserPerChat(3, TimeSpan.FromMinutes(2));
public Task Handle(WeatherCommand command, CancellationToken cancellationToken) {
try {
- GET_WEATHER_RATE_LIMITER.ValidateActionRate(command.Chat.Id, command.Sender.Id);
+ GetWeatherRateLimiter.ValidateActionRate(command.Chat.Id, command.Sender.Id);
} catch (RateLimitExceededException exc) {
- return _telegramBotClient.SendMessage(
+ return telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: $"Anda belum mendapat giliran. Coba lagi {exc.Cooldown}.",
parseMode: ParseMode.Html,
@@ -34,12 +30,12 @@ public Task Handle(WeatherCommand command, CancellationToken cancellationToken)
// Fire and forget
Task.Run(async () => {
try {
- (string title, string icon) = await _currentWeather.GetCurrentWeatherAsync(
+ (string title, string icon) = await currentWeather.GetCurrentWeatherAsync(
place: command.CityName,
cancellationToken: cancellationToken
);
- await _telegramBotClient.SendPhoto(
+ await telegramBotClient.SendPhoto(
chatId: command.Chat.Id,
photo: new InputFileUrl(icon),
caption: title,
@@ -50,8 +46,8 @@ await _telegramBotClient.SendPhoto(
} catch (OperationCanceledException) {
// Terminate gracefully
} catch (Exception exc) {
- _logger.LogError(exc, "Could not get weather");
- await _telegramBotClient.SendMessage(
+ logger.LogError(exc, "Could not get weather");
+ await telegramBotClient.SendMessage(
chatId: command.Chat.Id,
text: "Lokasi tidak dapat ditemukan
",
parseMode: ParseMode.Html,
diff --git a/BotNet.Commands/AI/Gemini/GeminiTextPrompt.cs b/BotNet.Commands/AI/Gemini/GeminiTextPrompt.cs
index 0d5b8f8..a3c30b5 100644
--- a/BotNet.Commands/AI/Gemini/GeminiTextPrompt.cs
+++ b/BotNet.Commands/AI/Gemini/GeminiTextPrompt.cs
@@ -16,7 +16,7 @@ IEnumerable thread
Thread = thread;
}
- public static GeminiTextPrompt FromAICallCommand(AICallCommand aiCallCommand, IEnumerable thread) {
+ public static GeminiTextPrompt FromAiCallCommand(AiCallCommand aiCallCommand, IEnumerable thread) {
// Call sign must be Gemini, AI, or Bot
if (aiCallCommand.CallSign is not "Gemini" and not "AI" and not "Bot") {
throw new ArgumentException("Call sign must be Gemini, AI, or Bot", nameof(aiCallCommand));
@@ -28,24 +28,31 @@ public static GeminiTextPrompt FromAICallCommand(AICallCommand aiCallCommand, IE
}
// Non-empty thread must begin with reply to message
- if (thread.FirstOrDefault() is {
- MessageId: { } firstMessageId,
- Chat.Id: { } firstChatId
- }) {
- if (firstMessageId != aiCallCommand.ReplyToMessage?.MessageId
- || firstChatId != aiCallCommand.Chat.Id) {
- throw new ArgumentException("Thread must begin with reply to message", nameof(thread));
- }
+ IEnumerable messageBases = thread as MessageBase[] ?? thread.ToArray();
+ if (messageBases.FirstOrDefault() is not {
+ MessageId: var firstMessageId,
+ Chat.Id: var firstChatId
+ }) {
+ return new GeminiTextPrompt(
+ prompt: aiCallCommand.Text,
+ command: aiCallCommand,
+ thread: messageBases
+ );
}
- return new(
+ if (firstMessageId != aiCallCommand.ReplyToMessage?.MessageId
+ || firstChatId != aiCallCommand.Chat.Id) {
+ throw new ArgumentException("Thread must begin with reply to message", nameof(thread));
+ }
+
+ return new GeminiTextPrompt(
prompt: aiCallCommand.Text,
command: aiCallCommand,
- thread: thread
+ thread: messageBases
);
}
- public static GeminiTextPrompt FromAIFollowUpMessage(AIFollowUpMessage aIFollowUpMessage, IEnumerable thread) {
+ public static GeminiTextPrompt FromAiFollowUpMessage(AiFollowUpMessage aIFollowUpMessage, IEnumerable thread) {
// Call sign must be Gemini, AI, or Bot
if (aIFollowUpMessage.CallSign is not "Gemini" and not "AI" and not "Bot") {
throw new ArgumentException("Call sign must be Gemini, AI, or Bot", nameof(aIFollowUpMessage));
@@ -57,20 +64,27 @@ public static GeminiTextPrompt FromAIFollowUpMessage(AIFollowUpMessage aIFollowU
}
// Non-empty thread must begin with reply to message
- if (thread.FirstOrDefault() is {
- MessageId: { } firstMessageId,
- Chat.Id: { } firstChatId
- }) {
- if (firstMessageId != aIFollowUpMessage.ReplyToMessage?.MessageId
- || firstChatId != aIFollowUpMessage.Chat.Id) {
- throw new ArgumentException("Thread must begin with reply to message", nameof(thread));
- }
+ IEnumerable messageBases = thread as MessageBase[] ?? thread.ToArray();
+ if (messageBases.FirstOrDefault() is not {
+ MessageId: var firstMessageId,
+ Chat.Id: var firstChatId
+ }) {
+ return new GeminiTextPrompt(
+ prompt: aIFollowUpMessage.Text,
+ command: aIFollowUpMessage,
+ thread: messageBases
+ );
+ }
+
+ if (firstMessageId != aIFollowUpMessage.ReplyToMessage.MessageId
+ || firstChatId != aIFollowUpMessage.Chat.Id) {
+ throw new ArgumentException("Thread must begin with reply to message", nameof(thread));
}
- return new(
+ return new GeminiTextPrompt(
prompt: aIFollowUpMessage.Text,
command: aIFollowUpMessage,
- thread: thread
+ thread: messageBases
);
}
}
diff --git a/BotNet.Commands/AI/OpenAI/OpenAIImageGenerationPrompt.cs b/BotNet.Commands/AI/OpenAI/OpenAIImageGenerationPrompt.cs
index 01320d3..7184839 100644
--- a/BotNet.Commands/AI/OpenAI/OpenAIImageGenerationPrompt.cs
+++ b/BotNet.Commands/AI/OpenAI/OpenAIImageGenerationPrompt.cs
@@ -3,7 +3,7 @@
using BotNet.Commands.SenderAggregate;
namespace BotNet.Commands.AI.OpenAI {
- public sealed record class OpenAIImageGenerationPrompt : ICommand {
+ public sealed record OpenAiImageGenerationPrompt : ICommand {
public string CallSign { get; }
public string Prompt { get; }
public MessageId PromptMessageId { get; }
@@ -11,7 +11,7 @@ public sealed record class OpenAIImageGenerationPrompt : ICommand {
public ChatBase Chat { get; }
public HumanSender Sender { get; }
- public OpenAIImageGenerationPrompt(
+ public OpenAiImageGenerationPrompt(
string callSign,
string prompt,
MessageId promptMessageId,
diff --git a/BotNet.Commands/AI/OpenAI/OpenAIImagePrompt.cs b/BotNet.Commands/AI/OpenAI/OpenAIImagePrompt.cs
index 4f8657f..bd279aa 100644
--- a/BotNet.Commands/AI/OpenAI/OpenAIImagePrompt.cs
+++ b/BotNet.Commands/AI/OpenAI/OpenAIImagePrompt.cs
@@ -1,14 +1,14 @@
using BotNet.Commands.BotUpdate.Message;
namespace BotNet.Commands.AI.OpenAI {
- public sealed record OpenAIImagePrompt : ICommand {
+ public sealed record OpenAiImagePrompt : ICommand {
public string CallSign { get; }
public string Prompt { get; }
public string ImageFileId { get; }
public HumanMessageBase Command { get; }
public IEnumerable Thread { get; }
- private OpenAIImagePrompt(
+ private OpenAiImagePrompt(
string callSign,
string prompt,
string imageFileId,
@@ -22,7 +22,7 @@ IEnumerable thread
Thread = thread;
}
- public static OpenAIImagePrompt FromAICallCommand(AICallCommand aiCallCommand, IEnumerable thread) {
+ public static OpenAiImagePrompt FromAiCallCommand(AiCallCommand aiCallCommand, IEnumerable thread) {
// Call sign must be GPT
if (aiCallCommand.CallSign is not "GPT") {
throw new ArgumentException("Call sign must be GPT.", nameof(aiCallCommand));
@@ -35,31 +35,40 @@ public static OpenAIImagePrompt FromAICallCommand(AICallCommand aiCallCommand, I
// File ID must be non-empty
string imageFileId;
+ IEnumerable messageBases = thread as MessageBase[] ?? thread.ToArray();
if (!string.IsNullOrWhiteSpace(aiCallCommand.ImageFileId)) {
imageFileId = aiCallCommand.ImageFileId;
- } else if (!string.IsNullOrWhiteSpace(thread.FirstOrDefault()?.ImageFileId)) {
- imageFileId = thread.First().ImageFileId!;
+ } else if (!string.IsNullOrWhiteSpace(messageBases.FirstOrDefault()?.ImageFileId)) {
+ imageFileId = messageBases.First().ImageFileId!;
} else {
throw new ArgumentException("File ID must be non-empty.", nameof(aiCallCommand));
}
// Non-empty thread must begin with reply to message
- if (thread.FirstOrDefault() is {
- MessageId: { } firstMessageId,
- Chat.Id: { } firstChatId
- }) {
- if (firstMessageId != aiCallCommand.ReplyToMessage?.MessageId
- || firstChatId != aiCallCommand.Chat.Id) {
- throw new ArgumentException("Thread must begin with reply to message.", nameof(thread));
- }
+ if (messageBases.FirstOrDefault() is not {
+ MessageId: var firstMessageId,
+ Chat.Id: var firstChatId
+ }) {
+ return new(
+ callSign: aiCallCommand.CallSign,
+ prompt: aiCallCommand.Text,
+ imageFileId: imageFileId,
+ command: aiCallCommand,
+ thread: messageBases
+ );
}
- return new(
+ if (firstMessageId != aiCallCommand.ReplyToMessage?.MessageId
+ || firstChatId != aiCallCommand.Chat.Id) {
+ throw new ArgumentException("Thread must begin with reply to message.", nameof(thread));
+ }
+
+ return new OpenAiImagePrompt(
callSign: aiCallCommand.CallSign,
prompt: aiCallCommand.Text,
imageFileId: imageFileId,
command: aiCallCommand,
- thread: thread
+ thread: messageBases
);
}
}
diff --git a/BotNet.Commands/AI/OpenAI/OpenAITextPrompt.cs b/BotNet.Commands/AI/OpenAI/OpenAITextPrompt.cs
index 2f6f706..be744f9 100644
--- a/BotNet.Commands/AI/OpenAI/OpenAITextPrompt.cs
+++ b/BotNet.Commands/AI/OpenAI/OpenAITextPrompt.cs
@@ -1,13 +1,13 @@
using BotNet.Commands.BotUpdate.Message;
namespace BotNet.Commands.AI.OpenAI {
- public sealed record OpenAITextPrompt : ICommand {
+ public sealed record OpenAiTextPrompt : ICommand {
public string CallSign { get; }
public string Prompt { get; }
public HumanMessageBase Command { get; }
public IEnumerable Thread { get; }
- private OpenAITextPrompt(
+ private OpenAiTextPrompt(
string callSign,
string prompt,
HumanMessageBase command,
@@ -19,7 +19,7 @@ IEnumerable thread
Thread = thread;
}
- public static OpenAITextPrompt FromAICallCommand(AICallCommand aiCallCommand, IEnumerable thread) {
+ public static OpenAiTextPrompt FromAiCallCommand(AiCallCommand aiCallCommand, IEnumerable thread) {
// Call sign must be GPT
if (aiCallCommand.CallSign is not "GPT") {
throw new ArgumentException("Call sign must be GPT.", nameof(aiCallCommand));
@@ -31,25 +31,33 @@ public static OpenAITextPrompt FromAICallCommand(AICallCommand aiCallCommand, IE
}
// Non-empty thread must begin with reply to message
- if (thread.FirstOrDefault() is {
- MessageId: { } firstMessageId,
- Chat.Id: { } firstChatId
- }) {
- if (firstMessageId != aiCallCommand.ReplyToMessage?.MessageId
- || firstChatId != aiCallCommand.Chat.Id) {
- throw new ArgumentException("Thread must begin with reply to message.", nameof(thread));
- }
+ IEnumerable messageBases = thread as MessageBase[] ?? thread.ToArray();
+ if (messageBases.FirstOrDefault() is not {
+ MessageId: var firstMessageId,
+ Chat.Id: var firstChatId
+ }) {
+ return new(
+ callSign: aiCallCommand.CallSign,
+ prompt: aiCallCommand.Text,
+ command: aiCallCommand,
+ thread: messageBases
+ );
+ }
+
+ if (firstMessageId != aiCallCommand.ReplyToMessage?.MessageId
+ || firstChatId != aiCallCommand.Chat.Id) {
+ throw new ArgumentException("Thread must begin with reply to message.", nameof(thread));
}
return new(
callSign: aiCallCommand.CallSign,
prompt: aiCallCommand.Text,
command: aiCallCommand,
- thread: thread
+ thread: messageBases
);
}
- public static OpenAITextPrompt FromAIFollowUpMessage(AIFollowUpMessage aiFollowUpMessage, IEnumerable thread) {
+ public static OpenAiTextPrompt FromAiFollowUpMessage(AiFollowUpMessage aiFollowUpMessage, IEnumerable thread) {
// Call sign must be GPT
if (aiFollowUpMessage.CallSign is not "GPT") {
throw new ArgumentException("Call sign must be GPT.", nameof(aiFollowUpMessage));
@@ -61,21 +69,29 @@ public static OpenAITextPrompt FromAIFollowUpMessage(AIFollowUpMessage aiFollowU
}
// Non-empty thread must begin with reply to message
- if (thread.FirstOrDefault() is {
- MessageId: { } firstMessageId,
- Chat.Id: { } firstChatId
- }) {
- if (firstMessageId != aiFollowUpMessage.ReplyToMessage.MessageId
- || firstChatId != aiFollowUpMessage.Chat.Id) {
- throw new ArgumentException("Thread must begin with reply to message.", nameof(thread));
- }
+ IEnumerable messageBases = thread as MessageBase[] ?? thread.ToArray();
+ if (messageBases.FirstOrDefault() is not {
+ MessageId: var firstMessageId,
+ Chat.Id: var firstChatId
+ }) {
+ return new(
+ callSign: aiFollowUpMessage.CallSign,
+ prompt: aiFollowUpMessage.Text,
+ command: aiFollowUpMessage,
+ thread: messageBases
+ );
+ }
+
+ if (firstMessageId != aiFollowUpMessage.ReplyToMessage.MessageId
+ || firstChatId != aiFollowUpMessage.Chat.Id) {
+ throw new ArgumentException("Thread must begin with reply to message.", nameof(thread));
}
return new(
callSign: aiFollowUpMessage.CallSign,
prompt: aiFollowUpMessage.Text,
command: aiFollowUpMessage,
- thread: thread
+ thread: messageBases
);
}
}
diff --git a/BotNet.Commands/BMKG/BMKGCommand.cs b/BotNet.Commands/BMKG/BMKGCommand.cs
index 5ea9b03..54d0bb3 100644
--- a/BotNet.Commands/BMKG/BMKGCommand.cs
+++ b/BotNet.Commands/BMKG/BMKGCommand.cs
@@ -3,12 +3,12 @@
using BotNet.Commands.SenderAggregate;
namespace BotNet.Commands.BMKG {
- public sealed record BMKGCommand : ICommand {
+ public sealed record BmkgCommand : ICommand {
public MessageId CommandMessageId { get; }
public ChatBase Chat { get; }
public HumanSender Sender { get; }
- private BMKGCommand(
+ private BmkgCommand(
MessageId commandMessageId,
ChatBase chat,
HumanSender sender
@@ -18,7 +18,7 @@ HumanSender sender
Sender = sender;
}
- public static BMKGCommand FromSlashCommand(SlashCommand slashCommand) {
+ public static BmkgCommand FromSlashCommand(SlashCommand slashCommand) {
// Must be /bmkg
if (slashCommand.Command != "/bmkg") {
throw new ArgumentException("Command must be /bmkg.", nameof(slashCommand));
diff --git a/BotNet.Commands/BotUpdate/Message/AICallCommand.cs b/BotNet.Commands/BotUpdate/Message/AICallCommand.cs
index 0f97d58..c46d0b6 100644
--- a/BotNet.Commands/BotUpdate/Message/AICallCommand.cs
+++ b/BotNet.Commands/BotUpdate/Message/AICallCommand.cs
@@ -5,8 +5,8 @@
using BotNet.Commands.SenderAggregate;
namespace BotNet.Commands.BotUpdate.Message {
- public sealed record AICallCommand : HumanMessageBase, ICommand {
- public static readonly ImmutableHashSet CALL_SIGNS = [
+ public sealed record AiCallCommand : HumanMessageBase, ICommand {
+ private static readonly ImmutableHashSet CALL_SIGNS = [
"AI",
"Bot",
"GPT",
@@ -16,7 +16,7 @@ public sealed record AICallCommand : HumanMessageBase, ICommand {
public string CallSign { get; }
- private AICallCommand(
+ private AiCallCommand(
MessageId messageId,
ChatBase chat,
HumanSender sender,
@@ -38,7 +38,7 @@ string callSign
public static bool TryCreate(
Telegram.Bot.Types.Message message,
CommandPriorityCategorizer commandPriorityCategorizer,
- [NotNullWhen(true)] out AICallCommand? aiCallCommand
+ [NotNullWhen(true)] out AiCallCommand? aiCallCommand
) {
// Chat must be private or group
if (!ChatBase.TryCreate(message.Chat, commandPriorityCategorizer, out ChatBase? chat)) {
diff --git a/BotNet.Commands/BotUpdate/Message/AIFollowUpMessage.cs b/BotNet.Commands/BotUpdate/Message/AIFollowUpMessage.cs
index 01765e0..ca1422f 100644
--- a/BotNet.Commands/BotUpdate/Message/AIFollowUpMessage.cs
+++ b/BotNet.Commands/BotUpdate/Message/AIFollowUpMessage.cs
@@ -4,17 +4,17 @@
using BotNet.Commands.SenderAggregate;
namespace BotNet.Commands.BotUpdate.Message {
- public sealed record AIFollowUpMessage : HumanMessageBase, ICommand {
- public override AIResponseMessage ReplyToMessage => (AIResponseMessage)base.ReplyToMessage!;
+ public sealed record AiFollowUpMessage : HumanMessageBase, ICommand {
+ public override AiResponseMessage ReplyToMessage => (AiResponseMessage)base.ReplyToMessage!;
public string CallSign => ReplyToMessage.CallSign;
- public AIFollowUpMessage(
+ private AiFollowUpMessage(
MessageId messageId,
ChatBase chat,
HumanSender sender,
string text,
string? imageFileId,
- AIResponseMessage replyToMessage
+ AiResponseMessage replyToMessage
) : base(
messageId: messageId,
chat: chat,
@@ -28,7 +28,7 @@ public static bool TryCreate(
Telegram.Bot.Types.Message message,
IEnumerable thread,
CommandPriorityCategorizer commandPriorityCategorizer,
- [NotNullWhen(true)] out AIFollowUpMessage? aiFollowUpMessage
+ [NotNullWhen(true)] out AiFollowUpMessage? aiFollowUpMessage
) {
// Chat must be private or group
if (!ChatBase.TryCreate(message.Chat, commandPriorityCategorizer, out ChatBase? chat)) {
@@ -37,8 +37,8 @@ public static bool TryCreate(
}
// Sender must be a user
- if (message.From is not { } from
- || !HumanSender.TryCreate(from, commandPriorityCategorizer, out HumanSender? sender)) {
+ if (message.From is not { } from ||
+ !HumanSender.TryCreate(from, commandPriorityCategorizer, out HumanSender? sender)) {
aiFollowUpMessage = null;
return false;
}
@@ -50,33 +50,27 @@ public static bool TryCreate(
}
// Must reply to AI response message
- if (thread.FirstOrDefault() is not AIResponseMessage { CallSign: string callSign } aiResponseMessage
- || aiResponseMessage.MessageId != message.ReplyToMessage?.MessageId) {
+ if (thread.FirstOrDefault() is not AiResponseMessage { CallSign: string } aiResponseMessage ||
+ aiResponseMessage.MessageId != message.ReplyToMessage?.MessageId) {
aiFollowUpMessage = null;
return false;
}
// Sender must be a user
if (message.From is not {
- IsBot: false,
- Id: long senderId,
- FirstName: string senderFirstName,
- LastName: var senderLastName
- }) {
+ IsBot: false,
+ }) {
aiFollowUpMessage = null;
return false;
}
- string senderFullName = senderLastName is null
- ? senderFirstName
- : $"{senderFirstName} {senderLastName}";
-
aiFollowUpMessage = new(
messageId: new(message.MessageId),
chat: chat,
sender: sender,
text: text,
- imageFileId: message.Photo?.FirstOrDefault()?.FileId,
+ imageFileId: message.Photo?.FirstOrDefault()
+ ?.FileId,
replyToMessage: aiResponseMessage
);
return true;
diff --git a/BotNet.Commands/BotUpdate/Message/AIResponseMessage.cs b/BotNet.Commands/BotUpdate/Message/AIResponseMessage.cs
index 5c8bd8b..32a010a 100644
--- a/BotNet.Commands/BotUpdate/Message/AIResponseMessage.cs
+++ b/BotNet.Commands/BotUpdate/Message/AIResponseMessage.cs
@@ -3,10 +3,10 @@
using BotNet.Commands.SenderAggregate;
namespace BotNet.Commands.BotUpdate.Message {
- public sealed record AIResponseMessage : MessageBase {
+ public sealed record AiResponseMessage : MessageBase {
public string CallSign { get; }
- private AIResponseMessage(
+ private AiResponseMessage(
MessageId messageId,
ChatBase chat,
BotSender sender,
@@ -25,7 +25,7 @@ string callSign
CallSign = callSign;
}
- public static AIResponseMessage FromMessage(
+ public static AiResponseMessage FromMessage(
Telegram.Bot.Types.Message message,
HumanMessageBase replyToMessage,
string callSign,
diff --git a/BotNet.Commands/ChatAggregate/Chat.cs b/BotNet.Commands/ChatAggregate/Chat.cs
index f5ea7fd..b2b2562 100644
--- a/BotNet.Commands/ChatAggregate/Chat.cs
+++ b/BotNet.Commands/ChatAggregate/Chat.cs
@@ -3,26 +3,18 @@
using Telegram.Bot.Types.Enums;
namespace BotNet.Commands.ChatAggregate {
- public abstract record ChatBase {
- public ChatId Id { get; }
- public string? Title { get; }
-
- protected ChatBase(
- ChatId id,
- string? title
- ) {
- Id = id;
- Title = title;
- }
-
+ public abstract record ChatBase(
+ ChatId Id,
+ string? Title
+ ) {
public static bool TryCreate(
Telegram.Bot.Types.Chat telegramChat,
CommandPriorityCategorizer priorityCategorizer,
[NotNullWhen(true)] out ChatBase? chat
) {
chat = telegramChat switch {
- Telegram.Bot.Types.Chat { Type: ChatType.Private } => PrivateChat.FromTelegramChat(telegramChat),
- Telegram.Bot.Types.Chat { Type: ChatType.Group or ChatType.Supergroup } => priorityCategorizer.IsHomeGroup(telegramChat.Id)
+ { Type: ChatType.Private } => PrivateChat.FromTelegramChat(telegramChat),
+ { Type: ChatType.Group or ChatType.Supergroup } => priorityCategorizer.IsHomeGroup(telegramChat.Id)
? HomeGroupChat.FromTelegramChat(telegramChat)
: GroupChat.FromTelegramChat(telegramChat),
_ => null
diff --git a/BotNet.Commands/CommandPrioritization/CommandPrioritizationOptions.cs b/BotNet.Commands/CommandPrioritization/CommandPrioritizationOptions.cs
index d358d41..088035b 100644
--- a/BotNet.Commands/CommandPrioritization/CommandPrioritizationOptions.cs
+++ b/BotNet.Commands/CommandPrioritization/CommandPrioritizationOptions.cs
@@ -1,6 +1,6 @@
namespace BotNet.Commands.CommandPrioritization {
public sealed class CommandPrioritizationOptions {
- public string[] HomeGroupChatIds { get; set; } = [];
- public string[] VIPUserIds { get; set; } = [];
+ public string[] HomeGroupChatIds { get; init; } = [];
+ public string[] VipUserIds { get; init; } = [];
}
}
diff --git a/BotNet.Commands/CommandPrioritization/CommandPriorityCategorizer.cs b/BotNet.Commands/CommandPrioritization/CommandPriorityCategorizer.cs
index dea68fb..f3e078d 100644
--- a/BotNet.Commands/CommandPrioritization/CommandPriorityCategorizer.cs
+++ b/BotNet.Commands/CommandPrioritization/CommandPriorityCategorizer.cs
@@ -17,7 +17,7 @@ IOptions optionsAccessor
.Distinct()
.ToImmutableHashSet();
- _vipUserIds = optionsAccessor.Value.VIPUserIds
+ _vipUserIds = optionsAccessor.Value.VipUserIds
.SelectMany(userId => userId.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
.Select(userIdStr => long.TryParse(userIdStr, out long userId) ? (long?)userId : null)
.Where(userId => userId.HasValue)
@@ -30,7 +30,7 @@ public bool IsHomeGroup(long chatId) {
return _homeGroupChatIds.Contains(chatId);
}
- public bool IsVIPUser(long userId) {
+ public bool IsVipUser(long userId) {
return _vipUserIds.Contains(userId);
}
}
diff --git a/BotNet.Commands/SQL/SQLCommand.cs b/BotNet.Commands/SQL/SQLCommand.cs
index d6d844e..f379c4c 100644
--- a/BotNet.Commands/SQL/SQLCommand.cs
+++ b/BotNet.Commands/SQL/SQLCommand.cs
@@ -6,13 +6,13 @@
using SqlParser.Ast;
namespace BotNet.Commands.SQL {
- public sealed record SQLCommand : ICommand {
+ public sealed record SqlCommand : ICommand {
public string RawStatement { get; }
public Statement.Select SelectStatement { get; }
- public MessageId SQLMessageId { get; }
+ public MessageId SqlMessageId { get; }
public ChatBase Chat { get; }
- private SQLCommand(
+ private SqlCommand(
string rawStatement,
Statement.Select selectStatement,
MessageId sqlMessageId,
@@ -20,14 +20,14 @@ ChatBase chat
) {
RawStatement = rawStatement;
SelectStatement = selectStatement;
- SQLMessageId = sqlMessageId;
+ SqlMessageId = sqlMessageId;
Chat = chat;
}
public static bool TryCreate(
Telegram.Bot.Types.Message message,
CommandPriorityCategorizer commandPriorityCategorizer,
- [NotNullWhen(true)] out SQLCommand? sqlCommand
+ [NotNullWhen(true)] out SqlCommand? sqlCommand
) {
// Must start with select
if (message.Text is not { } text || !text.StartsWith("select", StringComparison.OrdinalIgnoreCase)) {
@@ -44,7 +44,7 @@ public static bool TryCreate(
// Must be a valid SQL statement
Sequence ast;
try {
- ast = new SqlParser.Parser().ParseSql(text);
+ ast = new Parser().ParseSql(text);
} catch {
sqlCommand = null;
return false;
diff --git a/BotNet.Commands/SenderAggregate/Sender.cs b/BotNet.Commands/SenderAggregate/Sender.cs
index eb97176..edd7aed 100644
--- a/BotNet.Commands/SenderAggregate/Sender.cs
+++ b/BotNet.Commands/SenderAggregate/Sender.cs
@@ -6,7 +6,7 @@ public abstract record SenderBase(
SenderId Id,
string Name
) {
- public abstract string ChatGPTRole { get; }
+ public abstract string ChatGptRole { get; }
public abstract string GeminiRole { get; }
}
@@ -14,7 +14,7 @@ public record HumanSender(
SenderId Id,
string Name
) : SenderBase(Id, Name) {
- public override string ChatGPTRole => "user";
+ public override string ChatGptRole => "user";
public override string GeminiRole => "user";
public static bool TryCreate(
@@ -25,24 +25,26 @@ public static bool TryCreate(
if (user is not {
IsBot: false,
Id: long senderId,
- FirstName: string senderFirstName,
+ FirstName: { } senderFirstName,
LastName: var senderLastName
}) {
humanSender = null;
return false;
}
- if (commandPriorityCategorizer.IsVIPUser(senderId)) {
- humanSender = new VIPSender(
+ if (commandPriorityCategorizer.IsVipUser(senderId)) {
+ humanSender = new VipSender(
Id: senderId,
- Name: senderLastName is { } ? $"{senderFirstName} {senderLastName}" : senderFirstName
+ Name: senderLastName is not null
+ ? $"{senderFirstName} {senderLastName}" : senderFirstName
);
return true;
}
humanSender = new HumanSender(
Id: senderId,
- Name: senderLastName is { } ? $"{senderFirstName} {senderLastName}" : senderFirstName
+ Name: senderLastName is not null
+ ? $"{senderFirstName} {senderLastName}" : senderFirstName
);
return true;
}
@@ -52,7 +54,7 @@ public sealed record BotSender(
SenderId Id,
string Name
) : SenderBase(Id, Name) {
- public override string ChatGPTRole => "assistant";
+ public override string ChatGptRole => "assistant";
public override string GeminiRole => "model";
public static bool TryCreate(
@@ -62,12 +64,13 @@ public static bool TryCreate(
if (user is {
IsBot: true,
Id: long senderId,
- FirstName: string senderFirstName,
+ FirstName: { } senderFirstName,
LastName: var senderLastName
}) {
botSender = new BotSender(
Id: senderId,
- Name: senderLastName is { } ? $"{senderFirstName} {senderLastName}" : senderFirstName
+ Name: senderLastName is not null
+ ? $"{senderFirstName} {senderLastName}" : senderFirstName
);
return true;
}
@@ -77,7 +80,7 @@ public static bool TryCreate(
}
}
- public sealed record VIPSender(
+ public sealed record VipSender(
SenderId Id,
string Name
) : HumanSender(Id, Name);
diff --git a/BotNet.Services/BMKG/BMKG.cs b/BotNet.Services/BMKG/BMKG.cs
index 193352e..3984052 100644
--- a/BotNet.Services/BMKG/BMKG.cs
+++ b/BotNet.Services/BMKG/BMKG.cs
@@ -1,12 +1,12 @@
using System.Net.Http;
namespace BotNet.Services.BMKG {
- public class BMKG {
- protected string uriTemplate = "https://data.bmkg.go.id/DataMKG/TEWS/{0}.json";
- protected readonly HttpClient httpClient;
+ public class Bmkg {
+ protected const string UriTemplate = "https://data.bmkg.go.id/DataMKG/TEWS/{0}.json";
+ protected readonly HttpClient HttpClient;
- public BMKG(HttpClient client) {
- httpClient = client;
+ protected Bmkg(HttpClient client) {
+ HttpClient = client;
}
}
}
diff --git a/BotNet.Services/BMKG/EarthQuake.cs b/BotNet.Services/BMKG/EarthQuake.cs
index 21b458d..64b2806 100644
--- a/BotNet.Services/BMKG/EarthQuake.cs
+++ b/BotNet.Services/BMKG/EarthQuake.cs
@@ -1,9 +1,9 @@
namespace BotNet.Services.BMKG {
public record EarthQuake {
- public QuakeInfo InfoGempa { get; set; } = new QuakeInfo();
+ public QuakeInfo InfoGempa { get; set; } = new();
public record QuakeInfo {
- public Quake Gempa { get; set; } = new Quake();
+ public Quake Gempa { get; set; } = new();
public record Quake {
public string Tanggal { get; set; } = string.Empty;
@@ -15,10 +15,7 @@ public record Quake {
public string Potensi { get; set; } = string.Empty;
public string Dirasakan { get; set; } = string.Empty;
public string Shakemap{ get; set; } = string.Empty;
- public string ShakemapUrl {
- get => $"https://data.bmkg.go.id/DataMKG/TEWS/{Shakemap}";
- set { }
- }
+ public string ShakemapUrl => $"https://data.bmkg.go.id/DataMKG/TEWS/{Shakemap}";
}
}
diff --git a/BotNet.Services/BMKG/LatestEarthQuake.cs b/BotNet.Services/BMKG/LatestEarthQuake.cs
index 84c4e8b..09bcaee 100644
--- a/BotNet.Services/BMKG/LatestEarthQuake.cs
+++ b/BotNet.Services/BMKG/LatestEarthQuake.cs
@@ -4,13 +4,15 @@
using System.Threading.Tasks;
namespace BotNet.Services.BMKG {
- public class LatestEarthQuake(HttpClient client) : BMKG(client) {
- private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() { PropertyNameCaseInsensitive = true };
+ public class LatestEarthQuake(
+ HttpClient client
+ ) : Bmkg(client) {
+ private static readonly JsonSerializerOptions JsonSerializerOptions = new() { PropertyNameCaseInsensitive = true };
public async Task<(string Text, string ShakemapUrl)> GetLatestAsync() {
- string url = string.Format(uriTemplate, "autogempa");
+ string url = string.Format(UriTemplate, "autogempa");
- HttpResponseMessage response = await httpClient.GetAsync(url);
+ HttpResponseMessage response = await HttpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
if (response.Content.Headers.ContentType!.MediaType is not "application/json") {
@@ -19,27 +21,31 @@ public class LatestEarthQuake(HttpClient client) : BMKG(client) {
Stream bodyContent = await response.Content.ReadAsStreamAsync();
- EarthQuake? bodyResponse = await JsonSerializer.DeserializeAsync(bodyContent, JSON_SERIALIZER_OPTIONS);
+ EarthQuake? bodyResponse = await JsonSerializer.DeserializeAsync(bodyContent, JsonSerializerOptions);
if (bodyResponse is null) {
throw new JsonException("Failed to parse body");
}
- string textResult = "Gempa Terkini\n"
- + $"Magnitudo: {bodyResponse.InfoGempa.Gempa.Magnitude}\n"
- + $"Tanggal: {bodyResponse.InfoGempa.Gempa.Tanggal} {bodyResponse.InfoGempa.Gempa.Jam}\n"
- + $"Koordinat: {bodyResponse.InfoGempa.Gempa.Coordinates}\n"
- + $"Kedalaman: {bodyResponse.InfoGempa.Gempa.Kedalaman}\n"
- + $"Wilayah: {bodyResponse.InfoGempa.Gempa.Wilayah}\n"
- + $"Potensi: {bodyResponse.InfoGempa.Gempa.Potensi}\n"
- + "\n\nJaga diri, keluarga dan orang tersayang anda";
+ string textResult = $"""
+ Gempa Terkini
+ Magnitudo: {bodyResponse.InfoGempa.Gempa.Magnitude}
+ Tanggal: {bodyResponse.InfoGempa.Gempa.Tanggal} {bodyResponse.InfoGempa.Gempa.Jam}
+ Koordinat: {bodyResponse.InfoGempa.Gempa.Coordinates}
+ Kedalaman: {bodyResponse.InfoGempa.Gempa.Kedalaman}
+ Wilayah: {bodyResponse.InfoGempa.Gempa.Wilayah}
+ Potensi: {bodyResponse.InfoGempa.Gempa.Potensi}
+
+
+ Jaga diri, keluarga dan orang tersayang anda
+ """;
string shakemapUrl = bodyResponse.InfoGempa.Gempa.ShakemapUrl;
return (
Text: textResult,
ShakemapUrl: shakemapUrl
- );
+ );
}
}
}
diff --git a/BotNet.Services/BMKG/ServiceCollectionExtensions.cs b/BotNet.Services/BMKG/ServiceCollectionExtensions.cs
index b64a3d5..30c2973 100644
--- a/BotNet.Services/BMKG/ServiceCollectionExtensions.cs
+++ b/BotNet.Services/BMKG/ServiceCollectionExtensions.cs
@@ -2,7 +2,7 @@
namespace BotNet.Services.BMKG {
public static class ServiceCollectionExtensions {
- public static IServiceCollection AddBMKG(this IServiceCollection services) {
+ public static IServiceCollection AddBmkg(this IServiceCollection services) {
services.AddTransient();
return services;
diff --git a/BotNet.Services/BotCommands/OpenAI.cs b/BotNet.Services/BotCommands/OpenAI.cs
deleted file mode 100644
index d343ba4..0000000
--- a/BotNet.Services/BotCommands/OpenAI.cs
+++ /dev/null
@@ -1,576 +0,0 @@
-using System;
-using System.Collections.Immutable;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using BotNet.Services.OpenAI;
-using BotNet.Services.OpenAI.Models;
-using BotNet.Services.OpenAI.Skills;
-using BotNet.Services.RateLimit;
-using BotNet.Services.Stability.Models;
-using BotNet.Services.Stability.Skills;
-using Microsoft.Extensions.DependencyInjection;
-using RG.Ninja;
-using SkiaSharp;
-using Telegram.Bot;
-using Telegram.Bot.Types;
-using Telegram.Bot.Types.Enums;
-using Telegram.Bot.Types.ReplyMarkups;
-
-namespace BotNet.Services.BotCommands {
- [Obsolete("Must be refactored to AI call commands")]
- public static class OpenAI {
- private static readonly RateLimiter CHAT_GROUP_RATE_LIMITER = RateLimiter.PerUserPerChat(5, TimeSpan.FromMinutes(15));
- private static readonly RateLimiter CHAT_PRIVATE_RATE_LIMITER = RateLimiter.PerUser(20, TimeSpan.FromMinutes(15));
-
- public static async Task ChatWithSarcasticBotAsync(ITelegramBotClient botClient, IServiceProvider serviceProvider, Message message, string callSign, CancellationToken cancellationToken) {
- if (message.Text!.StartsWith(callSign, out string? s)
- && s.TrimStart() is string { Length: > 0 } chatMessage) {
- try {
- (message.Chat.Type == ChatType.Private
- ? CHAT_PRIVATE_RATE_LIMITER
- : CHAT_GROUP_RATE_LIMITER
- ).ValidateActionRate(message.Chat.Id, message.From!.Id);
- string result = await serviceProvider.GetRequiredService().ChatAsync(
- callSign: callSign,
- name: $"{message.From!.FirstName}{message.From.LastName?.Let(lastName => " " + lastName)}",
- question: chatMessage,
- cancellationToken: cancellationToken
- );
- ImmutableList attachments = serviceProvider.GetRequiredService().GenerateAttachments(result);
- if (attachments.Count == 0) {
- return await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: WebUtility.HtmlEncode(result),
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else if (attachments.Count == 1) {
- return await botClient.SendPhotoAsync(
- chatId: message.Chat.Id,
- photo: new InputFileUrl(attachments[0]),
- caption: WebUtility.HtmlEncode(result),
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else {
- Message sentMessage = await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: WebUtility.HtmlEncode(result),
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- await botClient.SendMediaGroupAsync(
- chatId: message.Chat.Id,
- media: from attachment in attachments
- select new InputMediaPhoto(new InputFileUrl(attachment.OriginalString)),
- cancellationToken: cancellationToken);
- return sentMessage;
- }
- } catch (RateLimitExceededException exc) when (exc is { Cooldown: var cooldown }) {
- if (message.Chat.Type == ChatType.Private) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil Pakde. Coba lagi {cooldown}.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil Pakde di sini. Coba lagi {cooldown} atau lanjutkan di private chat.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- replyMarkup: new InlineKeyboardMarkup(
- InlineKeyboardButton.WithUrl("Private chat 💬", "t.me/TeknumBot")
- ),
- cancellationToken: cancellationToken);
- }
- } catch (OperationCanceledException) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Timeout exceeded.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- }
- }
- return null;
- }
-
- public static async Task ChatWithSarcasticBotAsync(ITelegramBotClient botClient, IServiceProvider serviceProvider, Message message, ImmutableList<(string Sender, string? Text, string? ImageBase64)> thread, string callSign, CancellationToken cancellationToken) {
- try {
- (message.Chat.Type == ChatType.Private
- ? CHAT_PRIVATE_RATE_LIMITER
- : CHAT_GROUP_RATE_LIMITER
- ).ValidateActionRate(message.Chat.Id, message.From!.Id);
- string result = await serviceProvider.GetRequiredService().RespondToThreadAsync(
- callSign: callSign,
- name: $"{message.From!.FirstName}{message.From.LastName?.Let(lastName => " " + lastName)}",
- question: message.Text!,
- thread: thread,
- cancellationToken: cancellationToken
- );
- ImmutableList attachments = serviceProvider.GetRequiredService().GenerateAttachments(result);
- if (attachments.Count == 0) {
- return await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: WebUtility.HtmlEncode(result),
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else if (attachments.Count == 1) {
- return await botClient.SendPhotoAsync(
- chatId: message.Chat.Id,
- photo: new InputFileUrl(attachments[0]),
- caption: WebUtility.HtmlEncode(result),
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else {
- Message sentMessage = await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: WebUtility.HtmlEncode(result),
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- await botClient.SendMediaGroupAsync(
- chatId: message.Chat.Id,
- media: from attachment in attachments
- select new InputMediaPhoto(new InputFileUrl(attachment.OriginalString)),
- cancellationToken: cancellationToken);
- return sentMessage;
- }
- } catch (RateLimitExceededException exc) when (exc is { Cooldown: var cooldown }) {
- if (message.Chat.Type == ChatType.Private) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil Pakde. Coba lagi {cooldown}.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil Pakde di sini. Coba lagi {cooldown} atau lanjutkan di private chat.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- replyMarkup: new InlineKeyboardMarkup(
- InlineKeyboardButton.WithUrl("Private chat 💬", "t.me/TeknumBot")
- ),
- cancellationToken: cancellationToken);
- }
- } catch (OperationCanceledException) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Timeout exceeded.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- }
- return null;
- }
-
- private static readonly RateLimiter IMAGE_GENERATION_PER_USER_RATE_LIMITER = RateLimiter.PerUser(1, TimeSpan.FromMinutes(5));
- private static readonly RateLimiter IMAGE_GENERATION_PER_CHAT_RATE_LIMITER = RateLimiter.PerChat(2, TimeSpan.FromMinutes(3));
- public static async Task StreamChatWithFriendlyBotAsync(
- ITelegramBotClient botClient,
- IServiceProvider serviceProvider,
- Message message,
- CancellationToken cancellationToken
- ) {
- try {
- (message.Chat.Type == ChatType.Private
- ? CHAT_PRIVATE_RATE_LIMITER
- : CHAT_GROUP_RATE_LIMITER
- ).ValidateActionRate(message.Chat.Id, message.From!.Id);
-
- string? fileId;
- string? prompt;
- if (message is { Photo.Length: > 0, Caption: { } }) {
- fileId = message.Photo.OrderByDescending(photoSize => photoSize.Width).First().FileId;
- prompt = message.Caption;
- } else if (message.ReplyToMessage is { Photo.Length: > 0 }
- && message.Text is { }) {
- fileId = message.ReplyToMessage.Photo.OrderByDescending(photoSize => photoSize.Width).First().FileId;
- prompt = message.Text;
- } else if (message.ReplyToMessage is { Sticker: { } }
- && message.Text is { }) {
- fileId = message.ReplyToMessage.Sticker.FileId;
- prompt = message.Text;
- } else {
- fileId = null;
- prompt = null;
- }
-
- if (fileId != null && prompt != null) {
- (string? imageBase64, string? error) = await GetImageBase64Async(botClient, fileId, cancellationToken);
- if (error != null) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"{error}
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- return;
- }
-
- await serviceProvider.GetRequiredService().StreamChatAsync(
- message: prompt,
- imageBase64: imageBase64!,
- chatId: message.Chat.Id,
- replyToMessageId: message.MessageId
- );
- } else {
- IntentDetector intentDetector = serviceProvider.GetRequiredService();
- ChatIntent chatIntent = await intentDetector.DetectChatIntentAsync(
- message: message.Text!,
- cancellationToken: cancellationToken
- );
-
- switch (chatIntent) {
- case ChatIntent.Question:
- await serviceProvider.GetRequiredService().StreamChatAsync(
- message: message.Text!,
- chatId: message.Chat.Id,
- replyToMessageId: message.MessageId
- );
- break;
- case ChatIntent.ImageGeneration: {
- IMAGE_GENERATION_PER_USER_RATE_LIMITER.ValidateActionRate(
- chatId: message.Chat.Id,
- userId: message.From.Id
- );
- IMAGE_GENERATION_PER_CHAT_RATE_LIMITER.ValidateActionRate(
- chatId: message.Chat.Id,
- userId: message.From.Id
- );
- Message busyMessage = await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Generating image… ⏳",
- parseMode: ParseMode.Markdown,
- messageThreadId: message.MessageId,
- cancellationToken: cancellationToken
- );
- //Uri generatedImageUrl = await serviceProvider.GetRequiredService().GenerateImageAsync(
- // prompt: message.Text!,
- // cancellationToken: cancellationToken
- //);
- try {
- byte[] generatedImage = await serviceProvider.GetRequiredService().GenerateImageAsync(
- prompt: message.Text!,
- cancellationToken: cancellationToken
- );
- using MemoryStream generatedImageStream = new(generatedImage);
- try {
- await botClient.DeleteMessageAsync(
- chatId: busyMessage.Chat.Id,
- messageId: busyMessage.MessageId,
- cancellationToken: cancellationToken
- );
- } catch (OperationCanceledException) {
- throw;
- }
-
- Message generatedImageMessage = await botClient.SendPhotoAsync(
- chatId: message.Chat.Id,
- photo: new InputFileStream(generatedImageStream, "art.png"),
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken
- );
-
- // Track generated image for continuation
- serviceProvider.GetRequiredService().TrackMessage(
- messageId: generatedImageMessage.MessageId,
- sender: "GPT",
- text: null,
- imageBase64: Convert.ToBase64String(generatedImage),
- replyToMessageId: message.MessageId
- );
- } catch (ContentFilteredException exc) {
- await botClient.EditMessageTextAsync(
- chatId: busyMessage.Chat.Id,
- messageId: busyMessage.MessageId,
- text: $"{exc.Message ?? "Content filtered."}
",
- parseMode: ParseMode.Html
- );
- } catch {
- await botClient.EditMessageTextAsync(
- chatId: busyMessage.Chat.Id,
- messageId: busyMessage.MessageId,
- text: "Failed to generate image.
",
- parseMode: ParseMode.Html
- );
- }
- break;
- }
- }
- }
- } catch (RateLimitExceededException exc) when (exc is { Cooldown: var cooldown }) {
- if (message.Chat.Type == ChatType.Private) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil AI. Coba lagi {cooldown}.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil AI di sini. Coba lagi {cooldown} atau lanjutkan di private chat.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- replyMarkup: new InlineKeyboardMarkup(
- InlineKeyboardButton.WithUrl("Private chat 💬", "t.me/TeknumBot")
- ),
- cancellationToken: cancellationToken);
- }
- } catch (HttpRequestException exc) when (exc.StatusCode == HttpStatusCode.TooManyRequests) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Too many requests.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } catch (HttpRequestException) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Unknown error.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } catch (OperationCanceledException) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Timeout exceeded.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- }
- }
-
- public static async Task StreamChatWithFriendlyBotAsync(ITelegramBotClient botClient, IServiceProvider serviceProvider, Message message, ImmutableList<(string Sender, string? Text, string? ImageBase64)> thread, CancellationToken cancellationToken) {
- try {
- (message.Chat.Type == ChatType.Private
- ? CHAT_PRIVATE_RATE_LIMITER
- : CHAT_GROUP_RATE_LIMITER
- ).ValidateActionRate(message.Chat.Id, message.From!.Id);
-
- if (thread.FirstOrDefault().ImageBase64 is { } imageBase64) {
- IntentDetector intentDetector = serviceProvider.GetRequiredService();
- ImagePromptIntent imagePromptIntent = await intentDetector.DetectImagePromptIntentAsync(
- message: message.Text!,
- cancellationToken: cancellationToken
- );
-
- switch (imagePromptIntent) {
- case ImagePromptIntent.ImageVariation:
- IMAGE_GENERATION_PER_USER_RATE_LIMITER.ValidateActionRate(
- chatId: message.Chat.Id,
- userId: message.From.Id
- );
- IMAGE_GENERATION_PER_CHAT_RATE_LIMITER.ValidateActionRate(
- chatId: message.Chat.Id,
- userId: message.From.Id
- );
- Message busyMessage = await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Modifying image… ⏳",
- parseMode: ParseMode.Markdown,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken
- );
- try {
- byte[] promptImage = Convert.FromBase64String(imageBase64);
- byte[] modifiedImage = await serviceProvider.GetRequiredService().ModifyImageAsync(
- image: promptImage,
- prompt: message.Text!,
- cancellationToken
- );
- using MemoryStream modifiedImageStream = new(modifiedImage);
- try {
- await botClient.DeleteMessageAsync(
- chatId: busyMessage.Chat.Id,
- messageId: busyMessage.MessageId,
- cancellationToken: cancellationToken
- );
- } catch (OperationCanceledException) {
- throw;
- }
-
- Message modifiedImageMessage = await botClient.SendPhotoAsync(
- chatId: message.Chat.Id,
- photo: new InputFileStream(modifiedImageStream, "art.png"),
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken
- );
-
- // Track generated image for continuation
- serviceProvider.GetRequiredService().TrackMessage(
- messageId: modifiedImageMessage.MessageId,
- sender: "GPT",
- text: null,
- imageBase64: Convert.ToBase64String(modifiedImage),
- replyToMessageId: message.MessageId
- );
- } catch (ContentFilteredException exc) {
- await botClient.EditMessageTextAsync(
- chatId: busyMessage.Chat.Id,
- messageId: busyMessage.MessageId,
- text: $"{exc.Message ?? "Content filtered."}
",
- parseMode: ParseMode.Html
- );
- } catch {
- await botClient.EditMessageTextAsync(
- chatId: busyMessage.Chat.Id,
- messageId: busyMessage.MessageId,
- text: "Failed to modify image.
",
- parseMode: ParseMode.Html
- );
- }
- break;
- case ImagePromptIntent.Vision:
- await serviceProvider.GetRequiredService().StreamChatAsync(
- message: message.Text!,
- imageBase64: imageBase64,
- chatId: message.Chat.Id,
- replyToMessageId: message.MessageId
- );
- break;
- }
- } else {
- await serviceProvider.GetRequiredService().StreamChatAsync(
- message: message.Text!,
- thread: thread,
- chatId: message.Chat.Id,
- replyToMessageId: message.MessageId
- );
- }
- } catch (RateLimitExceededException exc) when (exc is { Cooldown: var cooldown }) {
- if (message.Chat.Type == ChatType.Private) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil AI. Coba lagi {cooldown}.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- } else {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: $"Anda terlalu banyak memanggil AI di sini. Coba lagi {cooldown} atau lanjutkan di private chat.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- replyMarkup: new InlineKeyboardMarkup(
- InlineKeyboardButton.WithUrl("Private chat 💬", "t.me/TeknumBot")
- ),
- cancellationToken: cancellationToken);
- }
- } catch (OperationCanceledException) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Timeout exceeded.
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
- }
- }
-
- private static async Task<(string? ImageBase64, string? Error)> GetImageBase64Async(ITelegramBotClient botClient, string fileId, CancellationToken cancellationToken) {
- // Download photo
- using MemoryStream originalImageStream = new();
- await botClient.GetInfoAndDownloadFile(
- fileId: fileId,
- destination: originalImageStream,
- cancellationToken: cancellationToken);
- byte[] originalImage = originalImageStream.ToArray();
-
- // Limit input image to 300KB
- if (originalImage.Length > 300 * 1024) {
- return (null, "Image larger than 300KB");
- }
-
- // Decode image
- originalImageStream.Position = 0;
- using SKCodec codec = SKCodec.Create(originalImageStream, out SKCodecResult codecResult);
- if (codecResult != SKCodecResult.Success) {
- return (null, "Invalid image");
- }
-
- if (codec.EncodedFormat != SKEncodedImageFormat.Jpeg
- && codec.EncodedFormat != SKEncodedImageFormat.Webp) {
- return (null, "Image must be compressed image");
- }
- SKBitmap bitmap = SKBitmap.Decode(codec);
-
- // Limit input image to 1280x1280
- if (bitmap.Width > 1280 || bitmap.Width > 1280) {
- return (null, "Image larger than 1280x1280");
- }
-
- // Handle stickers
- if (codec.EncodedFormat == SKEncodedImageFormat.Webp) {
- SKImage image = SKImage.FromBitmap(bitmap);
- SKData data = image.Encode(SKEncodedImageFormat.Jpeg, 20);
- using MemoryStream jpegStream = new();
- data.SaveTo(jpegStream);
-
- // Encode image as base64
- return (Convert.ToBase64String(jpegStream.ToArray()), null);
- }
-
- // Encode image as base64
- return (Convert.ToBase64String(originalImage), null);
- }
- }
-}
diff --git a/BotNet.Services/BotCommands/Preview.cs b/BotNet.Services/BotCommands/Preview.cs
deleted file mode 100644
index fdbba8a..0000000
--- a/BotNet.Services/BotCommands/Preview.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using BotNet.Services.Preview;
-using Microsoft.Extensions.DependencyInjection;
-using Telegram.Bot;
-using Telegram.Bot.Types;
-using Telegram.Bot.Types.Enums;
-
-namespace BotNet.Services.BotCommands {
- [Obsolete("Should be refactored to PreviewCommandHandler later")]
- public static class Preview {
- public static async Task GetPreviewAsync(ITelegramBotClient botClient, IServiceProvider serviceProvider, Message message, CancellationToken cancellationToken) {
- if (message.Entities?.FirstOrDefault() is { Type: MessageEntityType.BotCommand, Offset: 0, Length: int commandLength }
- && message.Text![commandLength..].Trim() is string commandArgument) {
-
- if (commandArgument.Length <= 0 && message.ReplyToMessage?.Text is null) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Gunakan /preview youtoube link
atau reply message dengan command /preview
",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
-
- return;
- }
-
- Uri? youtubeLink;
- Uri? previewYoutubeStoryboard;
-
- if (message.ReplyToMessage?.Text is string repliedToMessage) {
- youtubeLink = YoutubePreview.ValidateYoutubeLink(repliedToMessage);
- if (youtubeLink is null) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Youtube link tidak valid",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
-
- return;
- }
-
- previewYoutubeStoryboard = await serviceProvider.GetRequiredService().YoutubeStoryBoardAsync(youtubeLink, cancellationToken);
-
- await botClient.SendPhotoAsync(
- chatId: message.Chat.Id,
- photo: new InputFileUrl(previewYoutubeStoryboard),
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- parseMode: ParseMode.Html,
- cancellationToken: cancellationToken);
- } else if (commandArgument.Length >= 0) {
- youtubeLink = YoutubePreview.ValidateYoutubeLink(commandArgument);
- if (youtubeLink is null) {
- await botClient.SendTextMessageAsync(
- chatId: message.Chat.Id,
- text: "Youtube link tidak valid",
- parseMode: ParseMode.Html,
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- cancellationToken: cancellationToken);
-
- return;
- }
-
- previewYoutubeStoryboard = await serviceProvider.GetRequiredService().YoutubeStoryBoardAsync(youtubeLink, cancellationToken);
-
- await botClient.SendPhotoAsync(
- chatId: message.Chat.Id,
- photo: new InputFileUrl(previewYoutubeStoryboard),
- replyParameters: new ReplyParameters {
- MessageId = message.MessageId
- },
- parseMode: ParseMode.Html,
- cancellationToken: cancellationToken);
- }
- }
- }
- }
-}
diff --git a/BotNet.Services/BotProfile/BotProfileAccessor.cs b/BotNet.Services/BotProfile/BotProfileAccessor.cs
index 00465a0..f672a76 100644
--- a/BotNet.Services/BotProfile/BotProfileAccessor.cs
+++ b/BotNet.Services/BotProfile/BotProfileAccessor.cs
@@ -7,12 +7,10 @@ namespace BotNet.Services.BotProfile {
public sealed class BotProfileAccessor(
ITelegramBotClient telegramBotClient
) {
- private readonly ITelegramBotClient _telegramBotClient = telegramBotClient;
-
private User? _me;
public async Task GetBotProfileAsync(CancellationToken cancellationToken) {
- _me ??= await _telegramBotClient.GetMe(cancellationToken);
+ _me ??= await telegramBotClient.GetMe(cancellationToken);
return _me;
}
}
diff --git a/BotNet.Services/Brainfuck/BrainfuckInterpreter.cs b/BotNet.Services/Brainfuck/BrainfuckInterpreter.cs
index 70b30c0..83462a2 100644
--- a/BotNet.Services/Brainfuck/BrainfuckInterpreter.cs
+++ b/BotNet.Services/Brainfuck/BrainfuckInterpreter.cs
@@ -6,9 +6,10 @@
namespace BotNet.Services.Brainfuck {
public static class BrainfuckInterpreter {
public static string RunBrainfuck(string code) {
- byte[] program = Encoding.UTF8.GetBytes(code);
+ Span program = stackalloc byte[Encoding.UTF8.GetByteCount(code)];
+ Encoding.UTF8.GetBytes(code, program);
int programPointer = 0;
- byte[] memory = new byte[1024];
+ Span memory = stackalloc byte[1024];
int pointer = 0;
Stack loopPointers = new();
Dictionary loopCache = new();
diff --git a/BotNet.Services/Brainfuck/BrainfuckTranspiler.cs b/BotNet.Services/Brainfuck/BrainfuckTranspiler.cs
index 06c0118..0df2175 100644
--- a/BotNet.Services/Brainfuck/BrainfuckTranspiler.cs
+++ b/BotNet.Services/Brainfuck/BrainfuckTranspiler.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System;
+using System.Text;
namespace BotNet.Services.Brainfuck {
public class BrainfuckTranspiler {
@@ -36,7 +37,17 @@ public string TranspileBrainfuck(string message) {
return Generate(message);
}
- private static int GCD(int c, int a) => a == 0 ? c : GCD(a, c % a);
+ private static int Gcd(
+ int c,
+ int a
+ ) {
+ while (true) {
+ if (a == 0) return c;
+ int c1 = c;
+ c = a;
+ a = c1 % a;
+ }
+ }
private static int InverseMod(int c, int a) {
int f = 1, d = 0, b;
@@ -65,7 +76,7 @@ private void Next() {
for (int c = 0; 256 > c; c++) {
for (int a = 1; 40 > a; a++) {
for (int f = InverseMod(a, 256) & 255, d = 1; 40 > d; d++) {
- if (1 == GCD(a, d)) {
+ if (1 == Gcd(a, d)) {
int b;
int e;
if ((a & 1) != 0) {
@@ -118,16 +129,17 @@ private void Next() {
}
private string Generate(string s) {
- byte[] c = Encoding.UTF8.GetBytes(s);
- string d = "";
+ Span c = stackalloc byte[Encoding.UTF8.GetByteCount(s)];
+ Encoding.UTF8.GetBytes(s, c);
+ StringBuilder d = new();
for (int a = 0, f = c.Length, b = 0; b < f; b++) {
int e = c[b] & 255;
- string[] l = { ">" + _map[0][e], _map[a][e] };
+ string[] l = [$">{_map[0][e]}", _map[a][e]];
int g = ShortestStr(l);
- d += l[g] + ".";
+ d.Append(l[g]).Append('.');
a = e;
}
- return d;
+ return d.ToString();
}
}
}
diff --git a/BotNet.Services/BubbleWrap/BubbleWrapKeyboardGenerator.cs b/BotNet.Services/BubbleWrap/BubbleWrapKeyboardGenerator.cs
index f332a3d..9813e66 100644
--- a/BotNet.Services/BubbleWrap/BubbleWrapKeyboardGenerator.cs
+++ b/BotNet.Services/BubbleWrap/BubbleWrapKeyboardGenerator.cs
@@ -6,28 +6,27 @@ namespace BotNet.Services.BubbleWrap {
public sealed class BubbleWrapKeyboardGenerator(
IMemoryCache memoryCache
) {
- public static readonly InlineKeyboardMarkup EMPTY_KEYBOARD = BubbleWrapSheet.EmptySheet.ToKeyboardMarkup();
- private readonly IMemoryCache _memoryCache = memoryCache;
+ public static readonly InlineKeyboardMarkup EmptyKeyboard = BubbleWrapSheet.EmptySheet.ToKeyboardMarkup();
public InlineKeyboardMarkup HandleCallback(long chatId, int messageId, string sheetData) {
BubbleWrapId id = new(chatId, messageId);
BubbleWrapSheet expectedSheet = BubbleWrapSheet.ParseSheetData(sheetData);
- if (_memoryCache.TryGetValue(id, out BubbleWrapSheet? cachedSheet)) {
+ if (memoryCache.TryGetValue(id, out BubbleWrapSheet? cachedSheet)) {
cachedSheet = cachedSheet!.CombineWith(expectedSheet);
- _memoryCache.Set(
+ memoryCache.Set(
key: id,
value: cachedSheet,
absoluteExpirationRelativeToNow: TimeSpan.FromMinutes(1)
);
return cachedSheet.ToKeyboardMarkup();
- } else {
- _memoryCache.Set(
- key: id,
- value: expectedSheet,
- absoluteExpirationRelativeToNow: TimeSpan.FromMinutes(1)
- );
- return expectedSheet.ToKeyboardMarkup();
}
+
+ memoryCache.Set(
+ key: id,
+ value: expectedSheet,
+ absoluteExpirationRelativeToNow: TimeSpan.FromMinutes(1)
+ );
+ return expectedSheet.ToKeyboardMarkup();
}
private readonly record struct BubbleWrapId(
diff --git a/BotNet.Services/BubbleWrap/BubbleWrapSheet.cs b/BotNet.Services/BubbleWrap/BubbleWrapSheet.cs
index 6c285e3..ce07667 100644
--- a/BotNet.Services/BubbleWrap/BubbleWrapSheet.cs
+++ b/BotNet.Services/BubbleWrap/BubbleWrapSheet.cs
@@ -29,7 +29,7 @@ public static BubbleWrapSheet ParseSheetData(string sheetData = "FFFF") {
return new(data);
}
- public BubbleWrapSheet Pop(int row, int col) {
+ private BubbleWrapSheet Pop(int row, int col) {
if (!Data[row, col]) return this;
bool[,] data = (bool[,])Data.Clone();
@@ -47,7 +47,7 @@ public BubbleWrapSheet CombineWith(BubbleWrapSheet expectedSheet) {
return new(data);
}
- public string ToSheetData() {
+ private string ToSheetData() {
StringBuilder callbackData = new();
for (int row = 0; row < 4; row++) {
int bitmap = 0;
diff --git a/BotNet.Services/ChineseCalendar/ChineseCalendarScraper.cs b/BotNet.Services/ChineseCalendar/ChineseCalendarScraper.cs
index 15575bf..a4b5569 100644
--- a/BotNet.Services/ChineseCalendar/ChineseCalendarScraper.cs
+++ b/BotNet.Services/ChineseCalendar/ChineseCalendarScraper.cs
@@ -10,8 +10,7 @@
namespace BotNet.Services.ChineseCalendar {
public class ChineseCalendarScraper(HttpClient httpClient) {
- private const string URL_TEMPLATE = "https://www.chinesecalendaronline.com/{0}/{1}/{2}.htm";
- private readonly HttpClient _httpClient = httpClient;
+ private const string UrlTemplate = "https://www.chinesecalendaronline.com/{0}/{1}/{2}.htm";
public async Task<(
string Clash,
@@ -22,9 +21,9 @@ public class ChineseCalendarScraper(HttpClient httpClient) {
string[] AuspiciousActivities,
string[] InauspiciousActivities
)> GetYellowCalendarAsync(DateOnly date, CancellationToken cancellationToken) {
- string url = string.Format(URL_TEMPLATE, date.Year, date.Month, date.Day);
+ string url = string.Format(UrlTemplate, date.Year, date.Month, date.Day);
using HttpRequestMessage httpRequest = new(HttpMethod.Get, url);
- using HttpResponseMessage httpResponse = await _httpClient.SendAsync(httpRequest, cancellationToken);
+ using HttpResponseMessage httpResponse = await httpClient.SendAsync(httpRequest, cancellationToken);
httpResponse.EnsureSuccessStatusCode();
string html = await httpResponse.Content.ReadAsStringAsync(cancellationToken);
diff --git a/BotNet.Services/ClearScript/JsonConverters/ScriptObjectConverter.cs b/BotNet.Services/ClearScript/JsonConverters/ScriptObjectConverter.cs
index 64c2c26..61fe985 100644
--- a/BotNet.Services/ClearScript/JsonConverters/ScriptObjectConverter.cs
+++ b/BotNet.Services/ClearScript/JsonConverters/ScriptObjectConverter.cs
@@ -8,7 +8,7 @@ namespace BotNet.Services.ClearScript.JsonConverters {
public class ScriptObjectConverter : JsonConverter {
public override bool CanConvert(Type typeToConvert) => typeof(ScriptObject).IsAssignableFrom(typeToConvert);
- public override ScriptObject? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
+ public override ScriptObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
public override void Write(Utf8JsonWriter writer, ScriptObject value, JsonSerializerOptions options) {
if (value is IList) {
diff --git a/BotNet.Services/ClearScript/JsonConverters/UndefinedConverter.cs b/BotNet.Services/ClearScript/JsonConverters/UndefinedConverter.cs
index d64e294..9e11a5c 100644
--- a/BotNet.Services/ClearScript/JsonConverters/UndefinedConverter.cs
+++ b/BotNet.Services/ClearScript/JsonConverters/UndefinedConverter.cs
@@ -5,7 +5,7 @@
namespace BotNet.Services.ClearScript.JsonConverters {
public class UndefinedConverter : JsonConverter {
- public override Undefined? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
+ public override Undefined Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
public override void Write(Utf8JsonWriter writer, Undefined value, JsonSerializerOptions options) {
writer.WriteRawValue("undefined", skipInputValidation: true);
diff --git a/BotNet.Services/ClearScript/V8Evaluator.cs b/BotNet.Services/ClearScript/V8Evaluator.cs
index 8885493..174c0f0 100644
--- a/BotNet.Services/ClearScript/V8Evaluator.cs
+++ b/BotNet.Services/ClearScript/V8Evaluator.cs
@@ -7,8 +7,10 @@
using Microsoft.Extensions.Options;
namespace BotNet.Services.ClearScript {
- public class V8Evaluator {
- private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() {
+ public class V8Evaluator(
+ IOptions v8OptionsAccessor
+ ) {
+ private static readonly JsonSerializerOptions JsonSerializerOptions = new() {
Converters = {
new ScriptObjectConverter(),
new DoubleConverter(),
@@ -16,26 +18,19 @@ public class V8Evaluator {
new UndefinedConverter()
}
};
- private readonly V8Options _v8Options;
-
- public V8Evaluator(
- IOptions v8OptionsAccessor
- ) {
- _v8Options = v8OptionsAccessor.Value;
- }
+ private readonly V8Options _v8Options = v8OptionsAccessor.Value;
public async Task EvaluateAsync(string script, CancellationToken cancellationToken) {
- using V8ScriptEngine engine = new() {
- MaxRuntimeHeapSize = _v8Options.HeapSize,
- MaxRuntimeStackUsage = _v8Options.StackUsage
- };
+ using V8ScriptEngine engine = new();
+ engine.MaxRuntimeHeapSize = _v8Options.HeapSize;
+ engine.MaxRuntimeStackUsage = _v8Options.StackUsage;
using CancellationTokenSource timeoutSource = new(TimeSpan.FromSeconds(1));
timeoutSource.Token.Register(engine.Interrupt);
cancellationToken.Register(engine.Interrupt);
return await Task.Run(() => {
object? result = engine.Evaluate(script);
- return JsonSerializer.Serialize(result, JSON_SERIALIZER_OPTIONS);
- });
+ return JsonSerializer.Serialize(result, JsonSerializerOptions);
+ }, timeoutSource.Token);
}
}
}
diff --git a/BotNet.Services/ClearScript/V8Options.cs b/BotNet.Services/ClearScript/V8Options.cs
index a5b801a..c3ca29f 100644
--- a/BotNet.Services/ClearScript/V8Options.cs
+++ b/BotNet.Services/ClearScript/V8Options.cs
@@ -1,6 +1,6 @@
namespace BotNet.Services.ClearScript {
public class V8Options {
- public nuint HeapSize { get; set; }
- public nuint StackUsage { get; set; }
+ public nuint HeapSize { get; init; }
+ public nuint StackUsage { get; init; }
}
}
diff --git a/BotNet.Services/ColorCard/ColorCardRenderer.cs b/BotNet.Services/ColorCard/ColorCardRenderer.cs
index a65bb9d..d13a74a 100644
--- a/BotNet.Services/ColorCard/ColorCardRenderer.cs
+++ b/BotNet.Services/ColorCard/ColorCardRenderer.cs
@@ -5,15 +5,9 @@
using SkiaSharp;
namespace BotNet.Services.ColorCard {
- public class ColorCardRenderer {
- private readonly BotNetFontService _botNetFontService;
-
- public ColorCardRenderer(
- BotNetFontService botNetFontService
- ) {
- _botNetFontService = botNetFontService;
- }
-
+ public class ColorCardRenderer(
+ BotNetFontService botNetFontService
+ ) {
public byte[] RenderColorCard(string colorName) {
if (string.IsNullOrWhiteSpace(colorName)) throw new ArgumentNullException(nameof(colorName));
@@ -45,15 +39,14 @@ public byte[] RenderColorCard(string colorName) {
canvas.Clear(fillColor);
- using Stream fontStream = _botNetFontService.GetFontStyleById("JetBrainsMonoNL-Regular").OpenStream();
+ using Stream fontStream = botNetFontService.GetFontStyleById("JetBrainsMonoNL-Regular").OpenStream();
using SKTypeface typeface = SKTypeface.FromStream(fontStream);
- using SKPaint paint = new() {
- TextAlign = SKTextAlign.Center,
- Color = textColor,
- Typeface = typeface,
- TextSize = 50f,
- IsAntialias = true
- };
+ using SKPaint paint = new();
+ paint.TextAlign = SKTextAlign.Center;
+ paint.Color = textColor;
+ paint.Typeface = typeface;
+ paint.TextSize = 50f;
+ paint.IsAntialias = true;
SKRect textBound = new();
paint.MeasureText(normalizedName, ref textBound);
canvas.DrawText(
diff --git a/BotNet.Services/CopyPasta/CopyPastaLookup.cs b/BotNet.Services/CopyPasta/CopyPastaLookup.cs
index 7145280..4c347d4 100644
--- a/BotNet.Services/CopyPasta/CopyPastaLookup.cs
+++ b/BotNet.Services/CopyPasta/CopyPastaLookup.cs
@@ -6,17 +6,17 @@
namespace BotNet.Services.CopyPasta {
public class CopyPastaLookup {
- private static readonly ImmutableDictionary> DICTIONARY;
+ private static readonly ImmutableDictionary> Dictionary;
static CopyPastaLookup() {
using Stream stream = Assembly.GetAssembly(typeof(CopyPastaLookup))!.GetManifestResourceStream("BotNet.Services.CopyPasta.Pasta.json")!;
using StreamReader streamReader = new(stream);
string json = streamReader.ReadToEnd();
- DICTIONARY = JsonSerializer.Deserialize>>(json)!;
+ Dictionary = JsonSerializer.Deserialize>>(json)!;
}
public static bool TryGetAutoText(string key, [NotNullWhen(true)] out ImmutableList? values) {
- return DICTIONARY.TryGetValue(key, out values);
+ return Dictionary.TryGetValue(key, out values);
}
}
}
diff --git a/BotNet.Services/Craiyon/CraiyonClient.cs b/BotNet.Services/Craiyon/CraiyonClient.cs
index dead521..440cade 100644
--- a/BotNet.Services/Craiyon/CraiyonClient.cs
+++ b/BotNet.Services/Craiyon/CraiyonClient.cs
@@ -8,39 +8,35 @@
using BotNet.Services.Craiyon.Models;
namespace BotNet.Services.Craiyon {
- public class CraiyonClient {
- private const string URL = "https://backend.craiyon.com/generate";
- private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() {
+ public class CraiyonClient(
+ HttpClient httpClient
+ ) {
+ private const string Url = "https://backend.craiyon.com/generate";
+ private static readonly JsonSerializerOptions JsonSerializerOptions = new() {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
- private readonly HttpClient _httpClient;
-
- public CraiyonClient(
- HttpClient httpClient
- ) {
- _httpClient = httpClient;
- }
public async Task> GenerateImagesAsync(string prompt, CancellationToken cancellationToken) {
- using HttpRequestMessage request = new(HttpMethod.Post, URL) {
- Content = JsonContent.Create(
- inputValue: new {
- Prompt = prompt
- },
- options: JSON_SERIALIZER_OPTIONS
- )
- };
- using HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken);
+ using HttpRequestMessage request = new(HttpMethod.Post, Url);
+ request.Content = JsonContent.Create(
+ inputValue: new {
+ Prompt = prompt
+ },
+ options: JsonSerializerOptions
+ );
+ using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
- ImagesResult? imagesResult = await response.Content.ReadFromJsonAsync(JSON_SERIALIZER_OPTIONS, cancellationToken);
+ ImagesResult? imagesResult = await response.Content.ReadFromJsonAsync(JsonSerializerOptions, cancellationToken);
List images = new();
- if (imagesResult != null) {
- foreach (string encodedImage in imagesResult.Images) {
- byte[] image = Convert.FromBase64String(encodedImage.Replace("\\n", ""));
- images.Add(image);
- }
+ if (imagesResult == null) {
+ return images;
+ }
+
+ foreach (string encodedImage in imagesResult.Images) {
+ byte[] image = Convert.FromBase64String(encodedImage.Replace("\\n", ""));
+ images.Add(image);
}
return images;
}
diff --git a/BotNet.Services/DynamicExpresso/CSharpEvaluator.cs b/BotNet.Services/DynamicExpresso/CSharpEvaluator.cs
index 1a11a41..ad26998 100644
--- a/BotNet.Services/DynamicExpresso/CSharpEvaluator.cs
+++ b/BotNet.Services/DynamicExpresso/CSharpEvaluator.cs
@@ -1,17 +1,11 @@
using DynamicExpresso;
namespace BotNet.Services.DynamicExpresso {
- public class CSharpEvaluator {
- private readonly Interpreter _interpreter;
-
- public CSharpEvaluator(
- Interpreter interpreter
- ) {
- _interpreter = interpreter;
- }
-
+ public class CSharpEvaluator(
+ Interpreter interpreter
+ ) {
public object Evaluate(string expression) {
- return _interpreter.Eval(expression);
+ return interpreter.Eval(expression);
}
}
}
diff --git a/BotNet.Services/DynamicExpresso/ServiceCollectionExtensions.cs b/BotNet.Services/DynamicExpresso/ServiceCollectionExtensions.cs
index 4ee88d7..973047d 100644
--- a/BotNet.Services/DynamicExpresso/ServiceCollectionExtensions.cs
+++ b/BotNet.Services/DynamicExpresso/ServiceCollectionExtensions.cs
@@ -1,10 +1,10 @@
-using System.Linq;
-using DynamicExpresso;
-using Microsoft.Extensions.DependencyInjection;
+using System.Linq;
+using DynamicExpresso;
+using Microsoft.Extensions.DependencyInjection;
-namespace BotNet.Services.DynamicExpresso {
- public static class ServiceCollectionExtensions {
- public static IServiceCollection AddCSharpEvaluator(this IServiceCollection services) {
+namespace BotNet.Services.DynamicExpresso {
+ public static class ServiceCollectionExtensions {
+ public static IServiceCollection AddCSharpEvaluator(this IServiceCollection services) {
services.AddSingleton(new Interpreter(options: InterpreterOptions.Default | InterpreterOptions.LambdaExpressions)
.Reference(
from type in new[] {
@@ -80,9 +80,9 @@ from type in new[] {
}
select new ReferenceType(type)
)
- );
- services.AddTransient();
- return services;
- }
- }
-}
+ );
+ services.AddTransient();
+ return services;
+ }
+ }
+}
diff --git a/BotNet.Services/FancyText/FancyTextGenerator.cs b/BotNet.Services/FancyText/FancyTextGenerator.cs
index 5a8613b..1ba2567 100644
--- a/BotNet.Services/FancyText/FancyTextGenerator.cs
+++ b/BotNet.Services/FancyText/FancyTextGenerator.cs
@@ -7,27 +7,26 @@
using System.Threading.Tasks;
namespace BotNet.Services.FancyText {
- public class FancyTextGenerator {
- private static readonly Dictionary> CHAR_MAP_BY_STYLE = new();
- private static readonly SemaphoreSlim SEMAPHORE = new(1, 1);
+ public static class FancyTextGenerator {
+ private static readonly Dictionary> CharMapByStyle = new();
+ private static readonly SemaphoreSlim Semaphore = new(1, 1);
private static async Task> GetCharMapAsync(FancyTextStyle style, CancellationToken cancellationToken) {
- await SEMAPHORE.WaitAsync(cancellationToken);
+ await Semaphore.WaitAsync(cancellationToken);
try {
- if (CHAR_MAP_BY_STYLE.TryGetValue(style, out ImmutableDictionary? charMap)) {
+ if (CharMapByStyle.TryGetValue(style, out ImmutableDictionary? charMap)) {
return charMap;
}
- using Stream resourceStream = typeof(FancyTextStyle).Assembly.GetManifestResourceStream($"BotNet.Services.FancyText.CharMaps.{style}.json")!;
+
+ await using Stream resourceStream = typeof(FancyTextStyle).Assembly.GetManifestResourceStream($"BotNet.Services.FancyText.CharMaps.{style}.json")!;
using StreamReader resourceStreamReader = new(resourceStream);
- string resourceText = await resourceStreamReader.ReadToEndAsync();
+ string resourceText = await resourceStreamReader.ReadToEndAsync(cancellationToken);
Dictionary map = JsonSerializer.Deserialize>(resourceText)!;
charMap = map.ToImmutableDictionary(kvp => kvp.Key[0], kvp => kvp.Value);
- CHAR_MAP_BY_STYLE.Add(style, charMap!);
+ CharMapByStyle.Add(style, charMap);
return charMap;
- } catch {
- throw;
} finally {
- SEMAPHORE.Release();
+ Semaphore.Release();
}
}
diff --git a/BotNet.Services/Gemini/GeminiClient.cs b/BotNet.Services/Gemini/GeminiClient.cs
index ad22632..0dd0a77 100644
--- a/BotNet.Services/Gemini/GeminiClient.cs
+++ b/BotNet.Services/Gemini/GeminiClient.cs
@@ -6,19 +6,15 @@
using System.Threading;
using System.Threading.Tasks;
using BotNet.Services.Gemini.Models;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace BotNet.Services.Gemini {
public class GeminiClient(
HttpClient httpClient,
- IOptions geminiOptionsAccessor,
- ILogger logger
+ IOptions geminiOptionsAccessor
) {
- private const string BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent";
- private readonly HttpClient _httpClient = httpClient;
+ private const string BaseUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent";
private readonly string _apiKey = geminiOptionsAccessor.Value.ApiKey!;
- private readonly ILogger _logger = logger;
public async Task ChatAsync(IEnumerable messages, int maxTokens, CancellationToken cancellationToken) {
GeminiRequest geminiRequest = new(
@@ -33,15 +29,12 @@ public async Task ChatAsync(IEnumerable messages, int maxTokens
MaxOutputTokens: maxTokens
)
);
- using HttpRequestMessage request = new(HttpMethod.Post, BASE_URL + $"?key={_apiKey}") {
- Headers = {
- { "Accept", "application/json" }
- },
- Content = JsonContent.Create(
- inputValue: geminiRequest
- )
- };
- using HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken);
+ using HttpRequestMessage request = new(HttpMethod.Post, $"{BaseUrl}?key={_apiKey}");
+ request.Headers.Add("Accept", "application/json");
+ request.Content = JsonContent.Create(
+ inputValue: geminiRequest
+ );
+ using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
string responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
response.EnsureSuccessStatusCode();
diff --git a/BotNet.Services/Giphy/GiphyClient.cs b/BotNet.Services/Giphy/GiphyClient.cs
index e11706a..4283a58 100644
--- a/BotNet.Services/Giphy/GiphyClient.cs
+++ b/BotNet.Services/Giphy/GiphyClient.cs
@@ -14,7 +14,7 @@ namespace BotNet.Services.Giphy {
[Obsolete("Use TenorClient instead.")]
[ExcludeFromCodeCoverage]
public class GiphyClient {
- private const string GIF_SEARCH_ENDPOINT = "https://api.giphy.com/v1/gifs/search";
+ private const string GifSearchEndpoint = "https://api.giphy.com/v1/gifs/search";
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly JsonSerializerOptions _jsonSerializerOptions;
@@ -32,9 +32,9 @@ IOptions giphyOptionsAccessor
}
public async Task SearchGifsAsync(string query, CancellationToken cancellationToken) {
- string requestUrl = $"{GIF_SEARCH_ENDPOINT}?api_key={_apiKey}&q={WebUtility.UrlEncode(query)}&offset=0&limit=25&rating=g&lang=id";
+ string requestUrl = $"{GifSearchEndpoint}?api_key={_apiKey}&q={WebUtility.UrlEncode(query)}&offset=0&limit=25&rating=g&lang=id";
GifSearchResult? searchResult = await _httpClient.GetFromJsonAsync(requestUrl, _jsonSerializerOptions, cancellationToken);
- return searchResult?.Data ?? Array.Empty();
+ return searchResult?.Data ?? [];
}
}
}
diff --git a/BotNet.Services/GoogleMap/GeoCode.cs b/BotNet.Services/GoogleMap/GeoCode.cs
index 5a7bad0..0487d01 100644
--- a/BotNet.Services/GoogleMap/GeoCode.cs
+++ b/BotNet.Services/GoogleMap/GeoCode.cs
@@ -12,17 +12,12 @@ namespace BotNet.Services.GoogleMap {
///
/// This class intended to get geocoding from address.
///
- public class GeoCode {
- private readonly string? _apiKey;
- private const string URI_TEMPLATE = "https://maps.googleapis.com/maps/api/geocode/json";
- private readonly HttpClientHandler _httpClientHandler;
- private readonly HttpClient _httpClient;
-
- public GeoCode(IOptions options) {
- _apiKey = options.Value.ApiKey;
- _httpClientHandler = new();
- _httpClient = new(_httpClientHandler);
- }
+ public class GeoCode(
+ HttpClient httpClient,
+ IOptions options
+ ) {
+ private readonly string? _apiKey = options.Value.ApiKey;
+ private const string UriTemplate = "https://maps.googleapis.com/maps/api/geocode/json";
///
/// The response of this api call is consist of 2 parts.
@@ -43,8 +38,8 @@ public GeoCode(IOptions options) {
throw new HttpRequestException("Api key is needed");
}
- Uri uri = new(URI_TEMPLATE + $"?address={place}&key={_apiKey}");
- HttpResponseMessage response = await _httpClient.GetAsync(uri.AbsoluteUri);
+ Uri uri = new($"{UriTemplate}?address={place}&key={_apiKey}");
+ HttpResponseMessage response = await httpClient.GetAsync(uri.AbsoluteUri);
if (response is not { StatusCode: HttpStatusCode.OK, Content.Headers.ContentType.MediaType: string contentType }) {
throw new HttpRequestException("Unable to find location.");
@@ -70,7 +65,7 @@ public GeoCode(IOptions options) {
throw new HttpRequestException("No Result.");
}
- Result? result = body.Results[0];
+ Result result = body.Results[0];
double lat = result.Geometry!.Location!.Lat;
double lng = result.Geometry!.Location!.Lng;
diff --git a/BotNet.Services/GoogleMap/Models/Geometry.cs b/BotNet.Services/GoogleMap/Models/Geometry.cs
index 1a7335b..0247c10 100644
--- a/BotNet.Services/GoogleMap/Models/Geometry.cs
+++ b/BotNet.Services/GoogleMap/Models/Geometry.cs
@@ -3,6 +3,7 @@ public class Geometry {
public Coordinate? Location{ get; set; }
+ // ReSharper disable once InconsistentNaming
public string? Location_Type { get; set; }
public class Coordinate {
diff --git a/BotNet.Services/GoogleMap/Models/LocationType.cs b/BotNet.Services/GoogleMap/Models/LocationType.cs
index 185303a..654e579 100644
--- a/BotNet.Services/GoogleMap/Models/LocationType.cs
+++ b/BotNet.Services/GoogleMap/Models/LocationType.cs
@@ -1,4 +1,5 @@
-namespace BotNet.Services.GoogleMap.Models {
+// ReSharper disable InconsistentNaming
+namespace BotNet.Services.GoogleMap.Models {
public enum LocationType {
ROOFTOP,
RANGE_INTERPOLATED,
diff --git a/BotNet.Services/GoogleMap/Models/Result.cs b/BotNet.Services/GoogleMap/Models/Result.cs
index d83aaf9..3435197 100644
--- a/BotNet.Services/GoogleMap/Models/Result.cs
+++ b/BotNet.Services/GoogleMap/Models/Result.cs
@@ -1,6 +1,7 @@
namespace BotNet.Services.GoogleMap.Models {
public class Result {
+ // ReSharper disable once InconsistentNaming
public string? Formatted_Address { get; set; }
public Geometry? Geometry { get; set; }
diff --git a/BotNet.Services/GoogleMap/Models/StatusCode.cs b/BotNet.Services/GoogleMap/Models/StatusCode.cs
index 305fcda..6e3b1b8 100644
--- a/BotNet.Services/GoogleMap/Models/StatusCode.cs
+++ b/BotNet.Services/GoogleMap/Models/StatusCode.cs
@@ -1,4 +1,5 @@
-namespace BotNet.Services.GoogleMap.Models {
+// ReSharper disable InconsistentNaming
+namespace BotNet.Services.GoogleMap.Models {
public enum StatusCode {
OK,
ZERO_RESULTS,
diff --git a/BotNet.Services/GoogleMap/StaticMap.cs b/BotNet.Services/GoogleMap/StaticMap.cs
index 588b63c..486b8a0 100644
--- a/BotNet.Services/GoogleMap/StaticMap.cs
+++ b/BotNet.Services/GoogleMap/StaticMap.cs
@@ -10,11 +10,11 @@ public class StaticMap(
IOptions options
) {
private readonly string? _apiKey = options.Value.ApiKey;
- protected string mapPosition = "center";
- protected int zoom = 13;
- protected string size = "600x300";
- protected string marker = "color:red";
- private const string URI_TEMPLATE = "https://maps.googleapis.com/maps/api/staticmap";
+ private const string MapPosition = "center";
+ private const int Zoom = 13;
+ private const string Size = "600x300";
+ private const string Marker = "color:red";
+ private const string UriTemplate = "https://maps.googleapis.com/maps/api/staticmap";
///
/// Get static map image from google map api
@@ -30,7 +30,7 @@ public string SearchPlace(string? place) {
return "Api key is needed";
}
- Uri uri = new(URI_TEMPLATE + $"?{mapPosition}={place}&zoom={zoom}&size={size}&markers={marker}|{place}&key={_apiKey}");
+ Uri uri = new($"{UriTemplate}?{MapPosition}={place}&zoom={Zoom}&size={Size}&markers={Marker}|{place}&key={_apiKey}");
return uri.ToString();
}
diff --git a/BotNet.Services/GoogleSheets/FromColumnAttribute.cs b/BotNet.Services/GoogleSheets/FromColumnAttribute.cs
index 1e943fe..006b0b6 100644
--- a/BotNet.Services/GoogleSheets/FromColumnAttribute.cs
+++ b/BotNet.Services/GoogleSheets/FromColumnAttribute.cs
@@ -1,12 +1,10 @@
using System;
namespace BotNet.Services.GoogleSheets {
- [AttributeUsage(validOn: AttributeTargets.Property, AllowMultiple = false)]
- public sealed class FromColumnAttribute : Attribute {
- public string Column { get; }
-
- public FromColumnAttribute(string column) {
- Column = column;
- }
+ [AttributeUsage(validOn: AttributeTargets.Property)]
+ public sealed class FromColumnAttribute(
+ string column
+ ) : Attribute {
+ public string Column { get; } = column;
}
}
diff --git a/BotNet.Services/GoogleSheets/GoogleSheetsClient.cs b/BotNet.Services/GoogleSheets/GoogleSheetsClient.cs
index c627840..46a0370 100644
--- a/BotNet.Services/GoogleSheets/GoogleSheetsClient.cs
+++ b/BotNet.Services/GoogleSheets/GoogleSheetsClient.cs
@@ -12,13 +12,11 @@ namespace BotNet.Services.GoogleSheets {
public sealed class GoogleSheetsClient(
SheetsService sheetsService
) {
- private readonly SheetsService _sheetsService = sheetsService;
-
public async Task> GetDataAsync(string spreadsheetId, string range, string firstColumn, CancellationToken cancellationToken) {
int firstColumnIndex = GetColumnIndex(firstColumn);
// Fetch data
- SpreadsheetsResource.ValuesResource.GetRequest getRequest = _sheetsService.Spreadsheets.Values.Get(
+ SpreadsheetsResource.ValuesResource.GetRequest getRequest = sheetsService.Spreadsheets.Values.Get(
spreadsheetId: spreadsheetId,
range: range
);
@@ -63,7 +61,7 @@ public async Task> GetDataAsync(string spreadsheetId, string
if (decimal.TryParse(value, out decimal decimalValue)) {
parameters[i] = decimalValue;
} else {
- parameters[i] = (decimal?)null;
+ parameters[i] = null;
}
} else if (property.PropertyType == typeof(int)) {
if (int.TryParse(value, out int intValue)) {
@@ -75,7 +73,7 @@ public async Task> GetDataAsync(string spreadsheetId, string
if (int.TryParse(value, out int intValue)) {
parameters[i] = intValue;
} else {
- parameters[i] = (int?)null;
+ parameters[i] = null;
}
} else if (property.PropertyType == typeof(double)) {
if (double.TryParse(value, out double doubleValue)) {
@@ -87,7 +85,7 @@ public async Task> GetDataAsync(string spreadsheetId, string
if (double.TryParse(value, out double doubleValue)) {
parameters[i] = doubleValue;
} else {
- parameters[i] = (double?)null;
+ parameters[i] = null;
}
} else {
parameters[i] = Convert.ChangeType(value, property.PropertyType);
@@ -100,11 +98,11 @@ public async Task> GetDataAsync(string spreadsheetId, string
return builder.ToImmutable();
}
- public static int GetColumnIndex(string columnName) {
+ private static int GetColumnIndex(string columnName) {
int index = 0;
- for (int i = 0; i < columnName.Length; i++) {
+ foreach (char c in columnName) {
index *= 26;
- index += (columnName[i] - 'A' + 1);
+ index += (c - 'A' + 1);
}
return index - 1;
}
diff --git a/BotNet.Services/ImageConverter/IcoToPngConverter.cs b/BotNet.Services/ImageConverter/IcoToPngConverter.cs
index 3e7a8a6..21fef00 100644
--- a/BotNet.Services/ImageConverter/IcoToPngConverter.cs
+++ b/BotNet.Services/ImageConverter/IcoToPngConverter.cs
@@ -5,26 +5,17 @@
using SkiaSharp;
namespace BotNet.Services.ImageConverter {
- public class IcoToPngConverter {
- private readonly HttpClient _httpClient;
-
- public IcoToPngConverter(
- HttpClient httpClient
- ) {
- _httpClient = httpClient;
- }
-
+ public class IcoToPngConverter(
+ HttpClient httpClient
+ ) {
public async Task ConvertFromUrlAsync(string url, CancellationToken cancellationToken) {
- using HttpRequestMessage httpRequest = new(HttpMethod.Get, url) {
- Headers = {
- { "Accept", "image/ico" },
- { "User-Agent", "TEKNUM" }
- }
- };
- using HttpResponseMessage response = await _httpClient.SendAsync(httpRequest, cancellationToken);
+ using HttpRequestMessage httpRequest = new(HttpMethod.Get, url);
+ httpRequest.Headers.Add("Accept", "image/ico");
+ httpRequest.Headers.Add("User-Agent", "TEKNUM");
+ using HttpResponseMessage response = await httpClient.SendAsync(httpRequest, cancellationToken);
response.EnsureSuccessStatusCode();
- using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
+ await using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
SKBitmap bitmap = SKBitmap.Decode(stream);
SKImage image = SKImage.FromBitmap(bitmap);
diff --git a/BotNet.Services/Json/SnakeCaseNamingPolicy.cs b/BotNet.Services/Json/SnakeCaseNamingPolicy.cs
index 5166fe6..0bd358c 100644
--- a/BotNet.Services/Json/SnakeCaseNamingPolicy.cs
+++ b/BotNet.Services/Json/SnakeCaseNamingPolicy.cs
@@ -1,10 +1,24 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Text.Json;
namespace BotNet.Services.Json {
public class SnakeCaseNamingPolicy : JsonNamingPolicy {
public override string ConvertName(string name) {
- return string.Concat(name.Select((c, i) => i > 0 && char.IsUpper(c) ? "_" + c : c.ToString())).ToLower();
+ if (name == "") return "";
+ Span nameSpan = stackalloc char[name.Length + name.Skip(1).Count(char.IsUpper)];
+ int i = 0;
+ nameSpan[i++] = char.ToLower(name[0]);
+ foreach (char c in name.Skip(1)) {
+ if (char.IsUpper(c)) {
+ nameSpan[i++] = '_';
+ nameSpan[i++] = char.ToLower(c);
+ } else {
+ nameSpan[i++] = c;
+ }
+ }
+
+ return new string(nameSpan);
}
}
}
diff --git a/BotNet.Services/Khodam/KhodamCalculator.cs b/BotNet.Services/Khodam/KhodamCalculator.cs
index 22c0a4c..46ac5db 100644
--- a/BotNet.Services/Khodam/KhodamCalculator.cs
+++ b/BotNet.Services/Khodam/KhodamCalculator.cs
@@ -2,7 +2,7 @@
namespace BotNet.Services.Khodam {
public static class KhodamCalculator {
- private static readonly string[] ANIMALS = [
+ private static readonly string[] Animals = [
"Anjing",
"Ayam",
"Bebek",
@@ -26,7 +26,7 @@ public static class KhodamCalculator {
"Ular",
];
- private static readonly string[] ADJECTIVES = [
+ private static readonly string[] Adjectives = [
"Birahi",
"Hitam",
"Hutan",
@@ -43,7 +43,7 @@ public static class KhodamCalculator {
"Sunda",
];
- private static readonly string[] RARES = [
+ private static readonly string[] Rares = [
"Ayam Geprek",
"Ban Serep",
"Bintang Laut",
@@ -97,11 +97,11 @@ public static string CalculateKhodam(string name, long userId) {
// Rare
if (hashCode % 631 > 580) {
- return RARES[hashCode % RARES.Length];
+ return Rares[hashCode % Rares.Length];
}
// Animals
- return $"{ANIMALS[hashCode % ANIMALS.Length]} {ADJECTIVES[hashCode % ADJECTIVES.Length]}";
+ return $"{Animals[hashCode % Animals.Length]} {Adjectives[hashCode % Adjectives.Length]}";
}
}
}
diff --git a/BotNet.Services/KokizzuVPSBenchmark/ServiceCollectionExtensions.cs b/BotNet.Services/KokizzuVPSBenchmark/ServiceCollectionExtensions.cs
index 1fcf397..e85b2f3 100644
--- a/BotNet.Services/KokizzuVPSBenchmark/ServiceCollectionExtensions.cs
+++ b/BotNet.Services/KokizzuVPSBenchmark/ServiceCollectionExtensions.cs
@@ -3,8 +3,8 @@
namespace BotNet.Services.KokizzuVPSBenchmark {
public static class ServiceCollectionExtensions {
- public static IServiceCollection AddKokizzuVPSBenchmarkDataSource(this IServiceCollection services) {
- services.AddKeyedTransient("vps");
+ public static IServiceCollection AddKokizzuVpsBenchmarkDataSource(this IServiceCollection services) {
+ services.AddKeyedTransient("vps");
return services;
}
}
diff --git a/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmark.cs b/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmark.cs
index 00b85cf..aa79792 100644
--- a/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmark.cs
+++ b/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmark.cs
@@ -1,7 +1,7 @@
using BotNet.Services.GoogleSheets;
namespace BotNet.Services.KokizzuVPSBenchmark {
- public sealed record VPSBenchmark(
+ public sealed record VpsBenchmark(
[property: FromColumn("A")] string Provider,
[property: FromColumn("B")] string Location,
[property: FromColumn("C")] string BenchmarkDate,
diff --git a/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmarkDataSource.cs b/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmarkDataSource.cs
index 820ff4b..19b8b75 100644
--- a/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmarkDataSource.cs
+++ b/BotNet.Services/KokizzuVPSBenchmark/VPSBenchmarkDataSource.cs
@@ -6,15 +6,12 @@
using BotNet.Services.Sqlite;
namespace BotNet.Services.KokizzuVPSBenchmark {
- public sealed class VPSBenchmarkDataSource(
+ public sealed class VpsBenchmarkDataSource(
GoogleSheetsClient googleSheetsClient,
ScopedDatabase scopedDatabase
) : IScopedDataSource {
- private readonly GoogleSheetsClient _googleSheetsClient = googleSheetsClient;
- private readonly ScopedDatabase _scopedDatabase = scopedDatabase;
-
public async Task LoadTableAsync(CancellationToken cancellationToken) {
- _scopedDatabase.ExecuteNonQuery("""
+ scopedDatabase.ExecuteNonQuery("""
CREATE TABLE vps (
Provider TEXT,
Location VARCHAR(2),
@@ -37,7 +34,7 @@ AvgMbs REAL
)
""");
- ImmutableList data = await _googleSheetsClient.GetDataAsync(
+ ImmutableList data = await googleSheetsClient.GetDataAsync(
// Source: https://docs.google.com/spreadsheets/d/14nAIFzIzkQuSxiayhc5tSFWFCWFncrV-GCA3Q5BbS4g/edit#gid=0
spreadsheetId: "14nAIFzIzkQuSxiayhc5tSFWFCWFncrV-GCA3Q5BbS4g",
range: "'Result'!A3:T",
@@ -45,8 +42,8 @@ AvgMbs REAL
cancellationToken: cancellationToken
);
- foreach (VPSBenchmark vpsBenchmark in data) {
- _scopedDatabase.ExecuteNonQuery($"""
+ foreach (VpsBenchmark vpsBenchmark in data) {
+ scopedDatabase.ExecuteNonQuery($"""
INSERT INTO vps (
Provider,
Location,
diff --git a/BotNet.Services/MarkdownV2/MarkdownV2Sanitizer.cs b/BotNet.Services/MarkdownV2/MarkdownV2Sanitizer.cs
index 776c00e..9104f3c 100644
--- a/BotNet.Services/MarkdownV2/MarkdownV2Sanitizer.cs
+++ b/BotNet.Services/MarkdownV2/MarkdownV2Sanitizer.cs
@@ -3,7 +3,7 @@
namespace BotNet.Services.MarkdownV2 {
public static class MarkdownV2Sanitizer {
- private static readonly HashSet CHARACTERS_TO_ESCAPE = [
+ private static readonly HashSet CharactersToEscape = [
'[', ']', '(', ')', '~', '>', '#',
'+', '-', '=', '|', '{', '}', '.', '!'
];
@@ -18,7 +18,7 @@ public static string Sanitize(string input) {
char previousCharacter = '\0';
foreach (char character in input) {
// If the character is in our list, append a backslash before it
- if (CHARACTERS_TO_ESCAPE.Contains(character)
+ if (CharactersToEscape.Contains(character)
&& previousCharacter != '\\') {
sanitized.Append('\\');
}
@@ -28,17 +28,5 @@ public static string Sanitize(string input) {
return sanitized.ToString();
}
-
- private static int CountOccurences(string text, string substring) {
- int count = 0;
- int i = 0;
-
- while ((i = text.IndexOf(substring, i)) != -1) {
- i += substring.Length;
- count++;
- }
-
- return count;
- }
}
}
diff --git a/BotNet.Services/Meme/MemeGenerator.cs b/BotNet.Services/Meme/MemeGenerator.cs
index 33d3266..d5faa9b 100644
--- a/BotNet.Services/Meme/MemeGenerator.cs
+++ b/BotNet.Services/Meme/MemeGenerator.cs
@@ -5,17 +5,11 @@
using SkiaSharp;
namespace BotNet.Services.Meme {
- public class MemeGenerator {
- private readonly BotNetFontService _botNetFontService;
-
- public MemeGenerator(
- BotNetFontService botNetFontService
- ) {
- _botNetFontService = botNetFontService;
- }
-
+ public class MemeGenerator(
+ BotNetFontService botNetFontService
+ ) {
public byte[] CaptionRamad(string text) {
- return CaptionMeme(Templates.RAMAD, text);
+ return CaptionMeme(Templates.Ramad, text);
}
private byte[] CaptionMeme(Template template, string text) {
@@ -33,15 +27,14 @@ private byte[] CaptionMeme(Template template, string text) {
canvas.Save();
canvas.RotateDegrees(template.Rotation);
- using Stream fontStream = _botNetFontService.GetFontStyleById(template.FontStyleId).OpenStream();
+ using Stream fontStream = botNetFontService.GetFontStyleById(template.FontStyleId).OpenStream();
using SKTypeface typeface = SKTypeface.FromStream(fontStream);
- using SKPaint paint = new() {
- TextAlign = template.TextAlign,
- Color = template.TextColor,
- Typeface = typeface,
- TextSize = template.TextSize,
- IsAntialias = true
- };
+ using SKPaint paint = new();
+ paint.TextAlign = template.TextAlign;
+ paint.Color = template.TextColor;
+ paint.Typeface = typeface;
+ paint.TextSize = template.TextSize;
+ paint.IsAntialias = true;
float offset = 0f;
foreach (string line in WrapWords(text, template.MaxWidth, paint)) {
canvas.DrawText(
@@ -65,7 +58,7 @@ private byte[] CaptionMeme(Template template, string text) {
private static List WrapWords(string text, float maxWidth, SKPaint paint) {
string[] words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
- List lines = new();
+ List lines = [];
bool firstWord = true;
string line = "";
foreach (string word in words) {
diff --git a/BotNet.Services/Meme/Templates.cs b/BotNet.Services/Meme/Templates.cs
index 4b5d1b5..69093f5 100644
--- a/BotNet.Services/Meme/Templates.cs
+++ b/BotNet.Services/Meme/Templates.cs
@@ -15,7 +15,7 @@ float Y
);
internal static class Templates {
- public static readonly Template RAMAD = new(
+ public static readonly Template Ramad = new(
ImageResourceName: "BotNet.Services.Meme.Images.Ramad.jpg",
FontStyleId: "Inter-Regular",
TextAlign: SKTextAlign.Left,
diff --git a/BotNet.Services/OpenAI/AttachmentGenerator.cs b/BotNet.Services/OpenAI/AttachmentGenerator.cs
index 5b02cf9..246cbba 100644
--- a/BotNet.Services/OpenAI/AttachmentGenerator.cs
+++ b/BotNet.Services/OpenAI/AttachmentGenerator.cs
@@ -9,14 +9,14 @@ namespace BotNet.Services.OpenAI {
public partial class AttachmentGenerator(
IOptions hostingOptionsAccessor
) {
- private static readonly Regex HEX_COLOR_CODE_PATTERN = HexColorCodeRegex();
+ private static readonly Regex HexColorCodePattern = HexColorCodeRegex();
private readonly HostingOptions _hostingOptions = hostingOptionsAccessor.Value;
public ImmutableList GenerateAttachments(string message) {
ImmutableList.Builder builder = ImmutableList.Empty.ToBuilder();
// Detect hex color codes
- MatchCollection matches = HEX_COLOR_CODE_PATTERN.Matches(message);
+ MatchCollection matches = HexColorCodePattern.Matches(message);
foreach (Match match in matches) {
builder.Add(new Uri($"https://{_hostingOptions.HostName}/renderer/color?name={WebUtility.UrlEncode(match.Value)}"));
}
diff --git a/BotNet.Services/OpenAI/IntentDetector.cs b/BotNet.Services/OpenAI/IntentDetector.cs
index d4c8d15..5bbff39 100644
--- a/BotNet.Services/OpenAI/IntentDetector.cs
+++ b/BotNet.Services/OpenAI/IntentDetector.cs
@@ -5,10 +5,8 @@
namespace BotNet.Services.OpenAI {
public sealed class IntentDetector(
- OpenAIClient openAIClient
+ OpenAiClient openAiClient
) {
- private readonly OpenAIClient _openAIClient = openAIClient;
-
public async Task DetectChatIntentAsync(
string message,
CancellationToken cancellationToken
@@ -29,7 +27,7 @@ CancellationToken cancellationToken
""")
];
- string answer = await _openAIClient.ChatAsync(
+ string answer = await openAiClient.ChatAsync(
model: "gpt-3.5-turbo",
messages: messages,
maxTokens: 128,
@@ -64,7 +62,7 @@ CancellationToken cancellationToken
""")
];
- string answer = await _openAIClient.ChatAsync(
+ string answer = await openAiClient.ChatAsync(
model: "gpt-3.5-turbo",
messages: messages,
maxTokens: 128,
diff --git a/BotNet.Services/OpenAI/OpenAIClient.cs b/BotNet.Services/OpenAI/OpenAIClient.cs
index e5b5954..684dde6 100644
--- a/BotNet.Services/OpenAI/OpenAIClient.cs
+++ b/BotNet.Services/OpenAI/OpenAIClient.cs
@@ -15,46 +15,42 @@
using RG.Ninja;
namespace BotNet.Services.OpenAI {
- public class OpenAIClient(
+ public class OpenAiClient(
HttpClient httpClient,
- IOptions openAIOptionsAccessor,
- ILogger logger
+ IOptions openAiOptionsAccessor,
+ ILogger logger
) {
- private const string COMPLETION_URL_TEMPLATE = "https://api.openai.com/v1/engines/{0}/completions";
- private const string CHAT_URL = "https://api.openai.com/v1/chat/completions";
- private const string IMAGE_GENERATION_URL = "https://api.openai.com/v1/images/generations";
- private static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = new() {
+ private const string CompletionUrlTemplate = "https://api.openai.com/v1/engines/{0}/completions";
+ private const string ChatUrl = "https://api.openai.com/v1/chat/completions";
+ private const string ImageGenerationUrl = "https://api.openai.com/v1/images/generations";
+ private static readonly JsonSerializerOptions JsonSerializerOptions = new() {
PropertyNamingPolicy = new SnakeCaseNamingPolicy()
};
- private readonly HttpClient _httpClient = httpClient;
- private readonly string _apiKey = openAIOptionsAccessor.Value.ApiKey!;
- private readonly ILogger _logger = logger;
+
+ private readonly string _apiKey = openAiOptionsAccessor.Value.ApiKey!;
public async Task AutocompleteAsync(string engine, string prompt, string[]? stop, int maxTokens, double frequencyPenalty, double presencePenalty, double temperature, double topP, CancellationToken cancellationToken) {
- using HttpRequestMessage request = new(HttpMethod.Post, string.Format(COMPLETION_URL_TEMPLATE, engine)) {
- Headers = {
- { "Authorization", $"Bearer {_apiKey}" },
- { "Accept", "text/event-stream" }
+ using HttpRequestMessage request = new(HttpMethod.Post, string.Format(CompletionUrlTemplate, engine));
+ request.Headers.Add("Authorization", $"Bearer {_apiKey}");
+ request.Headers.Add("Accept", "text/event-stream");
+ request.Content = JsonContent.Create(
+ inputValue: new {
+ Prompt = prompt,
+ Temperature = temperature,
+ MaxTokens = maxTokens,
+ Stream = true,
+ TopP = topP,
+ FrequencyPenalty = frequencyPenalty,
+ PresencePenalty = presencePenalty,
+ Stop = stop
},
- Content = JsonContent.Create(
- inputValue: new {
- Prompt = prompt,
- Temperature = temperature,
- MaxTokens = maxTokens,
- Stream = true,
- TopP = topP,
- FrequencyPenalty = frequencyPenalty,
- PresencePenalty = presencePenalty,
- Stop = stop
- },
- options: JSON_SERIALIZER_OPTIONS
- )
- };
- using HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken);
+ options: JsonSerializerOptions
+ );
+ using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
StringBuilder result = new();
- using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
+ await using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using StreamReader streamReader = new(stream);
while (!streamReader.EndOfStream) {
string? line = await streamReader.ReadLineAsync(cancellationToken);
@@ -62,7 +58,7 @@ public async Task AutocompleteAsync(string engine, string prompt, string
if (line == "") continue;
if (!line.StartsWith("data: ", out string? json)) break;
if (json == "[DONE]") break;
- CompletionResult? completionResult = JsonSerializer.Deserialize(json, JSON_SERIALIZER_OPTIONS);
+ CompletionResult? completionResult = JsonSerializer.Deserialize(json, JsonSerializerOptions);
if (completionResult == null) break;
if (completionResult.Choices.Count == 0) break;
result.Append(completionResult.Choices[0].Text);
@@ -73,23 +69,20 @@ public async Task AutocompleteAsync(string engine, string prompt, string
}
public async Task ChatAsync(string model, IEnumerable messages, int maxTokens, CancellationToken cancellationToken) {
- using HttpRequestMessage request = new(HttpMethod.Post, CHAT_URL) {
- Headers = {
- { "Authorization", $"Bearer {_apiKey}" },
+ using HttpRequestMessage request = new(HttpMethod.Post, ChatUrl);
+ request.Headers.Add("Authorization", $"Bearer {_apiKey}");
+ request.Content = JsonContent.Create(
+ inputValue: new {
+ Model = model,
+ MaxTokens = maxTokens,
+ Messages = messages
},
- Content = JsonContent.Create(
- inputValue: new {
- Model = model,
- MaxTokens = maxTokens,
- Messages = messages
- },
- options: JSON_SERIALIZER_OPTIONS
- )
- };
- using HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken);
+ options: JsonSerializerOptions
+ );
+ using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
- CompletionResult? completionResult = await response.Content.ReadFromJsonAsync(JSON_SERIALIZER_OPTIONS, cancellationToken);
+ CompletionResult? completionResult = await response.Content.ReadFromJsonAsync(JsonSerializerOptions, cancellationToken);
if (completionResult == null) return "";
if (completionResult.Choices.Count == 0) return "";
return completionResult.Choices[0].Message?.Content!;
@@ -101,30 +94,27 @@ public async Task ChatAsync(string model, IEnumerable messa
int maxTokens,
[EnumeratorCancellation] CancellationToken cancellationToken
) {
- using HttpRequestMessage request = new(HttpMethod.Post, CHAT_URL) {
- Headers = {
- { "Authorization", $"Bearer {_apiKey}" },
- { "Accept", "text/event-stream" }
+ using HttpRequestMessage request = new(HttpMethod.Post, ChatUrl);
+ request.Headers.Add("Authorization", $"Bearer {_apiKey}");
+ request.Headers.Add("Accept", "text/event-stream");
+ request.Content = JsonContent.Create(
+ inputValue: new {
+ Model = model,
+ MaxTokens = maxTokens,
+ Messages = messages,
+ Stream = true
},
- Content = JsonContent.Create(
- inputValue: new {
- Model = model,
- MaxTokens = maxTokens,
- Messages = messages,
- Stream = true
- },
- options: JSON_SERIALIZER_OPTIONS
- )
- };
- using HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken);
+ options: JsonSerializerOptions
+ );
+ using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode) {
string errorMessage = await response.Content.ReadAsStringAsync(cancellationToken);
- _logger.LogError(errorMessage);
+ logger.LogError(errorMessage);
response.EnsureSuccessStatusCode();
}
StringBuilder result = new();
- using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
+ await using Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using StreamReader streamReader = new(stream);
while (!streamReader.EndOfStream) {
@@ -149,7 +139,7 @@ [EnumeratorCancellation] CancellationToken cancellationToken
yield break;
}
- CompletionResult? completionResult = JsonSerializer.Deserialize(json, JSON_SERIALIZER_OPTIONS);
+ CompletionResult? completionResult = JsonSerializer.Deserialize(json, JsonSerializerOptions);
if (completionResult == null || completionResult.Choices.Count == 0) {
yield return (
@@ -167,34 +157,31 @@ [EnumeratorCancellation] CancellationToken cancellationToken
Stop: true
);
yield break;
- } else {
- yield return (
- Result: result.ToString(),
- Stop: false
- );
}
+
+ yield return (
+ Result: result.ToString(),
+ Stop: false
+ );
}
}
public async Task GenerateImageAsync(string model, string prompt, CancellationToken cancellationToken) {
- using HttpRequestMessage request = new(HttpMethod.Post, IMAGE_GENERATION_URL) {
- Headers = {
- { "Authorization", $"Bearer {_apiKey}" }
+ using HttpRequestMessage request = new(HttpMethod.Post, ImageGenerationUrl);
+ request.Headers.Add("Authorization", $"Bearer {_apiKey}");
+ request.Content = JsonContent.Create(
+ inputValue: new {
+ Model = model,
+ Prompt = prompt,
+ N = 1,
+ Size = "1024x1024"
},
- Content = JsonContent.Create(
- inputValue: new {
- Model = model,
- Prompt = prompt,
- N = 1,
- Size = "1024x1024"
- },
- options: JSON_SERIALIZER_OPTIONS
- )
- };
- using HttpResponseMessage response = await _httpClient.SendAsync(request, cancellationToken);
+ options: JsonSerializerOptions
+ );
+ using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
- ImageGenerationResult? imageGenerationResult = await response.Content.ReadFromJsonAsync(JSON_SERIALIZER_OPTIONS, cancellationToken);
+ ImageGenerationResult? imageGenerationResult = await response.Content.ReadFromJsonAsync(JsonSerializerOptions, cancellationToken);
return new(imageGenerationResult!.Data[0].Url);
}
}
diff --git a/BotNet.Services/OpenAI/OpenAIOptions.cs b/BotNet.Services/OpenAI/OpenAIOptions.cs
index 131960e..ccd0ba9 100644
--- a/BotNet.Services/OpenAI/OpenAIOptions.cs
+++ b/BotNet.Services/OpenAI/OpenAIOptions.cs
@@ -1,5 +1,5 @@
namespace BotNet.Services.OpenAI {
- public class OpenAIOptions {
- public string? ApiKey { get; set; }
+ public class OpenAiOptions {
+ public string? ApiKey { get; init; }
}
}
diff --git a/BotNet.Services/OpenAI/OpenAIStreamingClient.cs b/BotNet.Services/OpenAI/OpenAIStreamingClient.cs
index 795c4dc..46ee13d 100644
--- a/BotNet.Services/OpenAI/OpenAIStreamingClient.cs
+++ b/BotNet.Services/OpenAI/OpenAIStreamingClient.cs
@@ -11,12 +11,10 @@
using Telegram.Bot.Types.Enums;
namespace BotNet.Services.OpenAI {
- public sealed class OpenAIStreamingClient(
+ public sealed class OpenAiStreamingClient(
IServiceProvider serviceProvider,
- ILogger logger
+ ILogger logger
) : IDisposable {
- private readonly IServiceProvider _serviceProvider = serviceProvider;
- private readonly ILogger _logger = logger;
private IServiceScope? _danglingServiceScope;
private bool _disposedValue;
@@ -28,12 +26,12 @@ public async Task StreamChatAsync(
long chatId,
int replyToMessageId
) {
- IServiceScope serviceScope = _serviceProvider.CreateScope();
+ IServiceScope serviceScope = serviceProvider.CreateScope();
_danglingServiceScope = serviceScope;
- OpenAIClient openAIClient = serviceScope.ServiceProvider.GetRequiredService();
+ OpenAiClient openAiClient = serviceScope.ServiceProvider.GetRequiredService();
ITelegramBotClient telegramBotClient = serviceScope.ServiceProvider.GetRequiredService();
- IAsyncEnumerable<(string Result, bool Stop)> enumerable = openAIClient.StreamChatAsync(
+ IAsyncEnumerable<(string Result, bool Stop)> enumerable = openAiClient.StreamChatAsync(
model: model,
messages: messages,
maxTokens: maxTokens,
@@ -53,7 +51,7 @@ int replyToMessageId
}
}
} catch (Exception exc) {
- _logger.LogError(exc, null);
+ logger.LogError(exc, null);
}
});
@@ -108,6 +106,7 @@ await Task.WhenAny(
_ = Task.Run(async () => {
try {
while (!downstreamTask.IsCompleted) {
+ // ReSharper disable once AccessToDisposedClosure
await Task.Delay(TimeSpan.FromSeconds(3), cts.Token);
if (lastSent != lastResult) {
@@ -118,11 +117,12 @@ await telegramBotClient.EditMessageText(
messageId: incompleteMessage.MessageId,
text: MarkdownV2Sanitizer.Sanitize(lastResult ?? "") + "… ⏳", // ellipsis, nbsp, hourglass emoji
parseMode: ParseMode.MarkdownV2,
+ // ReSharper disable once AccessToDisposedClosure
cancellationToken: cts.Token
);
} catch (Exception exc) when (exc is not OperationCanceledException) {
// Message might be deleted
- _logger.LogError(exc, null);
+ logger.LogError(exc, null);
break;
}
}
@@ -145,7 +145,7 @@ await telegramBotClient.EditMessageText(
cancellationToken: cts.Token
);
} catch (Exception exc) {
- _logger.LogError(exc, null);
+ logger.LogError(exc, null);
throw;
}
@@ -162,7 +162,7 @@ await telegramBotClient.EditMessageText(
// Message might be deleted, suppress exception
}
- cts.Cancel();
+ await cts.CancelAsync();
serviceScope.Dispose();
});
}
diff --git a/BotNet.Services/OpenAI/ServiceCollectionExtensions.cs b/BotNet.Services/OpenAI/ServiceCollectionExtensions.cs
index 1119442..a064012 100644
--- a/BotNet.Services/OpenAI/ServiceCollectionExtensions.cs
+++ b/BotNet.Services/OpenAI/ServiceCollectionExtensions.cs
@@ -3,9 +3,9 @@
namespace BotNet.Services.OpenAI {
public static class ServiceCollectionExtensions {
- public static IServiceCollection AddOpenAIClient(this IServiceCollection services) {
- services.AddTransient();
- services.AddTransient();
+ public static IServiceCollection AddOpenAiClient(this IServiceCollection services) {
+ services.AddTransient();
+ services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
diff --git a/BotNet.Services/OpenAI/Skills/AssistantBot.cs b/BotNet.Services/OpenAI/Skills/AssistantBot.cs
index 28fb4f2..bfdfd37 100644
--- a/BotNet.Services/OpenAI/Skills/AssistantBot.cs
+++ b/BotNet.Services/OpenAI/Skills/AssistantBot.cs
@@ -3,29 +3,30 @@
namespace BotNet.Services.OpenAI.Skills {
public class AssistantBot(
- OpenAIClient openAIClient
+ OpenAiClient openAiClient
) {
- private readonly OpenAIClient _openAIClient = openAIClient;
-
- public Task AskSomethingAsync(string name, string question, CancellationToken cancellationToken) {
+ public Task AskSomethingAsync(
+ string question,
+ CancellationToken cancellationToken
+ ) {
string prompt = $"I am a highly intelligent question answering bot. If you ask me a question that is rooted in truth, I will give you the answer. If you ask me a question that is nonsense, trickery, or has no clear answer, I will respond with \"Unknown\".\n\n"
- + "Q: What is human life expectancy in the United States?\n"
- + "A: Human life expectancy in the United States is 78 years.\n\n"
- + "Q: Who was president of the United States in 1955?\n"
- + "A: Dwight D. Eisenhower was president of the United States in 1955.\n\n"
- + "Q: Which party did he belong to?\n"
- + "A: He belonged to the Republican Party.\n\n"
- + "Q: What is the square root of banana?\n"
- + "A: Unknown\n\n"
- + "Q: How does a telescope work?\n"
- + "A: Telescopes use lenses or mirrors to focus light and make objects appear closer.\n\n"
- + "Q: Where were the 1992 Olympics held?\n"
- + "A: The 1992 Olympics were held in Barcelona, Spain.\n\n"
- + "Q: How many squigs are in a bonk?\n"
- + "A: Unknown\n\n"
- + $"Q: {question}\n"
- + "A:";
- return _openAIClient.AutocompleteAsync(
+ + "Q: What is human life expectancy in the United States?\n"
+ + "A: Human life expectancy in the United States is 78 years.\n\n"
+ + "Q: Who was president of the United States in 1955?\n"
+ + "A: Dwight D. Eisenhower was president of the United States in 1955.\n\n"
+ + "Q: Which party did he belong to?\n"
+ + "A: He belonged to the Republican Party.\n\n"
+ + "Q: What is the square root of banana?\n"
+ + "A: Unknown\n\n"
+ + "Q: How does a telescope work?\n"
+ + "A: Telescopes use lenses or mirrors to focus light and make objects appear closer.\n\n"
+ + "Q: Where were the 1992 Olympics held?\n"
+ + "A: The 1992 Olympics were held in Barcelona, Spain.\n\n"
+ + "Q: How many squigs are in a bonk?\n"
+ + "A: Unknown\n\n"
+ + $"Q: {question}\n"
+ + "A:";
+ return openAiClient.AutocompleteAsync(
engine: "text-davinci-002",
prompt: prompt,
stop: ["\n"],
diff --git a/BotNet.Services/OpenAI/Skills/FriendlyBot.cs b/BotNet.Services/OpenAI/Skills/FriendlyBot.cs
index b1a4013..6a551b4 100644
--- a/BotNet.Services/OpenAI/Skills/FriendlyBot.cs
+++ b/BotNet.Services/OpenAI/Skills/FriendlyBot.cs
@@ -7,19 +7,21 @@
namespace BotNet.Services.OpenAI.Skills {
public sealed class FriendlyBot(
- OpenAIClient openAIClient,
- OpenAIStreamingClient openAIStreamingClient
+ OpenAiClient openAiClient,
+ OpenAiStreamingClient openAiStreamingClient
) {
- private readonly OpenAIClient _openAIClient = openAIClient;
- private readonly OpenAIStreamingClient _openAIStreamingClient = openAIStreamingClient;
-
- public Task ChatAsync(string callSign, string name, string question, CancellationToken cancellationToken) {
+ public Task ChatAsync(
+ string callSign,
+ string name,
+ string question,
+ CancellationToken cancellationToken
+ ) {
string prompt = $"The following is a conversation with an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point.\n\n"
- + $"{name}: Hello, how are you?\n"
- + $"{callSign}: I am an AI created by TEKNUM. How can I help you today?\n\n"
- + $"{name}: {question}\n"
- + $"{callSign}: ";
- return _openAIClient.AutocompleteAsync(
+ + $"{name}: Hello, how are you?\n"
+ + $"{callSign}: I am an AI created by TEKNUM. How can I help you today?\n\n"
+ + $"{name}: {question}\n"
+ + $"{callSign}: ";
+ return openAiClient.AutocompleteAsync(
engine: "text-davinci-003",
prompt: prompt,
stop: [$"{name}:"],
@@ -32,18 +34,25 @@ public Task ChatAsync(string callSign, string name, string question, Can
);
}
- public Task RespondToThreadAsync(string callSign, string name, string question, ImmutableList<(string Sender, string Text)> thread, CancellationToken cancellationToken) {
+ public Task RespondToThreadAsync(
+ string callSign,
+ string name,
+ string question,
+ ImmutableList<(string Sender, string Text)> thread,
+ CancellationToken cancellationToken
+ ) {
string prompt = $"The following is a conversation with an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point.\n\n"
- + $"{name}: Hello, how are you?\n"
- + $"{callSign}: I am an AI created by TEKNUM. How can I help you today?\n\n";
+ + $"{name}: Hello, how are you?\n"
+ + $"{callSign}: I am an AI created by TEKNUM. How can I help you today?\n\n";
foreach ((string sender, string text) in thread) {
prompt += $"{sender}: {text}\n";
if (sender is "GPT" or "Pakde") prompt += "\n";
}
+
prompt +=
$"{name}: {question}\n"
+ $"{callSign}: ";
- return _openAIClient.AutocompleteAsync(
+ return openAiClient.AutocompleteAsync(
engine: "text-davinci-003",
prompt: prompt,
stop: [$"{name}:"],
@@ -56,13 +65,16 @@ public Task RespondToThreadAsync(string callSign, string name, string qu
);
}
- public Task ChatAsync(string message, CancellationToken cancellationToken) {
+ public Task ChatAsync(
+ string message,
+ CancellationToken cancellationToken
+ ) {
List messages = [
ChatMessage.FromText("system", "The following is a conversation with an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point."),
ChatMessage.FromText("user", message)
];
- return _openAIClient.ChatAsync(
+ return openAiClient.ChatAsync(
model: "gpt-4-1106-preview",
messages: messages,
maxTokens: 512,
@@ -70,13 +82,17 @@ public Task ChatAsync(string message, CancellationToken cancellationToke
);
}
- public async Task StreamChatAsync(string message, long chatId, int replyToMessageId) {
+ public async Task StreamChatAsync(
+ string message,
+ long chatId,
+ int replyToMessageId
+ ) {
List messages = [
ChatMessage.FromText("system", "The following is a conversation with an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point."),
ChatMessage.FromText("user", message)
];
- await _openAIStreamingClient.StreamChatAsync(
+ await openAiStreamingClient.StreamChatAsync(
model: "gpt-4-1106-preview",
messages: messages,
maxTokens: 512,
@@ -86,10 +102,13 @@ await _openAIStreamingClient.StreamChatAsync(
);
}
- public Task ChatAsync(string message, ImmutableList<(string Sender, string? Text, string? ImageBase64)> thread, CancellationToken cancellationToken) {
+ public Task ChatAsync(
+ string message,
+ ImmutableList<(string Sender, string? Text, string? ImageBase64)> thread,
+ CancellationToken cancellationToken
+ ) {
List messages = new() {
ChatMessage.FromText("system", "The following is a conversation with an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point."),
-
from tuple in thread
let role = tuple.Sender switch {
"GPT" => "assistant",
@@ -101,11 +120,10 @@ from tuple in thread
{ Text: { } text, ImageBase64: { } imageBase64 } => ChatMessage.FromTextWithImageBase64(role, text, imageBase64),
_ => ChatMessage.FromText(role, "")
},
-
ChatMessage.FromText("user", message)
};
- return _openAIClient.ChatAsync(
+ return openAiClient.ChatAsync(
model: "gpt-4-1106-preview",
messages: messages,
maxTokens: 512,
@@ -113,10 +131,14 @@ from tuple in thread
);
}
- public async Task StreamChatAsync(string message, ImmutableList<(string Sender, string? Text, string? ImageBase64)> thread, long chatId, int replyToMessageId) {
+ public async Task StreamChatAsync(
+ string message,
+ ImmutableList<(string Sender, string? Text, string? ImageBase64)> thread,
+ long chatId,
+ int replyToMessageId
+ ) {
List messages = new() {
ChatMessage.FromText("system", "The following is a conversation with an AI assistant. The assistant is helpful, creative, direct, concise, and always get to the point."),
-
from tuple in thread
let role = tuple.Sender switch {
"GPT" => "assistant",
@@ -128,11 +150,10 @@ from tuple in thread
{ Text: { } text, ImageBase64: { } imageBase64 } => ChatMessage.FromTextWithImageBase64(role, text, imageBase64),
_ => ChatMessage.FromText(role, "")
},
-
ChatMessage.FromText("user", message)
};
- await _openAIStreamingClient.StreamChatAsync(
+ await openAiStreamingClient.StreamChatAsync(
model: "gpt-4-1106-preview",
messages: messages,
maxTokens: 512,
diff --git a/BotNet.Services/OpenAI/Skills/ImageGenerationBot.cs b/BotNet.Services/OpenAI/Skills/ImageGenerationBot.cs
index b5f1e08..a83c50b 100644
--- a/BotNet.Services/OpenAI/Skills/ImageGenerationBot.cs
+++ b/BotNet.Services/OpenAI/Skills/ImageGenerationBot.cs
@@ -4,25 +4,24 @@
namespace BotNet.Services.OpenAI.Skills {
public class ImageGenerationBot(
- OpenAIClient openAIClient
+ OpenAiClient openAiClient
) {
- private readonly OpenAIClient _openAIClient = openAIClient;
- private static readonly SemaphoreSlim SEMAPHORE = new(1, 1);
+ private static readonly SemaphoreSlim Semaphore = new(1, 1);
public async Task GenerateImageAsync(
string prompt,
CancellationToken cancellationToken
) {
// dall-e-3 endpoint does not allow concurrent requests
- await SEMAPHORE.WaitAsync(cancellationToken);
+ await Semaphore.WaitAsync(cancellationToken);
try {
- return await _openAIClient.GenerateImageAsync(
+ return await openAiClient.GenerateImageAsync(
model: "dall-e-3",
prompt: prompt,
cancellationToken: cancellationToken
);
} finally {
- SEMAPHORE.Release();
+ Semaphore.Release();
}
}
}
diff --git a/BotNet.Services/OpenAI/Skills/SarcasticBot.cs b/BotNet.Services/OpenAI/Skills/SarcasticBot.cs
index 5d45139..3dfd76a 100644
--- a/BotNet.Services/OpenAI/Skills/SarcasticBot.cs
+++ b/BotNet.Services/OpenAI/Skills/SarcasticBot.cs
@@ -4,10 +4,8 @@
namespace BotNet.Services.OpenAI.Skills {
public class SarcasticBot(
- OpenAIClient openAIClient
+ OpenAiClient openAiClient
) {
- private readonly OpenAIClient _openAIClient = openAIClient;
-
public Task ChatAsync(string callSign, string name, string question, CancellationToken cancellationToken) {
string prompt = $"{callSign} adalah chatbot berbahasa Indonesia yang tidak ramah, kurang antusias dalam menjawab pertanyaan, dan suka mengomel.\n\n"
+ $"{name}: Satu kilogram itu berapa pound?\n"
@@ -20,7 +18,7 @@ public Task