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