diff --git a/CHANGELOG.md b/CHANGELOG.md index 581080202..37ba26692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o +## [4.3.20] - 20.01.2024 + +### Fixed +- Fixed `.config searches followedStreams.maxCount` not working + +## [4.3.19] - 20.01.2024 + +### Added +- Added `followedStreams.maxCount` to `searches.yml` which lets bot owners change the default of 10 per server + +### Changed +- Improvements to GPT ChatterBot (thx alexandra) +- Add a personality prompt to tweak the way chatgpt bot behaves +- Added Chat history support to chatgpt ChatterBot +- Chatgpt token usage now correctly calculated +- More chatgpt configs in `games.yml` + ## [4.3.18] - 26.12.2023 ### Added @@ -23,7 +40,6 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog. ### Removed - `.revimg` and `.revav` as google removed reverse image search -- ## [4.3.17] - 06.09.2023 diff --git a/src/Nadeko.Bot.Common/IPermissionChecker.cs b/src/Nadeko.Bot.Common/IPermissionChecker.cs index 82d008a1a..76a21a4e1 100644 --- a/src/Nadeko.Bot.Common/IPermissionChecker.cs +++ b/src/Nadeko.Bot.Common/IPermissionChecker.cs @@ -1,13 +1,39 @@ -using OneOf; +using Nadeko.Bot.Db.Models; +using OneOf; using OneOf.Types; namespace Nadeko.Bot.Common; public interface IPermissionChecker { - Task>> CheckAsync(IGuild guild, + Task CheckPermsAsync(IGuild guild, IMessageChannel channel, IUser author, string module, string? cmd); -} \ No newline at end of file +} + +[GenerateOneOf] +public partial class PermCheckResult + : OneOfBase +{ + public bool IsAllowed + => IsT0; + + public bool IsCooldown + => IsT1; + + public bool IsGlobalBlock + => IsT2; + + public bool IsDisallowed + => IsT3; +} + +public readonly record struct PermAllowed; + +public readonly record struct PermCooldown; + +public readonly record struct PermGlobalBlock; + +public readonly record struct PermDisallowed(int PermIndex, string PermText, bool IsVerbose); \ No newline at end of file diff --git a/src/Nadeko.Bot.Common/Nadeko.Bot.Common.csproj b/src/Nadeko.Bot.Common/Nadeko.Bot.Common.csproj index a5d86798a..cbf6974ae 100644 --- a/src/Nadeko.Bot.Common/Nadeko.Bot.Common.csproj +++ b/src/Nadeko.Bot.Common/Nadeko.Bot.Common.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -13,10 +13,11 @@ - - + + + - + @@ -25,7 +26,7 @@ True - + diff --git a/src/Nadeko.Bot.Common/Services/CommandHandler.cs b/src/Nadeko.Bot.Common/Services/CommandHandler.cs index aa629c612..61e24b164 100644 --- a/src/Nadeko.Bot.Common/Services/CommandHandler.cs +++ b/src/Nadeko.Bot.Common/Services/CommandHandler.cs @@ -60,7 +60,6 @@ public class CommandHandler : INService, IReadyExecutor, ICommandHandler public async Task OnReadyAsync() { - Log.Information("Command handler runnning on ready"); // clear users on short cooldown every GLOBAL_COMMANDS_COOLDOWN miliseconds using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(GLOBAL_COMMANDS_COOLDOWN)); while (await timer.WaitForNextTickAsync()) diff --git a/src/Nadeko.Bot.Common/Services/Impl/StatsService.cs b/src/Nadeko.Bot.Common/Services/Impl/StatsService.cs index 493508609..c78def0b8 100644 --- a/src/Nadeko.Bot.Common/Services/Impl/StatsService.cs +++ b/src/Nadeko.Bot.Common/Services/Impl/StatsService.cs @@ -7,7 +7,7 @@ namespace NadekoBot.Services; public sealed class StatsService : IStatsService, IReadyExecutor, INService { - public const string BOT_VERSION = "5.0.0-alpha1"; + public const string BOT_VERSION = "5.0.0-alpha2"; public string Author => "Kwoth#2452"; diff --git a/src/Nadeko.Bot.Common/_Extensions/Rgba32Extensions.cs b/src/Nadeko.Bot.Common/_Extensions/Rgba32Extensions.cs index d4dfe2bcf..bbb80de7b 100644 --- a/src/Nadeko.Bot.Common/_Extensions/Rgba32Extensions.cs +++ b/src/Nadeko.Bot.Common/_Extensions/Rgba32Extensions.cs @@ -23,7 +23,7 @@ public static class Rgba32Extensions { using var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count); var offset = xOffset; - imgFrame.Mutate(x => x.DrawImage(frame, new(offset, 0), new GraphicsOptions())); + imgFrame.Mutate(x => x.DrawImage(frame, new Point(offset, 0), new GraphicsOptions())); xOffset += imgArray[i].Bounds().Width; } } diff --git a/src/Nadeko.Bot.Db/Models/DiscordUser.cs b/src/Nadeko.Bot.Db/Models/DiscordUser.cs index b8969d00f..d64fb38ec 100644 --- a/src/Nadeko.Bot.Db/Models/DiscordUser.cs +++ b/src/Nadeko.Bot.Db/Models/DiscordUser.cs @@ -28,5 +28,5 @@ public class DiscordUser : DbEntity => UserId.GetHashCode(); public override string ToString() - => Username + "#" + Discriminator; + => Discriminator == "0000" ? Username : Username + "#" + Discriminator; } \ No newline at end of file diff --git a/src/Nadeko.Bot.Db/Models/anti/AntiRaidSetting.cs b/src/Nadeko.Bot.Db/Models/anti/AntiRaidSetting.cs index 9a26577a2..2524ac533 100644 --- a/src/Nadeko.Bot.Db/Models/anti/AntiRaidSetting.cs +++ b/src/Nadeko.Bot.Db/Models/anti/AntiRaidSetting.cs @@ -2,7 +2,6 @@ namespace Nadeko.Bot.Db.Models; -// todo db required, nullable? public class AntiRaidSetting : DbEntity { public int GuildConfigId { get; set; } diff --git a/src/Nadeko.Bot.Db/Nadeko.Bot.Db.csproj b/src/Nadeko.Bot.Db/Nadeko.Bot.Db.csproj index 54bfe5472..261251f53 100644 --- a/src/Nadeko.Bot.Db/Nadeko.Bot.Db.csproj +++ b/src/Nadeko.Bot.Db/Nadeko.Bot.Db.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable NadekoBot.Db @@ -9,19 +9,19 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + diff --git a/src/Nadeko.Bot.Modules.Administration/Nadeko.Bot.Modules.Administration.csproj b/src/Nadeko.Bot.Modules.Administration/Nadeko.Bot.Modules.Administration.csproj index 13300e68c..bc36938bb 100644 --- a/src/Nadeko.Bot.Modules.Administration/Nadeko.Bot.Modules.Administration.csproj +++ b/src/Nadeko.Bot.Modules.Administration/Nadeko.Bot.Modules.Administration.csproj @@ -1,14 +1,14 @@ - net7.0 + net8.0 enable enable - + diff --git a/src/Nadeko.Bot.Modules.Expresssions/Nadeko.Bot.Modules.Expresssions.csproj b/src/Nadeko.Bot.Modules.Expresssions/Nadeko.Bot.Modules.Expresssions.csproj index 8ae72bc33..70bbd60a7 100644 --- a/src/Nadeko.Bot.Modules.Expresssions/Nadeko.Bot.Modules.Expresssions.csproj +++ b/src/Nadeko.Bot.Modules.Expresssions/Nadeko.Bot.Modules.Expresssions.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable NadekoBot.Modules.Expresssions @@ -15,7 +15,7 @@ - + diff --git a/src/Nadeko.Bot.Modules.Expresssions/NadekoExpressionsService.cs b/src/Nadeko.Bot.Modules.Expresssions/NadekoExpressionsService.cs index f8f7768d6..5cabc76fc 100644 --- a/src/Nadeko.Bot.Modules.Expresssions/NadekoExpressionsService.cs +++ b/src/Nadeko.Bot.Modules.Expresssions/NadekoExpressionsService.cs @@ -20,20 +20,20 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor private const string PREPEND_EXPORT = """ - # Keys are triggers, Each key has a LIST of expressions in the following format: - # - res: Response string - # id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.) - # react: - # - - # at: Whether expression allows targets (see .h .exprat) - # ca: Whether expression expects trigger anywhere (see .h .exprca) - # dm: Whether expression DMs the response (see .h .exprdm) - # ad: Whether expression automatically deletes triggering message (see .h .exprad) - + # Keys are triggers, Each key has a LIST of expressions in the following format: + # - res: Response string + # id: Alphanumeric id used for commands related to the expression. (Note, when using .exprsimport, a new id will be generated.) + # react: + # - + # at: Whether expression allows targets (see .h .exprat) + # ca: Whether expression expects trigger anywhere (see .h .exprca) + # dm: Whether expression DMs the response (see .h .exprdm) + # ad: Whether expression automatically deletes triggering message (see .h .exprad) - """; + + """; private static readonly ISerializer _exportSerializer = new SerializerBuilder() .WithEventEmitter(args @@ -63,7 +63,9 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor private ConcurrentDictionary newguildExpressions = new(); private readonly DbService _db; + private readonly DiscordSocketClient _client; + // private readonly PermissionService _perms; // private readonly GlobalPermissionService _gperm; // private readonly CmdCdService _cmdCds; @@ -238,46 +240,40 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor if (expr is null || expr.Response == "-") return false; - var result = await _permChecker.CheckAsync( - guild, - msg.Channel, - msg.Author, - "ACTUALEXPRESSIONS", - expr.Trigger - ); - - if (!result.IsT0) - return false; - - // todo print error etc - try { - // if (guild is SocketGuild sg) - // { - // var pc = _perms.GetCacheFor(guild.Id); - // if (!pc.Permissions.CheckPermissions(msg, expr.Trigger, "ACTUALEXPRESSIONS", out var index)) - // { - // if (pc.Verbose) - // { - // var permissionMessage = _strings.GetText(strs.perm_prevent(index + 1, - // Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg))), - // sg.Id); - // - // try - // { - // await msg.Channel.SendErrorAsync(_eb, permissionMessage); - // } - // catch - // { - // } - // - // Log.Information("{PermissionMessage}", permissionMessage); - // } - // - // return true; - // } - // } + if (guild is SocketGuild sg) + { + var result = await _permChecker.CheckPermsAsync( + guild, + msg.Channel, + msg.Author, + "ACTUALEXPRESSIONS", + expr.Trigger + ); + + if (!result.IsAllowed) + { + if (result.TryPickT3(out var disallowed, out _)) + { + var permissionMessage = _strings.GetText(strs.perm_prevent(disallowed.PermIndex + 1, + Format.Bold(disallowed.PermText)), + sg.Id); + + try + { + await msg.Channel.SendErrorAsync(_eb, permissionMessage); + } + catch + { + } + + Log.Information("{PermissionMessage}", permissionMessage); + } + + return true; + } + } var sentMsg = await expr.Send(msg, _client, false); @@ -556,7 +552,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor foreach (var entry in data) { var trigger = entry.Key; - await uow.Set().AddRangeAsync(entry.Value.Where(expr => !string.IsNullOrWhiteSpace(expr.Res)) + await uow.Set().AddRangeAsync(entry.Value + .Where(expr => !string.IsNullOrWhiteSpace(expr.Res)) .Select(expr => new NadekoExpression { GuildId = guildId, diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/ChatterbotService.cs b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/ChatterbotService.cs index 5910d8ce8..ac32a69bb 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/ChatterbotService.cs +++ b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/ChatterbotService.cs @@ -76,8 +76,12 @@ public class ChatterBotService : IExecOnMessage case ChatBotImplementation.Gpt3: if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey)) return new OfficialGpt3Session(_creds.Gpt3ApiKey, - _gcs.Data.ChatGpt.Model, + _gcs.Data.ChatGpt.ModelName, + _gcs.Data.ChatGpt.ChatHistory, _gcs.Data.ChatGpt.MaxTokens, + _gcs.Data.ChatGpt.MinTokens, + _gcs.Data.ChatGpt.PersonalityPrompt, + _client.CurrentUser.Username, _httpFactory); Log.Information("Gpt3 will not work as the api key is missing."); @@ -125,17 +129,14 @@ public class ChatterBotService : IExecOnMessage if (message is null || cbs is null) return false; - var res = await _perms.CheckAsync(sg, + var res = await _perms.CheckPermsAsync(sg, usrMsg.Channel, usrMsg.Author, "games", CleverBotResponseStr.CLEVERBOT_RESPONSE); - // todo this needs checking, this might block all messages in a channel if cleverbot is enabled but blocked - // need to check what kind of block it is - // might be the case for other classes using permission checker - if (!res.IsT0) - return true; + if (!res.IsAllowed) + return false; var channel = (ITextChannel)usrMsg.Channel; var conf = _ps.GetConfig(); @@ -183,7 +184,7 @@ public class ChatterBotService : IExecOnMessage } _ = channel.TriggerTypingAsync(); - var response = await cbs.Think(message); + var response = await cbs.Think(message, usrMsg.Author.ToString()); await channel.SendConfirmAsync(_eb, title: null, response.SanitizeMentions(true) diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/Gpt3Response.cs b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/Gpt3Response.cs index 7ec0c6186..80c24c1d3 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/Gpt3Response.cs +++ b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/Gpt3Response.cs @@ -11,7 +11,13 @@ public class Gpt3Response public class Choice { - public string Text { get; set; } + [JsonPropertyName("message")] + public Message Message { get; init; } +} + +public class Message { + [JsonPropertyName("content")] + public string Content { get; init; } } public class Gpt3ApiRequest @@ -19,12 +25,22 @@ public class Gpt3ApiRequest [JsonPropertyName("model")] public string Model { get; init; } - [JsonPropertyName("prompt")] - public string Prompt { get; init; } + [JsonPropertyName("messages")] + public List Messages { get; init; } [JsonPropertyName("temperature")] public int Temperature { get; init; } [JsonPropertyName("max_tokens")] public int MaxTokens { get; init; } +} + +public class GPTMessage +{ + [JsonPropertyName("role")] + public string Role {get; init;} + [JsonPropertyName("content")] + public string Content {get; init;} + [JsonPropertyName("name")] + public string Name {get; init;} } \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/IChatterBotSession.cs b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/IChatterBotSession.cs index 5fff26978..15a93406d 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/IChatterBotSession.cs +++ b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/IChatterBotSession.cs @@ -3,5 +3,5 @@ namespace NadekoBot.Modules.Games.Common.ChatterBot; public interface IChatterBotSession { - Task Think(string input); + Task Think(string input, string username); } \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialCleverbotSession.cs b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialCleverbotSession.cs index 73ed93bd2..71979d2f0 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialCleverbotSession.cs +++ b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialCleverbotSession.cs @@ -18,7 +18,7 @@ public class OfficialCleverbotSession : IChatterBotSession _httpFactory = factory; } - public async Task Think(string input) + public async Task Think(string input, string username) { using var http = _httpFactory.CreateClient(); var dataString = await http.GetStringAsync(string.Format(QueryString, input, cs ?? "")); diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialGpt3Session.cs b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialGpt3Session.cs index 24eb2db98..75f4d3d3d 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialGpt3Session.cs +++ b/src/Nadeko.Bot.Modules.Gambling/Games/ChatterBot/_common/OfficialGpt3Session.cs @@ -1,63 +1,99 @@ #nullable disable using Newtonsoft.Json; using System.Net.Http.Json; +using SharpToken; namespace NadekoBot.Modules.Games.Common.ChatterBot; public class OfficialGpt3Session : IChatterBotSession { private string Uri - => $"https://api.openai.com/v1/completions"; + => $"https://api.openai.com/v1/chat/completions"; private readonly string _apiKey; private readonly string _model; + private readonly int _maxHistory; private readonly int _maxTokens; + private readonly int _minTokens; + private readonly string _nadekoUsername; + private readonly GptEncoding _encoding; + private List messages = new(); private readonly IHttpClientFactory _httpFactory; + + public OfficialGpt3Session( string apiKey, - Gpt3Model model, + ChatGptModel model, + int chatHistory, int maxTokens, + int minTokens, + string personality, + string nadekoUsername, IHttpClientFactory factory) { _apiKey = apiKey; _httpFactory = factory; switch (model) { - case Gpt3Model.Ada001: - _model = "text-ada-001"; + case ChatGptModel.Gpt35Turbo: + _model = "gpt-3.5-turbo"; break; - case Gpt3Model.Babbage001: - _model = "text-babbage-001"; + case ChatGptModel.Gpt4: + _model = "gpt-4"; break; - case Gpt3Model.Curie001: - _model = "text-curie-001"; - break; - case Gpt3Model.Davinci003: - _model = "text-davinci-003"; + case ChatGptModel.Gpt432k: + _model = "gpt-4-32k"; break; } - + _maxHistory = chatHistory; _maxTokens = maxTokens; + _minTokens = minTokens; + _nadekoUsername = nadekoUsername; + _encoding = GptEncoding.GetEncodingForModel(_model); + messages.Add(new GPTMessage(){Role = "user", Content = personality, Name = _nadekoUsername}); } - public async Task Think(string input) + public async Task Think(string input, string username) { + messages.Add(new GPTMessage(){Role = "user", Content = input, Name = username}); + while(messages.Count > _maxHistory + 2){ + messages.RemoveAt(1); + } + int tokensUsed = 0; + foreach(GPTMessage message in messages){ + tokensUsed += _encoding.Encode(message.Content).Count; + } + tokensUsed *= 2; //Unsure why this is the case, but the token count chatgpt reports back is double what I calculate. + //check if we have the minimum number of tokens available to use. Remove messages until we have enough, otherwise exit out and inform the user why. + while(_maxTokens - tokensUsed <= _minTokens){ + if(messages.Count > 2){ + int tokens = _encoding.Encode(messages[1].Content).Count * 2; + tokensUsed -= tokens; + messages.RemoveAt(1); + } + else{ + return "Token count exceeded, please increase the number of tokens in the bot config and restart."; + } + } 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, + Messages = messages, + MaxTokens = _maxTokens - tokensUsed, Temperature = 1, }); var dataString = await data.Content.ReadAsStringAsync(); try { var response = JsonConvert.DeserializeObject(dataString); - - return response?.Choices[0]?.Text; + string message = response?.Choices[0]?.Message?.Content; + //Can't rely on the return to except, now that we need to add it to the messages list. + _ = message ?? throw new ArgumentNullException(nameof(message)); + messages.Add(new GPTMessage(){Role = "assistant", Content = message, Name = _nadekoUsername}); + return message; } catch { diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/GamesConfig.cs b/src/Nadeko.Bot.Modules.Gambling/Games/GamesConfig.cs index 7ac518c4e..7d76c6056 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/GamesConfig.cs +++ b/src/Nadeko.Bot.Modules.Gambling/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; } = 2; + public int Version { get; set; } = 3; [Comment("Hangman related settings (.hangman command)")] public HangmanConfig Hangman { get; set; } = new() @@ -108,14 +108,22 @@ public sealed partial class GamesConfig : ICloneable 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; + gpt35turbo - cheapest + gpt4 - 30x more expensive, higher quality + gp432k - same model as above, but with a 32k token limit")] + public ChatGptModel ModelName { get; set; } = ChatGptModel.Gpt35Turbo; + + [Comment(@"How should the chat bot behave, what's its personality? (Usage of this counts towards the max tokens)")] + public string PersonalityPrompt { get; set; } = "You are a chat bot willing to have a conversation with anyone about anything."; + + [Comment(@"The maximum number of messages in a conversation that can be remembered. (This will increase the number of tokens used)")] + public int ChatHistory { get; set; } = 5; [Comment(@"The maximum number of tokens to use per GPT-3 API call")] public int MaxTokens { get; set; } = 100; + + [Comment(@"The minimum number of tokens to use per GPT-3 API call, such that chat history is removed to make room.")] + public int MinTokens { get; set; } = 30; } [Cloneable] @@ -151,10 +159,9 @@ public enum ChatBotImplementation Gpt3 } -public enum Gpt3Model +public enum ChatGptModel { - Ada001, - Babbage001, - Curie001, - Davinci003 + Gpt35Turbo, + Gpt4, + Gpt432k } \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/GamesConfigService.cs b/src/Nadeko.Bot.Modules.Gambling/Games/GamesConfigService.cs index 690a92e0b..4f08b6106 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/GamesConfigService.cs +++ b/src/Nadeko.Bot.Modules.Gambling/Games/GamesConfigService.cs @@ -28,20 +28,33 @@ 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, + AddParsedProp("gpt.modelName", + gs => gs.ChatGpt.ModelName, ConfigParsers.InsensitiveEnum, ConfigPrinters.ToString); + AddParsedProp("gpt.personality", + gs => gs.ChatGpt.PersonalityPrompt, + ConfigParsers.String, + ConfigPrinters.ToString); + AddParsedProp("gpt.chathistory", + gs => gs.ChatGpt.ChatHistory, + int.TryParse, + ConfigPrinters.ToString, + val => val > 0); AddParsedProp("gpt.max_tokens", gs => gs.ChatGpt.MaxTokens, int.TryParse, ConfigPrinters.ToString, val => val > 0); + AddParsedProp("gpt.min_tokens", + gs => gs.ChatGpt.MinTokens, + int.TryParse, + ConfigPrinters.ToString, + val => val > 0); Migrate(); } @@ -65,7 +78,16 @@ public sealed class GamesConfigService : ConfigServiceBase ModifyConfig(c => { c.Version = 2; - c.ChatBot = ChatBotImplementation.Cleverbot; + c.ChatBot = ChatBotImplementation.Cleverbot; + }); + } + + if (data.Version < 3) + { + ModifyConfig(c => + { + c.Version = 3; + c.ChatGpt.ModelName = ChatGptModel.Gpt35Turbo; }); } } diff --git a/src/Nadeko.Bot.Modules.Gambling/Nadeko.Bot.Modules.Gambling.csproj b/src/Nadeko.Bot.Modules.Gambling/Nadeko.Bot.Modules.Gambling.csproj index 8a68d457f..3be8d2383 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Nadeko.Bot.Modules.Gambling.csproj +++ b/src/Nadeko.Bot.Modules.Gambling/Nadeko.Bot.Modules.Gambling.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -14,8 +14,9 @@ - - + + + diff --git a/src/Nadeko.Bot.Modules.Help/Help.cs b/src/Nadeko.Bot.Modules.Help/Help.cs index 60e6e50bb..14049e189 100644 --- a/src/Nadeko.Bot.Modules.Help/Help.cs +++ b/src/Nadeko.Bot.Modules.Help/Help.cs @@ -76,10 +76,10 @@ public sealed class Help : NadekoModule var topLevelModules = new List(); foreach (var m in _cmds.Modules.GroupBy(x => x.GetTopLevelModule()).Select(x => x.Key)) { - var result = await _perms.CheckAsync(ctx.Guild, ctx.Channel, ctx.User, + var result = await _perms.CheckPermsAsync(ctx.Guild, ctx.Channel, ctx.User, m.Name, null); - if (result.IsT0) + if (result.IsAllowed) topLevelModules.Add(m); } @@ -222,9 +222,10 @@ public sealed class Help : NadekoModule .Name .StartsWith(module, StringComparison.InvariantCultureIgnoreCase))) { - var result = await _perms.CheckAsync(ctx.Guild, ctx.Channel, ctx.User, cmd.Module.GetTopLevelModule().Name, + var result = await _perms.CheckPermsAsync(ctx.Guild, ctx.Channel, ctx.User, cmd.Module.GetTopLevelModule().Name, cmd.Name); - if (result.IsT0) + + if (result.IsAllowed) allowed.Add(cmd); } @@ -311,7 +312,7 @@ public sealed class Help : NadekoModule .WithTitle(GetText(strs.cmd_group_commands(group.Name))) .WithOkColor(); - foreach (var cmd in group.Commands) + foreach (var cmd in group.Commands.DistinctBy(x => x.Aliases[0])) { eb.AddField(prefix + cmd.Aliases.First(), cmd.RealSummary(_strings, _medusae, Culture, prefix)); } diff --git a/src/Nadeko.Bot.Modules.Help/Nadeko.Bot.Modules.Help.csproj b/src/Nadeko.Bot.Modules.Help/Nadeko.Bot.Modules.Help.csproj index 4018c363d..eb7c28f75 100644 --- a/src/Nadeko.Bot.Modules.Help/Nadeko.Bot.Modules.Help.csproj +++ b/src/Nadeko.Bot.Modules.Help/Nadeko.Bot.Modules.Help.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -13,7 +13,7 @@ - + diff --git a/src/Nadeko.Bot.Modules.Music/Nadeko.Bot.Modules.Music.csproj b/src/Nadeko.Bot.Modules.Music/Nadeko.Bot.Modules.Music.csproj index eab0fce08..3f89f9023 100644 --- a/src/Nadeko.Bot.Modules.Music/Nadeko.Bot.Modules.Music.csproj +++ b/src/Nadeko.Bot.Modules.Music/Nadeko.Bot.Modules.Music.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -15,7 +15,7 @@ - + diff --git a/src/Nadeko.Bot.Modules.Patronage/Nadeko.Bot.Modules.Patronage.csproj b/src/Nadeko.Bot.Modules.Patronage/Nadeko.Bot.Modules.Patronage.csproj index 49ff4f201..539808298 100644 --- a/src/Nadeko.Bot.Modules.Patronage/Nadeko.Bot.Modules.Patronage.csproj +++ b/src/Nadeko.Bot.Modules.Patronage/Nadeko.Bot.Modules.Patronage.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/src/Nadeko.Bot.Modules.Permisssions/Nadeko.Bot.Modules.Permisssions.csproj b/src/Nadeko.Bot.Modules.Permisssions/Nadeko.Bot.Modules.Permisssions.csproj index b85ad4c11..45257727b 100644 --- a/src/Nadeko.Bot.Modules.Permisssions/Nadeko.Bot.Modules.Permisssions.csproj +++ b/src/Nadeko.Bot.Modules.Permisssions/Nadeko.Bot.Modules.Permisssions.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/src/Nadeko.Bot.Modules.Searches/Nadeko.Bot.Modules.Searches.csproj b/src/Nadeko.Bot.Modules.Searches/Nadeko.Bot.Modules.Searches.csproj index 1f61198f4..3d22747ab 100644 --- a/src/Nadeko.Bot.Modules.Searches/Nadeko.Bot.Modules.Searches.csproj +++ b/src/Nadeko.Bot.Modules.Searches/Nadeko.Bot.Modules.Searches.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -14,20 +14,20 @@ - + - - + + - + - + diff --git a/src/Nadeko.Bot.Modules.Searches/StreamNotification/StreamNotificationService.cs b/src/Nadeko.Bot.Modules.Searches/StreamNotification/StreamNotificationService.cs index 1ce0a2a79..8a515c9e9 100644 --- a/src/Nadeko.Bot.Modules.Searches/StreamNotification/StreamNotificationService.cs +++ b/src/Nadeko.Bot.Modules.Searches/StreamNotification/StreamNotificationService.cs @@ -27,6 +27,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor private readonly IPubSub _pubSub; private readonly IEmbedBuilderService _eb; + private readonly SearchesConfigService _config; public TypedKey> StreamsOnlineKey { get; } public TypedKey> StreamsOfflineKey { get; } @@ -48,14 +49,16 @@ public sealed class StreamNotificationService : INService, IReadyExecutor IHttpClientFactory httpFactory, IBot bot, IPubSub pubSub, - IEmbedBuilderService eb) + IEmbedBuilderService eb, + SearchesConfigService config) { _db = db; _client = client; _strings = strings; _pubSub = pubSub; _eb = eb; - + _config = config; + _streamTracker = new(httpFactory, creds); StreamsOnlineKey = new("streams.online"); @@ -68,34 +71,34 @@ public sealed class StreamNotificationService : INService, IReadyExecutor { var ids = client.GetGuildIds(); var guildConfigs = uow.Set() - .AsQueryable() - .Include(x => x.FollowedStreams) - .Where(x => ids.Contains(x.GuildId)) - .ToList(); + .AsQueryable() + .Include(x => x.FollowedStreams) + .Where(x => ids.Contains(x.GuildId)) + .ToList(); _offlineNotificationServers = new(guildConfigs - .Where(gc => gc.NotifyStreamOffline) - .Select(x => x.GuildId) - .ToList()); - + .Where(gc => gc.NotifyStreamOffline) + .Select(x => x.GuildId) + .ToList()); + _deleteOnOfflineServers = new(guildConfigs - .Where(gc => gc.DeleteStreamOnlineMessage) - .Select(x => x.GuildId) - .ToList()); + .Where(gc => gc.DeleteStreamOnlineMessage) + .Select(x => x.GuildId) + .ToList()); var followedStreams = guildConfigs.SelectMany(x => x.FollowedStreams).ToList(); _shardTrackedStreams = followedStreams.GroupBy(x => new - { - x.Type, - Name = x.Username.ToLower() - }) - .ToList() - .ToDictionary( - x => new StreamDataKey(x.Key.Type, x.Key.Name.ToLower()), - x => x.GroupBy(y => y.GuildId) - .ToDictionary(y => y.Key, - y => y.AsEnumerable().ToHashSet())); + { + x.Type, + Name = x.Username.ToLower() + }) + .ToList() + .ToDictionary( + x => new StreamDataKey(x.Key.Type, x.Key.Name.ToLower()), + x => x.GroupBy(y => y.GuildId) + .ToDictionary(y => y.Key, + y => y.AsEnumerable().ToHashSet())); // shard 0 will keep track of when there are no more guilds which track a stream if (client.ShardId == 0) @@ -106,12 +109,12 @@ public sealed class StreamNotificationService : INService, IReadyExecutor _streamTracker.AddLastData(fs.CreateKey(), null, false); _trackCounter = allFollowedStreams.GroupBy(x => new - { - x.Type, - Name = x.Username.ToLower() - }) - .ToDictionary(x => new StreamDataKey(x.Key.Type, x.Key.Name), - x => x.Select(fs => fs.GuildId).ToHashSet()); + { + x.Type, + Name = x.Username.ToLower() + }) + .ToDictionary(x => new StreamDataKey(x.Key.Type, x.Key.Name), + x => x.Select(fs => fs.GuildId).ToHashSet()); } } @@ -151,7 +154,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor continue; var deleteGroups = failingStreams.GroupBy(x => x.Type) - .ToDictionary(x => x.Key, x => x.Select(y => y.Name).ToList()); + .ToDictionary(x => x.Key, x => x.Select(y => y.Name).ToList()); await using var uow = _db.GetDbContext(); foreach (var kvp in deleteGroups) @@ -164,9 +167,9 @@ public sealed class StreamNotificationService : INService, IReadyExecutor string.Join(", ", kvp.Value)); var toDelete = uow.Set() - .AsQueryable() - .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username)) - .ToList(); + .AsQueryable() + .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username)) + .ToList(); uow.RemoveRange(toDelete); await uow.SaveChangesAsync(); @@ -245,17 +248,17 @@ public sealed class StreamNotificationService : INService, IReadyExecutor if (_shardTrackedStreams.TryGetValue(key, out var fss)) { await fss - // send offline stream notifications only to guilds which enable it with .stoff - .SelectMany(x => x.Value) - .Where(x => _offlineNotificationServers.Contains(x.GuildId)) - .Select(fs => _client.GetGuild(fs.GuildId) - ?.GetTextChannel(fs.ChannelId) - ?.EmbedAsync(GetEmbed(fs.GuildId, stream))) - .WhenAll(); + // send offline stream notifications only to guilds which enable it with .stoff + .SelectMany(x => x.Value) + .Where(x => _offlineNotificationServers.Contains(x.GuildId)) + .Select(fs => _client.GetGuild(fs.GuildId) + ?.GetTextChannel(fs.ChannelId) + ?.EmbedAsync(GetEmbed(fs.GuildId, stream))) + .WhenAll(); } } } - + private async ValueTask HandleStreamsOnline(List onlineStreams) { @@ -265,30 +268,30 @@ public sealed class StreamNotificationService : INService, IReadyExecutor if (_shardTrackedStreams.TryGetValue(key, out var fss)) { var messages = await fss.SelectMany(x => x.Value) - .Select(async fs => - { - var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId); + .Select(async fs => + { + var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId); - if (textChannel is null) - return default; + if (textChannel is null) + return default; - var rep = new ReplacementBuilder().WithOverride("%user%", () => fs.Username) - .WithOverride("%platform%", () => fs.Type.ToString()) - .Build(); + var rep = new ReplacementBuilder().WithOverride("%user%", () => fs.Username) + .WithOverride("%platform%", () => fs.Type.ToString()) + .Build(); - var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message); + var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message); - var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream, false), message); + var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream, false), message); + + // only cache the ids of channel/message pairs + if (_deleteOnOfflineServers.Contains(fs.GuildId)) + return (textChannel.Id, msg.Id); + else + return default; + }) + .WhenAll(); - // only cache the ids of channel/message pairs - if(_deleteOnOfflineServers.Contains(fs.GuildId)) - return (textChannel.Id, msg.Id); - else - return default; - }) - .WhenAll(); - // push online stream messages to redis // when streams go offline, any server which // has the online stream message deletion feature @@ -296,16 +299,15 @@ public sealed class StreamNotificationService : INService, IReadyExecutor try { var pairs = messages - .Where(x => x != default) - .Select(x => (x.Item1, x.Item2)) - .ToList(); + .Where(x => x != default) + .Select(x => (x.Item1, x.Item2)) + .ToList(); if (pairs.Count > 0) await OnlineMessagesSent(key.Type, key.Name, pairs); } catch { - } } } @@ -383,10 +385,10 @@ public sealed class StreamNotificationService : INService, IReadyExecutor await using (var uow = _db.GetDbContext()) { var fss = uow.Set() - .AsQueryable() - .Where(x => x.GuildId == guildId) - .OrderBy(x => x.Id) - .ToList(); + .AsQueryable() + .Where(x => x.GuildId == guildId) + .OrderBy(x => x.Id) + .ToList(); // out of range if (fss.Count <= index) @@ -449,7 +451,9 @@ public sealed class StreamNotificationService : INService, IReadyExecutor GuildId = guildId }; - if (gc.FollowedStreams.Count >= 10) + var config = _config.Data; + if (config.FollowedStreams.MaxCount is not -1 + && gc.FollowedStreams.Count >= config.FollowedStreams.MaxCount) return null; gc.FollowedStreams.Add(fs); @@ -474,10 +478,10 @@ public sealed class StreamNotificationService : INService, IReadyExecutor public IEmbedBuilder GetEmbed(ulong guildId, StreamData status, bool showViewers = true) { var embed = _eb.Create() - .WithTitle(status.Name) - .WithUrl(status.StreamUrl) - .WithDescription(status.StreamUrl) - .AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true); + .WithTitle(status.Name) + .WithUrl(status.StreamUrl) + .WithDescription(status.StreamUrl) + .AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true); if (showViewers) { @@ -526,7 +530,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor return newValue; } - + public bool ToggleStreamOnlineDelete(ulong guildId) { using var uow = _db.GetDbContext(); diff --git a/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfig.cs b/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfig.cs index b584276b3..172a05953 100644 --- a/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfig.cs +++ b/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfig.cs @@ -8,63 +8,72 @@ public partial class SearchesConfig : ICloneable { [Comment("DO NOT CHANGE")] public int Version { get; set; } = 0; - - [Comment(""" - Which engine should .search command - 'google_scrape' - default. Scrapes the webpage for results. May break. Requires no api keys. - 'google' - official google api. Requires googleApiKey and google.searchId set in creds.yml - 'searx' - requires at least one searx instance specified in the 'searxInstances' property below - """)] - public WebSearchEngine WebSearchEngine { get; set; } = WebSearchEngine.Google_Scrape; - - [Comment(""" - Which engine should .image command use - 'google'- official google api. googleApiKey and google.imageSearchId set in creds.yml - 'searx' requires at least one searx instance specified in the 'searxInstances' property below - """)] - public ImgSearchEngine ImgSearchEngine { get; set; } = ImgSearchEngine.Google; - [Comment(""" - Which search provider will be used for the `.youtube` command. - - - `ytDataApiv3` - uses google's official youtube data api. Requires `GoogleApiKey` set in creds and youtube data api enabled in developers console - - - `ytdl` - default, uses youtube-dl. Requires `youtube-dl` to be installed and it's path added to env variables. Slow. - - - `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 - """)] + Which engine should .search command + 'google_scrape' - default. Scrapes the webpage for results. May break. Requires no api keys. + 'google' - official google api. Requires googleApiKey and google.searchId set in creds.yml + 'searx' - requires at least one searx instance specified in the 'searxInstances' property below + """)] + public WebSearchEngine WebSearchEngine { get; set; } = WebSearchEngine.Google_Scrape; + + [Comment(""" + Which engine should .image command use + 'google'- official google api. googleApiKey and google.imageSearchId set in creds.yml + 'searx' requires at least one searx instance specified in the 'searxInstances' property below + """)] + public ImgSearchEngine ImgSearchEngine { get; set; } = ImgSearchEngine.Google; + + + [Comment(""" + Which search provider will be used for the `.youtube` command. + + - `ytDataApiv3` - uses google's official youtube data api. Requires `GoogleApiKey` set in creds and youtube data api enabled in developers console + + - `ytdl` - default, uses youtube-dl. Requires `youtube-dl` to be installed and it's path added to env variables. Slow. + + - `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 + """)] 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. - Nadeko will use a random one for each request. - Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com` - Instances specified must support 'format=json' query parameter. - - In case you're running your own searx instance, set - - search: - formats: - - json - - in 'searxng/settings.yml' on your server - - - If you're using a public instance, make sure that the instance you're using supports it (they usually don't) - """)] + 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` + Instances specified must support 'format=json' query parameter. + - In case you're running your own searx instance, set + + search: + formats: + - json + + in 'searxng/settings.yml' on your server + + - If you're using a public instance, make sure that the instance you're using supports it (they usually don't) + """)] public List SearxInstances { get; set; } = new List(); [Comment(""" - Set the invidious instance urls in case you want to use 'invidious' for `.youtube` search - Nadeko will use a random one for each request. - These instances may be used for music queue functionality in the future. - Use a fully qualified url. Example: https://my-invidious-instance.mydomain.com - - Instances specified must have api available. - You check that by opening an api endpoint in your browser. For example: https://my-invidious-instance.mydomain.com/api/v1/trending - """)] + Set the invidious instance urls in case you want to use 'invidious' for `.youtube` search + Nadeko will use a random one for each request. + These instances may be used for music queue functionality in the future. + Use a fully qualified url. Example: https://my-invidious-instance.mydomain.com + + Instances specified must have api available. + You check that by opening an api endpoint in your browser. For example: https://my-invidious-instance.mydomain.com/api/v1/trending + """)] public List InvidiousInstances { get; set; } = new List(); + + [Comment("Maximum number of followed streams per server")] + public FollowedStreamConfig FollowedStreams { get; set; } = new FollowedStreamConfig(); +} + +public sealed class FollowedStreamConfig +{ + [Comment("Maximum number of streams that each server can follow. -1 for infinite")] + public int MaxCount { get; set; } = 10; } public enum YoutubeSearcher diff --git a/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfigService.cs b/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfigService.cs index 4d58098b4..378c46a65 100644 --- a/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfigService.cs +++ b/src/Nadeko.Bot.Modules.Searches/_common/Config/SearchesConfigService.cs @@ -17,17 +17,22 @@ public class SearchesConfigService : ConfigServiceBase sc => sc.WebSearchEngine, ConfigParsers.InsensitiveEnum, ConfigPrinters.ToString); - + AddParsedProp("imgEngine", sc => sc.ImgSearchEngine, ConfigParsers.InsensitiveEnum, ConfigPrinters.ToString); - + AddParsedProp("ytProvider", sc => sc.YtProvider, ConfigParsers.InsensitiveEnum, ConfigPrinters.ToString); + AddParsedProp("followedStreams.maxCount", + sc => sc.FollowedStreams.MaxCount, + int.TryParse, + ConfigPrinters.ToString); + Migrate(); } @@ -41,5 +46,13 @@ public class SearchesConfigService : ConfigServiceBase c.WebSearchEngine = WebSearchEngine.Google_Scrape; }); } + + if (data.Version < 2) + { + ModifyConfig(c => + { + c.Version = 2; + }); + } } } \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj b/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj index 48d1877c8..e83c2e026 100644 --- a/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj +++ b/src/Nadeko.Bot.Modules.Utility/Nadeko.Bot.Modules.Utility.csproj @@ -1,19 +1,19 @@ - net7.0 + net8.0 enable enable - + - + diff --git a/src/Nadeko.Bot.Modules.Xp/Nadeko.Bot.Modules.Xp.csproj b/src/Nadeko.Bot.Modules.Xp/Nadeko.Bot.Modules.Xp.csproj index dc514acc1..59f44e807 100644 --- a/src/Nadeko.Bot.Modules.Xp/Nadeko.Bot.Modules.Xp.csproj +++ b/src/Nadeko.Bot.Modules.Xp/Nadeko.Bot.Modules.Xp.csproj @@ -1,14 +1,14 @@ - net7.0 + net8.0 enable enable - - + + diff --git a/src/Nadeko.Common/Nadeko.Common.csproj b/src/Nadeko.Common/Nadeko.Common.csproj index 9883c5a0b..90221a7f5 100644 --- a/src/Nadeko.Common/Nadeko.Common.csproj +++ b/src/Nadeko.Common/Nadeko.Common.csproj @@ -1,17 +1,17 @@ - net7.0 + net8.0 enable enable - - - + + + - + diff --git a/src/Nadeko.Econ/Nadeko.Econ.csproj b/src/Nadeko.Econ/Nadeko.Econ.csproj index 2219e5b2b..c35a03241 100644 --- a/src/Nadeko.Econ/Nadeko.Econ.csproj +++ b/src/Nadeko.Econ/Nadeko.Econ.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/src/Nadeko.Medusa/Nadeko.Medusa.csproj b/src/Nadeko.Medusa/Nadeko.Medusa.csproj index ac909115c..a10f54677 100644 --- a/src/Nadeko.Medusa/Nadeko.Medusa.csproj +++ b/src/Nadeko.Medusa/Nadeko.Medusa.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable @@ -10,8 +10,8 @@ - - + + diff --git a/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj b/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj index b9b8dcf8f..4798b7538 100644 --- a/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj +++ b/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 CS8981 @@ -10,7 +10,7 @@ - + diff --git a/src/NadekoBot.Tests/NadekoBot.Tests.csproj b/src/NadekoBot.Tests/NadekoBot.Tests.csproj index eadf0eb94..6989b5594 100644 --- a/src/NadekoBot.Tests/NadekoBot.Tests.csproj +++ b/src/NadekoBot.Tests/NadekoBot.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false diff --git a/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj b/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj index d9a3e5f68..a4f25f244 100644 --- a/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj +++ b/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Linux diff --git a/src/NadekoBot/Bot.cs b/src/NadekoBot/Bot.cs index de2c5871e..f30a87b5d 100644 --- a/src/NadekoBot/Bot.cs +++ b/src/NadekoBot/Bot.cs @@ -125,10 +125,10 @@ public sealed class Bot : IBot var svcs = new StandardKernel(new NinjectSettings() { - ThrowOnGetServiceNotFound = true, + // ThrowOnGetServiceNotFound = true, ActivationCacheDisabled = true, }); - + // this is required in order for medusa unloading to work svcs.Components.Remove(); svcs.Components.Add(); @@ -197,7 +197,7 @@ public sealed class Bot : IBot private void LoadTypeReaders(Assembly assembly) { - var filteredTypes = assembly.GetTypes() + var filteredTypes = assembly.GetExportedTypes() .Where(x => x.IsSubclassOf(typeof(TypeReader)) && x.BaseType?.GetGenericArguments().Length > 0 && !x.IsAbstract); @@ -208,6 +208,7 @@ public sealed class Bot : IBot if (baseType is null) continue; + Log.Information(ft.Name); var typeReader = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft); var typeArgs = baseType.GetGenericArguments(); _commandService.AddTypeReader(typeArgs[0], typeReader); diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index e3a1c2700..9f2d495e2 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable true @@ -20,15 +20,15 @@ - + all True - + - + @@ -38,12 +38,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + @@ -51,51 +51,53 @@ - - + + - - - + + + - - - + + + + + - + - - + + all True - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + - + - + @@ -111,9 +113,9 @@ - + diff --git a/src/NadekoBot/PermissionChecker.cs b/src/NadekoBot/PermissionChecker.cs index e526a936f..3d9a3e010 100644 --- a/src/NadekoBot/PermissionChecker.cs +++ b/src/NadekoBot/PermissionChecker.cs @@ -14,9 +14,10 @@ public sealed class PermissionChecker : IPermissionChecker, INService private readonly IEmbedBuilderService _ebs; private readonly CommandHandler _ch; - // todo .GetPrefix should be in a different place - public PermissionChecker(PermissionService perms, - GlobalPermissionService gperm, CmdCdService cmdCds, + public PermissionChecker( + PermissionService perms, + GlobalPermissionService gperm, + CmdCdService cmdCds, IEmbedBuilderService ebs, CommandHandler ch) { @@ -27,58 +28,41 @@ public sealed class PermissionChecker : IPermissionChecker, INService _ch = ch; } - public async Task>> CheckAsync( + public async Task CheckPermsAsync( IGuild guild, IMessageChannel channel, IUser author, string module, - string? cmd) + string? cmdName) { module = module.ToLowerInvariant(); - cmd = cmd?.ToLowerInvariant(); - // todo add proper string - if (cmd is not null && await _cmdCds.TryBlock(guild, author, cmd)) - return new Error(new()); + cmdName = cmdName?.ToLowerInvariant(); + + if (cmdName is not null && await _cmdCds.TryBlock(guild, author, cmdName)) + { + return new PermCooldown(); + } try { if (_gperm.BlockedModules.Contains(module)) { - Log.Information("u:{UserId} tried to use module {Module} which is globally disabled.", - author.Id, - module - ); - - return new Success(); + return new PermGlobalBlock(); + } + + if (cmdName is not null && _gperm.BlockedCommands.Contains(cmdName)) + { + return new PermGlobalBlock(); } - // todo check if this even works if (guild is SocketGuild sg) { - var pc = _perms.GetCacheFor(guild.Id); - if (!pc.Permissions.CheckPermissions(author, channel, cmd, module, out var index)) + var pc = _perms.GetCacheFor(sg.Id); + if (!pc.Permissions.CheckPermissions(author, channel, cmdName, module, out var index)) { - if (pc.Verbose) - { - // todo fix - var permissionMessage = strs.perm_prevent(index + 1, - Format.Bold(pc.Permissions[index].GetCommand(_ch.GetPrefix(guild), sg))); - - try - { - // await channel.SendErrorAsync(_ebs, - // title: null, - // text: GettextpermissionMessage); - } - catch - { - } - - Log.Information("{PermissionMessage}", permissionMessage); - } - - // todo add proper string - return new Error(new()); + return new PermDisallowed(index, + pc.Permissions[index].GetCommand(_ch.GetPrefix(guild), sg), + pc.Verbose); } } } @@ -86,6 +70,6 @@ public sealed class PermissionChecker : IPermissionChecker, INService { } - return new Success(); + return new PermAllowed(); } } \ No newline at end of file diff --git a/src/NadekoBot/Services/Impl/PubSub/RedisPubSub.cs b/src/NadekoBot/Services/Impl/PubSub/RedisPubSub.cs index a2f8fa63b..8635ec027 100644 --- a/src/NadekoBot/Services/Impl/PubSub/RedisPubSub.cs +++ b/src/NadekoBot/Services/Impl/PubSub/RedisPubSub.cs @@ -20,7 +20,9 @@ public sealed class RedisPubSub : IPubSub { var serialized = _serializer.Serialize(data); return _multi.GetSubscriber() - .PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget); + .PublishAsync(new RedisChannel($"{_creds.RedisKey()}:{key.Key}", RedisChannel.PatternMode.Literal), + serialized, + CommandFlags.FireAndForget); } public Task Sub(in TypedKey key, Func action) @@ -47,6 +49,9 @@ public sealed class RedisPubSub : IPubSub } } - return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", OnSubscribeHandler); + return _multi.GetSubscriber() + .SubscribeAsync( + new RedisChannel($"{_creds.RedisKey()}:{eventName}", RedisChannel.PatternMode.Literal), + OnSubscribeHandler); } } \ No newline at end of file diff --git a/src/NadekoBot/data/games.yml b/src/NadekoBot/data/games.yml index eefbc952d..5d0c369f8 100644 --- a/src/NadekoBot/data/games.yml +++ b/src/NadekoBot/data/games.yml @@ -1,5 +1,5 @@ # DO NOT CHANGE -version: 2 +version: 3 # Hangman related settings (.hangman command) hangman: # The amount of currency awarded to the winner of a hangman game @@ -57,14 +57,19 @@ raceAnimals: # Which chatbot API should bot use. # 'cleverbot' - bot will use Cleverbot API. # 'gpt3' - bot will use GPT-3 API -chatBot: gpt3 +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 +# Which GPT-3 Model should bot use. + # gpt35turbo - cheapest + # gpt4 - 30x more expensive, higher quality + # gp432k - same model as above, but with a 32k token limit + modelName: Gpt35Turbo + # How should the chat bot behave, whats its personality? (Usage of this counts towards the max tokens) + personalityPrompt: You are a chat bot willing to have a conversation with anyone about anything. + # The maximum number of messages in a conversation that can be remembered. (This will increase the number of tokens used) + chatHistory: 5 # The maximum number of tokens to use per GPT-3 API call maxTokens: 100 + # The minimum number of tokens to use per GPT-3 API call, such that chat history is removed to make room. + minTokens: 30 \ No newline at end of file diff --git a/src/NadekoBot/data/searches.yml b/src/NadekoBot/data/searches.yml index 0c2625bbb..e3b8d2b38 100644 --- a/src/NadekoBot/data/searches.yml +++ b/src/NadekoBot/data/searches.yml @@ -1,5 +1,5 @@ # DO NOT CHANGE -version: 1 +version: 2 # Which engine should .search command # 'google_scrape' - default. Scrapes the webpage for results. May break. Requires no api keys. # 'google' - official google api. Requires googleApiKey and google.searchId set in creds.yml @@ -41,3 +41,7 @@ searxInstances: [] # Instances specified must have api available. # You check that by opening an api endpoint in your browser. For example: https://my-invidious-instance.mydomain.com/api/v1/trending invidiousInstances: [] +# Maximum number of followed streams per server +followedStreams: +# Maximum number of streams that each server can follow. -1 for infinite + maxCount: 10 diff --git a/src/ayu/Ayu.Discord.Voice/Ayu.Discord.Voice.csproj b/src/ayu/Ayu.Discord.Voice/Ayu.Discord.Voice.csproj index c2b80e137..46071ccf6 100644 --- a/src/ayu/Ayu.Discord.Voice/Ayu.Discord.Voice.csproj +++ b/src/ayu/Ayu.Discord.Voice/Ayu.Discord.Voice.csproj @@ -8,7 +8,7 @@ - - + + \ No newline at end of file