Compare commits

...

22 Commits

Author SHA1 Message Date
Kwoth
9d3e80eb32 Fixed commands.en-US.yml 2022-07-03 23:13:00 +02:00
Kwoth
42cbb7f626 Updated CHANGELOG.md, Upped version to 4.2.14 2022-07-03 23:00:12 +02:00
Kwoth
4d175477f5 Bot will now 'try' to set status to invisible before going offline when '.die' command is used, but it doesn't seem to have (much/any) effect. .qsearch is more powerful 2022-07-03 22:26:41 +02:00
Kwoth
643987c41f Added .log userwarned 2022-07-03 21:58:05 +02:00
Kwoth
03396642a4 Added ban reason to .log userbanned (and if you used nadeko to ban someone, it will also show the mod who did the ban because nadeko adds it to the reason) 2022-07-03 17:03:27 +02:00
Kwoth
3fd5f0c97a Added warn punishment action for protection commands 2022-07-03 14:55:31 +02:00
Kwoth
5d78f29329 Added %server.icon% placeholder 2022-07-03 13:47:41 +02:00
Kwoth
2a98aceae6 Fixed elipsis alias bug, closes #295 2022-07-02 15:03:24 +02:00
Kwoth
5c933b676d .timely will now have a button to set a reminder 2022-07-02 14:15:02 +02:00
Kwoth
2e4de7723e Fixed .cash bank interaction not being ephemeral 2022-06-30 22:09:26 +02:00
Kwoth
a8e00a19ba Upped version to 4.2.12 2022-06-30 11:33:45 +02:00
Kwoth
8acf6b1194 Fixed .trivia --pokemon showing pokemon with id + 1 2022-06-30 11:32:36 +02:00
Kwoth
11d9db99ff Draw fixed, version upped 2022-06-29 17:53:15 +02:00
Kwoth
c66e0fb6b7 Possible fix for constant source generator crashes 2022-06-29 15:28:21 +02:00
Kwoth
1517a35ef7 Upped version to 4.2.10 2022-06-29 11:16:32 +02:00
Kwoth
c5179979d7 Fixed an issue with currency generation working only once 2022-06-29 00:18:15 +02:00
Kwoth
6b14c04e37 Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2022-06-28 10:55:59 +02:00
Kwoth
4ec3eb7855 Possible fix for the filterservice duplicate key bug 2022-06-28 10:55:35 +02:00
Kwoth
4752c4b7cd Update CHANGELOG.md 2022-06-26 08:54:40 +00:00
Kwoth
dfec2f589e Nuked nsfw from the public bot, it shouldn't show up anymore. Uncommented patron owner check 2022-06-25 21:03:16 +02:00
Kwoth
f616364d8a Upped version to 4.2.9 2022-06-25 10:42:16 +02:00
Kwoth
4294f8efd5 copy creds_example to output directory 2022-06-25 10:39:34 +02:00
39 changed files with 10468 additions and 87 deletions

View File

@@ -1,9 +1,52 @@
# Changelog
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## Unreleased
## [4.2.14] - 03.07.2022
### Added
- Added `.log userwarned` (Logging user warnings)
- Claiming `.timely` will now show a button which you can click to set a reminder
- Added `%server.icon%` placeholder
- Added `warn` punishment action for protection commands (it won't work with `.warnp`)
### Changed
- `.log userbanned` will now have a ban reason
- When `.die` is used, bot will try to update it's status to `Invisible`
### Fixed
- Fixed elipsis character issue with aliases/quotes. You should now be able to set an elipsis to be an alias of `.quoteprint`
## [4.2.13] - 30.06.2022
### Fixed
- Fixed `.cash` bank interaction not being ephemeral anymore
## [4.2.12] - 30.06.2022
### Fixed
- Fixed `.trivia --pokemon` showing incorrect pokemons
## [4.2.11] - 29.06.2022
### Fixed
- Fixed `.draw` command
## [4.2.10] - 29.06.2022
- Fixed currency generation working only once
## [4.2.9] - 25.06.2022
### Fixed
- Fixed `creds_example.yml` misssing from output directory
## [4.2.8] - 24.06.2022

View File

@@ -64,7 +64,9 @@ public class CmdAttribute : System.Attribute
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// #if DEBUG
// SpinWait.SpinUntil(() => Debugger.IsAttached);
// if (!Debugger.IsAttached)
// Debugger.Launch();
// // SpinWait.SpinUntil(() => Debugger.IsAttached);
// #endif
context.RegisterPostInitializationOutput(static ctx => ctx.AddSource(
"CmdAttribute.g.cs",
@@ -157,7 +159,9 @@ public class CmdAttribute : System.Attribute
.Distinct();
var methodModels = methods
.Select(x => MethodDeclarationToMethodModel(compilation, x!));
.Select(x => MethodDeclarationToMethodModel(compilation, x!))
.Where(static x => x is not null)
.Cast<MethodModel>();
var groups = methodModels
.GroupBy(static x => $"{x.Namespace}.{string.Join(".", x.Classes)}");
@@ -177,7 +181,7 @@ public class CmdAttribute : System.Attribute
var model = new FileModel(
methods: elems,
ns: elems[0].Namespace,
classHierarchy: elems[0].Classes
classHierarchy: elems![0].Classes
);
models.Add(model);
@@ -187,11 +191,21 @@ public class CmdAttribute : System.Attribute
return models;
}
private static MethodModel MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl)
private static MethodModel? MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl)
{
// SpinWait.SpinUntil(static () => Debugger.IsAttached);
var semanticModel = comp.GetSemanticModel(decl.SyntaxTree);
SemanticModel semanticModel;
try
{
semanticModel = comp.GetSemanticModel(decl.SyntaxTree);
}
catch
{
// for some reason this method can throw "Not part of this compilation" argument exception
return null;
}
var methodModel = new MethodModel(
@params: decl.ParameterList.Parameters
.Where(p => p.Type is not null)

View File

@@ -70,6 +70,7 @@ public sealed class Bot
: GatewayIntents.AllUnprivileged,
LogGatewayIntentWarnings = false,
FormatUsersInBidirectionalUnicode = false,
DefaultRetryMode = RetryMode.AlwaysRetry ^ RetryMode.RetryRatelimit
});
_commandService = new(new()
@@ -260,6 +261,7 @@ public sealed class Bot
Client.JoinedGuild += Client_JoinedGuild;
Client.LeftGuild += Client_LeftGuild;
// _ = Client.SetStatusAsync(UserStatus.Online);
Log.Information("Shard {ShardId} logged in", Client.ShardId);
}

View File

@@ -27,5 +27,6 @@ public enum LogType
UserPresence,
VoicePresence,
VoicePresenceTts,
UserMuted
UserMuted,
UserWarned,
}

View File

@@ -67,13 +67,13 @@ public abstract class NadekoModule : ModuleBase
// localized replies
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
{

View File

@@ -55,6 +55,7 @@ public class ReplacementBuilder
_reps.TryAdd("%server%", () => g is null ? "DM" : g.Name);
_reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
_reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
_reps.TryAdd("%server.icon%", () => g is null ? null : g.IconUrl);
_reps.TryAdd("%server.members%", () => g is { } sg ? sg.MemberCount.ToString() : "?");
_reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
_reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());

View File

@@ -44,12 +44,12 @@ public static class QuoteExtensions
var rngk = new NadekoRandom();
return (await quotes.AsQueryable()
.Where(q => q.GuildId == guildId
&& q.Keyword == keyword
&& EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
// && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase)
)
.ToListAsync()).OrderBy(_ => rngk.Next())
.FirstOrDefault();
&& (keyword == null || q.Keyword == keyword)
&& (EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
|| EF.Functions.Like(q.AuthorName, text)))
.ToListAsync())
.OrderBy(_ => rngk.Next())
.FirstOrDefault();
}
public static void RemoveAllByKeyword(this DbSet<Quote> quotes, ulong guildId, string keyword)

View File

@@ -48,7 +48,8 @@ public enum PunishmentAction
RemoveRoles,
ChatMute,
VoiceMute,
AddRole
AddRole,
Warn
}
public class AntiSpamIgnore : DbEntity

View File

@@ -4,7 +4,7 @@ namespace NadekoBot.Services.Database.Models;
public class LogSetting : DbEntity
{
public List<IgnoredLogItem> LogIgnores { get; set; } = new();
public ulong GuildId { get; set; }
public ulong? LogOtherId { get; set; }
public ulong? MessageUpdatedId { get; set; }
@@ -29,4 +29,5 @@ public class LogSetting : DbEntity
public ulong? LogVoicePresenceId { get; set; }
public ulong? LogVoicePresenceTTSId { get; set; }
public ulong? LogWarnsId { get; set; }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class logwarns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<ulong>(
name: "logwarnsid",
table: "logsettings",
type: "bigint unsigned",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "logwarnsid",
table: "logsettings");
}
}
}

View File

@@ -1406,6 +1406,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("bigint unsigned")
.HasColumnName("logvoicepresencettsid");
b.Property<ulong?>("LogWarnsId")
.HasColumnType("bigint unsigned")
.HasColumnName("logwarnsid");
b.Property<ulong?>("MessageDeletedId")
.HasColumnType("bigint unsigned")
.HasColumnName("messagedeletedid");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class logwarns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "logwarnsid",
table: "logsettings",
type: "numeric(20,0)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "logwarnsid",
table: "logsettings");
}
}
}

View File

@@ -1472,6 +1472,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("numeric(20,0)")
.HasColumnName("logvoicepresencettsid");
b.Property<decimal?>("LogWarnsId")
.HasColumnType("numeric(20,0)")
.HasColumnName("logwarnsid");
b.Property<decimal?>("MessageDeletedId")
.HasColumnType("numeric(20,0)")
.HasColumnName("messagedeletedid");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class logwarns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "StreamOnlineMessages",
type: "TEXT",
nullable: true,
oldClrType: typeof(string),
oldType: "TEXT");
migrationBuilder.AddColumn<ulong>(
name: "LogWarnsId",
table: "LogSettings",
type: "INTEGER",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LogWarnsId",
table: "LogSettings");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "StreamOnlineMessages",
type: "TEXT",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
}
}
}

View File

@@ -278,7 +278,6 @@ namespace NadekoBot.Migrations
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Type")
@@ -1100,6 +1099,9 @@ namespace NadekoBot.Migrations
b.Property<ulong?>("LogVoicePresenceTTSId")
.HasColumnType("INTEGER");
b.Property<ulong?>("LogWarnsId")
.HasColumnType("INTEGER");
b.Property<ulong?>("MessageDeletedId")
.HasColumnType("INTEGER");

View File

@@ -335,6 +335,7 @@ public partial class Administration
{
try
{
await _client.SetStatusAsync(UserStatus.DoNotDisturb);
await ReplyConfirmLocalizedAsync(strs.shutting_down);
}
catch

View File

@@ -26,6 +26,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
private readonly IMemoryCache _memoryCache;
private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = new();
private readonly UserPunishService _punishService;
public LogCommandService(
DiscordSocketClient client,
@@ -35,7 +36,8 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
ProtectionService prot,
GuildTimezoneService tz,
IMemoryCache memoryCache,
IEmbedBuilderService eb)
IEmbedBuilderService eb,
UserPunishService punishService)
{
_client = client;
_memoryCache = memoryCache;
@@ -45,6 +47,8 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_mute = mute;
_prot = prot;
_tz = tz;
_punishService = punishService;
using (var uow = db.GetDbContext())
{
var guildIds = client.Guilds.Select(x => x.Id).ToList();
@@ -78,6 +82,8 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_mute.UserUnmuted += MuteCommands_UserUnmuted;
_prot.OnAntiProtectionTriggered += TriggeredAntiProtection;
_punishService.OnUserWarned += PunishServiceOnOnUserWarned;
}
public async Task OnReadyAsync()
@@ -183,6 +189,30 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
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));
await logChannel.EmbedAsync(embed);
}
private Task _client_UserUpdated(SocketUser before, SocketUser uAfter)
{
_ = Task.Run(async () =>
@@ -296,6 +326,9 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
channelId = logSetting.LogVoicePresenceTTSId =
logSetting.LogVoicePresenceTTSId is null ? cid : default;
break;
case LogType.UserWarned:
channelId = logSetting.LogWarnsId = logSetting.LogWarnsId is null ? cid : default;
break;
}
uow.SaveChanges();
@@ -945,11 +978,25 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
ITextChannel? logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null)
return;
string? reason = null;
try
{
var ban = await guild.GetBanAsync(usr);
reason = ban?.Reason;
}
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));
var avatarUrl = usr.GetAvatarUrl();
@@ -1130,6 +1177,9 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
case LogType.UserMuted:
id = logSetting.UserMutedId;
break;
case LogType.UserWarned:
id = logSetting.LogWarnsId;
break;
}
if (id is null or 0)
@@ -1200,6 +1250,9 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
case LogType.VoicePresenceTts:
newLogSetting.LogVoicePresenceTTSId = null;
break;
case LogType.UserWarned:
newLogSetting.LogWarnsId = null;
break;
}
GuildLogSettings.AddOrUpdate(guildId, newLogSetting, (_, _) => newLogSetting);

View File

@@ -341,7 +341,8 @@ public partial class Administration
public async partial Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
{
// this should never happen. Addrole has its own method with higher priority
if (punish == PunishmentAction.AddRole)
// also disallow warn punishment for getting warned
if (punish is PunishmentAction.AddRole or PunishmentAction.Warn)
return;
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time);

View File

@@ -18,6 +18,8 @@ public class UserPunishService : INService, IReadyExecutor
private readonly BotConfigService _bcs;
private readonly DiscordSocketClient _client;
public event Func<Warning, Task> OnUserWarned = static delegate { return Task.CompletedTask; };
public UserPunishService(
MuteService mute,
DbService db,
@@ -93,6 +95,8 @@ public class UserPunishService : INService, IReadyExecutor
await uow.SaveChangesAsync();
}
_ = OnUserWarned(warn);
var totalCount = previousCount + weight;
var p = ps.Where(x => x.Count > previousCount && x.Count <= totalCount)
@@ -185,6 +189,9 @@ public class UserPunishService : INService, IReadyExecutor
guild.Id);
}
break;
case PunishmentAction.Warn:
await Warn(guild, user.Id, mod, 1, reason);
break;
}
}

View File

@@ -44,7 +44,7 @@ public partial class Gambling
var currentCard = cards.Draw();
cardObjects.Add(currentCard);
var cardName = currentCard.ToString().ToLowerInvariant().Replace(' ', '_');
images.Add(Image.Load(await File.ReadAllBytesAsync($"data/images/cards/{cardName}.png")));
images.Add(Image.Load(await File.ReadAllBytesAsync($"data/images/cards/{cardName}.jpg")));
}
using var img = images.Merge();

View File

@@ -7,6 +7,7 @@ using NadekoBot.Modules.Utility.Patronage;
using NadekoBot.Modules.Gambling.Bank;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Utility.Services;
using NadekoBot.Services.Currency;
using NadekoBot.Services.Database.Models;
using System.Collections.Immutable;
@@ -44,6 +45,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly GamblingConfigService _configService;
private readonly IBankService _bank;
private readonly IPatronageService _ps;
private readonly RemindService _remind;
private IUserMessage rdMsg;
@@ -54,7 +56,8 @@ public partial class Gambling : GamblingModule<GamblingService>
DownloadTracker tracker,
GamblingConfigService configService,
IBankService bank,
IPatronageService ps)
IPatronageService ps,
RemindService remind)
: base(configService)
{
_db = db;
@@ -62,6 +65,7 @@ public partial class Gambling : GamblingModule<GamblingService>
_client = client;
_bank = bank;
_ps = ps;
_remind = remind;
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0;
@@ -109,7 +113,40 @@ public partial class Gambling : GamblingModule<GamblingService>
Key = "timely:extra_percent",
PrettyName = "Timely"
};
public class RemindMeInteraction : NInteraction
{
public RemindMeInteraction(
[NotNull] DiscordSocketClient client,
ulong userId,
[NotNull] Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
protected override NadekoInteractionData Data
=> new NadekoInteractionData(
Emote: Emoji.Parse("⏰"),
CustomId: "timely:remind_me",
Text: "Remind me"
);
}
private Func<SocketMessageComponent, Task> RemindTimelyAction(DateTime when)
=> async smc =>
{
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
await _remind.AddReminderAsync(ctx.User.Id,
ctx.Channel.Id,
ctx.Guild.Id,
true,
when,
GetText(strs.timely_time));
await smc.RespondConfirmAsync(_eb, GetText(strs.remind_timely(tt)), ephemeral: true);
};
[Cmd]
public async partial Task Timely()
{
@@ -135,7 +172,11 @@ public partial class Gambling : GamblingModule<GamblingService>
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period));
var inter = new RemindMeInteraction(_client,
ctx.User.Id,
RemindTimelyAction(DateTime.UtcNow.Add(TimeSpan.FromHours(period))));
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period), inter.GetInteraction());
}
[Cmd]

View File

@@ -16,7 +16,7 @@ namespace NadekoBot.Modules.Gambling.Services;
public class PlantPickService : INService, IExecNoCommand
{
//channelId/last generation
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new();
public ConcurrentDictionary<ulong, long> LastGenerations { get; } = new();
private readonly DbService _db;
private readonly IBotStrings _strings;
private readonly IImageCache _images;
@@ -175,15 +175,15 @@ public class PlantPickService : INService, IExecNoCommand
try
{
var config = _gss.Data;
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue.ToBinary());
var rng = new NadekoRandom();
if (DateTime.UtcNow - TimeSpan.FromSeconds(config.Generation.GenCooldown)
< lastGeneration) //recently generated in this channel, don't generate again
< DateTime.FromBinary(lastGeneration)) //recently generated in this channel, don't generate again
return;
var num = rng.Next(1, 101) + (config.Generation.Chance * 100);
if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow, lastGeneration))
if (num > 100 && LastGenerations.TryUpdate(channel.Id, DateTime.UtcNow.ToBinary(), lastGeneration))
{
var dropAmount = config.Generation.MinAmount;
var dropAmountMax = config.Generation.MaxAmount;

View File

@@ -19,14 +19,14 @@ public class TriviaQuestionPool
{
var pokes = await _cache.GetPokemonMapAsync();
if (pokes is null or { Length: 0 })
if (pokes is null or { Count: 0 })
return default;
var num = _rng.Next(1, _maxPokemonId + 1);
return new(new()
{
Question = "Who's That Pokémon?",
Answer = pokes[num].Name.ToTitleCase(),
Answer = pokes[num].ToTitleCase(),
Category = "Pokemon",
ImageUrl = $@"https://nadeko.bot/images/pokemon/shadows/{num}.png",
AnswerImageUrl = $@"https://nadeko.bot/images/pokemon/real/{num}.png"

View File

@@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
namespace NadekoBot.Modules.Nsfw;
#if !GLOBAL_NADEKO
[NoPublicBot]
public partial class NSFW : NadekoModule<ISearchImagesService>
{
@@ -436,4 +437,5 @@ public partial class NSFW : NadekoModule<ISearchImagesService>
.WithFooter(
$"{data.Rating} ({data.Provider}) | {string.Join(" | ", data.Tags.Where(x => !string.IsNullOrWhiteSpace(x)).Take(5))}"));
}
}
}
#endif

View File

@@ -50,7 +50,7 @@ public sealed class FilterService : IExecOnMessage
new(configs.SelectMany(gc => gc.FilterLinksChannelIds.Select(fci => fci.ChannelId)));
var dict = configs.ToDictionary(gc => gc.GuildId,
gc => new ConcurrentHashSet<string>(gc.FilteredWords.Select(fw => fw.Word)));
gc => new ConcurrentHashSet<string>(gc.FilteredWords.Select(fw => fw.Word).Distinct()));
ServerFilteredWords = new(dict);

View File

@@ -27,7 +27,8 @@ public class CommandMapService : IInputTransformer, INService
AliasMaps = new(configs.ToDictionary(x => x.GuildId,
x => new ConcurrentDictionary<string, string>(x.CommandAliases.DistinctBy(ca => ca.Trigger)
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping),
StringComparer.OrdinalIgnoreCase)));
_db = db;
}
@@ -53,37 +54,76 @@ public class CommandMapService : IInputTransformer, INService
{
if (guild is null || string.IsNullOrWhiteSpace(input))
return null;
if (AliasMaps.TryGetValue(guild.Id, out var maps))
{
var keys = maps.Keys.OrderByDescending(x => x.Length);
foreach (var k in keys)
string word;
var index = input.IndexOf(' ', StringComparison.InvariantCulture);
if (index == -1)
{
string newInput;
if (input.StartsWith(k + " ", StringComparison.InvariantCultureIgnoreCase))
newInput = maps[k] + input.Substring(k.Length, input.Length - k.Length);
else if (input.Equals(k, StringComparison.InvariantCultureIgnoreCase))
newInput = maps[k];
else
continue;
try
{
var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
_ = Task.Run(async () =>
{
await Task.Delay(1500);
await toDelete.DeleteAsync(new()
{
RetryMode = RetryMode.AlwaysRetry
});
});
}
catch { }
return newInput;
word = input;
}
else
{
word = input[..index];
}
string newInput;
if (maps.TryGetValue(word, out var alias))
{
if (index == -1)
newInput = alias;
else
newInput = alias + ' ' + input[index..];
}
else
{
return null;
}
try
{
var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
_ = Task.Run(async () =>
{
await Task.Delay(1500);
await toDelete.DeleteAsync(new()
{
RetryMode = RetryMode.AlwaysRetry
});
});
}
catch { }
return newInput;
// var keys = maps.Keys.OrderByDescending(x => x.Length);
// foreach (var k in keys)
// {
// string newInput;
// if (input.StartsWith(k + " ", StringComparison.InvariantCultureIgnoreCase))
// newInput = maps[k] + input.Substring(k.Length, input.Length - k.Length);
// else if (input.Equals(k, StringComparison.InvariantCultureIgnoreCase))
// newInput = maps[k];
// else
// continue;
//
// try
// {
// var toDelete = await channel.SendConfirmAsync(_eb, $"{input} => {newInput}");
// _ = Task.Run(async () =>
// {
// await Task.Delay(1500);
// await toDelete.DeleteAsync(new()
// {
// RetryMode = RetryMode.AlwaysRetry
// });
// });
// }
// catch { }
//
// return newInput;
// }
}
return null;

View File

@@ -35,6 +35,7 @@ public sealed class PatronageService
= new($"quota:last_hourly_reset");
private readonly IBotCache _cache;
private readonly IBotCredsProvider _creds;
public PatronageService(
PatronageConfig pConf,
@@ -42,7 +43,8 @@ public sealed class PatronageService
DiscordSocketClient client,
ISubscriptionHandler subsHandler,
IEmbedBuilderService eb,
IBotCache cache)
IBotCache cache,
IBotCredsProvider creds)
{
_pConf = pConf;
_db = db;
@@ -50,6 +52,7 @@ public sealed class PatronageService
_subsHandler = subsHandler;
_eb = eb;
_cache = cache;
_creds = creds;
}
public Task OnReadyAsync()
@@ -495,8 +498,8 @@ public sealed class PatronageService
if (!confData.IsEnabled)
return default;
// if (_creds.IsOwner(userId))
// return default;
if (_creds.GetCreds().IsOwner(userId))
return default;
// get user tier
var patron = await GetPatronAsync(userId);

View File

@@ -1,4 +1,4 @@
#nullable disable
#nullable disable warnings
using NadekoBot.Common.Yml;
using NadekoBot.Db;
using NadekoBot.Services.Database.Models;
@@ -134,30 +134,40 @@ public partial class Utility
.WithFooter(
GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})"))));
[Cmd]
[RequireContext(ContextType.Guild)]
public async partial Task QuoteSearch(string keyword, [Leftover] string text)
private async Task QuoteSearchinternalAsync(string? keyword, string textOrAuthor)
{
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
if (string.IsNullOrWhiteSpace(textOrAuthor))
return;
keyword = keyword.ToUpperInvariant();
keyword = keyword?.ToUpperInvariant();
Quote keywordquote;
Quote quote;
await using (var uow = _db.GetDbContext())
{
keywordquote = await uow.Quotes.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, text);
quote = await uow.Quotes.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, textOrAuthor);
}
if (keywordquote is null)
if (quote is null)
return;
await ctx.Channel.SendMessageAsync($"`#{keywordquote.Id}` 💬 "
+ keyword.ToLowerInvariant()
await ctx.Channel.SendMessageAsync($"`#{quote.Id}` 💬 "
+ quote.Keyword.ToLowerInvariant()
+ ": "
+ keywordquote.Text.SanitizeAllMentions());
+ quote.Text.SanitizeAllMentions());
}
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public partial Task QuoteSearch(string textOrAuthor)
=> QuoteSearchinternalAsync(null, textOrAuthor);
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public partial Task QuoteSearch(string keyword, [Leftover] string textOrAuthor)
=> QuoteSearchinternalAsync(keyword, textOrAuthor);
[Cmd]
[RequireContext(ContextType.Guild)]
public async partial Task QuoteId(int id)

View File

@@ -178,4 +178,26 @@ public class RemindService : INService, IReadyExecutor
public string What { get; set; }
public TimeSpan Time { get; set; }
}
public async Task AddReminderAsync(ulong userId,
ulong channelId,
ulong? guildId,
bool isPrivate,
DateTime time,
string message)
{
var rem = new Reminder
{
UserId = userId,
ChannelId = channelId,
ServerId = guildId ?? 0,
IsPrivate = isPrivate,
When = time,
Message = message,
};
await using var ctx = _db.GetDbContext();
await ctx.Reminders
.AddAsync(rem);
}
}

View File

@@ -118,7 +118,7 @@
<None Update="nadeko_icon.ico;libopus.so;libsodium.so;libsodium.dll;opus.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="creds.yml">
<None Update="creds.yml;creds_example.yml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

View File

@@ -9,5 +9,5 @@ public interface ILocalDataCache
Task<IReadOnlyDictionary<string, SearchPokemon>> GetPokemonsAsync();
Task<IReadOnlyDictionary<string, SearchPokemonAbility>> GetPokemonAbilitiesAsync();
Task<TriviaQuestionModel[]> GetTriviaQuestionsAsync();
Task<PokemonNameId[]> GetPokemonMapAsync();
Task<IReadOnlyDictionary<int, string>> GetPokemonMapAsync();
}

View File

@@ -67,11 +67,37 @@ public sealed class LocalDataCache : ILocalDataCache, INService
=> await GetOrCreateCachedDataAsync(_pokemonAbilitiesKey, POKEMON_ABILITIES_FILE);
private static TypedKey<PokemonNameId[]> _pokeMapKey
= new("pokemon:ab_map");
private static TypedKey<IReadOnlyDictionary<int, string>> _pokeMapKey
= new("pokemon:ab_map2"); // 2 because ab_map was storing arrays
public async Task<PokemonNameId[]?> GetPokemonMapAsync()
=> await GetOrCreateCachedDataAsync(_pokeMapKey, POKEMON_MAP_PATH);
public async Task<IReadOnlyDictionary<int, string>?> GetPokemonMapAsync()
=> await _cache.GetOrAddAsync(_pokeMapKey,
async () =>
{
var fileName = POKEMON_MAP_PATH;
if (!File.Exists(fileName))
{
Log.Warning($"{fileName} is missing. Relevant data can't be loaded");
return default;
}
try
{
await using var stream = File.OpenRead(fileName);
var arr = await JsonSerializer.DeserializeAsync<PokemonNameId[]>(stream, _opts);
return (IReadOnlyDictionary<int, string>?)arr?.ToDictionary(x => x.Id, x => x.Name);
}
catch (Exception ex)
{
Log.Error(ex,
"Error reading {FileName} file: {ErrorMessage}",
fileName,
ex.Message);
return default;
}
});
private static TypedKey<TriviaQuestionModel[]> _triviaKey

View File

@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
public sealed class StatsService : IStatsService, IReadyExecutor, INService
{
public const string BOT_VERSION = "4.2.8";
public const string BOT_VERSION = "4.2.14";
public string Author
=> "Kwoth#2452";

View File

@@ -58,7 +58,8 @@ public static class SocketMessageComponentExtensions
bool ephemeral = false)
=> smc.RespondAsync(plainText,
embed: embed?.Build(),
embeds: embeds?.Map(x => x.Build()));
embeds: embeds?.Map(x => x.Build()),
ephemeral: ephemeral);
public static Task RespondAsync(
this SocketMessageComponent ch,

View File

@@ -601,9 +601,12 @@ quoteshow:
args:
- "123"
quotesearch:
desc: "Shows a random quote for a keyword that contains any text specified in the search."
desc: "Shows a random quote given a search query. Partially matches in several ways: 1) Only content of any quote, 2) only by author, 3) keyword and content, 3) or keyword and author"
args:
- "keyword text"
- "\"find this long text\""
- "AuthorName"
- "keyword some text"
- "keyword AuthorName"
quoteid:
desc: "Displays the quote with the specified ID number. Quote ID numbers can be found by typing `{0}liqu [num]` where `[num]` is a number of a page which contains 15 quotes."
args:

View File

@@ -596,6 +596,7 @@
"quote_deleted": "Quote #{0} deleted.",
"region": "Region",
"remind": "I will remind {0} to {1} in {2} `({3:d.M.yyyy.} at {4:HH:mm})`",
"remind_timely": "I will remind you about your timely reward {0}",
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
"remind_too_long": "Remind time has exceeded maximum.",
"repeater_redundant_no": "Repeater **#{0}** won't post redundant messages anymore.",
@@ -883,6 +884,7 @@
"timely_set": "Users will be able to claim {0} every {1}h",
"timely_set_none": "Users will not be able to claim any timely currency.",
"timely_reset": "All users will be able to claim timely currency again.",
"timely_time": "It's time for your timely reward.",
"price": "Price",
"market_cap": "Market Cap",
"market_cap_dominance": "Dominance",