diff --git a/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs b/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs
index 828570b..0b79a25 100644
--- a/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs
+++ b/BotNet.CommandHandlers/SQL/SQLCommandHandler.cs
@@ -6,6 +6,7 @@
using Microsoft.Extensions.DependencyInjection;
using SqlParser.Ast;
using Telegram.Bot;
+using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
namespace BotNet.CommandHandlers.SQL {
@@ -56,6 +57,7 @@ await _telegramBotClient.SendTextMessageAsync(
chatId: command.Chat.Id,
text: $$"""
Table '{{table}}' not found. Available tables are:
+ - pileg_dpr
- pilpres
- vps
@@ -73,6 +75,7 @@ await _telegramBotClient.SendTextMessageAsync(
// Execute query
using ScopedDatabase scopedDatabase = serviceScope.ServiceProvider.GetRequiredService();
StringBuilder resultBuilder = new();
+ int rows = 0;
try {
scopedDatabase.ExecuteReader(
@@ -118,6 +121,7 @@ await _telegramBotClient.SendTextMessageAsync(
}
}
resultBuilder.AppendLine(string.Join(',', values));
+ rows++;
}
}
);
@@ -133,13 +137,24 @@ await _telegramBotClient.SendTextMessageAsync(
}
// Send result
- await _telegramBotClient.SendTextMessageAsync(
- chatId: command.Chat.Id,
- text: "```csv\n" + resultBuilder.ToString() + "```",
- parseMode: ParseMode.MarkdownV2,
- replyToMessageId: command.SQLMessageId,
- cancellationToken: cancellationToken
- );
+ string csvResult = resultBuilder.ToString();
+ if (csvResult.Length > 4000) {
+ await _telegramBotClient.SendDocumentAsync(
+ chatId: command.Chat.Id,
+ caption: $"{rows} rows affected",
+ document: new InputFileStream(new MemoryStream(Encoding.UTF8.GetBytes(csvResult)), "result.csv"),
+ replyToMessageId: command.SQLMessageId,
+ cancellationToken: cancellationToken
+ );
+ } else {
+ await _telegramBotClient.SendTextMessageAsync(
+ chatId: command.Chat.Id,
+ text: "```csv\n" + resultBuilder.ToString() + $"```\n{rows} rows affected",
+ parseMode: ParseMode.MarkdownV2,
+ replyToMessageId: command.SQLMessageId,
+ cancellationToken: cancellationToken
+ );
+ }
}
private static void CollectTableNames(ref HashSet tables, TableFactor tableFactor) {
diff --git a/BotNet.Services/Pemilu2024/PilegDPRDataSource.cs b/BotNet.Services/Pemilu2024/PilegDPRDataSource.cs
new file mode 100644
index 0000000..54fdbc5
--- /dev/null
+++ b/BotNet.Services/Pemilu2024/PilegDPRDataSource.cs
@@ -0,0 +1,116 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using BotNet.Services.SQL;
+using BotNet.Services.Sqlite;
+
+namespace BotNet.Services.Pemilu2024 {
+ public sealed class PilegDPRDataSource(
+ ScopedDatabase scopedDatabase,
+ SirekapClient sirekapClient
+ ) : IScopedDataSource {
+ private const string PKB = "1";
+ private const string GERINDRA = "2";
+ private const string PDIP = "3";
+ private const string GOLKAR = "4";
+ private const string NASDEM = "5";
+ private const string PARTAI_BURUH = "6";
+ private const string GELORA = "7";
+ private const string PKS = "8";
+ private const string PKN = "9";
+ private const string HANURA = "10";
+ private const string GARUDA = "11";
+ private const string PAN = "12";
+ private const string PBB = "13";
+ private const string DEMOKRAT = "14";
+ private const string PSI = "15";
+ private const string PERINDO = "16";
+ private const string PPP = "17";
+ private const string PNA = "18";
+ private const string GABTHAT = "19";
+ private const string PDA = "20";
+ private const string PARTAI_ACEH = "21";
+ private const string PAS_ACEH = "22";
+ private const string PARTAI_SIRA = "23";
+ private const string PARTAI_UMMAT = "24";
+ private readonly ScopedDatabase _scopedDatabase = scopedDatabase;
+ private readonly SirekapClient _sirekapClient = sirekapClient;
+
+ public async Task LoadTableAsync(CancellationToken cancellationToken) {
+ _scopedDatabase.ExecuteNonQuery("""
+ CREATE TABLE pileg_dpr (
+ provinsi VARCHAR(50) PRIMARY KEY,
+ progress REAL,
+ pkb INTEGER,
+ gerindra INTEGER,
+ pdip INTEGER,
+ golkar INTEGER,
+ nasdem INTEGER,
+ partai_buruh INTEGER,
+ gelora INTEGER,
+ pks INTEGER,
+ pkn INTEGER,
+ hanura INTEGER,
+ garuda INTEGER,
+ pan INTEGER,
+ pbb INTEGER,
+ demokrat INTEGER,
+ psi INTEGER,
+ perindo INTEGER,
+ ppp INTEGER,
+ pna INTEGER,
+ gabthat INTEGER,
+ pda INTEGER,
+ partai_aceh INTEGER,
+ pas_aceh INTEGER,
+ partai_sira INTEGER,
+ partai_ummat INTEGER
+ )
+ """);
+
+ IList listProvinsi = await _sirekapClient.GetPronvisiListAsync(cancellationToken);
+ Dictionary provinsiByKode = listProvinsi.ToDictionary(
+ keySelector: provinsi => provinsi.Kode
+ );
+
+ ReportPilegDPR report = await _sirekapClient.GetReportPilegDPRAsync(cancellationToken);
+
+ foreach ((string kodeWilayah, ReportPilegDPR.Row row) in report.RowByKodeWilayah.OrderBy(pair => pair.Key)) {
+ _scopedDatabase.ExecuteNonQuery("""
+ INSERT INTO pileg_dpr (provinsi, progress, pkb, gerindra, pdip, golkar, nasdem, partai_buruh, gelora, pks, pkn, hanura, garuda, pan, pbb, demokrat, psi, perindo, ppp, pna, gabthat, pda, partai_aceh, pas_aceh, partai_sira, partai_ummat)
+ VALUES (@provinsi, @progress, @pkb, @gerindra, @pdip, @golkar, @nasdem, @partai_buruh, @gelora, @pks, @pkn, @hanura, @garuda, @pan, @pbb, @demokrat, @psi, @perindo, @ppp, @pna, @gabthat, @pda, @partai_aceh, @pas_aceh, @partai_sira, @partai_ummat)
+ """,
+ [
+ ( "@provinsi", provinsiByKode[kodeWilayah].Nama ),
+ ( "@progress", row.Persen ),
+ ( "@pkb", row.VotesByKodePartai!.TryGetValue(PKB, out int pkb) ? pkb : null),
+ ( "@gerindra", row.VotesByKodePartai!.TryGetValue(GERINDRA, out int gerindra) ? gerindra : null),
+ ( "@pdip", row.VotesByKodePartai!.TryGetValue(PDIP, out int pdip) ? pdip : null),
+ ( "@golkar", row.VotesByKodePartai!.TryGetValue(GOLKAR, out int golkar) ? golkar : null),
+ ( "@nasdem", row.VotesByKodePartai!.TryGetValue(NASDEM, out int nasdem) ? nasdem : null),
+ ( "@partai_buruh", row.VotesByKodePartai!.TryGetValue(PARTAI_BURUH, out int partai_buruh) ? partai_buruh : null),
+ ( "@gelora", row.VotesByKodePartai!.TryGetValue(GELORA, out int gelora) ? gelora : null),
+ ( "@pks", row.VotesByKodePartai!.TryGetValue(PKS, out int pks) ? pks : null),
+ ( "@pkn", row.VotesByKodePartai!.TryGetValue(PKN, out int pkn) ? pkn : null),
+ ( "@hanura", row.VotesByKodePartai!.TryGetValue(HANURA, out int hanura) ? hanura : null),
+ ( "@garuda", row.VotesByKodePartai!.TryGetValue(GARUDA, out int garuda) ? garuda : null),
+ ( "@pan", row.VotesByKodePartai!.TryGetValue(PAN, out int pan) ? pan : null),
+ ( "@pbb", row.VotesByKodePartai!.TryGetValue(PBB, out int pbb) ? pbb : null),
+ ( "@demokrat", row.VotesByKodePartai!.TryGetValue(DEMOKRAT, out int demokrat) ? demokrat : null),
+ ( "@psi", row.VotesByKodePartai!.TryGetValue(PSI, out int psi) ? psi : null),
+ ( "@perindo", row.VotesByKodePartai!.TryGetValue(PERINDO, out int perindo) ? perindo : null),
+ ( "@ppp", row.VotesByKodePartai!.TryGetValue(PPP, out int ppp) ? ppp : null),
+ ( "@pna", row.VotesByKodePartai!.TryGetValue(PNA, out int pna) ? pna : null),
+ ( "@gabthat", row.VotesByKodePartai!.TryGetValue(GABTHAT, out int gabthat) ? gabthat : null),
+ ( "@pda", row.VotesByKodePartai!.TryGetValue(PDA, out int pda) ? pda : null),
+ ( "@partai_aceh", row.VotesByKodePartai!.TryGetValue(PARTAI_ACEH, out int partai_aceh) ? partai_aceh : null),
+ ( "@pas_aceh", row.VotesByKodePartai!.TryGetValue(PAS_ACEH, out int pas_aceh) ? pas_aceh : null),
+ ( "@partai_sira", row.VotesByKodePartai!.TryGetValue(PARTAI_SIRA, out int partai_sira) ? partai_sira : null),
+ ( "@partai_ummat", row.VotesByKodePartai!.TryGetValue(PARTAI_UMMAT, out int partai_ummat) ? partai_ummat : null)
+ ]
+ );
+ }
+ }
+ }
+}
diff --git a/BotNet.Services/Pemilu2024/ServiceCollectionExtensions.cs b/BotNet.Services/Pemilu2024/ServiceCollectionExtensions.cs
index 7b7e91d..7cbb438 100644
--- a/BotNet.Services/Pemilu2024/ServiceCollectionExtensions.cs
+++ b/BotNet.Services/Pemilu2024/ServiceCollectionExtensions.cs
@@ -6,6 +6,7 @@ public static class ServiceCollectionExtensions {
public static IServiceCollection AddPemilu2024(this IServiceCollection services) {
services.AddTransient();
services.AddKeyedTransient("pilpres");
+ services.AddKeyedTransient("pileg_dpr");
return services;
}
}
diff --git a/BotNet.Services/Pemilu2024/SirekapClient.cs b/BotNet.Services/Pemilu2024/SirekapClient.cs
index 197bd8e..cce7410 100644
--- a/BotNet.Services/Pemilu2024/SirekapClient.cs
+++ b/BotNet.Services/Pemilu2024/SirekapClient.cs
@@ -63,5 +63,12 @@ public async Task GetReportPilpresByWilayahAsync(string kodeWilay
cancellationToken: cancellationToken
) ?? throw new JsonException("Unexpected response");
}
+
+ public async Task GetReportPilegDPRAsync(CancellationToken cancellationToken) {
+ return await _httpClient.GetFromJsonAsync(
+ requestUri: "https://sirekap-obj-data.kpu.go.id/pemilu/hhcw/pdpr.json",
+ cancellationToken: cancellationToken
+ ) ?? throw new JsonException("Unexpected response");
+ }
}
}
diff --git a/BotNet.Services/Pemilu2024/Types.cs b/BotNet.Services/Pemilu2024/Types.cs
index e88b938..c5f99e6 100644
--- a/BotNet.Services/Pemilu2024/Types.cs
+++ b/BotNet.Services/Pemilu2024/Types.cs
@@ -67,4 +67,45 @@ public sealed record Progress(
int Progres
);
}
+
+ public sealed record ReportPilegDPR(
+ [property: JsonPropertyName("ts")] string Timestamp,
+ string Psu,
+ string Mode,
+ IDictionary Chart,
+ [property: JsonPropertyName("table")] IDictionary RowByKodeWilayah,
+ ReportPilegDPR.Progress Progres
+ ) {
+ public sealed record Row {
+ public string? Psu { get; set; }
+ public decimal Persen { get; set; }
+ public bool StatusProgress { get; set; }
+
+ [JsonExtensionData]
+ public IDictionary? VotesByKodePartaiJson { get; set; }
+
+ [JsonIgnore]
+ public IDictionary? VotesByKodePartai {
+ get {
+ if (VotesByKodePartaiJson is null) {
+ return null;
+ }
+
+ Dictionary votesByKodeCalon = [];
+ foreach (KeyValuePair kvp in VotesByKodePartaiJson) {
+ if (kvp.Value.ValueKind == JsonValueKind.Number
+ && kvp.Value.TryGetInt32(out int votes)) {
+ votesByKodeCalon[kvp.Key] = votes;
+ }
+ }
+ return votesByKodeCalon;
+ }
+ }
+ }
+
+ public sealed record Progress(
+ int Total,
+ int Progres
+ );
+ }
}