diff --git a/CHANGELOG.md b/CHANGELOG.md index abeadcce9..e97d776ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o +## [4.3.13] - 20.02.2023 + +### Fixed + +- Fixed `.log userpresence` +- `.q` will now use `yt-dlp` if anything other than `ytProvider: Ytdl` is set in `data/searches.yml` +- Fixed Title links on some embeds + +## [4.3.12] - 12.02.2023 + +### Fixed + +- Fixed `.betstats` not working on european locales +- Timed `.ban` will work on users who are not in the server +- Fixed some bugs in the medusa system + ## [4.3.11] - 21.01.2023 ### Added diff --git a/docs/config-guide.md b/docs/config-guide.md index 1ca8e77f1..935dad6bb 100644 --- a/docs/config-guide.md +++ b/docs/config-guide.md @@ -24,6 +24,10 @@ The list below is not complete. Use commands above to see up-to-date list for yo `trivia.min_win_req` - Restricts a user's ability to make a trivia game with a win requirement less than the set value. `trivia.currency_reward` - Sets the amount of currency a user will win if they place first in a completed trivia game. +`hangman.currency_reward` - Sets the amount of currency a user will win if they win a game of hangman. +`chatbot` - Sets which chatbot API the bot should use, values: `gpt3`, `cleverbot`. +`gpt.model` - Sets which GPT-3 model the bot should use, values: `ada001`, `babbage001`, `curie001`, `davinci003`. +`gpt.max_tokens` - Sets the limit of tokens GPT-3 can use per call. Find out more about tokens [here](https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them). *more settings may be available in `data/games.yml` file* diff --git a/docs/guides/linux-guide.md b/docs/guides/linux-guide.md index 8df045db8..d1fdfbecd 100644 --- a/docs/guides/linux-guide.md +++ b/docs/guides/linux-guide.md @@ -20,7 +20,7 @@ It is recommended that you use **Ubuntu 20.04**, as there have been nearly no pr ##### Compatible operating systems: -- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10 +- Ubuntu: 16.04, 18.04, 20.04 - Mint: 19, 20 - Debian: 10, 11 - CentOS: 7 diff --git a/src/NadekoBot/Common/Creds.cs b/src/NadekoBot/Common/Creds.cs index 584a2f47e..8c8056694 100644 --- a/src/NadekoBot/Common/Creds.cs +++ b/src/NadekoBot/Common/Creds.cs @@ -63,6 +63,9 @@ public sealed class Creds : IBotCredentials [Comment("""Official cleverbot api key.""")] public string CleverbotApiKey { get; set; } + + [Comment(@"Official GPT-3 api key.")] + public string Gpt3ApiKey { get; set; } [Comment(""" Which cache implementation should bot use. @@ -144,7 +147,7 @@ public sealed class Creds : IBotCredentials public Creds() { - Version = 6; + Version = 7; Token = string.Empty; UsePrivilegedIntents = true; OwnerIds = new List(); @@ -154,6 +157,7 @@ public sealed class Creds : IBotCredentials Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty); BotListToken = string.Empty; CleverbotApiKey = string.Empty; + Gpt3ApiKey = string.Empty; BotCache = BotCacheImplemenation.Memory; RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password="; Db = new() diff --git a/src/NadekoBot/Common/IBotCredentials.cs b/src/NadekoBot/Common/IBotCredentials.cs index ab26b0fa4..a106deab6 100644 --- a/src/NadekoBot/Common/IBotCredentials.cs +++ b/src/NadekoBot/Common/IBotCredentials.cs @@ -14,6 +14,7 @@ public interface IBotCredentials int TotalShards { get; } Creds.PatreonSettings Patreon { get; } string CleverbotApiKey { get; } + string Gpt3ApiKey { get; } RestartConfig RestartCommand { get; } Creds.VotesSettings Votes { get; } string BotListToken { get; } diff --git a/src/NadekoBot/Common/Medusa/MedusaLoaderService.cs b/src/NadekoBot/Common/Medusa/MedusaLoaderService.cs index 2e25622ad..62c5f6811 100644 --- a/src/NadekoBot/Common/Medusa/MedusaLoaderService.cs +++ b/src/NadekoBot/Common/Medusa/MedusaLoaderService.cs @@ -472,6 +472,9 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor, pb.WithIsMultiple(paramData.IsParams) .WithIsOptional(paramData.IsOptional) .WithIsRemainder(paramData.IsLeftover); + + if (paramData.IsOptional) + pb.WithDefault(paramData.DefaultValue); }; [MethodImpl(MethodImplOptions.NoInlining)] @@ -800,6 +803,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor, var leftoverAttribute = pi.GetCustomAttribute(true); var hasDefaultValue = pi.HasDefaultValue; + var defaultValue = pi.DefaultValue; var isLeftover = leftoverAttribute != null; var isParams = pi.GetCustomAttribute() is not null; var paramType = pi.ParameterType; @@ -838,7 +842,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor, continue; } - canInject = false; + canInject = false; if (isParams) { @@ -859,7 +863,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor, throw new ArgumentException("Leftover attribute error."); } - cmdParams.Add(new ParamData(paramType, paramName, hasDefaultValue, isLeftover, isParams)); + cmdParams.Add(new ParamData(paramType, paramName, hasDefaultValue, defaultValue, isLeftover, isParams)); } diff --git a/src/NadekoBot/Common/Medusa/Models/ParamData.cs b/src/NadekoBot/Common/Medusa/Models/ParamData.cs index acb3b5008..dc38791a1 100644 --- a/src/NadekoBot/Common/Medusa/Models/ParamData.cs +++ b/src/NadekoBot/Common/Medusa/Models/ParamData.cs @@ -4,6 +4,7 @@ public sealed record ParamData( Type Type, string Name, bool IsOptional, + object? DefaultValue, bool IsLeftover, bool IsParams ); \ No newline at end of file diff --git a/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs b/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs index 5d53b2a65..00b70c611 100644 --- a/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs +++ b/src/NadekoBot/Common/TypeReaders/BotCommandTypeReader.cs @@ -46,9 +46,7 @@ public sealed class CommandOrExprTypeReader : NadekoTypeReader> ReadAsync(ICommandContext ctx, string input) { - input = input.ToUpperInvariant(); - - if (_exprs.ExpressionExists(ctx.Guild?.Id, input) || _exprs.ExpressionExists(null, input)) + if (_exprs.ExpressionExists(ctx.Guild?.Id, input)) return TypeReaderResult.FromSuccess(new CommandOrExprInfo(input, CommandOrExprInfo.Type.Custom)); var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(ctx, input); diff --git a/src/NadekoBot/Db/Extensions/NadekoExpressionExtensions.cs b/src/NadekoBot/Db/Extensions/NadekoExpressionExtensions.cs index 2ff117385..ffb25405d 100644 --- a/src/NadekoBot/Db/Extensions/NadekoExpressionExtensions.cs +++ b/src/NadekoBot/Db/Extensions/NadekoExpressionExtensions.cs @@ -12,10 +12,4 @@ public static class NadekoExpressionExtensions public static IEnumerable ForId(this DbSet exprs, ulong id) => exprs.AsNoTracking().AsQueryable().Where(x => x.GuildId == id).ToList(); - - public static NadekoExpression GetByGuildIdAndInput( - this DbSet exprs, - ulong? guildId, - string input) - => exprs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input); } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Mute/MuteService.cs b/src/NadekoBot/Modules/Administration/Mute/MuteService.cs index c4ff770d3..855dde0e9 100644 --- a/src/NadekoBot/Modules/Administration/Mute/MuteService.cs +++ b/src/NadekoBot/Modules/Administration/Mute/MuteService.cs @@ -356,24 +356,24 @@ public class MuteService : INService public async Task TimedBan( IGuild guild, - IUser user, + ulong userId, TimeSpan after, string reason, int pruneDays) { - await guild.AddBanAsync(user.Id, pruneDays, reason); + await guild.AddBanAsync(userId, pruneDays, reason); await using (var uow = _db.GetDbContext()) { var config = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.UnbanTimer)); config.UnbanTimer.Add(new() { - UserId = user.Id, + UserId = userId, UnbanAt = DateTime.UtcNow + after }); // add teh unmute timer to the database - uow.SaveChanges(); + await uow.SaveChangesAsync(); } - StartUn_Timer(guild.Id, user.Id, after, TimerType.Ban); // start the timer + StartUn_Timer(guild.Id, userId, after, TimerType.Ban); // start the timer } public async Task TimedRole( diff --git a/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs b/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs index b37d5858a..447bf2b1e 100644 --- a/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs +++ b/src/NadekoBot/Modules/Administration/ServerLog/ServerLogCommandService.cs @@ -9,9 +9,9 @@ using NadekoBot.Services.Database.Models; namespace NadekoBot.Modules.Administration; public sealed class LogCommandService : ILogCommandService, IReadyExecutor - #if !GLOBAL_NADEKO - , INService // don't load this service on global nadeko - #endif +#if !GLOBAL_NADEKO + , INService // don't load this service on global nadeko +#endif { public ConcurrentDictionary GuildLogSettings { get; } @@ -49,15 +49,15 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor _prot = prot; _tz = tz; _punishService = punishService; - + using (var uow = db.GetDbContext()) { var guildIds = client.Guilds.Select(x => x.Id).ToList(); var configs = uow.LogSettings.AsQueryable() - .AsNoTracking() - .Where(x => guildIds.Contains(x.GuildId)) - .Include(ls => ls.LogIgnores) - .ToList(); + .AsNoTracking() + .Where(x => guildIds.Contains(x.GuildId)) + .Include(ls => ls.LogIgnores) + .ToList(); GuildLogSettings = configs.ToDictionary(ls => ls.GuildId).ToConcurrent(); } @@ -73,12 +73,13 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS; _client.GuildMemberUpdated += _client_GuildUserUpdated; + _client.PresenceUpdated += _client_PresenceUpdated; _client.UserUpdated += _client_UserUpdated; _client.ChannelCreated += _client_ChannelCreated; _client.ChannelDestroyed += _client_ChannelDestroyed; _client.ChannelUpdated += _client_ChannelUpdated; _client.RoleDeleted += _client_RoleDeleted; - + _client.ThreadCreated += _client_ThreadCreated; _client.ThreadDeleted += _client_ThreadDeleted; @@ -86,10 +87,63 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor _mute.UserUnmuted += MuteCommands_UserUnmuted; _prot.OnAntiProtectionTriggered += TriggeredAntiProtection; - + _punishService.OnUserWarned += PunishServiceOnOnUserWarned; } + private async Task _client_PresenceUpdated(SocketUser user, SocketPresence? before, SocketPresence? after) + { + if (user is not SocketGuildUser gu) + return; + + if (!GuildLogSettings.TryGetValue(gu.Guild.Id, out var logSetting) + || before is null + || after is null) + return; + + ITextChannel? logChannel; + + if (!user.IsBot + && logSetting.LogUserPresenceId is not null + && (logChannel = + await TryGetLogChannel(gu.Guild, logSetting, LogType.UserPresence)) is not null) + { + if (before.Status != after.Status) + { + var str = "🎭" + + Format.Code(PrettyCurrentTime(gu.Guild)) + + GetText(logChannel.Guild, + strs.user_status_change("👤" + Format.Bold(gu.Username), + Format.Bold(after.Status.ToString()))); + PresenceUpdates.AddOrUpdate(logChannel, + new List + { + str + }, + (_, list) => + { + list.Add(str); + return list; + }); + } + else if (before.Activities.FirstOrDefault()?.Name != after.Activities.FirstOrDefault()?.Name) + { + var str = + $"👾`{PrettyCurrentTime(gu.Guild)}`👤__**{gu.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**."; + PresenceUpdates.AddOrUpdate(logChannel, + new List + { + str + }, + (_, list) => + { + list.Add(str); + return list; + }); + } + } + } + private Task _client_ThreadDeleted(Cacheable sch) { _ = Task.Run(async () => @@ -139,7 +193,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor ITextChannel? logChannel; if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ThreadCreated)) is null) return; - + var title = GetText(logChannel.Guild, strs.thread_created); await logChannel.EmbedAsync(_eb.Create() @@ -177,22 +231,24 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor var keys = PresenceUpdates.Keys.ToList(); await keys.Select(key => - { - if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages) - return Task.CompletedTask; + { + if (!((SocketGuild)key.Guild).CurrentUser.GetPermissions(key).SendMessages) + return Task.CompletedTask; - if (PresenceUpdates.TryRemove(key, out var msgs)) - { - var title = GetText(key.Guild, strs.presence_updates); - var desc = string.Join(Environment.NewLine, msgs); - return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)!); - } + if (PresenceUpdates.TryRemove(key, out var msgs)) + { + var title = GetText(key.Guild, strs.presence_updates); + var desc = string.Join(Environment.NewLine, msgs); + return key.SendConfirmAsync(_eb, title, desc.TrimTo(2048)!); + } - return Task.CompletedTask; - }) - .WhenAll(); + return Task.CompletedTask; + }) + .WhenAll(); + } + catch + { } - catch { } } } @@ -255,35 +311,35 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor logSetting.UserLeftId = logSetting.UserBannedId = logSetting.UserUnbannedId = logSetting.UserUpdatedId = logSetting.ChannelCreatedId = logSetting.ChannelDestroyedId = logSetting.ChannelUpdatedId = logSetting.LogUserPresenceId = logSetting.LogVoicePresenceId = logSetting.UserMutedId = - logSetting.LogVoicePresenceTTSId = value ? channelId : null; + logSetting.LogVoicePresenceTTSId = logSetting.ThreadCreatedId = logSetting.ThreadDeletedId + = logSetting.LogWarnsId = value ? channelId : null; await uow.SaveChangesAsync(); GuildLogSettings.AddOrUpdate(guildId, _ => logSetting, (_, _) => logSetting); } - private async Task PunishServiceOnOnUserWarned(Warning arg) { if (!GuildLogSettings.TryGetValue(arg.GuildId, out var logSetting) || logSetting.LogWarnsId is null) return; - + var g = _client.GetGuild(arg.GuildId); - + ITextChannel? logChannel; if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserWarned)) is null) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle($"⚠️ User Warned") - .WithDescription($"<@{arg.UserId}> | {arg.UserId}") - .AddField("Mod", arg.Moderator) - .AddField("Reason", string.IsNullOrWhiteSpace(arg.Reason) ? "-" : arg.Reason, true) - .WithFooter(CurrentTime(g)); + .WithOkColor() + .WithTitle($"⚠️ User Warned") + .WithDescription($"<@{arg.UserId}> | {arg.UserId}") + .AddField("Mod", arg.Moderator) + .AddField("Reason", string.IsNullOrWhiteSpace(arg.Reason) ? "-" : arg.Reason, true) + .WithFooter(CurrentTime(g)); await logChannel.EmbedAsync(embed); } - + private Task _client_UserUpdated(SocketUser before, SocketUser uAfter) { _ = Task.Run(async () => @@ -307,18 +363,18 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor if (before.Username != after.Username) { embed.WithTitle("👥 " + GetText(g, strs.username_changed)) - .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") - .AddField("Old Name", $"{before.Username}", true) - .AddField("New Name", $"{after.Username}", true) - .WithFooter(CurrentTime(g)) - .WithOkColor(); + .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") + .AddField("Old Name", $"{before.Username}", true) + .AddField("New Name", $"{after.Username}", true) + .WithFooter(CurrentTime(g)) + .WithOkColor(); } else if (before.AvatarId != after.AvatarId) { embed.WithTitle("👥" + GetText(g, strs.avatar_changed)) - .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") - .WithFooter(CurrentTime(g)) - .WithOkColor(); + .WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}") + .WithFooter(CurrentTime(g)) + .WithOkColor(); var bav = before.RealAvatarUrl(); if (bav.IsAbsoluteUri) @@ -482,10 +538,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor } var embed = _eb.Create() - .WithAuthor(mutes) - .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") - .WithFooter(CurrentTime(usr.Guild)) - .WithOkColor(); + .WithAuthor(mutes) + .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") + .WithFooter(CurrentTime(usr.Guild)) + .WithOkColor(); await logChannel.EmbedAsync(embed); } @@ -529,10 +585,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor } var embed = _eb.Create() - .WithAuthor(mutes) - .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") - .WithFooter($"{CurrentTime(usr.Guild)}") - .WithOkColor(); + .WithAuthor(mutes) + .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}") + .WithFooter($"{CurrentTime(usr.Guild)}") + .WithOkColor(); if (!string.IsNullOrWhiteSpace(reason)) embed.WithDescription(reason); @@ -583,11 +639,11 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor } var embed = _eb.Create() - .WithAuthor($"🛡 Anti-{protection}") - .WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment) - .WithDescription(string.Join("\n", users.Select(u => u.ToString()))) - .WithFooter(CurrentTime(logChannel.Guild)) - .WithOkColor(); + .WithAuthor($"🛡 Anti-{protection}") + .WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment) + .WithDescription(string.Join("\n", users.Select(u => u.ToString()))) + .WithFooter(CurrentTime(logChannel.Guild)) + .WithOkColor(); await logChannel.EmbedAsync(embed); } @@ -636,16 +692,16 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) is not null) { var embed = _eb.Create() - .WithOkColor() - .WithFooter(CurrentTime(before.Guild)) - .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}"); + .WithOkColor() + .WithFooter(CurrentTime(before.Guild)) + .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}"); if (before.Nickname != after.Nickname) { embed.WithAuthor("👥 " + GetText(logChannel.Guild, strs.nick_change)) - .AddField(GetText(logChannel.Guild, strs.old_nick), - $"{before.Nickname}#{before.Discriminator}") - .AddField(GetText(logChannel.Guild, strs.new_nick), - $"{after.Nickname}#{after.Discriminator}"); + .AddField(GetText(logChannel.Guild, strs.old_nick), + $"{before.Nickname}#{before.Discriminator}") + .AddField(GetText(logChannel.Guild, strs.new_nick), + $"{after.Nickname}#{after.Discriminator}"); await logChannel.EmbedAsync(embed); } @@ -655,7 +711,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor { var diffRoles = after.Roles.Where(r => !before.Roles.Contains(r)).Select(r => r.Name); embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_add)) - .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); await logChannel.EmbedAsync(embed); } @@ -663,59 +719,19 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor { await Task.Delay(1000); var diffRoles = before.Roles.Where(r => !after.Roles.Contains(r) && !IsRoleDeleted(r.Id)) - .Select(r => r.Name) - .ToList(); + .Select(r => r.Name) + .ToList(); if (diffRoles.Any()) { embed.WithAuthor("⚔ " + GetText(logChannel.Guild, strs.user_role_rem)) - .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); + .WithDescription(string.Join(", ", diffRoles).SanitizeMentions()); await logChannel.EmbedAsync(embed); } } } } - - if (!before.IsBot - && logSetting.LogUserPresenceId is not null - && (logChannel = - await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) is not null) - { - if (before.Status != after.Status) - { - var str = "🎭" - + Format.Code(PrettyCurrentTime(after.Guild)) - + GetText(logChannel.Guild, - strs.user_status_change("👤" + Format.Bold(after.Username), - Format.Bold(after.Status.ToString()))); - PresenceUpdates.AddOrUpdate(logChannel, - new List - { - str - }, - (_, list) => - { - list.Add(str); - return list; - }); - } - else if (before.Activities.FirstOrDefault()?.Name != after.Activities.FirstOrDefault()?.Name) - { - var str = - $"👾`{PrettyCurrentTime(after.Guild)}`👤__**{after.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**."; - PresenceUpdates.AddOrUpdate(logChannel, - new List - { - str - }, - (_, list) => - { - list.Add(str); - return list; - }); - } - } } catch { @@ -754,15 +770,15 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor if (before.Name != after.Name) { embed.WithTitle("ℹ️ " + GetText(logChannel.Guild, strs.ch_name_change)) - .WithDescription($"{after} | {after.Id}") - .AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name); + .WithDescription($"{after} | {after.Id}") + .AddField(GetText(logChannel.Guild, strs.ch_old_name), before.Name); } else if (beforeTextChannel?.Topic != afterTextChannel?.Topic) { embed.WithTitle("ℹ️ " + GetText(logChannel.Guild, strs.ch_topic_change)) - .WithDescription($"{after} | {after.Id}") - .AddField(GetText(logChannel.Guild, strs.old_topic), beforeTextChannel?.Topic ?? "-") - .AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-"); + .WithDescription($"{after} | {after.Id}") + .AddField(GetText(logChannel.Guild, strs.old_topic), beforeTextChannel?.Topic ?? "-") + .AddField(GetText(logChannel.Guild, strs.new_topic), afterTextChannel?.Topic ?? "-"); } else return; @@ -795,7 +811,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor ITextChannel? logChannel; if ((logChannel = await TryGetLogChannel(ch.Guild, logSetting, LogType.ChannelDestroyed)) is null) return; - + string title; if (ch is IVoiceChannel) title = GetText(logChannel.Guild, strs.voice_chan_destroyed); @@ -803,10 +819,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor title = GetText(logChannel.Guild, strs.text_chan_destroyed); await logChannel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle("🆕 " + title) - .WithDescription($"{ch.Name} | {ch.Id}") - .WithFooter(CurrentTime(ch.Guild))); + .WithOkColor() + .WithTitle("🆕 " + title) + .WithDescription($"{ch.Name} | {ch.Id}") + .WithFooter(CurrentTime(ch.Guild))); } catch { @@ -815,7 +831,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor }); return Task.CompletedTask; } - + private Task _client_ChannelCreated(IChannel ich) { _ = Task.Run(async () => @@ -839,10 +855,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor title = GetText(logChannel.Guild, strs.text_chan_created); await logChannel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle("🆕 " + title) - .WithDescription($"{ch.Name} | {ch.Id}") - .WithFooter(CurrentTime(ch.Guild))); + .WithOkColor() + .WithTitle("🆕 " + title) + .WithDescription($"{ch.Name} | {ch.Id}") + .WithFooter(CurrentTime(ch.Guild))); } catch (Exception) { @@ -942,11 +958,11 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserLeft)) is null) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left)) - .WithDescription(usr.ToString()) - .AddField("Id", usr.Id.ToString()) - .WithFooter(CurrentTime(guild)); + .WithOkColor() + .WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left)) + .WithDescription(usr.ToString()) + .AddField("Id", usr.Id.ToString()) + .WithFooter(CurrentTime(guild)); if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(usr.GetAvatarUrl()); @@ -975,17 +991,17 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined)) - .WithDescription($"{usr.Mention} `{usr}`") - .AddField("Id", usr.Id.ToString()) - .AddField(GetText(logChannel.Guild, strs.joined_server), - $"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}", - true) - .AddField(GetText(logChannel.Guild, strs.joined_discord), - $"{usr.CreatedAt:dd.MM.yyyy HH:mm}", - true) - .WithFooter(CurrentTime(usr.Guild)); + .WithOkColor() + .WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined)) + .WithDescription($"{usr.Mention} `{usr}`") + .AddField("Id", usr.Id.ToString()) + .AddField(GetText(logChannel.Guild, strs.joined_server), + $"{usr.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}", + true) + .AddField(GetText(logChannel.Guild, strs.joined_discord), + $"{usr.CreatedAt:dd.MM.yyyy HH:mm}", + true) + .WithFooter(CurrentTime(usr.Guild)); if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(usr.GetAvatarUrl()); @@ -1016,11 +1032,11 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) is null) return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned)) - .WithDescription(usr.ToString()!) - .AddField("Id", usr.Id.ToString()) - .WithFooter(CurrentTime(guild)); + .WithOkColor() + .WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned)) + .WithDescription(usr.ToString()!) + .AddField("Id", usr.Id.ToString()) + .WithFooter(CurrentTime(guild)); if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute)) embed.WithThumbnailUrl(usr.GetAvatarUrl()); @@ -1060,16 +1076,15 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor } catch { - } - + var embed = _eb.Create() - .WithOkColor() - .WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned)) - .WithDescription(usr.ToString()!) - .AddField("Id", usr.Id.ToString()) - .AddField("Reason", string.IsNullOrWhiteSpace(reason) ? "-" : reason) - .WithFooter(CurrentTime(guild)); + .WithOkColor() + .WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned)) + .WithDescription(usr.ToString()!) + .AddField("Id", usr.Id.ToString()) + .AddField("Reason", string.IsNullOrWhiteSpace(reason) ? "-" : reason) + .WithFooter(CurrentTime(guild)); var avatarUrl = usr.GetAvatarUrl(); @@ -1115,14 +1130,14 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor var resolvedMessage = msg.Resolve(TagHandling.FullName); var embed = _eb.Create() - .WithOkColor() - .WithTitle("🗑 " - + GetText(logChannel.Guild, strs.msg_del(((ITextChannel)msg.Channel).Name))) - .WithDescription(msg.Author.ToString()!) - .AddField(GetText(logChannel.Guild, strs.content), - string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage) - .AddField("Id", msg.Id.ToString()) - .WithFooter(CurrentTime(channel.Guild)); + .WithOkColor() + .WithTitle("🗑 " + + GetText(logChannel.Guild, strs.msg_del(((ITextChannel)msg.Channel).Name))) + .WithDescription(msg.Author.ToString()!) + .AddField(GetText(logChannel.Guild, strs.content), + string.IsNullOrWhiteSpace(resolvedMessage) ? "-" : resolvedMessage) + .AddField("Id", msg.Id.ToString()) + .WithFooter(CurrentTime(channel.Guild)); if (msg.Attachments.Any()) { embed.AddField(GetText(logChannel.Guild, strs.attachments), @@ -1175,19 +1190,19 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor return; var embed = _eb.Create() - .WithOkColor() - .WithTitle("📝 " - + GetText(logChannel.Guild, - strs.msg_update(((ITextChannel)after.Channel).Name))) - .WithDescription(after.Author.ToString()!) - .AddField(GetText(logChannel.Guild, strs.old_msg), - string.IsNullOrWhiteSpace(before.Content) - ? "-" - : before.Resolve(TagHandling.FullName)) - .AddField(GetText(logChannel.Guild, strs.new_msg), - string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(TagHandling.FullName)) - .AddField("Id", after.Id.ToString()) - .WithFooter(CurrentTime(channel.Guild)); + .WithOkColor() + .WithTitle("📝 " + + GetText(logChannel.Guild, + strs.msg_update(((ITextChannel)after.Channel).Name))) + .WithDescription(after.Author.ToString()!) + .AddField(GetText(logChannel.Guild, strs.old_msg), + string.IsNullOrWhiteSpace(before.Content) + ? "-" + : before.Resolve(TagHandling.FullName)) + .AddField(GetText(logChannel.Guild, strs.new_msg), + string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(TagHandling.FullName)) + .AddField("Id", after.Id.ToString()) + .WithFooter(CurrentTime(channel.Guild)); await logChannel.EmbedAsync(embed); } diff --git a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishCommands.cs b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishCommands.cs index 80e04d04b..e9142d8ea 100644 --- a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishCommands.cs +++ b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishCommands.cs @@ -402,12 +402,21 @@ public partial class Administration [UserPerm(GuildPerm.BanMembers)] [BotPerm(GuildPerm.BanMembers)] [Priority(1)] - public async Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null) + public Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null) + => Ban(time, user.Id, msg); + + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPerm.BanMembers)] + [BotPerm(GuildPerm.BanMembers)] + [Priority(0)] + public async Task Ban(StoopidTime time, ulong userId, [Leftover] string msg = null) { if (time.Time > TimeSpan.FromDays(49)) return; - var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, user.Id); + var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId); + if (guildUser is not null && !await CheckRoleHierarchy(guildUser)) return; @@ -429,13 +438,14 @@ public partial class Administration } } + var user = await ctx.Client.GetUserAsync(userId); var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7; - await _mute.TimedBan(ctx.Guild, user, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune); + await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune); var toSend = _eb.Create() .WithOkColor() .WithTitle("⛔️ " + GetText(strs.banned_user)) - .AddField(GetText(strs.username), user.ToString(), true) - .AddField("ID", user.Id.ToString(), true) + .AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true) + .AddField("ID", userId.ToString(), true) .AddField(GetText(strs.duration), time.Time.Humanize(3, minUnit: TimeUnit.Minute, culture: Culture), true); diff --git a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs index 1d6fb3273..7141905d9 100644 --- a/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs +++ b/src/NadekoBot/Modules/Administration/UserPunish/UserPunishService.cs @@ -157,7 +157,7 @@ public class UserPunishService : INService, IReadyExecutor if (minutes == 0) await guild.AddBanAsync(user, reason: reason, pruneDays: banPrune); else - await _mute.TimedBan(user.Guild, user, TimeSpan.FromMinutes(minutes), reason, banPrune); + await _mute.TimedBan(user.Guild, user.Id, TimeSpan.FromMinutes(minutes), reason, banPrune); break; case PunishmentAction.Softban: banPrune = await GetBanPruneAsync(user.GuildId) ?? 7; diff --git a/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs b/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs index 06d8c6269..2101e6a3c 100644 --- a/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs +++ b/src/NadekoBot/Modules/Expressions/NadekoExpressionsService.cs @@ -36,14 +36,14 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor """; private static readonly ISerializer _exportSerializer = new SerializerBuilder() - .WithEventEmitter(args - => new MultilineScalarFlowStyleEmitter(args)) - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithIndentedSequences() - .ConfigureDefaultValuesHandling(DefaultValuesHandling - .OmitDefaults) - .DisableAliases() - .Build(); + .WithEventEmitter(args + => new MultilineScalarFlowStyleEmitter(args)) + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithIndentedSequences() + .ConfigureDefaultValuesHandling(DefaultValuesHandling + .OmitDefaults) + .DisableAliases() + .Build(); public int Priority => 0; @@ -59,8 +59,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor // 1. expressions are almost never added (compared to how many times they are being looped through) // 2. only need write locks for this as we'll rebuild+replace the array on every edit // 3. there's never many of them (at most a thousand, usually < 100) - private NadekoExpression[] globalExpressions; - private ConcurrentDictionary newguildExpressions; + private NadekoExpression[] globalExpressions = Array.Empty(); + private ConcurrentDictionary newguildExpressions = new(); private readonly DbService _db; private readonly DiscordSocketClient _client; @@ -114,20 +114,20 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor { await using var uow = _db.GetDbContext(); var guildItems = await uow.Expressions.AsNoTracking() - .Where(x => allGuildIds.Contains(x.GuildId.Value)) - .ToListAsync(); + .Where(x => allGuildIds.Contains(x.GuildId.Value)) + .ToListAsync(); newguildExpressions = guildItems.GroupBy(k => k.GuildId!.Value) - .ToDictionary(g => g.Key, - g => g.Select(x => - { - x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention); - return x; - }) - .ToArray()) - .ToConcurrent(); + .ToDictionary(g => g.Key, + g => g.Select(x => + { + x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention); + return x; + }) + .ToArray()) + .ToConcurrent(); - _disabledGlobalExpressionGuilds = new (await uow.GuildConfigs + _disabledGlobalExpressionGuilds = new(await uow.GuildConfigs .Where(x => x.DisableGlobalExpressions) .Select(x => x.GuildId) .ToListAsyncLinqToDB()); @@ -135,14 +135,14 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor lock (_gexprWriteLock) { var globalItems = uow.Expressions.AsNoTracking() - .Where(x => x.GuildId == null || x.GuildId == 0) - .AsEnumerable() - .Select(x => - { - x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention); - return x; - }) - .ToArray(); + .Where(x => x.GuildId == null || x.GuildId == 0) + .AsEnumerable() + .Select(x => + { + x.Trigger = x.Trigger.Replace(MENTION_PH, _bot.Mention); + return x; + }) + .ToArray(); globalExpressions = globalItems; } @@ -169,7 +169,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor if (_disabledGlobalExpressionGuilds.Contains(channel.Guild.Id)) return null; - + var localGrs = globalExpressions; return MatchExpressions(content, localGrs); @@ -468,7 +468,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor await using (var uow = _db.GetDbContext()) { expr = uow.Expressions.GetById(id); - + if (expr is null || expr.GuildId != guildId) return (false, false); if (field == ExprField.AutoDelete) @@ -511,9 +511,25 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor public bool ExpressionExists(ulong? guildId, string input) { - using var uow = _db.GetDbContext(); - var expr = uow.Expressions.GetByGuildIdAndInput(guildId, input); - return expr is not null; + input = input.ToLowerInvariant(); + + var gexprs = globalExpressions; + foreach (var t in gexprs) + { + if (t.Trigger == input) + return true; + } + + if (guildId is ulong gid && newguildExpressions.TryGetValue(gid, out var guildExprs)) + { + foreach (var t in guildExprs) + { + if (t.Trigger == input) + return true; + } + } + + return false; } public string ExportExpressions(ulong? guildId) @@ -544,17 +560,17 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor { var trigger = entry.Key; await uow.Expressions.AddRangeAsync(entry.Value.Where(expr => !string.IsNullOrWhiteSpace(expr.Res)) - .Select(expr => new NadekoExpression - { - GuildId = guildId, - Response = expr.Res, - Reactions = expr.React?.Join("@@@"), - Trigger = trigger, - AllowTarget = expr.At, - ContainsAnywhere = expr.Ca, - DmResponse = expr.Dm, - AutoDeleteTrigger = expr.Ad - })); + .Select(expr => new NadekoExpression + { + GuildId = guildId, + Response = expr.Res, + Reactions = expr.React?.Join("@@@"), + Trigger = trigger, + AllowTarget = expr.At, + ContainsAnywhere = expr.Ca, + DmResponse = expr.Dm, + AutoDeleteTrigger = expr.Ad + })); } await uow.SaveChangesAsync(); @@ -727,12 +743,12 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor var gc = ctx.GuildConfigsForId(guildId, set => set); var toReturn = gc.DisableGlobalExpressions = !gc.DisableGlobalExpressions; await ctx.SaveChangesAsync(); - + if (toReturn) _disabledGlobalExpressionGuilds.Add(guildId); else _disabledGlobalExpressionGuilds.TryRemove(guildId); - + return toReturn; } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/ChatterBot/ChatterbotService.cs b/src/NadekoBot/Modules/Games/ChatterBot/ChatterbotService.cs index 64011e510..7900f34f1 100644 --- a/src/NadekoBot/Modules/Games/ChatterBot/ChatterbotService.cs +++ b/src/NadekoBot/Modules/Games/ChatterBot/ChatterbotService.cs @@ -1,6 +1,7 @@ #nullable disable using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db.Models; +using NadekoBot.Modules.Games.Common; using NadekoBot.Modules.Games.Common.ChatterBot; using NadekoBot.Modules.Permissions; using NadekoBot.Modules.Permissions.Common; @@ -27,6 +28,7 @@ public class ChatterBotService : IExecOnMessage private readonly IHttpClientFactory _httpFactory; private readonly IPatronageService _ps; private readonly CmdCdService _ccs; + private readonly GamesConfigService _gcs; public ChatterBotService( DiscordSocketClient client, @@ -38,7 +40,8 @@ public class ChatterBotService : IExecOnMessage IBotCredentials creds, IEmbedBuilderService eb, IPatronageService ps, - CmdCdService cmdCdService) + CmdCdService cmdCdService, + GamesConfigService gcs) { _client = client; _perms = perms; @@ -49,6 +52,7 @@ public class ChatterBotService : IExecOnMessage _httpFactory = factory; _ps = ps; _ccs = cmdCdService; + _gcs = gcs; _flKey = new FeatureLimitKey() { @@ -64,11 +68,26 @@ public class ChatterBotService : IExecOnMessage public IChatterBotSession CreateSession() { - if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey)) - return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory); + switch (_gcs.Data.ChatBot) + { + case ChatBotImplementation.Cleverbot: + if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey)) + return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory); - Log.Information("Cleverbot will not work as the api key is missing."); - return null; + Log.Information("Cleverbot will not work as the api key is missing."); + return null; + case ChatBotImplementation.Gpt3: + if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey)) + return new OfficialGpt3Session(_creds.Gpt3ApiKey, + _gcs.Data.ChatGpt.Model, + _gcs.Data.ChatGpt.MaxTokens, + _httpFactory); + + Log.Information("Gpt3 will not work as the api key is missing."); + return null; + default: + return null; + } } public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot) @@ -102,7 +121,7 @@ public class ChatterBotService : IExecOnMessage { if (guild is not SocketGuild sg) return false; - + try { var message = PrepareMessage(usrMsg, out var cbs); @@ -147,7 +166,7 @@ public class ChatterBotService : IExecOnMessage uint? monthly = quota.Quota is int mVal and >= 0 ? (uint)mVal : null; - + var maybeLimit = await _ps.TryIncrementQuotaCounterAsync(sg.OwnerId, sg.OwnerId == usrMsg.Author.Id, FeatureType.Limit, @@ -155,7 +174,7 @@ public class ChatterBotService : IExecOnMessage null, daily, monthly); - + if (maybeLimit.TryPickT1(out var ql, out var counters)) { if (ql.Quota == 0) @@ -166,7 +185,7 @@ public class ChatterBotService : IExecOnMessage "In order to use the cleverbot feature, the owner of this server should be [Patron Tier X](https://patreon.com/join/nadekobot) on patreon.", footer: "You may disable the cleverbot feature, and this message via '.cleverbot' command"); - + return true; } @@ -174,7 +193,7 @@ public class ChatterBotService : IExecOnMessage null!, $"You've reached your quota limit of **{ql.Quota}** responses {ql.QuotaPeriod.ToFullName()} for the cleverbot feature.", footer: "You may wait for the quota reset or ."); - + return true; } } @@ -185,7 +204,7 @@ public class ChatterBotService : IExecOnMessage title: null, response.SanitizeMentions(true) // , footer: counter > 0 ? counter.ToString() : null - ); + ); Log.Information(""" CleverBot Executed diff --git a/src/NadekoBot/Modules/Games/ChatterBot/_Common/ChatterBotResponse.cs b/src/NadekoBot/Modules/Games/ChatterBot/_Common/ChatterBotResponse.cs deleted file mode 100644 index 3836f34e2..000000000 --- a/src/NadekoBot/Modules/Games/ChatterBot/_Common/ChatterBotResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -#nullable disable -namespace NadekoBot.Modules.Games.Common.ChatterBot; - -public class ChatterBotResponse -{ - public string Convo_id { get; set; } - public string BotSay { get; set; } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/ChatterBot/_Common/Gpt3Response.cs b/src/NadekoBot/Modules/Games/ChatterBot/_Common/Gpt3Response.cs new file mode 100644 index 000000000..7ec0c6186 --- /dev/null +++ b/src/NadekoBot/Modules/Games/ChatterBot/_Common/Gpt3Response.cs @@ -0,0 +1,30 @@ +#nullable disable +using System.Text.Json.Serialization; + +namespace NadekoBot.Modules.Games.Common.ChatterBot; + +public class Gpt3Response +{ + [JsonPropertyName("choices")] + public Choice[] Choices { get; set; } +} + +public class Choice +{ + public string Text { get; set; } +} + +public class Gpt3ApiRequest +{ + [JsonPropertyName("model")] + public string Model { get; init; } + + [JsonPropertyName("prompt")] + public string Prompt { get; init; } + + [JsonPropertyName("temperature")] + public int Temperature { get; init; } + + [JsonPropertyName("max_tokens")] + public int MaxTokens { get; init; } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/ChatterBot/_Common/OfficialGpt3Session.cs b/src/NadekoBot/Modules/Games/ChatterBot/_Common/OfficialGpt3Session.cs new file mode 100644 index 000000000..24eb2db98 --- /dev/null +++ b/src/NadekoBot/Modules/Games/ChatterBot/_Common/OfficialGpt3Session.cs @@ -0,0 +1,69 @@ +#nullable disable +using Newtonsoft.Json; +using System.Net.Http.Json; + +namespace NadekoBot.Modules.Games.Common.ChatterBot; + +public class OfficialGpt3Session : IChatterBotSession +{ + private string Uri + => $"https://api.openai.com/v1/completions"; + + private readonly string _apiKey; + private readonly string _model; + private readonly int _maxTokens; + private readonly IHttpClientFactory _httpFactory; + + public OfficialGpt3Session( + string apiKey, + Gpt3Model model, + int maxTokens, + IHttpClientFactory factory) + { + _apiKey = apiKey; + _httpFactory = factory; + switch (model) + { + case Gpt3Model.Ada001: + _model = "text-ada-001"; + break; + case Gpt3Model.Babbage001: + _model = "text-babbage-001"; + break; + case Gpt3Model.Curie001: + _model = "text-curie-001"; + break; + case Gpt3Model.Davinci003: + _model = "text-davinci-003"; + break; + } + + _maxTokens = maxTokens; + } + + public async Task Think(string input) + { + using var http = _httpFactory.CreateClient(); + http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey); + var data = await http.PostAsJsonAsync(Uri, new Gpt3ApiRequest() + { + Model = _model, + Prompt = input, + MaxTokens = _maxTokens, + Temperature = 1, + }); + var dataString = await data.Content.ReadAsStringAsync(); + try + { + var response = JsonConvert.DeserializeObject(dataString); + + return response?.Choices[0]?.Text; + } + catch + { + Log.Warning("Unexpected GPT-3 response received: {ResponseString}", dataString); + return null; + } + } +} + diff --git a/src/NadekoBot/Modules/Games/GamesConfig.cs b/src/NadekoBot/Modules/Games/GamesConfig.cs index ac2912d73..7ac518c4e 100644 --- a/src/NadekoBot/Modules/Games/GamesConfig.cs +++ b/src/NadekoBot/Modules/Games/GamesConfig.cs @@ -8,7 +8,7 @@ namespace NadekoBot.Modules.Games.Common; public sealed partial class GamesConfig : ICloneable { [Comment("DO NOT CHANGE")] - public int Version { get; set; } + public int Version { get; set; } = 2; [Comment("Hangman related settings (.hangman command)")] public HangmanConfig Hangman { get; set; } = new() @@ -95,6 +95,27 @@ public sealed partial class GamesConfig : ICloneable Name = "Unicorn" } }; + + [Comment(@"Which chatbot API should bot use. +'cleverbot' - bot will use Cleverbot API. +'gpt3' - bot will use GPT-3 API")] + public ChatBotImplementation ChatBot { get; set; } = ChatBotImplementation.Gpt3; + + public ChatGptConfig ChatGpt { get; set; } = new(); +} + +[Cloneable] +public sealed partial class ChatGptConfig +{ + [Comment(@"Which GPT-3 Model should bot use. +'ada001' - cheapest and fastest +'babbage001' - 2nd option +'curie001' - 3rd option +'davinci003' - Most expensive, slowest")] + public Gpt3Model Model { get; set; } = Gpt3Model.Ada001; + + [Comment(@"The maximum number of tokens to use per GPT-3 API call")] + public int MaxTokens { get; set; } = 100; } [Cloneable] @@ -122,4 +143,18 @@ public sealed partial class RaceAnimal { public string Icon { get; set; } public string Name { get; set; } +} + +public enum ChatBotImplementation +{ + Cleverbot, + Gpt3 +} + +public enum Gpt3Model +{ + Ada001, + Babbage001, + Curie001, + Davinci003 } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/GamesConfigService.cs b/src/NadekoBot/Modules/Games/GamesConfigService.cs index f03c7e15a..690a92e0b 100644 --- a/src/NadekoBot/Modules/Games/GamesConfigService.cs +++ b/src/NadekoBot/Modules/Games/GamesConfigService.cs @@ -28,6 +28,20 @@ public sealed class GamesConfigService : ConfigServiceBase long.TryParse, ConfigPrinters.ToString, val => val >= 0); + + AddParsedProp("chatbot", + gs => gs.ChatBot, + ConfigParsers.InsensitiveEnum, + ConfigPrinters.ToString); + AddParsedProp("gpt.model", + gs => gs.ChatGpt.Model, + ConfigParsers.InsensitiveEnum, + ConfigPrinters.ToString); + AddParsedProp("gpt.max_tokens", + gs => gs.ChatGpt.MaxTokens, + int.TryParse, + ConfigPrinters.ToString, + val => val > 0); Migrate(); } @@ -45,5 +59,14 @@ public sealed class GamesConfigService : ConfigServiceBase }; }); } + + if (data.Version < 2) + { + ModifyConfig(c => + { + c.Version = 2; + c.ChatBot = ChatBotImplementation.Cleverbot; + }); + } } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/GamesService.cs b/src/NadekoBot/Modules/Games/GamesService.cs index 1159a253e..435d052d5 100644 --- a/src/NadekoBot/Modules/Games/GamesService.cs +++ b/src/NadekoBot/Modules/Games/GamesService.cs @@ -23,7 +23,6 @@ public class GamesService : INService, IReadyExecutor //channelId, game public ConcurrentDictionary AcrophobiaGames { get; } = new(); - public ConcurrentDictionary RunningTrivias { get; } = new(); public Dictionary TicTacToeGames { get; } = new(); public ConcurrentDictionary RunningContests { get; } = new(); public ConcurrentDictionary NunchiGames { get; } = new(); diff --git a/src/NadekoBot/Modules/Music/_Common/Resolvers/YtdlYoutubeResolver.cs b/src/NadekoBot/Modules/Music/_Common/Resolvers/YtdlYoutubeResolver.cs index b5141d6ff..f529da19b 100644 --- a/src/NadekoBot/Modules/Music/_Common/Resolvers/YtdlYoutubeResolver.cs +++ b/src/NadekoBot/Modules/Music/_Common/Resolvers/YtdlYoutubeResolver.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Text.RegularExpressions; +using NadekoBot.Modules.Searches; namespace NadekoBot.Modules.Music; @@ -27,10 +28,11 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver private readonly IGoogleApiService _google; - public YtdlYoutubeResolver(ITrackCacher trackCacher, IGoogleApiService google) + public YtdlYoutubeResolver(ITrackCacher trackCacher, IGoogleApiService google, SearchesConfigService scs) { _trackCacher = trackCacher; _google = google; + _ytdlPlaylistOperation = new("-4 " + "--geo-bypass " @@ -44,7 +46,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver + "--no-check-certificate " + "-i " + "--yes-playlist " - + "-- \"{0}\""); + + "-- \"{0}\"", scs.Data.YtProvider != YoutubeSearcher.Ytdl); _ytdlIdOperation = new("-4 " + "--geo-bypass " @@ -56,7 +58,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver + "--get-thumbnail " + "--get-duration " + "--no-check-certificate " - + "-- \"{0}\""); + + "-- \"{0}\"", scs.Data.YtProvider != YoutubeSearcher.Ytdl); _ytdlSearchOperation = new("-4 " + "--geo-bypass " @@ -69,7 +71,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver + "--get-duration " + "--no-check-certificate " + "--default-search " - + "\"ytsearch:\" -- \"{0}\""); + + "\"ytsearch:\" -- \"{0}\"", scs.Data.YtProvider != YoutubeSearcher.Ytdl); } private YtTrackData ResolveYtdlData(string ytdlOutputString) diff --git a/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs b/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs index d0e56792e..98df23b70 100644 --- a/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs +++ b/src/NadekoBot/Modules/Nsfw/ISearchImagesService.cs @@ -22,6 +22,6 @@ public interface ISearchImagesService ValueTask ToggleBlacklistTag(ulong guildId, string tag); ValueTask GetBlacklistedTags(ulong guildId); Task Butts(); - Task GetNhentaiByIdAsync(uint id); - Task GetNhentaiBySearchAsync(string search); + // Task GetNhentaiByIdAsync(uint id); + // Task GetNhentaiBySearchAsync(string search); } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Nhentai/INhentaiService.cs b/src/NadekoBot/Modules/Nsfw/Nhentai/INhentaiService.cs index 9e654df12..f2ea05260 100644 --- a/src/NadekoBot/Modules/Nsfw/Nhentai/INhentaiService.cs +++ b/src/NadekoBot/Modules/Nsfw/Nhentai/INhentaiService.cs @@ -1,9 +1,9 @@ -using NadekoBot.Modules.Searches.Common; - -namespace NadekoBot.Modules.Nsfw; - -public interface INhentaiService -{ - Task GetAsync(uint id); - Task> GetIdsBySearchAsync(string search); -} \ No newline at end of file +// using NadekoBot.Modules.Searches.Common; +// +// namespace NadekoBot.Modules.Nsfw; +// +// public interface INhentaiService +// { +// Task GetAsync(uint id); +// Task> GetIdsBySearchAsync(string search); +// } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Nhentai/NhentaiScraperService.cs b/src/NadekoBot/Modules/Nsfw/Nhentai/NhentaiScraperService.cs index ddb5f1079..35cfc708f 100644 --- a/src/NadekoBot/Modules/Nsfw/Nhentai/NhentaiScraperService.cs +++ b/src/NadekoBot/Modules/Nsfw/Nhentai/NhentaiScraperService.cs @@ -1,115 +1,115 @@ -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; -using NadekoBot.Modules.Searches.Common; - -namespace NadekoBot.Modules.Nsfw; - -public sealed class NhentaiScraperService : INhentaiService, INService -{ - private readonly IHttpClientFactory _httpFactory; - - private static readonly HtmlParser _htmlParser = new(new() - { - IsScripting = false, - IsEmbedded = false, - IsSupportingProcessingInstructions = false, - IsKeepingSourceReferences = false, - IsNotSupportingFrames = true - }); - - public NhentaiScraperService(IHttpClientFactory httpFactory) - { - _httpFactory = httpFactory; - } - - private HttpClient GetHttpClient() - { - var http = _httpFactory.CreateClient(); - http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36"); - http.DefaultRequestHeaders.Add("Cookie", "cf_clearance=I5pR71P4wJkRBFTLFjBndI.GwfKwT.Gx06uS8XNmRJo-1657214595-0-150; csrftoken=WMWRLtsQtBVQYvYkbqXKJHI9T1JwWCdd3tNhoxHn7aHLUYHAqe60XFUKAoWsJtda"); - return http; - } - - public async Task GetAsync(uint id) - { - using var http = GetHttpClient(); - try - { - var url = $"https://nhentai.net/g/{id}/"; - var strRes = await http.GetStringAsync(url); - var doc = await _htmlParser.ParseDocumentAsync(strRes); - - var title = doc.QuerySelector("#info .title")?.TextContent; - var fullTitle = doc.QuerySelector("meta[itemprop=\"name\"]")?.Attributes["content"]?.Value - ?? title; - var thumb = (doc.QuerySelector("#cover a img") as IHtmlImageElement)?.Dataset["src"]; - - var tagsElem = doc.QuerySelector("#tags"); - - var pageCount = tagsElem?.QuerySelector("a.tag[href^=\"/search/?q=pages\"] span")?.TextContent; - var likes = doc.QuerySelector(".buttons .btn-disabled.btn.tooltip span span")?.TextContent?.Trim('(', ')'); - var uploadedAt = (tagsElem?.QuerySelector(".tag-container .tags time.nobold") as IHtmlTimeElement)?.DateTime; - - var tags = tagsElem?.QuerySelectorAll(".tag-container .tags > a.tag[href^=\"/tag\"]") - .Cast() - .Select(x => new Tag() - { - Name = x.QuerySelector("span:first-child")?.TextContent, - Url = $"https://nhentai.net{x.PathName}" - }) - .ToArray(); - - if (string.IsNullOrWhiteSpace(fullTitle)) - return null; - - if (!int.TryParse(pageCount, out var pc)) - return null; - - if (!int.TryParse(likes, out var lc)) - return null; - - if (!DateTime.TryParse(uploadedAt, out var ua)) - return null; - - return new Gallery(id, - url, - fullTitle, - title, - thumb, - pc, - lc, - ua, - tags); - } - catch (HttpRequestException) - { - Log.Warning("Nhentai with id {NhentaiId} not found", id); - return null; - } - } - - public async Task> GetIdsBySearchAsync(string search) - { - using var http = GetHttpClient(); - try - { - var url = $"https://nhentai.net/search/?q={Uri.EscapeDataString(search)}&sort=popular-today"; - var strRes = await http.GetStringAsync(url); - var doc = await _htmlParser.ParseDocumentAsync(strRes); - - var elems = doc.QuerySelectorAll(".container .gallery a") - .Cast() - .Where(x => x.PathName.StartsWith("/g/")) - .Select(x => x.PathName[3..^1]) - .Select(uint.Parse) - .ToArray(); - - return elems; - } - catch (HttpRequestException) - { - Log.Warning("Nhentai search for {NhentaiSearch} failed", search); - return Array.Empty(); - } - } -} \ No newline at end of file +// using AngleSharp.Html.Dom; +// using AngleSharp.Html.Parser; +// using NadekoBot.Modules.Searches.Common; +// +// namespace NadekoBot.Modules.Nsfw; +// +// public sealed class NhentaiScraperService : INhentaiService, INService +// { +// private readonly IHttpClientFactory _httpFactory; +// +// private static readonly HtmlParser _htmlParser = new(new() +// { +// IsScripting = false, +// IsEmbedded = false, +// IsSupportingProcessingInstructions = false, +// IsKeepingSourceReferences = false, +// IsNotSupportingFrames = true +// }); +// +// public NhentaiScraperService(IHttpClientFactory httpFactory) +// { +// _httpFactory = httpFactory; +// } +// +// private HttpClient GetHttpClient() +// { +// var http = _httpFactory.CreateClient(); +// http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36"); +// http.DefaultRequestHeaders.Add("Cookie", "cf_clearance=I5pR71P4wJkRBFTLFjBndI.GwfKwT.Gx06uS8XNmRJo-1657214595-0-150; csrftoken=WMWRLtsQtBVQYvYkbqXKJHI9T1JwWCdd3tNhoxHn7aHLUYHAqe60XFUKAoWsJtda"); +// return http; +// } +// +// public async Task GetAsync(uint id) +// { +// using var http = GetHttpClient(); +// try +// { +// var url = $"https://nhentai.net/g/{id}/"; +// var strRes = await http.GetStringAsync(url); +// var doc = await _htmlParser.ParseDocumentAsync(strRes); +// +// var title = doc.QuerySelector("#info .title")?.TextContent; +// var fullTitle = doc.QuerySelector("meta[itemprop=\"name\"]")?.Attributes["content"]?.Value +// ?? title; +// var thumb = (doc.QuerySelector("#cover a img") as IHtmlImageElement)?.Dataset["src"]; +// +// var tagsElem = doc.QuerySelector("#tags"); +// +// var pageCount = tagsElem?.QuerySelector("a.tag[href^=\"/search/?q=pages\"] span")?.TextContent; +// var likes = doc.QuerySelector(".buttons .btn-disabled.btn.tooltip span span")?.TextContent?.Trim('(', ')'); +// var uploadedAt = (tagsElem?.QuerySelector(".tag-container .tags time.nobold") as IHtmlTimeElement)?.DateTime; +// +// var tags = tagsElem?.QuerySelectorAll(".tag-container .tags > a.tag[href^=\"/tag\"]") +// .Cast() +// .Select(x => new Tag() +// { +// Name = x.QuerySelector("span:first-child")?.TextContent, +// Url = $"https://nhentai.net{x.PathName}" +// }) +// .ToArray(); +// +// if (string.IsNullOrWhiteSpace(fullTitle)) +// return null; +// +// if (!int.TryParse(pageCount, out var pc)) +// return null; +// +// if (!int.TryParse(likes, out var lc)) +// return null; +// +// if (!DateTime.TryParse(uploadedAt, out var ua)) +// return null; +// +// return new Gallery(id, +// url, +// fullTitle, +// title, +// thumb, +// pc, +// lc, +// ua, +// tags); +// } +// catch (HttpRequestException) +// { +// Log.Warning("Nhentai with id {NhentaiId} not found", id); +// return null; +// } +// } +// +// public async Task> GetIdsBySearchAsync(string search) +// { +// using var http = GetHttpClient(); +// try +// { +// var url = $"https://nhentai.net/search/?q={Uri.EscapeDataString(search)}&sort=popular-today"; +// var strRes = await http.GetStringAsync(url); +// var doc = await _htmlParser.ParseDocumentAsync(strRes); +// +// var elems = doc.QuerySelectorAll(".container .gallery a") +// .Cast() +// .Where(x => x.PathName.StartsWith("/g/")) +// .Select(x => x.PathName[3..^1]) +// .Select(uint.Parse) +// .ToArray(); +// +// return elems; +// } +// catch (HttpRequestException) +// { +// Log.Warning("Nhentai search for {NhentaiSearch} failed", search); +// return Array.Empty(); +// } +// } +// } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Nsfw/Nsfw.cs b/src/NadekoBot/Modules/Nsfw/Nsfw.cs index 3a180f1cf..0dfd1d7d8 100644 --- a/src/NadekoBot/Modules/Nsfw/Nsfw.cs +++ b/src/NadekoBot/Modules/Nsfw/Nsfw.cs @@ -360,67 +360,65 @@ public partial class NSFW : NadekoModule } } - [Cmd] - [RequireContext(ContextType.Guild)] - [RequireNsfw(Group = "nsfw_or_dm")] - [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] - [Priority(1)] - public async Task Nhentai(uint id) - { - var g = await _service.GetNhentaiByIdAsync(id); - - if (g is null) - { - await ReplyErrorLocalizedAsync(strs.not_found); - return; - } - - await SendNhentaiGalleryInternalAsync(g); - } - - [Cmd] - [RequireContext(ContextType.Guild)] - [RequireNsfw(Group = "nsfw_or_dm")] - [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] - [Priority(0)] - public async Task Nhentai([Leftover] string query) - { - var g = await _service.GetNhentaiBySearchAsync(query); - - if (g is null) - { - await ReplyErrorLocalizedAsync(strs.not_found); - return; - } - - await SendNhentaiGalleryInternalAsync(g); - } - - private async Task SendNhentaiGalleryInternalAsync(Gallery g) - { - var count = 0; - var tagString = g.Tags.Shuffle() - .Select(tag => $"[{tag.Name}]({tag.Url})") - .TakeWhile(tag => (count += tag.Length) < 1000) - .Join(" "); - - var embed = _eb.Create() - .WithTitle(g.Title) - .WithDescription(g.FullTitle) - .WithImageUrl(g.Thumbnail) - .WithUrl(g.Url) - .AddField(GetText(strs.favorites), g.Likes, true) - .AddField(GetText(strs.pages), g.PageCount, true) - .AddField(GetText(strs.tags), - string.IsNullOrWhiteSpace(tagString) - ? "?" - : tagString, - true) - .WithFooter(g.UploadedAt.ToString("f")) - .WithOkColor(); - - await ctx.Channel.EmbedAsync(embed); - } + // [RequireNsfw(Group = "nsfw_or_dm")] + // [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + // [Priority(1)] + // public async Task Nhentai(uint id) + // { + // var g = await _service.GetNhentaiByIdAsync(id); + // + // if (g is null) + // { + // await ReplyErrorLocalizedAsync(strs.not_found); + // return; + // } + // + // await SendNhentaiGalleryInternalAsync(g); + // } + // + // [Cmd] + // [RequireContext(ContextType.Guild)] + // [RequireNsfw(Group = "nsfw_or_dm")] + // [RequireContext(ContextType.DM, Group = "nsfw_or_dm")] + // [Priority(0)] + // public async Task Nhentai([Leftover] string query) + // { + // var g = await _service.GetNhentaiBySearchAsync(query); + // + // if (g is null) + // { + // await ReplyErrorLocalizedAsync(strs.not_found); + // return; + // } + // + // await SendNhentaiGalleryInternalAsync(g); + // } + // + // private async Task SendNhentaiGalleryInternalAsync(Gallery g) + // { + // var count = 0; + // var tagString = g.Tags.Shuffle() + // .Select(tag => $"[{tag.Name}]({tag.Url})") + // .TakeWhile(tag => (count += tag.Length) < 1000) + // .Join(" "); + // + // var embed = _eb.Create() + // .WithTitle(g.Title) + // .WithDescription(g.FullTitle) + // .WithImageUrl(g.Thumbnail) + // .WithUrl(g.Url) + // .AddField(GetText(strs.favorites), g.Likes, true) + // .AddField(GetText(strs.pages), g.PageCount, true) + // .AddField(GetText(strs.tags), + // string.IsNullOrWhiteSpace(tagString) + // ? "?" + // : tagString, + // true) + // .WithFooter(g.UploadedAt.ToString("f")) + // .WithOkColor(); + // + // await ctx.Channel.EmbedAsync(embed); + // } private async Task InternalDapiCommand( string[] tags, diff --git a/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs b/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs index 76639250a..edf8c8851 100644 --- a/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs +++ b/src/NadekoBot/Modules/Nsfw/SearchImagesService.cs @@ -19,17 +19,15 @@ public class SearchImagesService : ISearchImagesService, INService private readonly SearchImageCacher _cache; private readonly IHttpClientFactory _httpFactory; private readonly DbService _db; - private readonly INhentaiService _nh; private readonly object _taglock = new(); public SearchImagesService( DbService db, SearchImageCacher cacher, - IHttpClientFactory httpFactory, - INhentaiService nh) + IHttpClientFactory httpFactory + ) { - _nh = nh; _db = db; _rng = new NadekoRandom(); _cache = cacher; @@ -277,6 +275,7 @@ public class SearchImagesService : ISearchImagesService, INService } } + /* #region Nhentai public Task GetNhentaiByIdAsync(uint id) @@ -294,4 +293,5 @@ public class SearchImagesService : ISearchImagesService, INService } #endregion + */ } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/JokeCommands.cs b/src/NadekoBot/Modules/Searches/JokeCommands.cs index 95736efc6..a0229726f 100644 --- a/src/NadekoBot/Modules/Searches/JokeCommands.cs +++ b/src/NadekoBot/Modules/Searches/JokeCommands.cs @@ -39,7 +39,7 @@ public partial class Searches [Cmd] public async Task MagicItem() { - if (!_service.WowJokes.Any()) + if (!_service.MagicItems.Any()) { await ReplyErrorLocalizedAsync(strs.magicitems_not_loaded); return; diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs index 91601ae75..c928c308a 100644 --- a/src/NadekoBot/Modules/Searches/Searches.cs +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable using Microsoft.Extensions.Caching.Memory; using NadekoBot.Modules.Administration.Services; using NadekoBot.Modules.Searches.Common; @@ -199,7 +199,7 @@ public partial class Searches : NadekoModule if (!await ValidateQuery(ffs)) return; - var shortenedUrl = await _google.ShortenUrl($"https://lmgtfy.com/?q={Uri.EscapeDataString(ffs)}"); + var shortenedUrl = await _google.ShortenUrl($"https://letmegooglethat.com/?q={Uri.EscapeDataString(ffs)}"); await SendConfirmAsync($"<{shortenedUrl}>"); } @@ -325,7 +325,7 @@ public partial class Searches : NadekoModule return _eb.Create() .WithOkColor() .WithUrl(item.Permalink) - .WithAuthor(item.Word) + .WithTitle(item.Word) .WithDescription(item.Definition); }, items.Length, @@ -612,4 +612,4 @@ public partial class Searches : NadekoModule [JsonProperty("result_url")] public string ResultUrl { get; set; } } -} \ No newline at end of file +} diff --git a/src/NadekoBot/Modules/Searches/_Common/Config/SearchesConfig.cs b/src/NadekoBot/Modules/Searches/_Common/Config/SearchesConfig.cs index a9b7165ff..b584276b3 100644 --- a/src/NadekoBot/Modules/Searches/_Common/Config/SearchesConfig.cs +++ b/src/NadekoBot/Modules/Searches/_Common/Config/SearchesConfig.cs @@ -36,7 +36,7 @@ public partial class SearchesConfig : ICloneable - `invidious` - recommended advanced, uses invidious api. Requires at least one invidious instance specified in the `invidiousInstances` property """)] - public YoutubeSearcher YtProvider { get; set; } = YoutubeSearcher.Ytdl; + public YoutubeSearcher YtProvider { get; set; } = YoutubeSearcher.Ytdlp; [Comment(""" Set the searx instance urls in case you want to use 'searx' for either img or web search. diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 48f13266a..93b167a36 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -45,6 +45,7 @@ + @@ -78,13 +79,14 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -93,7 +95,7 @@ - + @@ -101,6 +103,7 @@ + @@ -134,7 +137,7 @@ $(VersionPrefix).$(VersionSuffix) $(VersionPrefix) - + false diff --git a/src/NadekoBot/Services/Currency/GamblingTxTracker.cs b/src/NadekoBot/Services/Currency/GamblingTxTracker.cs index cef108c8e..3aa02ff20 100644 --- a/src/NadekoBot/Services/Currency/GamblingTxTracker.cs +++ b/src/NadekoBot/Services/Currency/GamblingTxTracker.cs @@ -104,8 +104,7 @@ public sealed class GamblingTxTracker : ITxTracker, INService, IReadyExecutor public async Task> GetAllAsync() { await using var ctx = _db.GetDbContext(); - return await ctx - .GetTable() - .ToListAsync(); + return await ctx.Set() + .ToListAsyncEF(); } } \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/BotCredsProvider.cs b/src/NadekoBot/Services/Impl/BotCredsProvider.cs index 2243666ec..a7dcd3d15 100644 --- a/src/NadekoBot/Services/Impl/BotCredsProvider.cs +++ b/src/NadekoBot/Services/Impl/BotCredsProvider.cs @@ -34,19 +34,19 @@ public sealed class BotCredsProvider : IBotCredsProvider public BotCredsProvider(int? totalShards = null, string credPath = null) { - _totalShards = totalShards; - - if (!string.IsNullOrWhiteSpace(credPath)) - { - CredsPath = credPath; - CredsExamplePath = Path.Combine(Path.GetDirectoryName(credPath), CREDS_EXAMPLE_FILE_NAME); - } - else - { - CredsPath = Path.Combine(Directory.GetCurrentDirectory(), CREDS_FILE_NAME); - CredsExamplePath = Path.Combine(Directory.GetCurrentDirectory(), CREDS_EXAMPLE_FILE_NAME); + _totalShards = totalShards; + + if (!string.IsNullOrWhiteSpace(credPath)) + { + CredsPath = credPath; + CredsExamplePath = Path.Combine(Path.GetDirectoryName(credPath), CREDS_EXAMPLE_FILE_NAME); } - + else + { + CredsPath = Path.Combine(Directory.GetCurrentDirectory(), CREDS_FILE_NAME); + CredsExamplePath = Path.Combine(Directory.GetCurrentDirectory(), CREDS_EXAMPLE_FILE_NAME); + } + try { if (!File.Exists(CredsExamplePath)) @@ -69,8 +69,8 @@ public sealed class BotCredsProvider : IBotCredsProvider _config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true) .AddEnvironmentVariables("NadekoBot_") - .Build(); - + .Build(); + _changeToken = ChangeToken.OnChange(() => _config.GetReloadToken(), Reload); Reload(); } @@ -131,14 +131,14 @@ public sealed class BotCredsProvider : IBotCredsProvider ymlData = Yaml.Serializer.Serialize(creds); File.WriteAllText(CREDS_FILE_NAME, ymlData); - } - + } + private string OldCredsJsonPath => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); private string OldCredsJsonBackupPath - => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json.bak"); - + => Path.Combine(Directory.GetCurrentDirectory(), "credentials.json.bak"); + private void MigrateCredentials() { if (File.Exists(OldCredsJsonPath)) @@ -177,15 +177,18 @@ public sealed class BotCredsProvider : IBotCredsProvider Log.Warning( "Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness"); - } - + } + if (File.Exists(CREDS_FILE_NAME)) { var creds = Yaml.Deserializer.Deserialize(File.ReadAllText(CREDS_FILE_NAME)); if (creds.Version <= 5) { - creds.Version = 6; creds.BotCache = BotCacheImplemenation.Redis; + } + if (creds.Version <= 6) + { + creds.Version = 7; File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds)); } } diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index 4427fadcd..e1371e5e5 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -7,7 +7,7 @@ namespace NadekoBot.Services; public sealed class StatsService : IStatsService, IReadyExecutor, INService { - public const string BOT_VERSION = "4.3.11"; + public const string BOT_VERSION = "4.3.13"; public string Author => "Kwoth#2452"; diff --git a/src/NadekoBot/creds_example.yml b/src/NadekoBot/creds_example.yml index 8b2b8d803..37642330d 100644 --- a/src/NadekoBot/creds_example.yml +++ b/src/NadekoBot/creds_example.yml @@ -1,5 +1,5 @@ # DO NOT CHANGE -version: 6 +version: 7 # Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/ token: '' # List of Ids of the users who have bot owner permissions @@ -56,6 +56,8 @@ patreon: botListToken: '' # Official cleverbot api key. cleverbotApiKey: '' +# Official GPT-3 api key. +gpt3ApiKey: '' # 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 diff --git a/src/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml index 3be9458ef..c09a86afa 100644 --- a/src/NadekoBot/data/aliases.yml +++ b/src/NadekoBot/data/aliases.yml @@ -664,8 +664,6 @@ avatar: - av hentai: - hentai -nhentai: - - nhentai danbooru: - danbooru derpibooru: @@ -807,6 +805,7 @@ hentaibomb: - hentaibomb cleverbot: - cleverbot + - chatgpt shorten: - shorten wikia: diff --git a/src/NadekoBot/data/games.yml b/src/NadekoBot/data/games.yml index 0261304a0..eefbc952d 100644 --- a/src/NadekoBot/data/games.yml +++ b/src/NadekoBot/data/games.yml @@ -1,5 +1,5 @@ # DO NOT CHANGE -version: 1 +version: 2 # Hangman related settings (.hangman command) hangman: # The amount of currency awarded to the winner of a hangman game @@ -8,8 +8,8 @@ hangman: trivia: # The amount of currency awarded to the winner of the trivia game. currencyReward: 0 - # Users won't be able to start trivia games which have -# a smaller win requirement than the one specified by this setting. + # Users won't be able to start trivia games which have + # a smaller win requirement than the one specified by this setting. minimumWinReq: 1 # List of responses for the .8ball command. A random one will be selected every time eightBallResponses: @@ -54,3 +54,17 @@ raceAnimals: name: Crab - icon: "🦄" name: Unicorn +# Which chatbot API should bot use. +# 'cleverbot' - bot will use Cleverbot API. +# 'gpt3' - bot will use GPT-3 API +chatBot: gpt3 + +chatGpt: + # Which GPT-3 Model should bot use. + # 'ada001' - cheapest and fastest + # 'babbage001' - 2nd option + # 'curie001' - 3rd option + # 'davinci003' - Most expensive, slowest + model: davinci003 + # The maximum number of tokens to use per GPT-3 API call + maxTokens: 100 diff --git a/src/NadekoBot/data/searches.yml b/src/NadekoBot/data/searches.yml index 4422f2318..0c2625bbb 100644 --- a/src/NadekoBot/data/searches.yml +++ b/src/NadekoBot/data/searches.yml @@ -18,7 +18,7 @@ imgSearchEngine: Google # - `ytdlp` - recommended easy, uses `yt-dlp`. Requires `yt-dlp` to be installed and it's path added to env variables # # - `invidious` - recommended advanced, uses invidious api. Requires at least one invidious instance specified in the `invidiousInstances` property -ytProvider: Ytdl +ytProvider: Ytdlp # Set the searx instance urls in case you want to use 'searx' for either img or web search. # Nadeko will use a random one for each request. # Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com` diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index 17e932f80..646530554 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -1117,11 +1117,6 @@ hentai: desc: "Shows a hentai image from a random website (gelbooru, danbooru, konachan or yandere) with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags. Only 1 tag allowed." args: - "yuri" -nhentai: - desc: "Shows basic information about a hentai with the specified id, or a valid nhentai search query." - args: - - "273426" - - "cute girl" autohentai: desc: "Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tag groups. Random group will be chosen every time the image is sent. Max 2 tags per group. 20 seconds minimum. Provide no parameters to disable." args: @@ -1377,7 +1372,7 @@ listservers: args: - "3" cleverbot: - desc: "Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Expressions starting with %bot.mention% won't work if cleverbot is enabled." + desc: "Toggles cleverbot/chatgpt session. When enabled, the bot will reply to messages starting with bot mention in the server. Expressions starting with %bot.mention% won't work if cleverbot/chatgpt is enabled." args: - "" shorten: