diff --git a/EllieBot.sln b/EllieBot.sln
index 306d678..0d4b7ec 100644
--- a/EllieBot.sln
+++ b/EllieBot.sln
@@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Marmalade", "src\Elli
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.Voice", "src\EllieBot.Voice\EllieBot.Voice.csproj", "{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EllieBot.GrpcApiBase", "src\EllieBot.GrpcApiBase\EllieBot.GrpcApiBase.csproj", "{3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -64,6 +66,10 @@ Global
{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D93CE3C-80B4-49C7-A9A2-99988920AAEC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -76,6 +82,7 @@ Global
{F1A77F56-71B0-430E-AE46-94CDD7D43874} = {B28FB883-9688-41EB-BF5A-945F4A4EB628}
{76AC715D-12FF-4CBE-9585-A861139A2D0C} = {B28FB883-9688-41EB-BF5A-945F4A4EB628}
{1D93CE3C-80B4-49C7-A9A2-99988920AAEC} = {B28FB883-9688-41EB-BF5A-945F4A4EB628}
+ {3B71F0BF-AE6C-480C-AB88-FCE23EDC7D91} = {B28FB883-9688-41EB-BF5A-945F4A4EB628}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79F61C2C-CDBB-4361-A234-91A0B334CFE4}
diff --git a/src/EllieBot.GrpcApiBase/EllieBot.GrpcApiBase.csproj b/src/EllieBot.GrpcApiBase/EllieBot.GrpcApiBase.csproj
new file mode 100644
index 0000000..c941779
--- /dev/null
+++ b/src/EllieBot.GrpcApiBase/EllieBot.GrpcApiBase.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+ Server
+
+
+
+
\ No newline at end of file
diff --git a/src/EllieBot.GrpcApiBase/protos/econ.proto b/src/EllieBot.GrpcApiBase/protos/econ.proto
new file mode 100644
index 0000000..2d6f2f1
--- /dev/null
+++ b/src/EllieBot.GrpcApiBase/protos/econ.proto
@@ -0,0 +1,26 @@
+syntax = "proto3";
+
+option csharp_namespace = "EllieBot.GrpcApi";
+
+package econ;
+
+service GrpcEcon {
+ rpc GetEconomy(EconomyRequest) returns (EconomyReply);
+}
+
+message EconomyRequest {
+ string guildId = 1;
+}
+
+message EconomyReply {
+ uint64 totalOwned = 1;
+ uint64 byTopOnePercent = 2;
+ uint64 plantedAmount = 3;
+ uint64 ownedByTheBot = 4;
+ uint64 inTheBank = 5;
+ uint64 totalEconomy = 6;
+}
+
+message CurrencyLbRequest {
+ int32 page = 1;
+}
\ No newline at end of file
diff --git a/src/EllieBot.GrpcApiBase/protos/exprs.proto b/src/EllieBot.GrpcApiBase/protos/exprs.proto
new file mode 100644
index 0000000..2d4528e
--- /dev/null
+++ b/src/EllieBot.GrpcApiBase/protos/exprs.proto
@@ -0,0 +1,50 @@
+syntax = "proto3";
+
+option csharp_namespace = "EllieBot.GrpcApi";
+
+import "google/protobuf/empty.proto";
+
+package exprs;
+
+service GrpcExprs {
+ rpc GetExprs(GetExprsRequest) returns (GetExprsReply);
+ rpc AddExpr(AddExprRequest) returns (AddExprReply);
+ rpc DeleteExpr(DeleteExprRequest) returns (google.protobuf.Empty);
+}
+
+message DeleteExprRequest {
+ string id = 1;
+ uint64 guildId = 2;
+}
+
+message GetExprsRequest {
+ uint64 guildId = 1;
+ string query = 2;
+ int32 page = 3;
+}
+
+message GetExprsReply {
+ repeated ExprDto expressions = 1;
+ int32 totalCount = 2;
+}
+
+message ExprDto {
+ string id = 1;
+ string trigger = 2;
+ string response = 3;
+
+ bool ca = 4;
+ bool ad = 5;
+ bool dm = 6;
+ bool at = 7;
+}
+
+message AddExprRequest {
+ uint64 guildId = 1;
+ ExprDto expr = 2;
+}
+
+message AddExprReply {
+ string id = 1;
+ bool success = 2;
+}
diff --git a/src/EllieBot.GrpcApiBase/protos/greet.proto b/src/EllieBot.GrpcApiBase/protos/greet.proto
new file mode 100644
index 0000000..f2eeec4
--- /dev/null
+++ b/src/EllieBot.GrpcApiBase/protos/greet.proto
@@ -0,0 +1,57 @@
+syntax = "proto3";
+
+option csharp_namespace = "EllieBot.GrpcApi";
+
+package greet;
+
+service GrpcGreet {
+ rpc GetGreetSettings (GetGreetRequest) returns (GetGreetReply);
+ rpc UpdateGreet (UpdateGreetRequest) returns (UpdateGreetReply);
+ rpc TestGreet (TestGreetRequest) returns (TestGreetReply);
+}
+
+message GetGreetReply {
+ GrpcGreetSettings greet = 1;
+ GrpcGreetSettings greetDm = 2;
+ GrpcGreetSettings bye = 3;
+ GrpcGreetSettings boost = 4;
+}
+
+message GrpcGreetSettings {
+ optional uint64 channelId = 1;
+ string message = 2;
+ bool isEnabled = 3;
+ GrpcGreetType type = 4;
+}
+
+message GetGreetRequest {
+ uint64 guildId = 1;
+}
+
+message UpdateGreetRequest {
+ uint64 guildId = 1;
+ GrpcGreetSettings settings = 2;
+}
+
+enum GrpcGreetType {
+ Greet = 0;
+ GreetDm = 1;
+ Bye = 2;
+ Boost = 3;
+}
+
+message UpdateGreetReply {
+ bool success = 1;
+}
+
+message TestGreetRequest {
+ uint64 guildId = 1;
+ uint64 channelId = 2;
+ uint64 userId = 3;
+ GrpcGreetType type = 4;
+}
+
+message TestGreetReply {
+ bool success = 1;
+ string error = 2;
+}
diff --git a/src/EllieBot.GrpcApiBase/protos/info.proto b/src/EllieBot.GrpcApiBase/protos/info.proto
new file mode 100644
index 0000000..463f112
--- /dev/null
+++ b/src/EllieBot.GrpcApiBase/protos/info.proto
@@ -0,0 +1,52 @@
+syntax = "proto3";
+
+option csharp_namespace = "EllieBot.GrpcApi";
+
+package info;
+
+service GrpcInfo {
+ rpc GetServerInfo(ServerInfoRequest) returns (GetServerInfoReply);
+}
+
+message ServerInfoRequest {
+ uint64 guildId = 1;
+}
+
+message GetServerInfoReply {
+ uint64 id = 1;
+ string name = 2;
+ string iconUrl = 3;
+ uint64 ownerId = 4;
+ string ownerName = 5;
+ repeated RoleReply roles = 6;
+ repeated EmojiReply emojis = 7;
+ repeated string features = 8;
+ int32 textChannels = 9;
+ int32 voiceChannels = 10;
+ int32 memberCount = 11;
+ int64 createdAt = 12;
+}
+
+message RoleReply {
+ uint64 id = 1;
+ string name = 2;
+ string iconUrl = 3;
+ string color = 4;
+}
+
+message EmojiReply {
+ string name = 1;
+ string url = 2;
+ string code = 3;
+}
+
+message ChannelReply {
+ uint64 id = 1;
+ string name = 2;
+ ChannelType type = 3;
+}
+
+enum ChannelType {
+ Text = 0;
+ Voice = 1;
+}
diff --git a/src/EllieBot.GrpcApiBase/protos/other.proto b/src/EllieBot.GrpcApiBase/protos/other.proto
new file mode 100644
index 0000000..3a7aecf
--- /dev/null
+++ b/src/EllieBot.GrpcApiBase/protos/other.proto
@@ -0,0 +1,81 @@
+syntax = "proto3";
+
+option csharp_namespace = "EllieBot.GrpcApi";
+
+import "google/protobuf/empty.proto";
+import "google/protobuf/timestamp.proto";
+
+package other;
+
+service GrpcOther {
+ rpc GetTextChannels(GetTextChannelsRequest) returns (GetTextChannelsReply);
+
+ rpc GetCurrencyLb(GetLbRequest) returns (CurrencyLbReply);
+ rpc GetXpLb(GetLbRequest) returns (XpLbReply);
+ rpc GetWaifuLb(GetLbRequest) returns (WaifuLbReply);
+
+ rpc GetShardStatuses(google.protobuf.Empty) returns (GetShardStatusesReply);
+}
+
+message GetShardStatusesReply {
+ repeated ShardStatusReply shards = 1;
+}
+
+message ShardStatusReply {
+ int32 id = 1;
+ string status = 2;
+
+ int32 guildCount = 3;
+ google.protobuf.Timestamp lastUpdate = 4;
+}
+
+message GetTextChannelsRequest{
+ uint64 guildId = 1;
+}
+
+message GetTextChannelsReply {
+ repeated TextChannelReply textChannels = 1;
+}
+
+message TextChannelReply {
+ uint64 id = 1;
+ string name = 2;
+}
+
+message CurrencyLbReply {
+ repeated CurrencyLbEntryReply entries = 1;
+}
+
+message CurrencyLbEntryReply {
+ string user = 1;
+ uint64 userId = 2;
+ int64 amount = 3;
+ string avatar = 4;
+}
+
+message GetLbRequest {
+ int32 page = 1;
+ int32 perPage = 2;
+}
+
+message XpLbReply {
+ repeated XpLbEntryReply entries = 1;
+}
+
+message XpLbEntryReply {
+ string user = 1;
+ uint64 userId = 2;
+ int64 totalXp = 3;
+ int64 level = 4;
+}
+
+message WaifuLbReply {
+ repeated WaifuLbEntry entries = 1;
+}
+
+message WaifuLbEntry {
+ string user = 1;
+ string claimedBy = 2;
+ int64 value = 3;
+ bool isMutual = 4;
+}
diff --git a/src/EllieBot.GrpcApiBase/protos/warn.proto b/src/EllieBot.GrpcApiBase/protos/warn.proto
new file mode 100644
index 0000000..5dc82ee
--- /dev/null
+++ b/src/EllieBot.GrpcApiBase/protos/warn.proto
@@ -0,0 +1,83 @@
+syntax = "proto3";
+
+option csharp_namespace = "EllieBot.GrpcApi";
+
+package warn;
+
+service GrpcWarn {
+ rpc GetWarnSettings (WarnSettingsRequest) returns (WarnSettingsReply);
+ rpc AddWarnp (AddWarnpRequest) returns (AddWarnpReply);
+ rpc DeleteWarnp (DeleteWarnpRequest) returns (DeleteWarnpReply);
+ rpc GetUserWarnings(GetUserWarningsRequest) returns (GetUserWarningsReply);
+ rpc ClearWarning(ClearWarningRequest) returns (ClearWarningReply);
+ rpc SetWarnExpiry(SetWarnExpiryRequest) returns (SetWarnExpiryReply);
+}
+message WarnSettingsRequest {
+ uint64 guildId = 1;
+}
+
+message WarnPunishment {
+ int32 threshold = 1;
+ string action = 2;
+ int64 duration = 3;
+}
+
+message WarnSettingsReply {
+ repeated WarnPunishment punishments = 1;
+ int32 expiryDays = 2;
+}
+
+message AddWarnpRequest {
+ uint64 guildId = 1;
+ WarnPunishment punishment = 2;
+}
+
+message AddWarnpReply {
+ bool success = 1;
+}
+
+message DeleteWarnpRequest {
+ uint64 guildId = 1;
+ int32 warnpIndex = 2;
+}
+
+message DeleteWarnpReply {
+ bool success = 1;
+}
+
+message GetUserWarningsRequest {
+ uint64 guildId = 1;
+ uint64 user_id = 2;
+}
+
+message GetUserWarningsReply {
+ repeated Warning warnings = 1;
+}
+
+message Warning {
+ int32 id = 1;
+ string reason = 2;
+ int64 timestamp = 3;
+ int64 expiry_timestamp = 4;
+ bool cleared = 5;
+ string clearedBy = 6;
+}
+
+message ClearWarningRequest {
+ uint64 guildId = 1;
+ uint64 userId = 2;
+ optional int32 warnId = 3;
+}
+
+message ClearWarningReply {
+ bool success = 1;
+}
+
+message SetWarnExpiryRequest {
+ uint64 guildId = 1;
+ int32 expiryDays = 2;
+}
+
+message SetWarnExpiryReply {
+ bool success = 1;
+}
diff --git a/src/EllieBot/EllieBot.csproj b/src/EllieBot/EllieBot.csproj
index 61cc0a7..1ef7da7 100644
--- a/src/EllieBot/EllieBot.csproj
+++ b/src/EllieBot/EllieBot.csproj
@@ -34,13 +34,12 @@
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
+
+
@@ -103,6 +102,7 @@
+
@@ -113,9 +113,6 @@
-
- Protos\coordinator.proto
-
true
PreserveNewest
@@ -131,7 +128,10 @@
-
+
+ _common\CoordinatorProtos\coordinator.proto
+
+
diff --git a/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs b/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs
index a660b4d..783e3ab 100644
--- a/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs
+++ b/src/EllieBot/Modules/Expressions/EllieExpressionsService.cs
@@ -789,7 +789,7 @@ public sealed class EllieExpressionsService : IExecOnMessage, IReadyExecutor
if (newguildExpressions.TryGetValue(guildId, out var exprs))
{
- return (exprs.Where(x => x.Trigger.Contains(query))
+ return (exprs.Where(x => x.Trigger.Contains(query) || x.Response.Contains(query))
.Skip(page * 9)
.Take(9)
.ToArray(), exprs.Length);
diff --git a/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs b/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs
index a25bdd9..8afb886 100644
--- a/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs
+++ b/src/EllieBot/Modules/Gambling/Waifus/WaifuClaimCommands.cs
@@ -227,7 +227,7 @@ public partial class Gambling
if (page > 100)
page = 100;
- var waifus = _service.GetTopWaifusAtPage(page).ToList();
+ var waifus = await _service.GetTopWaifusAtPage(page);
if (waifus.Count == 0)
{
diff --git a/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs b/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs
index 5a00ab5..9bbce79 100644
--- a/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs
+++ b/src/EllieBot/Modules/Gambling/Waifus/WaifuService.cs
@@ -300,10 +300,10 @@ public class WaifuService : IEService, IReadyExecutor
return (oldAff, success, remaining);
}
- public IEnumerable GetTopWaifusAtPage(int page, int perPage = 9)
+ public async Task> GetTopWaifusAtPage(int page, int perPage = 9)
{
- using var uow = _db.GetDbContext();
- return uow.Set().GetTop(perPage, page * perPage);
+ await using var uow = _db.GetDbContext();
+ return await uow.Set().GetTop(perPage, page * perPage);
}
public ulong GetWaifuUserId(ulong ownerId, string name)
diff --git a/src/EllieBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs b/src/EllieBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs
index 126b760..d0615eb 100644
--- a/src/EllieBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs
+++ b/src/EllieBot/Modules/Gambling/Waifus/db/WaifuExtensions.cs
@@ -25,30 +25,30 @@ public static class WaifuExtensions
return includes(waifus).AsQueryable().FirstOrDefault(wi => wi.Waifu.UserId == userId);
}
- public static IEnumerable GetTop(this DbSet waifus, int count, int skip = 0)
+ public static async Task> GetTop(this DbSet waifus, int count, int skip = 0)
{
ArgumentOutOfRangeException.ThrowIfNegative(count);
if (count == 0)
return [];
- return waifus.Include(wi => wi.Waifu)
- .Include(wi => wi.Affinity)
- .Include(wi => wi.Claimer)
- .OrderByDescending(wi => wi.Price)
- .Skip(skip)
- .Take(count)
- .Select(x => new WaifuLbResult
- {
- Affinity = x.Affinity == null ? null : x.Affinity.Username,
- AffinityDiscrim = x.Affinity == null ? null : x.Affinity.Discriminator,
- Claimer = x.Claimer == null ? null : x.Claimer.Username,
- ClaimerDiscrim = x.Claimer == null ? null : x.Claimer.Discriminator,
- Username = x.Waifu.Username,
- Discrim = x.Waifu.Discriminator,
- Price = x.Price
- })
- .ToList();
+ return await waifus.Include(wi => wi.Waifu)
+ .Include(wi => wi.Affinity)
+ .Include(wi => wi.Claimer)
+ .OrderByDescending(wi => wi.Price)
+ .Skip(skip)
+ .Take(count)
+ .Select(x => new WaifuLbResult
+ {
+ Affinity = x.Affinity == null ? null : x.Affinity.Username,
+ AffinityDiscrim = x.Affinity == null ? null : x.Affinity.Discriminator,
+ Claimer = x.Claimer == null ? null : x.Claimer.Username,
+ ClaimerDiscrim = x.Claimer == null ? null : x.Claimer.Discriminator,
+ Username = x.Waifu.Username,
+ Discrim = x.Waifu.Discriminator,
+ Price = x.Price
+ })
+ .ToListAsyncEF();
}
public static decimal GetTotalValue(this DbSet waifus)
diff --git a/src/EllieBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs b/src/EllieBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs
index eeb3a1c..4b0af89 100644
--- a/src/EllieBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs
+++ b/src/EllieBot/Modules/Music/_common/Resolvers/YtdlYoutubeResolver.cs
@@ -43,8 +43,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver
+ "--no-check-certificate "
+ "-i "
+ "--yes-playlist "
- + "-- \"{0}\"",
- scs.Data.YtProvider != YoutubeSearcher.Ytdl);
+ + "-- \"{0}\"");
_ytdlIdOperation = new("-4 "
+ "--geo-bypass "
@@ -56,8 +55,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver
+ "--get-thumbnail "
+ "--get-duration "
+ "--no-check-certificate "
- + "-- \"{0}\"",
- scs.Data.YtProvider != YoutubeSearcher.Ytdl);
+ + "-- \"{0}\"");
_ytdlSearchOperation = new("-4 "
+ "--geo-bypass "
@@ -70,8 +68,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver
+ "--get-duration "
+ "--no-check-certificate "
+ "--default-search "
- + "\"ytsearch:\" -- \"{0}\"",
- scs.Data.YtProvider != YoutubeSearcher.Ytdl);
+ + "\"ytsearch:\" -- \"{0}\"");
}
private YtTrackData ResolveYtdlData(string ytdlOutputString)
diff --git a/src/EllieBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs b/src/EllieBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs
index fa3c634..40f802a 100644
--- a/src/EllieBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs
+++ b/src/EllieBot/Modules/Searches/Search/DefaultSearchServiceFactory.cs
@@ -7,10 +7,9 @@ public sealed class DefaultSearchServiceFactory : ISearchServiceFactory, IEServi
{
private readonly SearchesConfigService _scs;
private readonly SearxSearchService _sss;
+ private readonly YtDlpSearchService _ytdlp;
private readonly GoogleSearchService _gss;
- private readonly YtdlpYoutubeSearchService _ytdlp;
- private readonly YtdlYoutubeSearchService _ytdl;
private readonly YoutubeDataApiSearchService _ytdata;
private readonly InvidiousYtSearchService _iYtSs;
private readonly GoogleScrapeService _gscs;
@@ -20,19 +19,17 @@ public sealed class DefaultSearchServiceFactory : ISearchServiceFactory, IEServi
GoogleSearchService gss,
GoogleScrapeService gscs,
SearxSearchService sss,
- YtdlpYoutubeSearchService ytdlp,
- YtdlYoutubeSearchService ytdl,
+ YtDlpSearchService ytdlp,
YoutubeDataApiSearchService ytdata,
InvidiousYtSearchService iYtSs)
{
_scs = scs;
_sss = sss;
+ _ytdlp = ytdlp;
_gss = gss;
_gscs = gscs;
_iYtSs = iYtSs;
- _ytdlp = ytdlp;
- _ytdl = ytdl;
_ytdata = ytdata;
}
@@ -57,9 +54,8 @@ public sealed class DefaultSearchServiceFactory : ISearchServiceFactory, IEServi
=> _scs.Data.YtProvider switch
{
YoutubeSearcher.YtDataApiv3 => _ytdata,
- YoutubeSearcher.Ytdlp => _ytdlp,
- YoutubeSearcher.Ytdl => _ytdl,
YoutubeSearcher.Invidious => _iYtSs,
- _ => _ytdl
+ YoutubeSearcher.Ytdlp => _ytdlp,
+ _ => throw new ArgumentOutOfRangeException()
};
}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/SearchCommands.cs b/src/EllieBot/Modules/Searches/Search/SearchCommands.cs
index 63e0821..4bd4d2c 100644
--- a/src/EllieBot/Modules/Searches/Search/SearchCommands.cs
+++ b/src/EllieBot/Modules/Searches/Search/SearchCommands.cs
@@ -93,16 +93,12 @@ public partial class Searches
return;
}
- var embeds = new List(4);
-
-
EmbedBuilder CreateEmbed(IImageSearchResultEntry entry)
{
return _sender.CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithTitle(query)
- .WithUrl("https://google.com")
.WithImageUrl(entry.Link);
}
@@ -120,86 +116,81 @@ public partial class Searches
.WithDescription(GetText(strs.no_search_results));
var embed = CreateEmbed(item);
- embeds.Add(embed);
return embed;
})
.SendAsync();
}
- private TypedKey GetYtCacheKey(string query)
- => new($"search:youtube:{query}");
+ private TypedKey GetYtCacheKey(string query)
+ => new($"search:yt:{query}");
- private async Task AddYoutubeUrlToCacheAsync(string query, string url)
+ private async Task AddYoutubeUrlToCacheAsync(string query, string[] url)
=> await _cache.AddAsync(GetYtCacheKey(query), url, expiry: 1.Hours());
- private async Task GetYoutubeUrlFromCacheAsync(string query)
+ private async Task GetYoutubeUrlFromCacheAsync(string query)
{
var result = await _cache.GetAsync(GetYtCacheKey(query));
- if (!result.TryGetValue(out var url) || string.IsNullOrWhiteSpace(url))
+ if (!result.TryGetValue(out var urls) || urls.Length == 0)
return null;
- return new VideoInfo()
+ return urls.Map(url => new VideoInfo()
{
Url = url
- };
+ });
}
[Cmd]
- public async Task Youtube([Leftover] string? query = null)
+ public async Task Youtube([Leftover] string query)
{
- query = query?.Trim();
-
- if (string.IsNullOrWhiteSpace(query))
- {
- await Response().Error(strs.specify_search_params).SendAsync();
- return;
- }
+ query = query.Trim();
_ = ctx.Channel.TriggerTypingAsync();
- var maybeResult = await GetYoutubeUrlFromCacheAsync(query)
- ?? await _searchFactory.GetYoutubeSearchService().SearchAsync(query);
- if (maybeResult is not { } result || result is { Url: null })
+ var maybeResults = await GetYoutubeUrlFromCacheAsync(query)
+ ?? await _searchFactory.GetYoutubeSearchService().SearchAsync(query);
+
+ if (maybeResults is not { } result || result.Length == 0)
{
await Response().Error(strs.no_results).SendAsync();
return;
}
- await AddYoutubeUrlToCacheAsync(query, result.Url);
- await Response().Text(result.Url).SendAsync();
+ await AddYoutubeUrlToCacheAsync(query, result.Map(x => x.Url));
+
+ await Response().Text(result[0].Url).SendAsync();
}
-// [Cmd]
-// public async Task DuckDuckGo([Leftover] string query = null)
-// {
-// query = query?.Trim();
-// if (!await ValidateQuery(query))
-// return;
-//
-// _ = ctx.Channel.TriggerTypingAsync();
-//
-// var data = await _service.DuckDuckGoSearchAsync(query);
-// if (data is null)
-// {
-// await Response().Error(strs.no_results).SendAsync();
-// return;
-// }
-//
-// var desc = data.Results.Take(5)
-// .Select(res => $@"[**{res.Title}**]({res.Link})
-// {res.Text.TrimTo(380 - res.Title.Length - res.Link.Length)}");
-//
-// var descStr = string.Join("\n\n", desc);
-//
-// var embed = _sender.CreateEmbed()
-// .WithAuthor(ctx.User.ToString(),
-// "https://upload.wikimedia.org/wikipedia/en/9/90/The_DuckDuckGo_Duck.png")
-// .WithDescription($"{GetText(strs.search_for)} **{query}**\n\n" + descStr)
-// .WithOkColor();
-//
-// await Response().Embed(embed).SendAsync();
-// }
+ // [Cmd]
+ // public async Task DuckDuckGo([Leftover] string query = null)
+ // {
+ // query = query?.Trim();
+ // if (!await ValidateQuery(query))
+ // return;
+ //
+ // _ = ctx.Channel.TriggerTypingAsync();
+ //
+ // var data = await _service.DuckDuckGoSearchAsync(query);
+ // if (data is null)
+ // {
+ // await Response().Error(strs.no_results).SendAsync();
+ // return;
+ // }
+ //
+ // var desc = data.Results.Take(5)
+ // .Select(res => $@"[**{res.Title}**]({res.Link})
+ // {res.Text.TrimTo(380 - res.Title.Length - res.Link.Length)}");
+ //
+ // var descStr = string.Join("\n\n", desc);
+ //
+ // var embed = _sender.CreateEmbed()
+ // .WithAuthor(ctx.User.ToString(),
+ // "https://upload.wikimedia.org/wikipedia/en/9/90/The_DuckDuckGo_Duck.png")
+ // .WithDescription($"{GetText(strs.search_for)} **{query}**\n\n" + descStr)
+ // .WithOkColor();
+ //
+ // await Response().Embed(embed).SendAsync();
+ // }
}
}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs b/src/EllieBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs
index 5b9bfab..bbfca62 100644
--- a/src/EllieBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs
+++ b/src/EllieBot/Modules/Searches/Search/Youtube/IYoutubeSearchService.cs
@@ -2,5 +2,5 @@
public interface IYoutubeSearchService
{
- Task SearchAsync(string query);
+ Task SearchAsync(string query);
}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs b/src/EllieBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs
index 17bd519..99ae4c2 100644
--- a/src/EllieBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs
+++ b/src/EllieBot/Modules/Searches/Search/Youtube/InvidiousYtSearchService.cs
@@ -18,7 +18,7 @@ public sealed class InvidiousYtSearchService : IYoutubeSearchService, IEService
_rng = new();
}
- public async Task SearchAsync(string query)
+ public async Task SearchAsync(string query)
{
ArgumentNullException.ThrowIfNull(query);
@@ -35,6 +35,7 @@ public sealed class InvidiousYtSearchService : IYoutubeSearchService, IEService
var url = $"{instance}/api/v1/search"
+ $"?q={query}"
+ $"&type=video";
+
using var http = _http.CreateClient();
var res = await http.GetFromJsonAsync>(
url);
@@ -42,6 +43,6 @@ public sealed class InvidiousYtSearchService : IYoutubeSearchService, IEService
if (res is null or { Count: 0 })
return null;
- return new VideoInfo(res[0].VideoId);
+ return res.Map(r => new VideoInfo(r.VideoId));
}
}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs b/src/EllieBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs
index e8bfcd2..9c69a5e 100644
--- a/src/EllieBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs
+++ b/src/EllieBot/Modules/Searches/Search/Youtube/YoutubeDataApiSearchService.cs
@@ -9,18 +9,15 @@ public sealed class YoutubeDataApiSearchService : IYoutubeSearchService, IEServi
_gapi = gapi;
}
- public async Task SearchAsync(string query)
+ public async Task SearchAsync(string query)
{
ArgumentNullException.ThrowIfNull(query);
var results = await _gapi.GetVideoLinksByKeywordAsync(query);
- var first = results.FirstOrDefault();
- if (first is null)
+
+ if (results.Count == 0)
return null;
- return new()
- {
- Url = first
- };
+ return results.Map(r => new VideoInfo(r));
}
}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/Youtube/YtDlpSearchService.cs b/src/EllieBot/Modules/Searches/Search/Youtube/YtDlpSearchService.cs
new file mode 100644
index 0000000..b36a726
--- /dev/null
+++ b/src/EllieBot/Modules/Searches/Search/Youtube/YtDlpSearchService.cs
@@ -0,0 +1,26 @@
+namespace EllieBot.Modules.Searches.Youtube;
+
+public class YtDlpSearchService : IYoutubeSearchService, IEService
+{
+ private YtdlOperation CreateYtdlOp(int count)
+ => new YtdlOperation("-4 "
+ + "--ignore-errors --flat-playlist --skip-download --quiet "
+ + "--geo-bypass "
+ + "--encoding UTF8 "
+ + "--get-id "
+ + "--no-check-certificate "
+ + "--default-search "
+ + $"\"ytsearch{count}:\" -- \"{{0}}\"");
+
+ public async Task SearchAsync(string query)
+ {
+ var op = CreateYtdlOp(5);
+ var data = await op.GetDataAsync(query);
+ var items = data?.Split('\n');
+ if (items is null or { Length: 0 })
+ return null;
+
+ return items
+ .Map(x => new VideoInfo(x));
+ }
+}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/Youtube/YtdlYoutubeSearchService.cs b/src/EllieBot/Modules/Searches/Search/Youtube/YtdlYoutubeSearchService.cs
deleted file mode 100644
index 3ac59f8..0000000
--- a/src/EllieBot/Modules/Searches/Search/Youtube/YtdlYoutubeSearchService.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace EllieBot.Modules.Searches.Youtube;
-
-public sealed class YtdlYoutubeSearchService : YoutubedlxServiceBase, IEService
-{
- public override async Task SearchAsync(string query)
- => await InternalGetInfoAsync(query, false);
-}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/Youtube/YtdlpYoutubeSearchService.cs b/src/EllieBot/Modules/Searches/Search/Youtube/YtdlpYoutubeSearchService.cs
deleted file mode 100644
index d7e66fa..0000000
--- a/src/EllieBot/Modules/Searches/Search/Youtube/YtdlpYoutubeSearchService.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace EllieBot.Modules.Searches.Youtube;
-
-public sealed class YtdlpYoutubeSearchService : YoutubedlxServiceBase, IEService
-{
- public override async Task SearchAsync(string query)
- => await InternalGetInfoAsync(query, true);
-}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/Search/Youtube/YtdlxServiceBase.cs b/src/EllieBot/Modules/Searches/Search/Youtube/YtdlxServiceBase.cs
deleted file mode 100644
index 6239bdd..0000000
--- a/src/EllieBot/Modules/Searches/Search/Youtube/YtdlxServiceBase.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace EllieBot.Modules.Searches.Youtube;
-
-public abstract class YoutubedlxServiceBase : IYoutubeSearchService
-{
- private YtdlOperation CreateYtdlOp(bool isYtDlp)
- => new YtdlOperation("-4 "
- + "--geo-bypass "
- + "--encoding UTF8 "
- + "--get-id "
- + "--no-check-certificate "
- + "--default-search "
- + "\"ytsearch:\" -- \"{0}\"",
- isYtDlp: isYtDlp);
-
- protected async Task InternalGetInfoAsync(string query, bool isYtDlp)
- {
- var op = CreateYtdlOp(isYtDlp);
- var data = await op.GetDataAsync(query);
- var items = data?.Split('\n');
- if (items is null or { Length: 0 })
- return null;
-
- var id = items.FirstOrDefault(x => x.Length is > 5 and < 15);
- if (id is null)
- return null;
-
- return new VideoInfo()
- {
- Url = $"https://youtube.com/watch?v={id}"
- };
- }
-
- public abstract Task SearchAsync(string query);
-}
\ No newline at end of file
diff --git a/src/EllieBot/Modules/Searches/_common/Config/SearchesConfig.cs b/src/EllieBot/Modules/Searches/_common/Config/SearchesConfig.cs
index 8cb0227..fb872aa 100644
--- a/src/EllieBot/Modules/Searches/_common/Config/SearchesConfig.cs
+++ b/src/EllieBot/Modules/Searches/_common/Config/SearchesConfig.cs
@@ -77,9 +77,9 @@ public sealed class FollowedStreamConfig
public enum YoutubeSearcher
{
- YtDataApiv3,
- Ytdl,
- Ytdlp,
- Invid,
+ YtDataApiv3 = 0,
+ Ytdl = 1,
+ Ytdlp = 1,
+ Invid = 3,
Invidious = 3
}
\ No newline at end of file
diff --git a/src/EllieBot/Services/GrpcApi/ExprsSvc.cs b/src/EllieBot/Services/GrpcApi/ExprsSvc.cs
new file mode 100644
index 0000000..57c49d9
--- /dev/null
+++ b/src/EllieBot/Services/GrpcApi/ExprsSvc.cs
@@ -0,0 +1,73 @@
+using Google.Protobuf.WellKnownTypes;
+using Grpc.Core;
+using EllieBot.Db.Models;
+using EllieBot.Modules.EllieExpressions;
+
+namespace EllieBot.GrpcApi;
+
+public class ExprsSvc : GrpcExprs.GrpcExprsBase, IEService
+{
+ private readonly EllieExpressionsService _svc;
+
+ public ExprsSvc(EllieExpressionsService svc)
+ {
+ _svc = svc;
+ }
+
+ public override async Task AddExpr(AddExprRequest request, ServerCallContext context)
+ {
+ EllieExpression expr;
+ if (!string.IsNullOrWhiteSpace(request.Expr.Id))
+ {
+ expr = await _svc.EditAsync(request.GuildId,
+ new kwum(request.Expr.Id),
+ request.Expr.Response,
+ request.Expr.Ca,
+ request.Expr.Ad,
+ request.Expr.Dm);
+ }
+ else
+ {
+ expr = await _svc.AddAsync(request.GuildId,
+ request.Expr.Trigger,
+ request.Expr.Response,
+ request.Expr.Ca,
+ request.Expr.Ad,
+ request.Expr.Dm);
+ }
+
+
+ return new AddExprReply()
+ {
+ Id = new kwum(expr.Id).ToString(),
+ Success = true,
+ };
+ }
+
+ public override async Task GetExprs(GetExprsRequest request, ServerCallContext context)
+ {
+ var (exprs, totalCount) = await _svc.FindExpressionsAsync(request.GuildId, request.Query, request.Page);
+
+ var reply = new GetExprsReply();
+ reply.TotalCount = totalCount;
+ reply.Expressions.AddRange(exprs.Select(x => new ExprDto()
+ {
+ Ad = x.AutoDeleteTrigger,
+ At = x.AllowTarget,
+ Ca = x.ContainsAnywhere,
+ Dm = x.DmResponse,
+ Response = x.Response,
+ Id = new kwum(x.Id).ToString(),
+ Trigger = x.Trigger,
+ }));
+
+ return reply;
+ }
+
+ public override async Task DeleteExpr(DeleteExprRequest request, ServerCallContext context)
+ {
+ await _svc.DeleteAsync(request.GuildId, new kwum(request.Id));
+
+ return new Empty();
+ }
+}
diff --git a/src/EllieBot/Services/GrpcApi/GreetByeSvc.cs b/src/EllieBot/Services/GrpcApi/GreetByeSvc.cs
new file mode 100644
index 0000000..c0fec41
--- /dev/null
+++ b/src/EllieBot/Services/GrpcApi/GreetByeSvc.cs
@@ -0,0 +1,121 @@
+using Grpc.Core;
+using GreetType = EllieBot.Services.GreetType;
+
+namespace EllieBot.GrpcApi;
+
+public sealed class GreetByeSvc : GrpcGreet.GrpcGreetBase, IEService
+{
+ private readonly GreetService _gs;
+ private readonly DiscordSocketClient _client;
+
+ public GreetByeSvc(GreetService gs, DiscordSocketClient client)
+ {
+ _gs = gs;
+ _client = client;
+ }
+
+ public GreetSettings GetDefaultGreet(GreetType type)
+ => new GreetSettings()
+ {
+ GreetType = type
+ };
+
+ private static GrpcGreetSettings ToConf(GreetSettings? conf)
+ {
+ if (conf is null)
+ return new GrpcGreetSettings();
+
+ return new GrpcGreetSettings()
+ {
+ Message = conf.MessageText,
+ Type = (GrpcGreetType)conf.GreetType,
+ ChannelId = conf.ChannelId ?? 0,
+ IsEnabled = conf.IsEnabled
+ };
+ }
+
+ public override async Task GetGreetSettings(GetGreetRequest request, ServerCallContext context)
+ {
+ var guildId = request.GuildId;
+
+ var greetConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.Greet);
+ var byeConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.Bye);
+ var boostConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.Boost);
+ var greetDmConf = await _gs.GetGreetSettingsAsync(guildId, GreetType.GreetDm);
+ // todo timer
+
+ return new GetGreetReply()
+ {
+ Greet = ToConf(greetConf),
+ Bye = ToConf(byeConf),
+ Boost = ToConf(boostConf),
+ GreetDm = ToConf(greetDmConf)
+ };
+ }
+
+ public override async Task UpdateGreet(UpdateGreetRequest request, ServerCallContext context)
+ {
+ var gid = request.GuildId;
+ var s = request.Settings;
+ var msg = s.Message;
+
+ await _gs.SetMessage(gid, GetGreetType(s.Type), msg);
+ await _gs.SetGreet(gid, s.ChannelId, GetGreetType(s.Type), s.IsEnabled);
+
+ return new()
+ {
+ Success = true
+ };
+ }
+
+ public override Task TestGreet(TestGreetRequest request, ServerCallContext context)
+ => TestGreet(request.GuildId, request.ChannelId, request.UserId, request.Type);
+
+ private async Task TestGreet(
+ ulong guildId,
+ ulong channelId,
+ ulong userId,
+ GrpcGreetType gtDto)
+ {
+ var g = _client.GetGuild(guildId) as IGuild;
+ if (g is null)
+ {
+ return new()
+ {
+ Error = "Guild doesn't exist",
+ Success = false,
+ };
+ }
+
+ var gu = await g.GetUserAsync(userId);
+ var ch = await g.GetTextChannelAsync(channelId);
+
+ if (gu is null || ch is null)
+ return new TestGreetReply()
+ {
+ Error = "Guild or channel doesn't exist",
+ Success = false,
+ };
+
+
+ var gt = GetGreetType(gtDto);
+
+ await _gs.Test(guildId, gt, ch, gu);
+ return new TestGreetReply()
+ {
+ Success = true
+ };
+ }
+
+ private static GreetType GetGreetType(GrpcGreetType gtDto)
+ {
+ return gtDto switch
+ {
+ GrpcGreetType.Greet => GreetType.Greet,
+ GrpcGreetType.GreetDm => GreetType.GreetDm,
+ GrpcGreetType.Bye => GreetType.Bye,
+ GrpcGreetType.Boost => GreetType.Boost,
+ _ => throw new ArgumentOutOfRangeException(nameof(gtDto), gtDto, null)
+ };
+ }
+}
diff --git a/src/EllieBot/Services/GrpcApi/OtherSvc.cs b/src/EllieBot/Services/GrpcApi/OtherSvc.cs
new file mode 100644
index 0000000..f720803
--- /dev/null
+++ b/src/EllieBot/Services/GrpcApi/OtherSvc.cs
@@ -0,0 +1,123 @@
+using Google.Protobuf.WellKnownTypes;
+using Grpc.Core;
+using EllieBot.Modules.Gambling.Services;
+using EllieBot.Modules.Xp.Services;
+
+namespace EllieBot.GrpcApi;
+
+public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
+{
+ private readonly IDiscordClient _client;
+ private readonly XpService _xp;
+ private readonly ICurrencyService _cur;
+ private readonly WaifuService _waifus;
+ private readonly ICoordinator _coord;
+
+ public OtherSvc(
+ DiscordSocketClient client,
+ XpService xp,
+ ICurrencyService cur,
+ WaifuService waifus,
+ ICoordinator coord)
+ {
+ _client = client;
+ _xp = xp;
+ _cur = cur;
+ _waifus = waifus;
+ _coord = coord;
+ }
+
+ public override async Task GetTextChannels(GetTextChannelsRequest request, ServerCallContext context)
+ {
+ var g = await _client.GetGuildAsync(request.GuildId);
+ var reply = new GetTextChannelsReply();
+
+ var chs = await g.GetTextChannelsAsync();
+
+ reply.TextChannels.AddRange(chs.Select(x => new TextChannelReply()
+ {
+ Id = x.Id,
+ Name = x.Name,
+ }));
+
+ return reply;
+ }
+
+ public override async Task GetCurrencyLb(GetLbRequest request, ServerCallContext context)
+ {
+ var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);
+
+ var reply = new CurrencyLbReply();
+ var entries = users.Select(async x =>
+ {
+ var user = await _client.GetUserAsync(x.UserId, CacheMode.CacheOnly);
+ return new CurrencyLbEntryReply()
+ {
+ Amount = x.CurrencyAmount,
+ User = user.ToString(),
+ UserId = x.UserId,
+ Avatar = user.RealAvatarUrl().ToString()
+ };
+ });
+
+ reply.Entries.AddRange(await entries.WhenAll());
+
+ return reply;
+ }
+
+ public override async Task GetXpLb(GetLbRequest request, ServerCallContext context)
+ {
+ var users = await _xp.GetUserXps(request.Page, request.PerPage);
+
+ var reply = new XpLbReply();
+
+ var entries = users.Select(x =>
+ {
+ var lvl = new LevelStats(x.TotalXp);
+
+ return new XpLbEntryReply()
+ {
+ Level = lvl.Level,
+ TotalXp = x.TotalXp,
+ User = x.Username,
+ UserId = x.UserId
+ };
+ });
+
+ reply.Entries.AddRange(entries);
+
+ return reply;
+ }
+
+ public override async Task GetWaifuLb(GetLbRequest request, ServerCallContext context)
+ {
+ var waifus = await _waifus.GetTopWaifusAtPage(request.Page, request.PerPage);
+
+ var reply = new WaifuLbReply();
+ reply.Entries.AddRange(waifus.Select(x => new WaifuLbEntry()
+ {
+ ClaimedBy = x.Claimer,
+ IsMutual = x.Claimer == x.Affinity,
+ Value = x.Price,
+ User = x.Username,
+ }));
+ return reply;
+ }
+
+ public override Task GetShardStatuses(Empty request, ServerCallContext context)
+ {
+ var reply = new GetShardStatusesReply();
+
+ var shards = _coord.GetAllShardStatuses();
+
+ reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
+ {
+ Id = x.ShardId,
+ Status = x.ConnectionState.ToString(),
+ GuildCount = x.GuildCount,
+ LastUpdate = Timestamp.FromDateTime(x.LastUpdate),
+ }));
+
+ return Task.FromResult(reply);
+ }
+}
diff --git a/src/EllieBot/Services/GrpcApi/ServerInfoSvc.cs b/src/EllieBot/Services/GrpcApi/ServerInfoSvc.cs
new file mode 100644
index 0000000..2bf3089
--- /dev/null
+++ b/src/EllieBot/Services/GrpcApi/ServerInfoSvc.cs
@@ -0,0 +1,50 @@
+using EllieBot.GrpcApi;
+using Grpc.Core;
+
+namespace EllieBot.GrpcApi;
+
+public sealed class ServerInfoSvc : GrpcInfo.GrpcInfoBase, IEService
+{
+ private readonly IStatsService _stats;
+
+ public ServerInfoSvc(IStatsService stats)
+ {
+ _stats = stats;
+ }
+
+ public override Task GetServerInfo(ServerInfoRequest request, ServerCallContext context)
+ {
+ var info = _stats.GetGuildInfo(request.GuildId);
+
+ var reply = new GetServerInfoReply()
+ {
+ Id = info.Id,
+ Name = info.Name,
+ IconUrl = info.IconUrl,
+ OwnerId = info.OwnerId,
+ OwnerName = info.Owner,
+ TextChannels = info.TextChannels,
+ VoiceChannels = info.VoiceChannels,
+ MemberCount = info.MemberCount,
+ CreatedAt = info.CreatedAt.Ticks,
+ };
+
+ reply.Features.AddRange(info.Features);
+ reply.Emojis.AddRange(info.Emojis.Select(x => new EmojiReply()
+ {
+ Name = x.Name,
+ Url = x.Url,
+ Code = x.ToString()
+ }));
+
+ reply.Roles.AddRange(info.Roles.Select(x => new RoleReply()
+ {
+ Id = x.Id,
+ Name = x.Name,
+ IconUrl = x.GetIconUrl() ?? string.Empty,
+ Color = x.Color.ToString()
+ }));
+
+ return Task.FromResult(reply);
+ }
+}
diff --git a/src/EllieBot/Services/GrpcApiService.cs b/src/EllieBot/Services/GrpcApiService.cs
new file mode 100644
index 0000000..fb2c8d4
--- /dev/null
+++ b/src/EllieBot/Services/GrpcApiService.cs
@@ -0,0 +1,63 @@
+using Grpc;
+using Grpc.Core;
+using EllieBot.Common.ModuleBehaviors;
+
+namespace EllieBot.GrpcApi;
+
+public class GrpcApiService : IEService, IReadyExecutor
+{
+ private Server? _app;
+
+ private static readonly bool _isEnabled = true;
+ private readonly string _host = "localhost";
+ private readonly int _port = 5030;
+ private readonly ServerCredentials _creds = ServerCredentials.Insecure;
+
+ private readonly OtherSvc _other;
+ private readonly ExprsSvc _exprs;
+ private readonly ServerInfoSvc _info;
+ private readonly GreetByeSvc _greet;
+
+ public GrpcApiService(
+ OtherSvc other,
+ ExprsSvc exprs,
+ ServerInfoSvc info,
+ GreetByeSvc greet)
+ {
+ _other = other;
+ _exprs = exprs;
+ _info = info;
+ _greet = greet;
+ }
+
+ public async Task OnReadyAsync()
+ {
+ if (!_isEnabled)
+ return;
+
+ try
+ {
+ _app = new()
+ {
+ Services =
+ {
+ GrpcOther.BindService(_other),
+ GrpcExprs.BindService(_exprs),
+ GrpcInfo.BindService(_info),
+ GrpcGreet.BindService(_greet)
+ },
+ Ports =
+ {
+ new(_host, _port, _creds),
+ }
+ };
+ _app.Start();
+ }
+ finally
+ {
+ _app?.ShutdownAsync().GetAwaiter().GetResult();
+ }
+
+ Log.Information("Grpc Api Server started on port {Host}:{Port}", _host, _port);
+ }
+}
\ No newline at end of file
diff --git a/src/EllieBot/_common/Creds.cs b/src/EllieBot/_common/Creds.cs
index 65de7e6..30ad6b7 100644
--- a/src/EllieBot/_common/Creds.cs
+++ b/src/EllieBot/_common/Creds.cs
@@ -6,27 +6,28 @@ namespace EllieBot.Common;
public sealed class Creds : IBotCredentials
{
[Comment("""DO NOT CHANGE""")]
- public int Version { get; set; }
+ public int Version { get; set; } = 10;
[Comment("""Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/""")]
public string Token { get; set; }
[Comment("""
- List of Ids of the users who have bot owner permissions
- **DO NOT ADD PEOPLE YOU DON'T TRUST**
- """)]
+ List of Ids of the users who have bot owner permissions
+ **DO NOT ADD PEOPLE YOU DON'T TRUST**
+ """)]
public ICollection OwnerIds { get; set; }
-
- [Comment("Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")]
+
+ [Comment(
+ "Keep this on 'true' unless you're sure your bot shouldn't use privileged intents or you're waiting to be accepted")]
public bool UsePrivilegedIntents { get; set; }
[Comment("""
- The number of shards that the bot will be running on.
- Leave at 1 if you don't know what you're doing.
-
- note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
- Also, in that case you should be using EllieBot.Coordinator to start the bot, and it will correctly override this value.
- """)]
+ The number of shards that the bot will be running on.
+ Leave at 1 if you don't know what you're doing.
+
+ note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
+ Also, in that case you should be using EllieBot.Coordinator to start the bot, and it will correctly override this value.
+ """)]
public int TotalShards { get; set; }
[Comment("""
@@ -38,34 +39,34 @@ public sealed class Creds : IBotCredentials
⚠ This does not currently work and is a work in progress.
""")]
public string EllieAiToken { get; set; }
-
- [Comment(
+
+ [Comment(
"""
- Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
- Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
- Used only for Youtube Data Api (at the moment).
- """)]
+ Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
+ Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
+ Used only for Youtube Data Api (at the moment).
+ """)]
public string GoogleApiKey { get; set; }
-
- [Comment(
+
+ [Comment(
"""
- Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
- Enable SafeSearch
- Remove all Sites to Search
- Enable Search the entire web
- Copy the 'Search Engine ID' to the SearchId field
-
- Do all steps again but enable image search for the ImageSearchId
- """)]
+ Create a new custom search here https://programmablesearchengine.google.com/cse/create/new
+ Enable SafeSearch
+ Remove all Sites to Search
+ Enable Search the entire web
+ Copy the 'Search Engine ID' to the SearchId field
+
+ Do all steps again but enable image search for the ImageSearchId
+ """)]
public GoogleApiConfig Google { get; set; }
[Comment("""Settings for voting system for discordbots. Meant for use on global Ellie.""")]
public VotesSettings Votes { get; set; }
[Comment("""
- Patreon auto reward system settings.
- go to https://www.patreon.com/portal -> my clients -> create client
- """)]
+ Patreon auto reward system settings.
+ go to https://www.patreon.com/portal -> my clients -> create client
+ """)]
public PatreonSettings Patreon { get; set; }
[Comment("""Api key for sending stats to DiscordBotList.""")]
@@ -76,27 +77,27 @@ public sealed class Creds : IBotCredentials
[Comment(@"OpenAi api key.")]
public string Gpt3ApiKey { get; set; }
-
+
[Comment("""
- Which cache implementation should bot use.
- 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset.
- 'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml
- """)]
+ Which cache implementation should bot use.
+ 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset.
+ 'redis' - Uses redis (which needs to be separately downloaded and installed). The cache will persist through bot restarts. You can configure connection string in creds.yml
+ """)]
public BotCacheImplemenation BotCache { get; set; }
-
+
[Comment("""
- Redis connection string. Don't change if you don't know what you're doing.
- Only used if botCache is set to 'redis'
- """)]
+ Redis connection string. Don't change if you don't know what you're doing.
+ Only used if botCache is set to 'redis'
+ """)]
public string RedisOptions { get; set; }
[Comment("""Database options. Don't change if you don't know what you're doing. Leave null for default values""")]
public DbOptions Db { get; set; }
[Comment("""
- Address and port of the coordinator endpoint. Leave empty for default.
- Change only if you've changed the coordinator address or port.
- """)]
+ Address and port of the coordinator endpoint. Leave empty for default.
+ Change only if you've changed the coordinator address or port.
+ """)]
public string CoordinatorUrl { get; set; }
[Comment(
@@ -104,34 +105,34 @@ public sealed class Creds : IBotCredentials
public string RapidApiKey { get; set; }
[Comment("""
- https://locationiq.com api key (register and you will receive the token in the email).
- Used only for .time command.
- """)]
+ https://locationiq.com api key (register and you will receive the token in the email).
+ Used only for .time command.
+ """)]
public string LocationIqApiKey { get; set; }
[Comment("""
- https://timezonedb.com api key (register and you will receive the token in the email).
- Used only for .time command
- """)]
+ https://timezonedb.com api key (register and you will receive the token in the email).
+ Used only for .time command
+ """)]
public string TimezoneDbApiKey { get; set; }
[Comment("""
- https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
- Used for cryptocurrency related commands.
- """)]
+ https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
+ Used for cryptocurrency related commands.
+ """)]
public string CoinmarketcapApiKey { get; set; }
-
-// [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
-// Used for stocks related commands.")]
-// public string PolygonIoApiKey { get; set; }
+
+ // [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
+ // Used for stocks related commands.")]
+ // public string PolygonIoApiKey { get; set; }
[Comment("""Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api""")]
public string OsuApiKey { get; set; }
[Comment("""
- Optional Trovo client id.
- You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.
- """)]
+ Optional Trovo client id.
+ You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.
+ """)]
public string TrovoClientId { get; set; }
[Comment("""Obtain by creating an application at https://dev.twitch.tv/console/apps""")]
@@ -141,23 +142,30 @@ public sealed class Creds : IBotCredentials
public string TwitchClientSecret { get; set; }
[Comment("""
- Command and args which will be used to restart the bot.
- Only used if bot is executed directly (NOT through the coordinator)
- placeholders:
- {0} -> shard id
- {1} -> total shards
- Linux default
- cmd: dotnet
- args: "EllieBot.dll -- {0}"
- Windows default
- cmd: EllieBot.exe
- args: "{0}"
- """)]
+ Command and args which will be used to restart the bot.
+ Only used if bot is executed directly (NOT through the coordinator)
+ placeholders:
+ {0} -> shard id
+ {1} -> total shards
+ Linux default
+ cmd: dotnet
+ args: "EllieBot.dll -- {0}"
+ Windows default
+ cmd: EllieBot.exe
+ args: "{0}"
+ """)]
public RestartConfig RestartCommand { get; set; }
+
+ [Comment("""
+ Settings for the grpc api.
+ We don't provide support for this.
+ If you leave certPath empty, the api will run on http.
+ """)]
+ public ApiConfig Api { get; set; }
+
public Creds()
{
- Version = 9;
Token = string.Empty;
UsePrivilegedIntents = true;
OwnerIds = new List();
@@ -180,24 +188,26 @@ public sealed class Creds : IBotCredentials
RestartCommand = new RestartConfig();
Google = new GoogleApiConfig();
+
+ Api = new ApiConfig();
}
-
+
public class DbOptions
: IDbOptions
{
[Comment("""
- Database type. "sqlite", "mysql" and "postgresql" are supported.
- Default is "sqlite"
- """)]
+ Database type. "sqlite", "mysql" and "postgresql" are supported.
+ Default is "sqlite"
+ """)]
public string Type { get; set; }
[Comment("""
- Database connection string.
- You MUST change this if you're not using "sqlite" type.
- Default is "Data Source=data/EllieBot.db"
- Example for mysql: "Server=localhost;Port=3306;Uid=root;Pwd=my_super_secret_mysql_password;Database=ellie"
- Example for postgresql: "Server=localhost;Port=5432;User Id=postgres;Password=my_super_secret_postgres_password;Database=ellie;"
- """)]
+ Database connection string.
+ You MUST change this if you're not using "sqlite" type.
+ Default is "Data Source=data/EllieBot.db"
+ Example for mysql: "Server=localhost;Port=3306;Uid=root;Pwd=my_super_secret_mysql_password;Database=ellie"
+ Example for postgresql: "Server=localhost;Port=5432;User Id=postgres;Password=my_super_secret_postgres_password;Database=ellie;"
+ """)]
public string ConnectionString { get; set; }
}
@@ -232,29 +242,29 @@ public sealed class Creds : IBotCredentials
public sealed record VotesSettings : IVotesSettings
{
[Comment("""
- top.gg votes service url
- This is the url of your instance of the EllieBot.Votes api
- Example: https://votes.my.cool.bot.com
- """)]
+ top.gg votes service url
+ This is the url of your instance of the EllieBot.Votes api
+ Example: https://votes.my.cool.bot.com
+ """)]
public string TopggServiceUrl { get; set; }
[Comment("""
- Authorization header value sent to the TopGG service url with each request
- This should be equivalent to the TopggKey in your EllieBot.Votes api appsettings.json file
- """)]
+ Authorization header value sent to the TopGG service url with each request
+ This should be equivalent to the TopggKey in your EllieBot.Votes api appsettings.json file
+ """)]
public string TopggKey { get; set; }
[Comment("""
- discords.com votes service url
- This is the url of your instance of the EllieBot.Votes api
- Example: https://votes.my.cool.bot.com
- """)]
+ discords.com votes service url
+ This is the url of your instance of the EllieBot.Votes api
+ Example: https://votes.my.cool.bot.com
+ """)]
public string DiscordsServiceUrl { get; set; }
[Comment("""
- Authorization header value sent to the Discords service url with each request
- This should be equivalent to the DiscordsKey in your EllieBot.Votes api appsettings.json file
- """)]
+ Authorization header value sent to the Discords service url with each request
+ This should be equivalent to the DiscordsKey in your EllieBot.Votes api appsettings.json file
+ """)]
public string DiscordsKey { get; set; }
public VotesSettings()
@@ -273,13 +283,19 @@ public sealed class Creds : IBotCredentials
DiscordsKey = discordsKey;
}
}
+
+ public sealed record ApiConfig
+ {
+ public bool Enabled { get; set; } = false;
+ public string CertPath { get; set; } = string.Empty;
+ public string CertPassword { get; set; } = string.Empty;
+ public string Host { get; set; } = "localhost";
+ public int Port { get; set; } = 43120;
+ }
}
public class GoogleApiConfig : IGoogleApiConfig
{
public string SearchId { get; init; }
public string ImageSearchId { get; init; }
-}
-
-
-
+}
\ No newline at end of file
diff --git a/src/EllieBot/Services/Impl/BotCredsProvider.cs b/src/EllieBot/_common/Impl/BotCredsProvider.cs
similarity index 94%
rename from src/EllieBot/Services/Impl/BotCredsProvider.cs
rename to src/EllieBot/_common/Impl/BotCredsProvider.cs
index d28925d..5d9a83d 100644
--- a/src/EllieBot/Services/Impl/BotCredsProvider.cs
+++ b/src/EllieBot/_common/Impl/BotCredsProvider.cs
@@ -140,15 +140,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
creds.BotCache = BotCacheImplemenation.Redis;
}
- if (creds.Version <= 6)
+ if (creds.Version <= 9)
{
- creds.Version = 7;
- File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
- }
-
- if (creds.Version <= 8)
- {
- creds.Version = 9;
+ creds.Version = 10;
File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
}
}
diff --git a/src/EllieBot/Services/Impl/GoogleApiService.cs b/src/EllieBot/_common/Impl/GoogleApiService.cs
similarity index 97%
rename from src/EllieBot/Services/Impl/GoogleApiService.cs
rename to src/EllieBot/_common/Impl/GoogleApiService.cs
index 9d4a494..972a228 100644
--- a/src/EllieBot/Services/Impl/GoogleApiService.cs
+++ b/src/EllieBot/_common/Impl/GoogleApiService.cs
@@ -75,7 +75,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, IEService
return (await query.ExecuteAsync()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId).Skip(1);
}
- public async Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1)
+ public async Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1)
{
if (string.IsNullOrWhiteSpace(keywords))
throw new ArgumentNullException(nameof(keywords));
@@ -87,7 +87,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, IEService
query.Q = keywords;
query.Type = "video";
query.SafeSearch = SearchResource.ListRequest.SafeSearchEnum.Strict;
- return (await query.ExecuteAsync()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId);
+ return (await query.ExecuteAsync()).Items.Select(i => "https://www.youtube.com/watch?v=" + i.Id.VideoId).ToArray();
}
public async Task> GetVideoInfosByKeywordAsync(
diff --git a/src/EllieBot/Services/Impl/GoogleApiService_SupportedLanguages.cs b/src/EllieBot/_common/Impl/GoogleApiService_SupportedLanguages.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/GoogleApiService_SupportedLanguages.cs
rename to src/EllieBot/_common/Impl/GoogleApiService_SupportedLanguages.cs
diff --git a/src/EllieBot/Services/Impl/ImageCache.cs b/src/EllieBot/_common/Impl/ImageCache.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/ImageCache.cs
rename to src/EllieBot/_common/Impl/ImageCache.cs
diff --git a/src/EllieBot/Services/Impl/LocalDataCache.cs b/src/EllieBot/_common/Impl/LocalDataCache.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/LocalDataCache.cs
rename to src/EllieBot/_common/Impl/LocalDataCache.cs
diff --git a/src/EllieBot/Services/Impl/Localization.cs b/src/EllieBot/_common/Impl/Localization.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/Localization.cs
rename to src/EllieBot/_common/Impl/Localization.cs
diff --git a/src/EllieBot/Services/Impl/PubSub/JsonSeria.cs b/src/EllieBot/_common/Impl/PubSub/JsonSeria.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/PubSub/JsonSeria.cs
rename to src/EllieBot/_common/Impl/PubSub/JsonSeria.cs
diff --git a/src/EllieBot/Services/Impl/PubSub/RedisPubSub.cs b/src/EllieBot/_common/Impl/PubSub/RedisPubSub.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/PubSub/RedisPubSub.cs
rename to src/EllieBot/_common/Impl/PubSub/RedisPubSub.cs
diff --git a/src/EllieBot/Services/Impl/PubSub/YamlSeria.cs b/src/EllieBot/_common/Impl/PubSub/YamlSeria.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/PubSub/YamlSeria.cs
rename to src/EllieBot/_common/Impl/PubSub/YamlSeria.cs
diff --git a/src/EllieBot/Services/Impl/RedisBotCache.cs b/src/EllieBot/_common/Impl/RedisBotCache.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/RedisBotCache.cs
rename to src/EllieBot/_common/Impl/RedisBotCache.cs
diff --git a/src/EllieBot/Services/Impl/RedisBotStringsProvider.cs b/src/EllieBot/_common/Impl/RedisBotStringsProvider.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/RedisBotStringsProvider.cs
rename to src/EllieBot/_common/Impl/RedisBotStringsProvider.cs
diff --git a/src/EllieBot/Services/Impl/RemoteGrpcCoordinator.cs b/src/EllieBot/_common/Impl/RemoteGrpcCoordinator.cs
similarity index 100%
rename from src/EllieBot/Services/Impl/RemoteGrpcCoordinator.cs
rename to src/EllieBot/_common/Impl/RemoteGrpcCoordinator.cs
diff --git a/src/EllieBot/_common/Services/IGoogleApiService.cs b/src/EllieBot/_common/Services/IGoogleApiService.cs
index 856bd51..d9fa0c7 100644
--- a/src/EllieBot/_common/Services/IGoogleApiService.cs
+++ b/src/EllieBot/_common/Services/IGoogleApiService.cs
@@ -5,7 +5,7 @@ public interface IGoogleApiService
{
IReadOnlyDictionary Languages { get; }
- Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1);
+ Task> GetVideoLinksByKeywordAsync(string keywords, int count = 1);
Task> GetVideoInfosByKeywordAsync(string keywords, int count = 1);
Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1);
Task> GetRelatedVideosAsync(string id, int count = 1, string user = null);
diff --git a/src/EllieBot/_common/Services/Impl/YtdlOperation.cs b/src/EllieBot/_common/Services/Impl/YtdlOperation.cs
index 3813b80..95e8846 100644
--- a/src/EllieBot/_common/Services/Impl/YtdlOperation.cs
+++ b/src/EllieBot/_common/Services/Impl/YtdlOperation.cs
@@ -10,10 +10,9 @@ public class YtdlOperation
private readonly string _baseArgString;
private readonly bool _isYtDlp;
- public YtdlOperation(string baseArgString, bool isYtDlp = false)
+ public YtdlOperation(string baseArgString)
{
_baseArgString = baseArgString;
- _isYtDlp = isYtDlp;
}
private Process CreateProcess(string[] args)
@@ -23,7 +22,7 @@ public class YtdlOperation
{
StartInfo = new()
{
- FileName = _isYtDlp ? "yt-dlp" : "youtube-dl",
+ FileName = "yt-dlp",
Arguments = string.Format(_baseArgString, newArgs),
UseShellExecute = false,
RedirectStandardError = true,
@@ -47,18 +46,18 @@ public class YtdlOperation
var str = await process.StandardOutput.ReadToEndAsync();
var err = await process.StandardError.ReadToEndAsync();
if (!string.IsNullOrEmpty(err))
- Log.Warning("YTDL warning: {YtdlWarning}", err);
+ Log.Warning("yt-dlp warning: {YtdlWarning}", err);
return str;
}
catch (Win32Exception)
{
- Log.Error("youtube-dl is likely not installed. Please install it before running the command again");
+ Log.Error("yt-dlp is likely not installed. Please install it before running the command again");
return default;
}
catch (Exception ex)
{
- Log.Error(ex, "Exception running youtube-dl: {ErrorMessage}", ex.Message);
+ Log.Error(ex, "Exception running yt-dlp: {ErrorMessage}", ex.Message);
return default;
}
}
diff --git a/src/EllieBot/data/marmalades/marmalade.yml b/src/EllieBot/data/marmalades/marmalade.yml
index 34bfa71..4eb7a5c 100644
--- a/src/EllieBot/data/marmalades/marmalade.yml
+++ b/src/EllieBot/data/marmalades/marmalade.yml
@@ -2,3 +2,4 @@
version: 1
# List of marmalades automatically loaded at startup
loaded:
+ - ngrpc