mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d51dfa88f1 | ||
|
869b9d3b9d | ||
|
f4b26c5b40 | ||
|
15f629ec53 | ||
|
9406a9cc34 | ||
|
52438f45e1 | ||
|
7b2ce072ee | ||
|
89d93dcffb | ||
|
0fb34b1c61 | ||
|
980a6b0af8 | ||
|
5c7a467caa | ||
|
35fd5c415d | ||
|
2762108986 | ||
|
876d63fd8b | ||
|
263ef4b47f | ||
|
1c540476d3 | ||
|
1d0f3d3fd6 | ||
|
c7cec25a29 | ||
|
23c8dc00e8 | ||
|
0da8190637 | ||
|
d766295286 | ||
|
21e3c64e01 | ||
|
9ddcd6d89e | ||
|
a154d5881c | ||
|
fb594e50fd | ||
|
75c5a003bf |
22
CHANGELOG.md
22
CHANGELOG.md
@@ -2,7 +2,27 @@
|
||||
|
||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## [5.0.3] - 10.05.2024
|
||||
## [5.0.6] - 14.05.2024
|
||||
|
||||
### Changed
|
||||
|
||||
- `.greet` and `.bye` will now be automatically disabled if the bot losses permissions to post in the specified channel
|
||||
- Removed response replies from `.blackjack` and `.pick` as the original message will always be deleted
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.blackjack` response string as it contained no user name
|
||||
- Fixed `.ttt` and `.gift` strings not mentioning the user
|
||||
|
||||
## [5.0.5] - 11.05.2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- `%server.members%` placeholder fixed
|
||||
- `.say #channel <message>` should now be working properly again
|
||||
- `.repeat`, `.greet`, `.bye` and `.boost` command can now once again mention anyone
|
||||
|
||||
## [5.0.4] - 10.05.2024
|
||||
|
||||
### Added
|
||||
|
||||
|
@@ -1,23 +0,0 @@
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
nadeko:
|
||||
image: insert-image-name-here:latest
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
TZ: Europe/Paris
|
||||
NadekoBot_RedisOptions: redis,name=nadeko
|
||||
#NadekoBot_ShardRunCommand: dotnet
|
||||
#NadekoBot_ShardRunArguments: /app/NadekoBot.dll {0} {1}
|
||||
volumes:
|
||||
- /srv/nadeko/conf:/app/conf:ro
|
||||
- /srv/nadeko/data:/app/data
|
||||
|
||||
redis:
|
||||
image: redis:4-alpine
|
||||
sysctls:
|
||||
- net.core.somaxconn=511
|
||||
command: redis-server --maxmemory 32M --maxmemory-policy volatile-lru
|
||||
volumes:
|
||||
- /srv/nadeko/redis-data:/data
|
@@ -15,11 +15,13 @@
|
||||
|
||||
##### Compatible operating systems:
|
||||
|
||||
- Ubuntu: 20.04, 22.04, 22.10 +
|
||||
- Debian: 11 +
|
||||
- CentOS: 7
|
||||
- openSUSE 15
|
||||
- Fedora: 33, 34, 35
|
||||
- Ubuntu: 20.04, 21.04, 21.10, 22.04
|
||||
- Mint: 19, 20
|
||||
- Debian: 10, 11, 12
|
||||
- RockyLinux: 8, 9
|
||||
- AlmaLinux: 8, 9
|
||||
- openSUSE Leap: 15.5, 15.6 & Tumbleweed
|
||||
- Fedora: 38, 39, 40, 41, 42
|
||||
|
||||
## Linux From Source
|
||||
|
||||
|
@@ -93,8 +93,12 @@ Open PowerShell as described above and run the following commands:
|
||||
- ⚠️ Make sure you don't have your database, credentials or any other nadekobot folder open in some application, this might prevent some of the steps from executing succesfully
|
||||
2. Navigate to your bot's folder, example:
|
||||
- `cd ~/Desktop/nadekobot`
|
||||
3. Pull the new version
|
||||
- `git pull`
|
||||
3. Pull the new version, and make sure you're on the v5 branch
|
||||
- *⚠️ the first 3 lines can be omitted if you're already on v5. If you're updating from v4, you must run them*
|
||||
- `git remote set-branches origin '*'`
|
||||
- `git fetch -v --depth=1`
|
||||
- `git checkout v5`
|
||||
- `git pull`
|
||||
- ⚠️ If this fails, you may want to stash or remove your code changes if you don't know how to resolve merge conflicts
|
||||
4. **Backup** old output in case your data is overwritten
|
||||
- `cp -r -fo output/ output-old`
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net.Core" Version="3.204.0" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.2" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Version)' == '' ">
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.2" />
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -417,8 +417,7 @@ namespace NadekoBot.Coordinator
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
if (shardId >= _shardStatuses.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(shardId));
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(shardId, _shardStatuses.Length);
|
||||
|
||||
return _shardStatuses[shardId];
|
||||
}
|
||||
|
@@ -80,10 +80,10 @@ public sealed class Bot : IBot
|
||||
// _interactionService = new(Client.Rest);
|
||||
|
||||
Client.Log += Client_Log;
|
||||
_loadedAssemblies = new[]
|
||||
{
|
||||
typeof(Bot).Assembly, // bot
|
||||
};
|
||||
_loadedAssemblies =
|
||||
[
|
||||
typeof(Bot).Assembly // bot
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
@@ -7,19 +7,20 @@ namespace NadekoBot.Db;
|
||||
public static class GuildConfigExtensions
|
||||
{
|
||||
private static List<WarningPunishment> DefaultWarnPunishments
|
||||
=> new()
|
||||
{
|
||||
=>
|
||||
[
|
||||
new()
|
||||
{
|
||||
Count = 3,
|
||||
Punishment = PunishmentAction.Kick
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Count = 5,
|
||||
Punishment = PunishmentAction.Ban
|
||||
}
|
||||
};
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Gets full stream role settings for the guild with the specified id.
|
||||
|
@@ -22,8 +22,7 @@ public static class WarningExtensions
|
||||
string mod,
|
||||
int index)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
var warn = warnings.AsQueryable()
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
|
@@ -33,10 +33,7 @@ public class Permissionv2 : DbEntity, IIndexed
|
||||
};
|
||||
|
||||
public static List<Permissionv2> GetDefaultPermlist
|
||||
=> new()
|
||||
{
|
||||
AllowAllPerm
|
||||
};
|
||||
=> [AllowAllPerm];
|
||||
}
|
||||
|
||||
public enum PrimaryPermissionType
|
||||
|
@@ -97,7 +97,7 @@ public class GreetService : INService, IReadyExecutor
|
||||
{
|
||||
var newContent = await _repSvc.ReplaceAsync(toSend,
|
||||
new(client: _client, guild: user.Guild, channel: channel, users: user));
|
||||
var toDelete = await _sender.Response(channel).Text(newContent).SendAsync();
|
||||
var toDelete = await _sender.Response(channel).Text(newContent).Sanitize(false).SendAsync();
|
||||
if (conf.BoostMessageDeleteAfter > 0)
|
||||
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
||||
|
||||
@@ -202,12 +202,6 @@ public class GreetService : INService, IReadyExecutor
|
||||
if (!users.Any())
|
||||
return;
|
||||
|
||||
// var rep = new ReplacementBuilder().WithChannel(channel)
|
||||
// .WithClient(_client)
|
||||
// .WithServer(_client, (SocketGuild)channel.Guild)
|
||||
// .WithManyUsers(users)
|
||||
// .Build();
|
||||
|
||||
var repCtx = new ReplacementContext(client: _client,
|
||||
guild: channel.Guild,
|
||||
channel: channel,
|
||||
@@ -217,11 +211,12 @@ public class GreetService : INService, IReadyExecutor
|
||||
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||
try
|
||||
{
|
||||
var toDelete = await _sender.Response(channel).Text(text).SendAsync();
|
||||
var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync();
|
||||
if (conf.AutoDeleteByeMessagesTimer > 0)
|
||||
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
||||
}
|
||||
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|
||||
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions
|
||||
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
||||
{
|
||||
Log.Warning(ex,
|
||||
@@ -243,26 +238,21 @@ public class GreetService : INService, IReadyExecutor
|
||||
if (users.Count == 0)
|
||||
return;
|
||||
|
||||
// var rep = new ReplacementBuilder()
|
||||
// .WithChannel(channel)
|
||||
// .WithClient(_client)
|
||||
// .WithServer(_client, (SocketGuild)channel.Guild)
|
||||
// .WithManyUsers(users)
|
||||
// .Build();
|
||||
|
||||
var repCtx = new ReplacementContext(client: _client,
|
||||
guild: channel.Guild,
|
||||
channel: channel,
|
||||
users: users.ToArray());
|
||||
|
||||
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
|
||||
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||
try
|
||||
{
|
||||
var toDelete = await _sender.Response(channel).Text(text).SendAsync();
|
||||
var toDelete = await _sender.Response(channel).Text(text).Sanitize(false).SendAsync();
|
||||
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
||||
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
||||
}
|
||||
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|
||||
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions
|
||||
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
||||
{
|
||||
Log.Warning(ex,
|
||||
@@ -329,13 +319,13 @@ public class GreetService : INService, IReadyExecutor
|
||||
// if there are no embeds, add an embed with the footer
|
||||
smartText = seta with
|
||||
{
|
||||
Embeds = new[]
|
||||
{
|
||||
Embeds =
|
||||
[
|
||||
new SmartEmbedArrayElementText()
|
||||
{
|
||||
Footer = CreateFooterSource(user)
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -360,7 +350,7 @@ public class GreetService : INService, IReadyExecutor
|
||||
}
|
||||
}
|
||||
|
||||
await _sender.Response(user).Text(smartText).SendAsync();
|
||||
await _sender.Response(user).Text(smartText).Sanitize(false).SendAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -573,8 +563,6 @@ public class GreetService : INService, IReadyExecutor
|
||||
|
||||
public bool SetBoostMessage(ulong guildId, ref string message)
|
||||
{
|
||||
message = message.SanitizeMentions();
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||
conf.BoostMessage = message;
|
||||
|
@@ -175,7 +175,7 @@ public sealed class SomethingOnlyChannelService : IExecOnMessage
|
||||
// ignore owner and admin
|
||||
if (user.Id == tch.Guild.OwnerId || user.GuildPermissions.Administrator)
|
||||
{
|
||||
Log.Information("{Type}-Only Channel: Ignoring owner od admin ({ChannelId})", type, msg.Channel.Id);
|
||||
Log.Information("{Type}-Only Channel: Ignoring owner or admin ({ChannelId})", type, msg.Channel.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -291,8 +291,7 @@ public class MuteService : INService
|
||||
|
||||
public async Task<IRole> GetMuteRole(IGuild guild)
|
||||
{
|
||||
if (guild is null)
|
||||
throw new ArgumentNullException(nameof(guild));
|
||||
ArgumentNullException.ThrowIfNull(guild);
|
||||
|
||||
const string defaultMuteRoleName = "nadeko-mute";
|
||||
|
||||
|
@@ -69,8 +69,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
|
||||
|
||||
public async Task<string> RemovePlayingAsync(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toRemove = await uow.Set<RotatingPlayingStatus>().AsQueryable().AsNoTracking().Skip(index).FirstOrDefaultAsync();
|
||||
|
@@ -22,8 +22,7 @@ public class PruneService : INService
|
||||
ArgumentNullException.ThrowIfNull(channel, nameof(channel));
|
||||
|
||||
var originalAmount = amount;
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
||||
using var cancelSource = new CancellationTokenSource();
|
||||
if (!_pruningGuilds.TryAdd(channel.GuildId, cancelSource))
|
||||
|
@@ -250,11 +250,9 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
||||
int group = 0,
|
||||
int levelReq = 0)
|
||||
{
|
||||
if (group < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(group));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(group);
|
||||
|
||||
if (levelReq < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(group));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(levelReq);
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
@@ -307,10 +305,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_cache.AddOrUpdate(msg.Id,
|
||||
_ => new()
|
||||
{
|
||||
obj
|
||||
},
|
||||
_ => [obj],
|
||||
(_, list) =>
|
||||
{
|
||||
list.RemoveAll(x => x.Emote == emote);
|
||||
|
@@ -24,7 +24,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
||||
private readonly GuildTimezoneService _tz;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
|
||||
private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = new();
|
||||
private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = [];
|
||||
private readonly UserPunishService _punishService;
|
||||
private readonly IMessageSenderService _sender;
|
||||
|
||||
@@ -115,10 +115,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
||||
strs.user_status_change("👤" + Format.Bold(gu.Username),
|
||||
Format.Bold(after.Status.ToString())));
|
||||
PresenceUpdates.AddOrUpdate(logChannel,
|
||||
new List<string>
|
||||
{
|
||||
str
|
||||
},
|
||||
[str],
|
||||
(_, list) =>
|
||||
{
|
||||
list.Add(str);
|
||||
@@ -130,10 +127,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
||||
var str =
|
||||
$"👾`{PrettyCurrentTime(gu.Guild)}`👤__**{gu.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**.";
|
||||
PresenceUpdates.AddOrUpdate(logChannel,
|
||||
new List<string>
|
||||
{
|
||||
str
|
||||
},
|
||||
[str],
|
||||
(_, list) =>
|
||||
{
|
||||
list.Add(str);
|
||||
@@ -881,10 +875,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
||||
if (!string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
PresenceUpdates.AddOrUpdate(logChannel,
|
||||
new List<string>
|
||||
{
|
||||
str
|
||||
},
|
||||
[str],
|
||||
(_, list) =>
|
||||
{
|
||||
list.Add(str);
|
||||
|
@@ -64,8 +64,7 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
long weight,
|
||||
string reason)
|
||||
{
|
||||
if (weight <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(weight));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(weight);
|
||||
|
||||
var modName = mod.ToString();
|
||||
|
||||
|
@@ -130,8 +130,7 @@ public class VcRoleService : INService
|
||||
|
||||
public void AddVcRole(ulong guildId, IRole role, ulong vcId)
|
||||
{
|
||||
if (role is null)
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
ArgumentNullException.ThrowIfNull(role);
|
||||
|
||||
var guildVcRoles = VcRoles.GetOrAdd(guildId, new ConcurrentDictionary<ulong, IRole>());
|
||||
|
||||
|
@@ -356,7 +356,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
if (maybeGuildId is { } guildId)
|
||||
{
|
||||
newguildExpressions.AddOrUpdate(guildId,
|
||||
new[] { expr },
|
||||
[expr],
|
||||
(_, old) =>
|
||||
{
|
||||
var newArray = old.ToArray();
|
||||
@@ -389,7 +389,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
|
||||
|
||||
if (maybeGuildId is { } guildId)
|
||||
newguildExpressions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
|
||||
newguildExpressions.AddOrUpdate(guildId, [expr], (_, old) => old.With(expr));
|
||||
else
|
||||
return _pubSub.Pub(_gexprAddedKey, expr);
|
||||
|
||||
|
@@ -61,8 +61,7 @@ public sealed class AnimalRace : IDisposable
|
||||
|
||||
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
||||
{
|
||||
if (bet < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(bet);
|
||||
|
||||
var user = new AnimalRacingUser(userName, userId, bet);
|
||||
|
||||
|
@@ -17,8 +17,7 @@ public sealed class BankService : IBankService, INService
|
||||
|
||||
public async Task<bool> AwardAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.GetTable<BankUser>()
|
||||
@@ -41,9 +40,8 @@ public sealed class BankService : IBankService, INService
|
||||
|
||||
public async Task<bool> TakeAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.Set<BankUser>()
|
||||
.ToLinqToDBTable()
|
||||
@@ -58,9 +56,8 @@ public sealed class BankService : IBankService, INService
|
||||
|
||||
public async Task<bool> DepositAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
||||
if (!await _cur.RemoveAsync(userId, amount, new("bank", "deposit")))
|
||||
return false;
|
||||
|
||||
@@ -86,9 +83,8 @@ public sealed class BankService : IBankService, INService
|
||||
|
||||
public async Task<bool> WithdrawAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.Set<BankUser>()
|
||||
.ToLinqToDBTable()
|
||||
|
@@ -50,12 +50,12 @@ public partial class Gambling
|
||||
bj.GameEnded += Bj_GameEnded;
|
||||
bj.Start();
|
||||
|
||||
await Response().Confirm(strs.bj_created).SendAsync();
|
||||
await Response().NoReply().Confirm(strs.bj_created(ctx.User.ToString())).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await bj.Join(ctx.User, amount))
|
||||
await Response().Confirm(strs.bj_joined).SendAsync();
|
||||
await Response().NoReply().Confirm(strs.bj_joined(ctx.User.ToString())).SendAsync();
|
||||
else
|
||||
{
|
||||
Log.Information("{User} can't join a blackjack game as it's in {BlackjackState} state already",
|
||||
|
@@ -49,8 +49,7 @@ public class User : Player
|
||||
|
||||
public User(IUser user, long bet)
|
||||
{
|
||||
if (bet <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bet);
|
||||
|
||||
Bet = bet;
|
||||
DiscordUser = user;
|
||||
|
@@ -12,9 +12,9 @@ public partial class Gambling
|
||||
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
|
||||
{
|
||||
private static readonly string[] _numbers =
|
||||
{
|
||||
[
|
||||
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"
|
||||
};
|
||||
];
|
||||
|
||||
private int RepostCounter
|
||||
{
|
||||
|
@@ -16,7 +16,7 @@ public partial class Gambling
|
||||
|
||||
private static readonly Regex _fudgeRegex = new(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
|
||||
|
||||
private static readonly char[] _fateRolls = { '-', ' ', '+' };
|
||||
private static readonly char[] _fateRolls = ['-', ' ', '+'];
|
||||
private readonly IImageCache _images;
|
||||
|
||||
public DiceRollCommands(IImageCache images)
|
||||
|
@@ -466,7 +466,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return;
|
||||
}
|
||||
|
||||
await Response().Confirm(strs.gifted(N(amount), Format.Bold(receiver.ToString()))).SendAsync();
|
||||
await Response().Confirm(strs.gifted(N(amount), Format.Bold(receiver.ToString()), ctx.User)).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -508,7 +508,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
|
||||
await _cs.AddAsync(usr.Id, amount, new("award", ctx.User.ToString()!, msg, ctx.User.Id));
|
||||
await Response().Confirm(strs.awarded(N(amount), $"<@{usrId}>")).SendAsync();
|
||||
await Response().Confirm(strs.awarded(N(amount), $"<@{usrId}>", ctx.User)).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -776,7 +776,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var cleanRichest = await uow.Set<DiscordUser>()
|
||||
.GetTopRichest(_client.CurrentUser.Id, 0, 10_000);
|
||||
.GetTopRichest(_client.CurrentUser.Id, 0, 1000);
|
||||
|
||||
var sg = (SocketGuild)ctx.Guild!;
|
||||
return cleanRichest.Where(x => sg.GetUser(x.UserId) is not null).ToList();
|
||||
@@ -787,10 +787,14 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return await uow.Set<DiscordUser>().GetTopRichest(_client.CurrentUser.Id, curPage);
|
||||
}
|
||||
}
|
||||
|
||||
var res = Response()
|
||||
.Paginated();
|
||||
|
||||
await Response()
|
||||
.Paginated()
|
||||
.PageItems(GetTopRichest)
|
||||
.TotalElements(900)
|
||||
.PageSize(9)
|
||||
.CurrentPage(page)
|
||||
.Page((toSend, curPage) =>
|
||||
|
@@ -131,8 +131,8 @@ public partial class BetRollConfig
|
||||
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
|
||||
|
||||
public BetRollConfig()
|
||||
=> Pairs = new BetRollPair[]
|
||||
{
|
||||
=> Pairs =
|
||||
[
|
||||
new()
|
||||
{
|
||||
WhenAbove = 99,
|
||||
@@ -148,7 +148,7 @@ public partial class BetRollConfig
|
||||
WhenAbove = 66,
|
||||
MultiplyBy = 2
|
||||
}
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
@@ -207,7 +207,7 @@ public partial class LuckyLadderSettings
|
||||
public decimal[] Multipliers { get; set; }
|
||||
|
||||
public LuckyLadderSettings()
|
||||
=> Multipliers = new[] { 2.4M, 1.7M, 1.5M, 1.2M, 0.5M, 0.3M, 0.2M, 0.1M };
|
||||
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.2M, 0.5M, 0.3M, 0.2M, 0.1M];
|
||||
}
|
||||
|
||||
[Cloneable]
|
||||
@@ -228,11 +228,11 @@ public sealed partial class WaifuConfig
|
||||
List of items available for gifting.
|
||||
If negative is true, gift will instead reduce waifu value.
|
||||
""")]
|
||||
public List<WaifuItemModel> Items { get; set; } = new();
|
||||
public List<WaifuItemModel> Items { get; set; } = [];
|
||||
|
||||
public WaifuConfig()
|
||||
=> Items = new()
|
||||
{
|
||||
=> Items =
|
||||
[
|
||||
new("🥔", 5, "Potato"),
|
||||
new("🍪", 10, "Cookie"),
|
||||
new("🥖", 20, "Bread"),
|
||||
@@ -269,7 +269,7 @@ public sealed partial class WaifuConfig
|
||||
new("🚁", 20000, "Helicopter"),
|
||||
new("🚀", 30000, "Spaceship"),
|
||||
new("🌕", 50000, "Moon")
|
||||
};
|
||||
];
|
||||
|
||||
public class WaifuDecayConfig
|
||||
{
|
||||
|
@@ -24,6 +24,7 @@ public abstract class GamblingModule<TService> : NadekoModule<TService>
|
||||
{
|
||||
if (amount < 1)
|
||||
return false;
|
||||
|
||||
if (amount < Config.MinBet)
|
||||
{
|
||||
await Response().Error(strs.min_bet_limit(Format.Bold(Config.MinBet.ToString()) + CurrencySign)).SendAsync();
|
||||
|
@@ -27,7 +27,7 @@ public partial class Gambling
|
||||
|
||||
if (picked > 0)
|
||||
{
|
||||
var msg = await Response().Confirm(strs.picked(N(picked))).SendAsync();
|
||||
var msg = await Response().NoReply().Confirm(strs.picked(N(picked))).SendAsync();
|
||||
msg.DeleteAfter(10);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public partial class Gambling
|
||||
{
|
||||
if (--page < 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
|
||||
var enabledIn = _service.GetAllGeneratingChannels();
|
||||
|
||||
return Response()
|
||||
|
@@ -20,10 +20,8 @@ public class ShopService : IShopService, INService
|
||||
|
||||
public async Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (newPrice <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(newPrice));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(newPrice);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
@@ -38,8 +36,8 @@ public class ShopService : IShopService, INService
|
||||
|
||||
public async Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName)
|
||||
{
|
||||
if (index < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newName))
|
||||
throw new ArgumentNullException(nameof(newName));
|
||||
|
||||
@@ -56,10 +54,8 @@ public class ShopService : IShopService, INService
|
||||
|
||||
public async Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2)
|
||||
{
|
||||
if (index1 < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index1));
|
||||
if (index2 < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(index2));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index1);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index2);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
@@ -76,10 +72,8 @@ public class ShopService : IShopService, INService
|
||||
|
||||
public async Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex)
|
||||
{
|
||||
if (fromIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(fromIndex));
|
||||
if (toIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(toIndex));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(fromIndex);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(toIndex);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
|
@@ -27,10 +27,10 @@ public static class WaifuExtensions
|
||||
|
||||
public static IEnumerable<WaifuLbResult> GetTop(this DbSet<WaifuInfo> waifus, int count, int skip = 0)
|
||||
{
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(count);
|
||||
|
||||
if (count == 0)
|
||||
return new List<WaifuLbResult>();
|
||||
return [];
|
||||
|
||||
return waifus.Include(wi => wi.Waifu)
|
||||
.Include(wi => wi.Affinity)
|
||||
|
@@ -14,5 +14,5 @@ public interface IGamblingService
|
||||
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
||||
Task<FlipResult[]> FlipAsync(int count);
|
||||
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
||||
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? guessValue, byte? guessColor);
|
||||
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor);
|
||||
}
|
@@ -20,9 +20,8 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
|
||||
public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("lula", "bet"));
|
||||
@@ -47,8 +46,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
|
||||
public async Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
@@ -77,11 +75,9 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
|
||||
public async Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess)
|
||||
{
|
||||
if (amount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||
|
||||
if (guess > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(guess));
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(guess, 1);
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
@@ -105,19 +101,18 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? guessValue, byte? guessColor)
|
||||
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor)
|
||||
{
|
||||
if (amount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||
|
||||
if (guessColor is null && guessValue is null)
|
||||
if (maybeGuessColor is null && maybeGuessValue is null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
if (guessColor > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(guessColor));
|
||||
if (maybeGuessColor > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(maybeGuessColor));
|
||||
|
||||
if (guessValue > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(guessValue));
|
||||
if (maybeGuessValue > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(maybeGuessValue));
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
@@ -130,7 +125,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
}
|
||||
|
||||
var game = new BetdrawGame();
|
||||
var result = game.Draw((BetdrawValueGuess?)guessValue, (BetdrawColorGuess?)guessColor, amount);
|
||||
var result = game.Draw((BetdrawValueGuess?)maybeGuessValue, (BetdrawColorGuess?)maybeGuessColor, amount);
|
||||
|
||||
var won = (long)result.Won;
|
||||
if (won > 0)
|
||||
@@ -143,9 +138,8 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
|
||||
public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
|
||||
@@ -170,9 +164,8 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
|
||||
public Task<FlipResult[]> FlipAsync(int count)
|
||||
{
|
||||
if (count < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(count, 1);
|
||||
|
||||
var game = new BetflipGame(0);
|
||||
|
||||
var results = new FlipResult[count];
|
||||
@@ -242,11 +235,8 @@ public sealed class NewGamblingService : IGamblingService, INService
|
||||
|
||||
public async Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick)
|
||||
{
|
||||
if (amount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
if (pick > 2)
|
||||
throw new ArgumentOutOfRangeException(nameof(pick));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(pick, 2);
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
|
@@ -42,7 +42,7 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
private readonly NadekoRandom _rng;
|
||||
|
||||
private readonly HashSet<ulong> _usersWhoVoted = new();
|
||||
private readonly HashSet<ulong> _usersWhoVoted = [];
|
||||
|
||||
public AcrophobiaGame(Options options)
|
||||
{
|
||||
|
@@ -67,7 +67,7 @@ public class ChatterBotService : IExecOnMessage
|
||||
if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey))
|
||||
return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory);
|
||||
|
||||
Log.Information("Cleverbot will not work as the api key is missing.");
|
||||
Log.Information("Cleverbot will not work as the api key is missing");
|
||||
return null;
|
||||
case ChatBotImplementation.Gpt3:
|
||||
if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey))
|
||||
@@ -80,7 +80,7 @@ public class ChatterBotService : IExecOnMessage
|
||||
_client.CurrentUser.Username,
|
||||
_httpFactory);
|
||||
|
||||
Log.Information("Gpt3 will not work as the api key is missing.");
|
||||
Log.Information("Gpt3 will not work as the api key is missing");
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
@@ -128,7 +128,7 @@ public class ChatterBotService : IExecOnMessage
|
||||
var res = await _perms.CheckPermsAsync(sg,
|
||||
usrMsg.Channel,
|
||||
usrMsg.Author,
|
||||
"games",
|
||||
CleverBotResponseStr.CLEVERBOT_RESPONSE,
|
||||
CleverBotResponseStr.CLEVERBOT_RESPONSE);
|
||||
|
||||
if (!res.IsAllowed)
|
||||
|
@@ -24,8 +24,8 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
||||
};
|
||||
|
||||
[Comment("List of responses for the .8ball command. A random one will be selected every time")]
|
||||
public List<string> EightBallResponses { get; set; } = new()
|
||||
{
|
||||
public List<string> EightBallResponses { get; set; } =
|
||||
[
|
||||
"Most definitely yes.",
|
||||
"For sure.",
|
||||
"Totally!",
|
||||
@@ -49,52 +49,59 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
||||
"Don't even think about it.",
|
||||
"Definitely no.",
|
||||
"NO - It may cause disease contraction!"
|
||||
};
|
||||
];
|
||||
|
||||
[Comment("List of animals which will be used for the animal race game (.race)")]
|
||||
public List<RaceAnimal> RaceAnimals { get; set; } = new()
|
||||
{
|
||||
public List<RaceAnimal> RaceAnimals { get; set; } =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Icon = "🐼",
|
||||
Name = "Panda"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Icon = "🐻",
|
||||
Name = "Bear"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Icon = "🐧",
|
||||
Name = "Pengu"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Icon = "🐨",
|
||||
Name = "Koala"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Icon = "🐬",
|
||||
Name = "Dolphin"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Icon = "🐞",
|
||||
Name = "Ladybird"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Icon = "🦀",
|
||||
Name = "Crab"
|
||||
},
|
||||
|
||||
new()
|
||||
{
|
||||
Icon = "🦄",
|
||||
Name = "Unicorn"
|
||||
}
|
||||
};
|
||||
];
|
||||
|
||||
[Comment(@"Which chatbot API should bot use.
|
||||
'cleverbot' - bot will use Cleverbot API.
|
||||
|
@@ -33,8 +33,8 @@ public sealed class NunchiGame : IDisposable
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
private HashSet<(ulong Id, string Name)> participants = new();
|
||||
private readonly HashSet<(ulong Id, string Name)> _passed = new();
|
||||
private HashSet<(ulong Id, string Name)> participants = [];
|
||||
private readonly HashSet<(ulong Id, string Name)> _passed = [];
|
||||
private Timer killTimer;
|
||||
|
||||
public NunchiGame(ulong creatorId, string creatorName)
|
||||
|
@@ -17,9 +17,9 @@ public class TicTacToe
|
||||
private IGuildUser winner;
|
||||
|
||||
private readonly string[] _numbers =
|
||||
{
|
||||
[
|
||||
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:"
|
||||
};
|
||||
];
|
||||
|
||||
private IUserMessage previousMessage;
|
||||
private Timer timeoutTimer;
|
||||
@@ -42,7 +42,7 @@ public class TicTacToe
|
||||
_options = options;
|
||||
_sender = sender;
|
||||
|
||||
_users = new[] { firstUser, null };
|
||||
_users = [firstUser, null];
|
||||
_state = new int?[,] { { null, null, null }, { null, null, null }, { null, null, null } };
|
||||
|
||||
phase = Phase.Starting;
|
||||
|
@@ -37,7 +37,7 @@ public partial class Games
|
||||
|
||||
game = new(Strings, _client, channel, (IGuildUser)ctx.User, options, _sender);
|
||||
_service.TicTacToeGames.Add(channel.Id, game);
|
||||
await Response().Confirm(strs.ttt_created).SendAsync();
|
||||
await Response().Confirm(strs.ttt_created(ctx.User)).SendAsync();
|
||||
|
||||
game.OnEnded += _ =>
|
||||
{
|
||||
|
@@ -9,13 +9,13 @@ public class TriviaQuestion
|
||||
public const int MAX_STRING_LENGTH = 22;
|
||||
|
||||
//represents the min size to judge levDistance with
|
||||
private static readonly HashSet<Tuple<int, int>> _strictness = new()
|
||||
{
|
||||
private static readonly HashSet<Tuple<int, int>> _strictness =
|
||||
[
|
||||
new(9, 0),
|
||||
new(14, 1),
|
||||
new(19, 2),
|
||||
new(22, 3)
|
||||
};
|
||||
];
|
||||
|
||||
public string Category
|
||||
=> _qModel.Category;
|
||||
|
@@ -1,16 +1,13 @@
|
||||
#nullable disable
|
||||
using Amazon.S3;
|
||||
using NadekoBot.Modules.Help.Common;
|
||||
using NadekoBot.Modules.Help.Services;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Nadeko.Common.Medusa;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace NadekoBot.Modules.Help;
|
||||
|
||||
public sealed class Help : NadekoModule<HelpService>
|
||||
public sealed partial class Help : NadekoModule<HelpService>
|
||||
{
|
||||
public const string PATREON_URL = "https://patreon.com/nadekobot";
|
||||
public const string PAYPAL_URL = "https://paypal.me/Kwoth";
|
||||
@@ -72,7 +69,7 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
return;
|
||||
|
||||
var topLevelModules = new List<ModuleInfo>();
|
||||
foreach (var m in _cmds.Modules.GroupBy(x => x.GetTopLevelModule()).Select(x => x.Key))
|
||||
foreach (var m in _cmds.Modules.GroupBy(x => x.GetTopLevelModule()).OrderBy(x => x.Key.Name).Select(x => x.Key))
|
||||
{
|
||||
var result = await _perms.CheckPermsAsync(ctx.Guild,
|
||||
ctx.Channel,
|
||||
@@ -80,6 +77,11 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
m.Name,
|
||||
null);
|
||||
|
||||
#if GLOBAL_NADEKO
|
||||
if (m.Preconditions.Any(x => x is NoPublicBotAttribute))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
if (result.IsAllowed)
|
||||
topLevelModules.Add(m);
|
||||
}
|
||||
@@ -87,6 +89,7 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(topLevelModules)
|
||||
.PageSize(12)
|
||||
.CurrentPage(page)
|
||||
.AddFooter(false)
|
||||
.Page((items, _) =>
|
||||
@@ -99,13 +102,13 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
return embed;
|
||||
}
|
||||
|
||||
items.OrderBy(module => module.Name)
|
||||
.ToList()
|
||||
.ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}",
|
||||
GetModuleDescription(module.Name)
|
||||
+ "\n"
|
||||
+ Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))),
|
||||
true));
|
||||
items
|
||||
.ToList()
|
||||
.ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}",
|
||||
GetModuleDescription(module.Name)
|
||||
+ "\n"
|
||||
+ Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))),
|
||||
true));
|
||||
|
||||
return embed;
|
||||
})
|
||||
@@ -243,13 +246,16 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
var succ = new HashSet<CommandInfo>();
|
||||
if (opts.View != CommandsOptions.ViewType.All)
|
||||
{
|
||||
succ = new((await cmds.Select(async x =>
|
||||
{
|
||||
var pre = await x.CheckPreconditionsAsync(Context, _services);
|
||||
return (Cmd: x, Succ: pre.IsSuccess);
|
||||
})
|
||||
.WhenAll()).Where(x => x.Succ)
|
||||
.Select(x => x.Cmd));
|
||||
succ =
|
||||
[
|
||||
..(await cmds.Select(async x =>
|
||||
{
|
||||
var pre = await x.CheckPreconditionsAsync(Context, _services);
|
||||
return (Cmd: x, Succ: pre.IsSuccess);
|
||||
})
|
||||
.WhenAll()).Where(x => x.Succ)
|
||||
.Select(x => x.Cmd)
|
||||
];
|
||||
|
||||
if (opts.View == CommandsOptions.ViewType.Hide)
|
||||
// if hidden is specified, completely remove these commands from the list
|
||||
@@ -310,7 +316,7 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
{
|
||||
string cmdName;
|
||||
if (cmd.Aliases.Count > 1)
|
||||
cmdName = Format.Code(prefix +cmd.Aliases[0]) + " | " + Format.Code(prefix + cmd.Aliases[1]);
|
||||
cmdName = Format.Code(prefix + cmd.Aliases[0]) + " | " + Format.Code(prefix + cmd.Aliases[1]);
|
||||
else
|
||||
cmdName = Format.Code(prefix + cmd.Aliases.First());
|
||||
|
||||
@@ -354,17 +360,16 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
public async Task H([Leftover] CommandInfo com = null)
|
||||
{
|
||||
var channel = ctx.Channel;
|
||||
|
||||
if (com is null)
|
||||
{
|
||||
var ch = channel is ITextChannel ? await ctx.User.CreateDMChannelAsync() : channel;
|
||||
try
|
||||
{
|
||||
var ch = channel is ITextChannel ? await ctx.User.CreateDMChannelAsync() : channel;
|
||||
var data = await GetHelpString();
|
||||
if (data == default)
|
||||
return;
|
||||
|
||||
await Response().Text(data).SendAsync();
|
||||
await Response().Channel(ch).Text(data).SendAsync();
|
||||
try
|
||||
{
|
||||
await ctx.OkAsync();
|
||||
@@ -419,90 +424,8 @@ public sealed class Help : NadekoModule<HelpService>
|
||||
.ToList());
|
||||
|
||||
var readableData = JsonConvert.SerializeObject(cmdData, Formatting.Indented);
|
||||
var uploadData = JsonConvert.SerializeObject(cmdData, Formatting.None);
|
||||
|
||||
// for example https://nyc.digitaloceanspaces.com (without your space name)
|
||||
var serviceUrl = Environment.GetEnvironmentVariable("do_spaces_address");
|
||||
|
||||
// generate spaces access key on https://cloud.digitalocean.com/account/api/tokens
|
||||
// you will get 2 keys, first, shorter one is id, longer one is secret
|
||||
var accessKey = Environment.GetEnvironmentVariable("do_access_key_id");
|
||||
var secretAcccessKey = Environment.GetEnvironmentVariable("do_access_key_secret");
|
||||
|
||||
// if all env vars are set, upload the unindented file (to save space) there
|
||||
if (!(serviceUrl is null || accessKey is null || secretAcccessKey is null))
|
||||
{
|
||||
var config = new AmazonS3Config
|
||||
{
|
||||
ServiceURL = serviceUrl
|
||||
};
|
||||
|
||||
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||
|
||||
using (var client = new AmazonS3Client(accessKey, secretAcccessKey, config))
|
||||
{
|
||||
await client.PutObjectAsync(new()
|
||||
{
|
||||
BucketName = "nadeko-pictures",
|
||||
ContentType = "application/json",
|
||||
ContentBody = uploadData,
|
||||
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
||||
Key = $"cmds/{StatsService.BotVersion}.json",
|
||||
CannedACL = S3CannedACL.PublicRead
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var versionListString = "[]";
|
||||
try
|
||||
{
|
||||
using var oldVersionObject = await dlClient.GetObjectAsync(new()
|
||||
{
|
||||
BucketName = "nadeko-pictures",
|
||||
Key = "cmds/versions.json"
|
||||
});
|
||||
|
||||
await using var ms = new MemoryStream();
|
||||
await oldVersionObject.ResponseStream.CopyToAsync(ms);
|
||||
versionListString = Encoding.UTF8.GetString(ms.ToArray());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Information("No old version list found. Creating a new one");
|
||||
}
|
||||
|
||||
var versionList = JsonSerializer.Deserialize<List<string>>(versionListString);
|
||||
if (versionList is not null && !versionList.Contains(StatsService.BotVersion))
|
||||
{
|
||||
// save the file with new version added
|
||||
// versionList.Add(StatsService.BotVersion);
|
||||
versionListString = JsonSerializer.Serialize(versionList.Prepend(StatsService.BotVersion),
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
// upload the updated version list
|
||||
using var client = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||
await client.PutObjectAsync(new()
|
||||
{
|
||||
BucketName = "nadeko-pictures",
|
||||
ContentType = "application/json",
|
||||
ContentBody = versionListString,
|
||||
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
||||
Key = "cmds/versions.json",
|
||||
CannedACL = S3CannedACL.PublicRead
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
"Version {Version} already exists in the version file. " + "Did you forget to increment it?",
|
||||
StatsService.BotVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// also send the file, but indented one, to chat
|
||||
|
||||
// send the indented file to chat
|
||||
await using var rDataStream = new MemoryStream(Encoding.ASCII.GetBytes(readableData));
|
||||
await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen));
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace NadekoBot.Modules;
|
||||
|
||||
[OwnerOnly]
|
||||
[NoPublicBot]
|
||||
public partial class Medusa : NadekoModule<IMedusaLoaderService>
|
||||
{
|
||||
private readonly IMedusaeRepositoryService _repo;
|
||||
|
@@ -6,17 +6,62 @@ public class MedusaeRepositoryService : IMedusaeRepositoryService, INService
|
||||
{
|
||||
// Simulate retrieving data from a database or API
|
||||
await Task.Delay(100);
|
||||
return new List<ModuleItem>
|
||||
{
|
||||
new ModuleItem { Name = "RSS Reader", Description = "Keep up to date with your favorite websites", Command = ".meinstall rss" },
|
||||
new ModuleItem { Name = "Password Manager", Description = "Safely store and manage all your passwords", Command = ".meinstall passwordmanager" },
|
||||
new ModuleItem { Name = "Browser Extension", Description = "Enhance your browsing experience with useful tools", Command = ".meinstall browserextension" },
|
||||
new ModuleItem { Name = "Video Downloader", Description = "Download videos from popular websites", Command = ".meinstall videodownloader" },
|
||||
new ModuleItem { Name = "Virtual Private Network", Description = "Securely browse the web and protect your privacy", Command = ".meinstall vpn" },
|
||||
new ModuleItem { Name = "Ad Blocker", Description = "Block annoying ads and improve page load times", Command = ".meinstall adblocker" },
|
||||
new ModuleItem { Name = "Cloud Storage", Description = "Store and share your files online", Command = ".meinstall cloudstorage" },
|
||||
new ModuleItem { Name = "Social Media Manager", Description = "Manage all your social media accounts in one place", Command = ".meinstall socialmediamanager" },
|
||||
new ModuleItem { Name = "Code Editor", Description = "Write and edit code online", Command = ".meinstall codeeditor" }
|
||||
};
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = "RSS Reader",
|
||||
Description = "Keep up to date with your favorite websites",
|
||||
Command = ".meinstall rss"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Password Manager",
|
||||
Description = "Safely store and manage all your passwords",
|
||||
Command = ".meinstall passwordmanager"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Browser Extension",
|
||||
Description = "Enhance your browsing experience with useful tools",
|
||||
Command = ".meinstall browserextension"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Video Downloader",
|
||||
Description = "Download videos from popular websites",
|
||||
Command = ".meinstall videodownloader"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Virtual Private Network",
|
||||
Description = "Securely browse the web and protect your privacy",
|
||||
Command = ".meinstall vpn"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Ad Blocker",
|
||||
Description = "Block annoying ads and improve page load times",
|
||||
Command = ".meinstall adblocker"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Cloud Storage",
|
||||
Description = "Store and share your files online",
|
||||
Command = ".meinstall cloudstorage"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Social Media Manager",
|
||||
Description = "Manage all your social media accounts in one place",
|
||||
Command = ".meinstall socialmediamanager"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "Code Editor",
|
||||
Description = "Write and edit code online",
|
||||
Command = ".meinstall codeeditor"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
@@ -28,11 +28,10 @@ public sealed class AyuVoiceStateService : INService
|
||||
_dnetApiClient = prop.GetValue(_client, null);
|
||||
_sendVoiceStateUpdateMethodInfo = _dnetApiClient.GetType()
|
||||
.GetMethod("SendVoiceStateUpdateAsync",
|
||||
new[]
|
||||
{
|
||||
typeof(ulong), typeof(ulong?), typeof(bool),
|
||||
[
|
||||
typeof(ulong), typeof(ulong?), typeof(bool),
|
||||
typeof(bool), typeof(RequestOptions)
|
||||
});
|
||||
]);
|
||||
|
||||
_client.LeftGuild += ClientOnLeftGuild;
|
||||
}
|
||||
@@ -55,7 +54,7 @@ public sealed class AyuVoiceStateService : INService
|
||||
bool isMuted = false)
|
||||
// return _voiceStateUpdate(guildId, channelId, isDeafened, isMuted);
|
||||
=> (Task)_sendVoiceStateUpdateMethodInfo.Invoke(_dnetApiClient,
|
||||
new object[] { guildId, channelId, isMuted, isDeafened, null });
|
||||
[guildId, channelId, isMuted, isDeafened, null]);
|
||||
|
||||
private Task SendLeaveVoiceChannelInternalAsync(ulong guildId)
|
||||
=> InvokeSendVoiceStateUpdateAsync(guildId);
|
||||
|
@@ -11,9 +11,9 @@ public sealed partial class YtLoader : INService
|
||||
private static readonly byte[] _ytResultJsonEnd = Encoding.UTF8.GetBytes(";<");
|
||||
|
||||
private static readonly string[] _durationFormats =
|
||||
{
|
||||
[
|
||||
@"m\:ss", @"mm\:ss", @"h\:mm\:ss", @"hh\:mm\:ss", @"hhh\:mm\:ss"
|
||||
};
|
||||
];
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
|
@@ -201,12 +201,9 @@ public sealed partial class MusicQueue : IMusicQueue
|
||||
|
||||
public IQueuedTrackInfo? MoveTrack(int from, int to)
|
||||
{
|
||||
if (from < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(from));
|
||||
if (to < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(to));
|
||||
if (to == from)
|
||||
throw new ArgumentException($"{nameof(from)} and {nameof(to)} must be different");
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(from);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(to);
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(to, from);
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
@@ -270,20 +267,8 @@ public sealed partial class MusicQueue : IMusicQueue
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
var list = tracks.ToList();
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
var struck = rng.Next(i, list.Count);
|
||||
(list[struck], list[i]) = (list[i], list[struck]);
|
||||
|
||||
// could preserving the index during shuffling be done better?
|
||||
if (i == index)
|
||||
index = struck;
|
||||
else if (struck == index)
|
||||
index = i;
|
||||
}
|
||||
|
||||
var list = tracks.ToArray();
|
||||
rng.Shuffle(list);
|
||||
tracks = new(list);
|
||||
}
|
||||
}
|
||||
|
@@ -7,9 +7,9 @@ namespace NadekoBot.Modules.Music;
|
||||
public sealed class YtdlYoutubeResolver : IYoutubeResolver
|
||||
{
|
||||
private static readonly string[] _durationFormats =
|
||||
{
|
||||
[
|
||||
"ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss"
|
||||
};
|
||||
];
|
||||
|
||||
private static readonly Regex _expiryRegex = new(@"(?:[\?\&]expire\=(?<timestamp>\d+))");
|
||||
|
||||
|
@@ -8,8 +8,7 @@ public static class MusicPlaylistExtensions
|
||||
{
|
||||
public static List<MusicPlaylist> GetPlaylistsOnPage(this DbSet<MusicPlaylist> playlists, int num)
|
||||
{
|
||||
if (num < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(num));
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(num, 1);
|
||||
|
||||
return playlists.AsQueryable().Skip((num - 1) * 20).Take(20).Include(pl => pl.Songs).ToList();
|
||||
}
|
||||
|
@@ -1,149 +1,156 @@
|
||||
namespace NadekoBot.Modules.Patronage;
|
||||
using NadekoBot.Modules.Patronage;
|
||||
|
||||
[OnlyPublicBot]
|
||||
public partial class Patronage : NadekoModule
|
||||
namespace NadekoBot.Modules.Help;
|
||||
|
||||
public partial class Help
|
||||
{
|
||||
private readonly PatronageService _service;
|
||||
private readonly PatronageConfig _pConf;
|
||||
|
||||
public Patronage(PatronageService service, PatronageConfig pConf)
|
||||
[OnlyPublicBot]
|
||||
public partial class Patronage : NadekoModule
|
||||
{
|
||||
_service = service;
|
||||
_pConf = pConf;
|
||||
}
|
||||
private readonly PatronageService _service;
|
||||
private readonly PatronageConfig _pConf;
|
||||
|
||||
[Cmd]
|
||||
[Priority(2)]
|
||||
public Task Patron()
|
||||
=> InternalPatron(ctx.User);
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
[OwnerOnly]
|
||||
public Task Patron(IUser user)
|
||||
=> InternalPatron(user);
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
[OwnerOnly]
|
||||
public async Task PatronMessage(PatronTier tierAndHigher, string message)
|
||||
{
|
||||
_ = ctx.Channel.TriggerTypingAsync();
|
||||
var result = await _service.SendMessageToPatronsAsync(tierAndHigher, message);
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.patron_msg_sent(
|
||||
Format.Code(tierAndHigher.ToString()),
|
||||
Format.Bold(result.Success.ToString()),
|
||||
Format.Bold(result.Failed.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
// [OwnerOnly]
|
||||
// public async Task PatronGift(IUser user, int amount)
|
||||
// {
|
||||
// // i can't figure out a good way to gift more than one month at the moment.
|
||||
//
|
||||
// if (amount < 1)
|
||||
// return;
|
||||
//
|
||||
// var patron = _service.GiftPatronAsync(user, amount);
|
||||
//
|
||||
// var eb = _sender.CreateEmbed();
|
||||
//
|
||||
// await Response().Embed(eb.WithDescription($"Added **{days}** days of Patron benefits to {user.Mention}!")
|
||||
// .AddField("Tier", Format.Bold(patron.Tier.ToString()), true)
|
||||
// .AddField("Amount", $"**{patron.Amount / 100.0f:N1}$**", true)
|
||||
// .AddField("Until", TimestampTag.FromDateTime(patron.ValidThru.AddDays(1)))).SendAsync();
|
||||
//
|
||||
//
|
||||
// }
|
||||
|
||||
private async Task InternalPatron(IUser user)
|
||||
{
|
||||
if (!_pConf.Data.IsEnabled)
|
||||
public Patronage(PatronageService service, PatronageConfig pConf)
|
||||
{
|
||||
await Response().Error(strs.patron_not_enabled).SendAsync();
|
||||
return;
|
||||
_service = service;
|
||||
_pConf = pConf;
|
||||
}
|
||||
|
||||
var patron = await _service.GetPatronAsync(user.Id);
|
||||
var quotaStats = await _service.GetUserQuotaStatistic(user.Id);
|
||||
[Cmd]
|
||||
[Priority(2)]
|
||||
public Task Patron()
|
||||
=> InternalPatron(ctx.User);
|
||||
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithAuthor(user)
|
||||
.WithTitle(GetText(strs.patron_info))
|
||||
.WithOkColor();
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
[OwnerOnly]
|
||||
public Task Patron(IUser user)
|
||||
=> InternalPatron(user);
|
||||
|
||||
if (quotaStats.Commands.Count == 0
|
||||
&& quotaStats.Groups.Count == 0
|
||||
&& quotaStats.Modules.Count == 0)
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
[OwnerOnly]
|
||||
public async Task PatronMessage(PatronTier tierAndHigher, string message)
|
||||
{
|
||||
eb.WithDescription(GetText(strs.no_quota_found));
|
||||
_ = ctx.Channel.TriggerTypingAsync();
|
||||
var result = await _service.SendMessageToPatronsAsync(tierAndHigher, message);
|
||||
|
||||
await Response()
|
||||
.Confirm(strs.patron_msg_sent(
|
||||
Format.Code(tierAndHigher.ToString()),
|
||||
Format.Bold(result.Success.ToString()),
|
||||
Format.Bold(result.Failed.ToString())))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
|
||||
// [OwnerOnly]
|
||||
// public async Task PatronGift(IUser user, int amount)
|
||||
// {
|
||||
// // i can't figure out a good way to gift more than one month at the moment.
|
||||
//
|
||||
// if (amount < 1)
|
||||
// return;
|
||||
//
|
||||
// var patron = _service.GiftPatronAsync(user, amount);
|
||||
//
|
||||
// var eb = _sender.CreateEmbed();
|
||||
//
|
||||
// await Response().Embed(eb.WithDescription($"Added **{days}** days of Patron benefits to {user.Mention}!")
|
||||
// .AddField("Tier", Format.Bold(patron.Tier.ToString()), true)
|
||||
// .AddField("Amount", $"**{patron.Amount / 100.0f:N1}$**", true)
|
||||
// .AddField("Until", TimestampTag.FromDateTime(patron.ValidThru.AddDays(1)))).SendAsync();
|
||||
//
|
||||
//
|
||||
// }
|
||||
|
||||
private async Task InternalPatron(IUser user)
|
||||
{
|
||||
eb.AddField(GetText(strs.tier), Format.Bold(patron.Tier.ToFullName()), true)
|
||||
.AddField(GetText(strs.pledge), $"**{patron.Amount / 100.0f:N1}$**", true);
|
||||
|
||||
if (patron.Tier != PatronTier.None)
|
||||
eb.AddField(GetText(strs.expires), patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(), true);
|
||||
|
||||
eb.AddField(GetText(strs.quotas), "", false);
|
||||
|
||||
if (quotaStats.Commands.Count > 0)
|
||||
if (!_pConf.Data.IsEnabled)
|
||||
{
|
||||
var text = GetQuotaList(quotaStats.Commands);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
eb.AddField(GetText(strs.commands), text, true);
|
||||
await Response().Error(strs.patron_not_enabled).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (quotaStats.Groups.Count > 0)
|
||||
var patron = await _service.GetPatronAsync(user.Id);
|
||||
var quotaStats = await _service.GetUserQuotaStatistic(user.Id);
|
||||
|
||||
var eb = _sender.CreateEmbed()
|
||||
.WithAuthor(user)
|
||||
.WithTitle(GetText(strs.patron_info))
|
||||
.WithOkColor();
|
||||
|
||||
if (quotaStats.Commands.Count == 0
|
||||
&& quotaStats.Groups.Count == 0
|
||||
&& quotaStats.Modules.Count == 0)
|
||||
{
|
||||
var text = GetQuotaList(quotaStats.Groups);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
eb.AddField(GetText(strs.groups), text, true);
|
||||
eb.WithDescription(GetText(strs.no_quota_found));
|
||||
}
|
||||
else
|
||||
{
|
||||
eb.AddField(GetText(strs.tier), Format.Bold(patron.Tier.ToFullName()), true)
|
||||
.AddField(GetText(strs.pledge), $"**{patron.Amount / 100.0f:N1}$**", true);
|
||||
|
||||
if (patron.Tier != PatronTier.None)
|
||||
eb.AddField(GetText(strs.expires),
|
||||
patron.ValidThru.AddDays(1).ToShortAndRelativeTimestampTag(),
|
||||
true);
|
||||
|
||||
eb.AddField(GetText(strs.quotas), "", false);
|
||||
|
||||
if (quotaStats.Commands.Count > 0)
|
||||
{
|
||||
var text = GetQuotaList(quotaStats.Commands);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
eb.AddField(GetText(strs.commands), text, true);
|
||||
}
|
||||
|
||||
if (quotaStats.Groups.Count > 0)
|
||||
{
|
||||
var text = GetQuotaList(quotaStats.Groups);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
eb.AddField(GetText(strs.groups), text, true);
|
||||
}
|
||||
|
||||
if (quotaStats.Modules.Count > 0)
|
||||
{
|
||||
var text = GetQuotaList(quotaStats.Modules);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
eb.AddField(GetText(strs.modules), text, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (quotaStats.Modules.Count > 0)
|
||||
|
||||
try
|
||||
{
|
||||
var text = GetQuotaList(quotaStats.Modules);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
eb.AddField(GetText(strs.modules), text, true);
|
||||
await Response().User(ctx.User).Embed(eb).SendAsync();
|
||||
_ = ctx.OkAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Response().Error(strs.cant_dm).SendAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetQuotaList(IReadOnlyDictionary<string, FeatureQuotaStats> featureQuotaStats)
|
||||
{
|
||||
var text = string.Empty;
|
||||
foreach (var (key, q) in featureQuotaStats)
|
||||
{
|
||||
text += $"\n\t`{key}`\n";
|
||||
if (q.Hourly != default)
|
||||
text += $" {GetEmoji(q.Hourly)} {q.Hourly.Cur}/{q.Hourly.Max} per hour\n";
|
||||
if (q.Daily != default)
|
||||
text += $" {GetEmoji(q.Daily)} {q.Daily.Cur}/{q.Daily.Max} per day\n";
|
||||
if (q.Monthly != default)
|
||||
text += $" {GetEmoji(q.Monthly)} {q.Monthly.Cur}/{q.Monthly.Max} per month\n";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Response().User(ctx.User).Embed(eb).SendAsync();
|
||||
_ = ctx.OkAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Response().Error(strs.cant_dm).SendAsync();
|
||||
return text;
|
||||
}
|
||||
|
||||
private string GetEmoji((uint Cur, uint Max) limit)
|
||||
=> limit.Cur < limit.Max
|
||||
? "✅"
|
||||
: "⚠️";
|
||||
}
|
||||
|
||||
private string GetQuotaList(IReadOnlyDictionary<string, FeatureQuotaStats> featureQuotaStats)
|
||||
{
|
||||
var text = string.Empty;
|
||||
foreach (var (key, q) in featureQuotaStats)
|
||||
{
|
||||
text += $"\n\t`{key}`\n";
|
||||
if (q.Hourly != default)
|
||||
text += $" {GetEmoji(q.Hourly)} {q.Hourly.Cur}/{q.Hourly.Max} per hour\n";
|
||||
if (q.Daily != default)
|
||||
text += $" {GetEmoji(q.Daily)} {q.Daily.Cur}/{q.Daily.Max} per day\n";
|
||||
if (q.Monthly != default)
|
||||
text += $" {GetEmoji(q.Monthly)} {q.Monthly.Cur}/{q.Monthly.Max} per month\n";
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private string GetEmoji((uint Cur, uint Max) limit)
|
||||
=> limit.Cur < limit.Max
|
||||
? "✅"
|
||||
: "⚠️";
|
||||
}
|
@@ -111,7 +111,6 @@ public sealed class PatronageService
|
||||
var lastDate = lastRun.ToDateOnly();
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await using var tran = await ctx.Database.BeginTransactionAsync();
|
||||
|
||||
if ((lastDate.Day == 1 || (lastDate.Month != nowDate.Month)) && nowDate.Day > 1)
|
||||
{
|
||||
@@ -141,7 +140,6 @@ public sealed class PatronageService
|
||||
|
||||
// assumes that the code above runs in less than an hour
|
||||
await _cache.AddAsync(_quotaKey, now.ToBinary());
|
||||
await tran.CommitAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -171,7 +169,6 @@ public sealed class PatronageService
|
||||
|
||||
var lastChargeUtc = subscriber.LastCharge.Value.ToUniversalTime();
|
||||
var dateInOneMonth = lastChargeUtc.Date.AddMonths(1);
|
||||
// await using var tran = await ctx.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var dbPatron = await ctx.GetTable<PatronUser>()
|
||||
|
@@ -16,8 +16,7 @@ public partial class Permissions
|
||||
|
||||
private async Task ListBlacklistInternal(string title, BlacklistType type, int page = 0)
|
||||
{
|
||||
if (page < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||
|
||||
var list = _service.GetBlacklist();
|
||||
var allItems = await list.Where(x => x.Type == type)
|
||||
|
@@ -112,9 +112,8 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
|
||||
public void AddCooldown(ulong guildId, string name, int secs)
|
||||
{
|
||||
if (secs <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(secs));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(secs);
|
||||
|
||||
var sett = _settings.GetOrAdd(guildId, static _ => new());
|
||||
sett[name] = secs;
|
||||
|
||||
|
@@ -162,7 +162,7 @@ public partial class Searches
|
||||
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
||||
.AddField(GetText(strs.status), animeData.AiringStatus, true)
|
||||
.AddField(GetText(strs.genres),
|
||||
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" }),
|
||||
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : ["none"]),
|
||||
true)
|
||||
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
||||
await Response().Embed(embed).SendAsync();
|
||||
@@ -194,7 +194,7 @@ public partial class Searches
|
||||
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
||||
.AddField(GetText(strs.status), mangaData.PublishingStatus, true)
|
||||
.AddField(GetText(strs.genres),
|
||||
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" }),
|
||||
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : ["none"]),
|
||||
true)
|
||||
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
||||
|
||||
|
@@ -1,13 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public class FinnHubSearchResponse
|
||||
{
|
||||
[JsonPropertyName("count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public List<FinnHubSearchResult> Result { get; set; }
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public class FinnHubSearchResult
|
||||
{
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonPropertyName("displaySymbol")]
|
||||
public string DisplaySymbol { get; set; }
|
||||
|
||||
[JsonPropertyName("symbol")]
|
||||
public string Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
}
|
@@ -1,55 +0,0 @@
|
||||
// using System.Net.Http.Json;
|
||||
//
|
||||
// namespace NadekoBot.Modules.Searches;
|
||||
//
|
||||
// public sealed class PolygonApiClient : IDisposable
|
||||
// {
|
||||
// private const string BASE_URL = "https://api.polygon.io/v3";
|
||||
//
|
||||
// private readonly HttpClient _httpClient;
|
||||
// private readonly string _apiKey;
|
||||
//
|
||||
// public PolygonApiClient(HttpClient httpClient, string apiKey)
|
||||
// {
|
||||
// _httpClient = httpClient;
|
||||
// _apiKey = apiKey;
|
||||
// }
|
||||
//
|
||||
// public async Task<IReadOnlyCollection<PolygonTickerData>> TickersAsync(string? ticker = null, string? query = null)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// query = null;
|
||||
//
|
||||
// if(query is not null)
|
||||
// query = Uri.EscapeDataString(query);
|
||||
//
|
||||
// var requestString = $"{BASE_URL}/reference/tickers"
|
||||
// + "?type=CS"
|
||||
// + "&active=true"
|
||||
// + "&order=asc"
|
||||
// + "&limit=1000"
|
||||
// + $"&apiKey={_apiKey}";
|
||||
//
|
||||
// if (!string.IsNullOrWhiteSpace(ticker))
|
||||
// requestString += $"&ticker={ticker}";
|
||||
//
|
||||
// if (!string.IsNullOrWhiteSpace(query))
|
||||
// requestString += $"&search={query}";
|
||||
//
|
||||
//
|
||||
// var response = await _httpClient.GetFromJsonAsync<PolygonTickerResponse>(requestString);
|
||||
//
|
||||
// if (response is null)
|
||||
// return Array.Empty<PolygonTickerData>();
|
||||
//
|
||||
// return response.Results;
|
||||
// }
|
||||
//
|
||||
// // public async Task<PolygonTickerDetailsV3> TickerDetailsV3Async(string ticker)
|
||||
// // {
|
||||
// // return new();
|
||||
// // }
|
||||
//
|
||||
// public void Dispose()
|
||||
// => _httpClient.Dispose();
|
||||
// }
|
@@ -1,26 +0,0 @@
|
||||
// namespace NadekoBot.Modules.Searches;
|
||||
//
|
||||
// public sealed class PolygonStockDataService : IStockDataService
|
||||
// {
|
||||
// private readonly IHttpClientFactory _httpClientFactory;
|
||||
// private readonly IBotCredsProvider _credsProvider;
|
||||
//
|
||||
// public PolygonStockDataService(IHttpClientFactory httpClientFactory, IBotCredsProvider credsProvider)
|
||||
// {
|
||||
// _httpClientFactory = httpClientFactory;
|
||||
// _credsProvider = credsProvider;
|
||||
// }
|
||||
//
|
||||
// public async Task<IReadOnlyCollection<StockData>> GetStockDataAsync(string? query = null)
|
||||
// {
|
||||
// using var httpClient = _httpClientFactory.CreateClient();
|
||||
// using var client = new PolygonApiClient(httpClient, string.Empty);
|
||||
// var data = await client.TickersAsync(query: query);
|
||||
//
|
||||
// return data.Map(static x => new StockData()
|
||||
// {
|
||||
// Name = x.Name,
|
||||
// Ticker = x.Ticker,
|
||||
// });
|
||||
// }
|
||||
// }
|
@@ -1,43 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public class PolygonTickerData
|
||||
{
|
||||
[JsonPropertyName("ticker")]
|
||||
public string Ticker { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("market")]
|
||||
public string Market { get; set; }
|
||||
|
||||
[JsonPropertyName("locale")]
|
||||
public string Locale { get; set; }
|
||||
|
||||
[JsonPropertyName("primary_exchange")]
|
||||
public string PrimaryExchange { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("active")]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[JsonPropertyName("currency_name")]
|
||||
public string CurrencyName { get; set; }
|
||||
|
||||
[JsonPropertyName("cik")]
|
||||
public string Cik { get; set; }
|
||||
|
||||
[JsonPropertyName("composite_figi")]
|
||||
public string CompositeFigi { get; set; }
|
||||
|
||||
[JsonPropertyName("share_class_figi")]
|
||||
public string ShareClassFigi { get; set; }
|
||||
|
||||
[JsonPropertyName("last_updated_utc")]
|
||||
public DateTime LastUpdatedUtc { get; set; }
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
#nullable disable
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public class PolygonTickerResponse
|
||||
{
|
||||
[JsonPropertyName("status")]
|
||||
public string Status { get; set; }
|
||||
|
||||
[JsonPropertyName("results")]
|
||||
public List<PolygonTickerData> Results { get; set; }
|
||||
}
|
@@ -247,10 +247,7 @@ public class FeedsService : INService
|
||||
foreach (var feed in gc.FeedSubs)
|
||||
{
|
||||
_subs.AddOrUpdate(feed.Url.ToLower(),
|
||||
new List<FeedSub>
|
||||
{
|
||||
feed
|
||||
},
|
||||
[feed],
|
||||
(_, old) =>
|
||||
{
|
||||
old.Add(feed);
|
||||
@@ -275,7 +272,7 @@ public class FeedsService : INService
|
||||
return false;
|
||||
var toRemove = items[index];
|
||||
_subs.AddOrUpdate(toRemove.Url.ToLower(),
|
||||
new List<FeedSub>(),
|
||||
[],
|
||||
(_, old) =>
|
||||
{
|
||||
old.Remove(toRemove);
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
public class PlainGoogleScrapeSearchResult : ISearchResult
|
||||
{
|
||||
public string? Answer { get; init; } = null!;
|
||||
public IReadOnlyCollection<ISearchResultEntry> Entries { get; init; } = null!;
|
||||
public ISearchResultInformation Info { get; init; } = null!;
|
||||
public required string? Answer { get; init; }
|
||||
public required IReadOnlyCollection<ISearchResultEntry> Entries { get; init; }
|
||||
public required ISearchResultInformation Info { get; init; }
|
||||
}
|
@@ -1,5 +1,4 @@
|
||||
#nullable disable
|
||||
using Html2Markdown;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -23,8 +22,8 @@ public class SearchesService : INService
|
||||
Birds
|
||||
}
|
||||
|
||||
public List<WoWJoke> WowJokes { get; } = new();
|
||||
public List<MagicItem> MagicItems { get; } = new();
|
||||
public List<WoWJoke> WowJokes { get; } = [];
|
||||
public List<MagicItem> MagicItems { get; } = [];
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IGoogleApiService _google;
|
||||
private readonly IImageCache _imgs;
|
||||
@@ -68,7 +67,7 @@ public class SearchesService : INService
|
||||
_yomamaJokes = File.ReadAllLines("data/yomama.txt").Shuffle().ToList();
|
||||
else
|
||||
{
|
||||
_yomamaJokes = new();
|
||||
_yomamaJokes = [];
|
||||
Log.Warning("data/yomama.txt is missing. .yomama command won't work");
|
||||
}
|
||||
}
|
||||
@@ -229,25 +228,15 @@ public class SearchesService : INService
|
||||
{
|
||||
var subpath = tag.ToString().ToLowerInvariant();
|
||||
|
||||
int max;
|
||||
switch (tag)
|
||||
var max = tag switch
|
||||
{
|
||||
case ImageTag.Food:
|
||||
max = 773;
|
||||
break;
|
||||
case ImageTag.Dogs:
|
||||
max = 750;
|
||||
break;
|
||||
case ImageTag.Cats:
|
||||
max = 773;
|
||||
break;
|
||||
case ImageTag.Birds:
|
||||
max = 578;
|
||||
break;
|
||||
default:
|
||||
max = 100;
|
||||
break;
|
||||
}
|
||||
ImageTag.Food => 773,
|
||||
ImageTag.Dogs => 750,
|
||||
ImageTag.Cats => 773,
|
||||
ImageTag.Birds => 578,
|
||||
_ => 100,
|
||||
};
|
||||
|
||||
|
||||
return $"https://nadeko-pictures.nyc3.digitaloceanspaces.com/{subpath}/"
|
||||
+ _rng.Next(1, max).ToString("000")
|
||||
@@ -380,11 +369,11 @@ public class SearchesService : INService
|
||||
return null;
|
||||
if (!string.IsNullOrWhiteSpace(data.Img))
|
||||
data.Img = await _google.ShortenUrl(data.Img);
|
||||
if (!string.IsNullOrWhiteSpace(data.Text))
|
||||
{
|
||||
var converter = new Converter();
|
||||
data.Text = converter.Convert(data.Text);
|
||||
}
|
||||
// if (!string.IsNullOrWhiteSpace(data.Text))
|
||||
// {
|
||||
// var converter = new Converter();
|
||||
// data.Text = converter.Convert(data.Text);
|
||||
// }
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -427,7 +416,7 @@ public class SearchesService : INService
|
||||
async () =>
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
|
||||
|
||||
// https://api.steampowered.com/ISteamApps/GetAppList/v2/
|
||||
var gamesStr = await http.GetStringAsync("https://api.steampowered.com/ISteamApps/GetAppList/v2/");
|
||||
var apps = JsonConvert
|
||||
@@ -449,7 +438,7 @@ public class SearchesService : INService
|
||||
|
||||
if (gamesMap is null)
|
||||
return -1;
|
||||
|
||||
|
||||
query = query.Trim();
|
||||
|
||||
var keyList = gamesMap.Keys.ToList();
|
||||
|
@@ -202,10 +202,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
_trackCounter[key].Add(info.GuildId);
|
||||
else
|
||||
{
|
||||
_trackCounter[key] = new()
|
||||
{
|
||||
info.GuildId
|
||||
};
|
||||
_trackCounter[key] = [info.GuildId];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,12 +569,12 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
{
|
||||
if (map.TryGetValue(guildId, out var set))
|
||||
return set;
|
||||
return map[guildId] = new();
|
||||
return map[guildId] = [];
|
||||
}
|
||||
|
||||
_shardTrackedStreams[key] = new()
|
||||
{
|
||||
{ guildId, new() }
|
||||
{ guildId, [] }
|
||||
};
|
||||
return _shardTrackedStreams[key][guildId];
|
||||
}
|
||||
|
@@ -41,10 +41,7 @@ public class PicartoProvider : Provider
|
||||
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string login)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string>
|
||||
{
|
||||
login
|
||||
});
|
||||
var data = await GetStreamDataAsync([login]);
|
||||
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
@@ -52,7 +49,7 @@ public class PicartoProvider : Provider
|
||||
public override async Task<IReadOnlyCollection<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||
{
|
||||
if (logins.Count == 0)
|
||||
return new List<StreamData>();
|
||||
return [];
|
||||
|
||||
using var http = _httpClientFactory.CreateClient();
|
||||
var toReturn = new List<StreamData>();
|
||||
|
@@ -66,10 +66,7 @@ public sealed class TwitchHelixProvider : Provider
|
||||
|
||||
public override async Task<StreamData?> GetStreamDataAsync(string login)
|
||||
{
|
||||
var data = await GetStreamDataAsync(new List<string>
|
||||
{
|
||||
login
|
||||
});
|
||||
var data = await GetStreamDataAsync([login]);
|
||||
|
||||
return data.FirstOrDefault();
|
||||
}
|
||||
@@ -125,7 +122,7 @@ public sealed class TwitchHelixProvider : Provider
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Something went wrong retreiving {StreamPlatform} streams", Platform);
|
||||
return new List<StreamData>();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +160,7 @@ public sealed class TwitchHelixProvider : Provider
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Something went wrong retreiving {StreamPlatform} streams", Platform);
|
||||
return new List<StreamData>();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -269,7 +269,11 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
||||
var text = SmartText.CreateFrom(repeater.Message);
|
||||
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||
|
||||
var newMsg = await _sender.Response(channel).Text(text).SendAsync();
|
||||
var newMsg = await _sender.Response(channel)
|
||||
.Text(text)
|
||||
.Sanitize(false)
|
||||
.SendAsync();
|
||||
|
||||
_ = newMsg.AddReactionAsync(new Emoji("🔄"));
|
||||
|
||||
if (_noRedundant.Contains(repeater.Id))
|
||||
@@ -359,8 +363,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
||||
|
||||
public async Task<RunningRepeater?> RemoveByIndexAsync(ulong guildId, int index)
|
||||
{
|
||||
if (index > MAX_REPEATERS * 2)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(index, MAX_REPEATERS * 2);
|
||||
|
||||
await using var uow = _db.GetDbContext();
|
||||
var toRemove = await uow.Set<Repeater>()
|
||||
|
@@ -58,13 +58,13 @@ public class ConverterService : INService, IReadyExecutor
|
||||
var currencyRates = await GetCurrencyRates();
|
||||
var baseType = new ConvertUnit
|
||||
{
|
||||
Triggers = new[] { currencyRates.Base },
|
||||
Triggers = [currencyRates.Base],
|
||||
Modifier = decimal.One,
|
||||
UnitType = unitTypeString
|
||||
};
|
||||
var units = currencyRates.ConversionRates.Select(u => new ConvertUnit
|
||||
{
|
||||
Triggers = new[] { u.Key },
|
||||
Triggers = [u.Key],
|
||||
Modifier = u.Value,
|
||||
UnitType = unitTypeString
|
||||
})
|
||||
|
@@ -85,6 +85,7 @@ public partial class Utility : NadekoModule
|
||||
|
||||
await Response()
|
||||
.Text(message)
|
||||
.Channel(channel)
|
||||
.UserBasedMentions()
|
||||
.SendAsync();
|
||||
}
|
||||
@@ -479,7 +480,7 @@ public partial class Utility : NadekoModule
|
||||
try
|
||||
{
|
||||
if (tags.Length == 0)
|
||||
tags = new[] { name };
|
||||
tags = [name];
|
||||
|
||||
await ctx.Guild.CreateStickerAsync(
|
||||
name,
|
||||
|
@@ -332,8 +332,7 @@ public class ClubService : INService, IClubService
|
||||
|
||||
public List<ClubInfo> GetClubLeaderboardPage(int page)
|
||||
{
|
||||
if (page < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(page));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Set<ClubInfo>().GetClubLeaderboardPage(page);
|
||||
|
@@ -1194,7 +1194,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||
}
|
||||
|
||||
//avatar
|
||||
if (stats.User.AvatarId is not null && template.User.Icon.Show)
|
||||
if (template.User.Icon.Show)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1469,7 +1469,6 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||
}
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
// await using var tran = await ctx.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
if (await ctx.GetTable<XpShopOwnedItem>()
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Version>5.0.3</Version>
|
||||
<Version>5.0.5</Version>
|
||||
|
||||
<!-- Output/build -->
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
@@ -28,22 +28,20 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<Publish>True</Publish>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.307"/>
|
||||
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
||||
<PackageReference Include="Discord.Net" Version="3.204.0"/>
|
||||
<PackageReference Include="CoreCLR-NCalc" Version="3.0.203"/>
|
||||
<PackageReference Include="CoreCLR-NCalc" Version="3.1.246" />
|
||||
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138"/>
|
||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.62.1.3205"/>
|
||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414" />
|
||||
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084"/>
|
||||
<!-- <PackageReference Include="Grpc.AspNetCore" Version="2.62.0" />-->
|
||||
<PackageReference Include="Google.Protobuf" Version="3.26.1"/>
|
||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.62.0"/>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.62.0">
|
||||
<PackageReference Include="Grpc.Tools" Version="2.63.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Html2Markdown" Version="6.2.0.3"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.5.0"/>
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
|
||||
@@ -70,14 +68,14 @@
|
||||
<PackageReference Include="OneOf" Version="3.0.263"/>
|
||||
<PackageReference Include="OneOf.SourceGenerator" Version="3.0.263"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="7.0.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="7.0.1" />
|
||||
|
||||
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17"/>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.8"/>
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14"/>
|
||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009"/>
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.7.33"/>
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.2"/>
|
||||
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||
<PackageReference Include="SharpToken" Version="2.0.2"/>
|
||||
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
|
||||
@@ -93,7 +91,7 @@
|
||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="8.1.0"/>
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/>
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
|
||||
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
|
||||
@@ -102,7 +100,7 @@
|
||||
<PackageReference Include="TwitchLib.Api" Version="3.4.1"/>
|
||||
|
||||
<!-- sqlselectcsv and stock -->
|
||||
<PackageReference Include="CsvHelper" Version="28.0.1"/>
|
||||
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
@@ -43,8 +43,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
||||
if (string.IsNullOrWhiteSpace(keywords))
|
||||
throw new ArgumentNullException(nameof(keywords));
|
||||
|
||||
if (count <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||
|
||||
var match = _plRegex.Match(keywords);
|
||||
if (match.Length > 1)
|
||||
@@ -62,9 +61,8 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
|
||||
if (count <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||
|
||||
var query = _yt.Search.List("snippet");
|
||||
query.MaxResults = count;
|
||||
query.Q = id;
|
||||
@@ -82,8 +80,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
||||
if (string.IsNullOrWhiteSpace(keywords))
|
||||
throw new ArgumentNullException(nameof(keywords));
|
||||
|
||||
if (count <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||
|
||||
var query = _yt.Search.List("snippet");
|
||||
query.MaxResults = count;
|
||||
@@ -100,8 +97,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
||||
if (string.IsNullOrWhiteSpace(keywords))
|
||||
throw new ArgumentNullException(nameof(keywords));
|
||||
|
||||
if (count <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||
|
||||
var query = _yt.Search.List("snippet");
|
||||
query.MaxResults = count;
|
||||
@@ -150,8 +146,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
||||
if (string.IsNullOrWhiteSpace(playlistId))
|
||||
throw new ArgumentNullException(nameof(playlistId));
|
||||
|
||||
if (count <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||
|
||||
string nextPageToken = null;
|
||||
|
||||
|
@@ -112,7 +112,7 @@ public class Localization : ILocalization
|
||||
{
|
||||
Cmd = key,
|
||||
Desc = key,
|
||||
Usage = new[] { key }
|
||||
Usage = [key]
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -8,13 +8,13 @@ namespace NadekoBot.Common;
|
||||
|
||||
public sealed class RedisBotCache : IBotCache
|
||||
{
|
||||
private static readonly Type[] _supportedTypes = new []
|
||||
{
|
||||
private static readonly Type[] _supportedTypes =
|
||||
[
|
||||
typeof(bool), typeof(int), typeof(uint), typeof(long),
|
||||
typeof(ulong), typeof(float), typeof(double),
|
||||
typeof(string), typeof(byte[]), typeof(ReadOnlyMemory<byte>), typeof(Memory<byte>),
|
||||
typeof(RedisValue),
|
||||
};
|
||||
typeof(RedisValue)
|
||||
];
|
||||
|
||||
private static readonly JsonSerializerOptions _opts = new()
|
||||
{
|
||||
|
@@ -46,12 +46,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
|
||||
if (arrayIndex < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
|
||||
if (arrayIndex >= array.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(arrayIndex, array.Length);
|
||||
|
||||
CopyToInternal(array, arrayIndex);
|
||||
}
|
||||
|
@@ -6,10 +6,13 @@ namespace NadekoBot.Extensions;
|
||||
|
||||
public static class StringExtensions
|
||||
{
|
||||
private static readonly HashSet<char> _lettersAndDigits = new(Enumerable.Range(48, 10)
|
||||
.Concat(Enumerable.Range(65, 26))
|
||||
.Concat(Enumerable.Range(97, 26))
|
||||
.Select(x => (char)x));
|
||||
private static readonly HashSet<char> _lettersAndDigits =
|
||||
[
|
||||
..Enumerable.Range(48, 10)
|
||||
.Concat(Enumerable.Range(65, 26))
|
||||
.Concat(Enumerable.Range(97, 26))
|
||||
.Select(x => (char)x)
|
||||
];
|
||||
|
||||
private static readonly Regex _filterRegex = new(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
@@ -43,7 +46,7 @@ public static class StringExtensions
|
||||
|
||||
public static string ToTitleCase(this string str)
|
||||
{
|
||||
var tokens = str.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var tokens = str.Split([" "], StringSplitOptions.RemoveEmptyEntries);
|
||||
for (var i = 0; i < tokens.Length; i++)
|
||||
{
|
||||
var token = tokens[i];
|
||||
|
@@ -38,8 +38,7 @@ public sealed class NadekoRandom : Random
|
||||
|
||||
public long NextLong(long minValue, long maxValue)
|
||||
{
|
||||
if (minValue > maxValue)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(minValue, maxValue);
|
||||
if (minValue == maxValue)
|
||||
return minValue;
|
||||
var bytes = new byte[sizeof(long)];
|
||||
|
@@ -9,8 +9,7 @@ public sealed class QueueRunner
|
||||
|
||||
public QueueRunner(int delayMs = 0, int maxCapacity = -1)
|
||||
{
|
||||
if (delayMs < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(delayMs));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(delayMs);
|
||||
|
||||
_delayMs = delayMs;
|
||||
_channel = maxCapacity switch
|
||||
|
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
[SuppressMessage("Style", "IDE0022:Use expression body for methods")]
|
||||
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||
|
@@ -9,8 +9,7 @@ public sealed class RatelimitAttribute : PreconditionAttribute
|
||||
|
||||
public RatelimitAttribute(int seconds)
|
||||
{
|
||||
if (seconds <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(seconds));
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(seconds);
|
||||
|
||||
Seconds = seconds;
|
||||
}
|
||||
|
@@ -146,14 +146,14 @@ public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||
Prefix = ".";
|
||||
RotateStatuses = false;
|
||||
GroupGreets = false;
|
||||
DmHelpTextKeywords = new()
|
||||
{
|
||||
DmHelpTextKeywords =
|
||||
[
|
||||
"help",
|
||||
"commands",
|
||||
"cmds",
|
||||
"module",
|
||||
"can you do"
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
||||
@@ -178,8 +178,8 @@ public sealed partial class BlockedConfig
|
||||
|
||||
public BlockedConfig()
|
||||
{
|
||||
Modules = new();
|
||||
Commands = new();
|
||||
Modules = [];
|
||||
Commands = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,5 +12,5 @@ public abstract class DbService
|
||||
public abstract Task SetupAsync();
|
||||
|
||||
public abstract DbContext CreateRawDbContext(string dbType, string connString);
|
||||
public abstract DbContext GetDbContext();
|
||||
public abstract NadekoContext GetDbContext();
|
||||
}
|
@@ -259,10 +259,10 @@ public class Deck
|
||||
}
|
||||
|
||||
private readonly string[] _regIndicators =
|
||||
{
|
||||
[
|
||||
"🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:",
|
||||
"🇯", "🇶", "🇰"
|
||||
};
|
||||
];
|
||||
|
||||
public Card(CardSuit s, int cardNum)
|
||||
{
|
||||
|
@@ -64,7 +64,7 @@ public sealed class MedusaNinjectIocModule : IIocModule, IDisposable
|
||||
var assembly = typeof(JsonSerializerOptions).Assembly;
|
||||
var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler");
|
||||
var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public);
|
||||
clearCacheMethod?.Invoke(null, new object?[] { null });
|
||||
clearCacheMethod?.Invoke(null, [null]);
|
||||
|
||||
isLoaded = false;
|
||||
}
|
||||
|
@@ -140,7 +140,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
=> alias.Equals(commandName, StringComparison.InvariantCultureIgnoreCase)))
|
||||
?.OptionalStrings
|
||||
.Args
|
||||
?? new[] { string.Empty };
|
||||
?? [string.Empty];
|
||||
}
|
||||
|
||||
public Task ReloadStrings()
|
||||
@@ -375,7 +375,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
var targetType = parserType.BaseType!.GetGenericArguments()[0];
|
||||
var typeReaderInstance = (TypeReader)Activator.CreateInstance(
|
||||
typeof(ParamParserAdapter<>).MakeGenericType(targetType),
|
||||
args: new[] { parserObj, strings, _cont })!;
|
||||
args: [parserObj, strings, _cont])!;
|
||||
|
||||
typeReaders.Add(targetType, typeReaderInstance);
|
||||
}
|
||||
@@ -888,7 +888,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
var cmdAttribute = method.GetCustomAttribute<cmdAttribute>(true)!;
|
||||
var aliases = cmdAttribute.Aliases;
|
||||
if (aliases.Length == 0)
|
||||
aliases = new[] { method.Name.ToLowerInvariant() };
|
||||
aliases = [method.Name.ToLowerInvariant()];
|
||||
|
||||
cmds.Add(new(
|
||||
aliases,
|
||||
|
@@ -40,7 +40,7 @@ public sealed partial class ReplacementPatternStore
|
||||
Register("%server.id%", static (IGuild g) => g.Id.ToString());
|
||||
Register("%server.name%", static (IGuild g) => g.Name);
|
||||
Register("%server.icon%", static (IGuild g) => g.IconUrl);
|
||||
Register("%server.members%", static (IGuild g) => g.ApproximateMemberCount?.ToString() ?? "?");
|
||||
Register("%server.members%", static (IGuild g) => (g as SocketGuild)?.MemberCount.ToString() ?? "?");
|
||||
Register("%server.boosters%", static (IGuild g) => g.PremiumSubscriptionCount.ToString());
|
||||
Register("%server.boost_level%", static (IGuild g) => ((int)g.PremiumTier).ToString());
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ public partial class ResponseBuilder
|
||||
|
||||
public async Task SendAsync(bool ephemeral = false)
|
||||
{
|
||||
var lastPage = (_paginationBuilder.TotalElements - 1)
|
||||
var lastPage = (_paginationBuilder.Elems - 1)
|
||||
/ _paginationBuilder.ItemsPerPage;
|
||||
|
||||
var items = (await _paginationBuilder.ItemsFunc(currentPage)).ToArray();
|
||||
|
@@ -357,9 +357,10 @@ public sealed partial class ResponseBuilder
|
||||
fileName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public PaginatedResponseBuilder Paginated()
|
||||
=> new(this);
|
||||
|
||||
}
|
||||
|
||||
public class PaginatedResponseBuilder
|
||||
@@ -396,7 +397,7 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
|
||||
|
||||
public Func<int, Task<SimpleInteractionBase>>? InteractionFunc { get; private set; }
|
||||
|
||||
public int TotalElements { get; private set; } = 1;
|
||||
public int Elems { get; private set; } = 1;
|
||||
public int ItemsPerPage { get; private set; } = 9;
|
||||
public bool AddPaginatedFooter { get; private set; } = true;
|
||||
public bool IsEphemeral { get; private set; }
|
||||
@@ -411,10 +412,16 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
|
||||
public SourcedPaginatedResponseBuilder<T> Items(IReadOnlyCollection<T> col)
|
||||
{
|
||||
items = col;
|
||||
TotalElements = col.Count;
|
||||
Elems = col.Count;
|
||||
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> TotalElements(int i)
|
||||
{
|
||||
Elems = i;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IEnumerable<T>>> func)
|
||||
{
|
||||
|
@@ -113,7 +113,7 @@ public static class ServiceCollectionExtensions
|
||||
typeof(IInputTransformer),
|
||||
typeof(INService)
|
||||
];
|
||||
|
||||
|
||||
foreach (var svc in a.GetTypes()
|
||||
.Where(type => type.IsClass && types.Any(t => type.IsAssignableTo(t)) && !type.HasAttribute<DIIgnoreAttribute>()
|
||||
#if GLOBAL_NADEKO
|
||||
|
@@ -94,10 +94,8 @@ public class CommandHandler : INService, IReadyExecutor, ICommandHandler
|
||||
|
||||
public string SetPrefix(IGuild guild, string prefix)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prefix))
|
||||
throw new ArgumentNullException(nameof(prefix));
|
||||
if (guild is null)
|
||||
throw new ArgumentNullException(nameof(guild));
|
||||
ArgumentNullException.ThrowIfNullOrWhiteSpace(prefix);
|
||||
ArgumentNullException.ThrowIfNull(guild);
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
|
@@ -20,10 +20,10 @@ public class DefaultWallet : IWallet
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var userId = UserId;
|
||||
return await ctx
|
||||
.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(x => x.CurrencyAmount)
|
||||
.FirstOrDefaultAsync();
|
||||
.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(x => x.CurrencyAmount)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> Take(long amount, TxData? txData)
|
||||
@@ -35,12 +35,12 @@ public class DefaultWallet : IWallet
|
||||
|
||||
var userId = UserId;
|
||||
var changed = await ctx
|
||||
.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId == userId && x.CurrencyAmount >= amount)
|
||||
.UpdateAsync(x => new()
|
||||
{
|
||||
CurrencyAmount = x.CurrencyAmount - amount
|
||||
});
|
||||
.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId == userId && x.CurrencyAmount >= amount)
|
||||
.UpdateAsync(x => new()
|
||||
{
|
||||
CurrencyAmount = x.CurrencyAmount - amount
|
||||
});
|
||||
|
||||
if (changed == 0)
|
||||
return false;
|
||||
@@ -48,17 +48,17 @@ public class DefaultWallet : IWallet
|
||||
if (txData is not null)
|
||||
{
|
||||
await ctx
|
||||
.GetTable<CurrencyTransaction>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Amount = -amount,
|
||||
Note = txData.Note,
|
||||
UserId = userId,
|
||||
Type = txData.Type,
|
||||
Extra = txData.Extra,
|
||||
OtherId = txData.OtherId,
|
||||
DateAdded = DateTime.UtcNow
|
||||
});
|
||||
.GetTable<CurrencyTransaction>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Amount = -amount,
|
||||
Note = txData.Note,
|
||||
UserId = userId,
|
||||
Type = txData.Type,
|
||||
Extra = txData.Extra,
|
||||
OtherId = txData.OtherId,
|
||||
DateAdded = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -72,43 +72,37 @@ public class DefaultWallet : IWallet
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var userId = UserId;
|
||||
|
||||
await using (var tran = await ctx.Database.BeginTransactionAsync())
|
||||
{
|
||||
var changed = await ctx
|
||||
.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.UpdateAsync(x => new()
|
||||
{
|
||||
CurrencyAmount = x.CurrencyAmount + amount
|
||||
});
|
||||
|
||||
if (changed == 0)
|
||||
{
|
||||
await ctx
|
||||
.GetTable<DiscordUser>()
|
||||
.Value(x => x.UserId, userId)
|
||||
.Value(x => x.Username, "Unknown")
|
||||
.Value(x => x.Discriminator, "????")
|
||||
.Value(x => x.CurrencyAmount, amount)
|
||||
.InsertAsync();
|
||||
}
|
||||
|
||||
await tran.CommitAsync();
|
||||
}
|
||||
await ctx.GetTable<DiscordUser>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = userId,
|
||||
Username = "Unknown",
|
||||
Discriminator = "????",
|
||||
CurrencyAmount = amount,
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
CurrencyAmount = old.CurrencyAmount + amount
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = userId
|
||||
});
|
||||
|
||||
if (txData is not null)
|
||||
{
|
||||
await ctx.GetTable<CurrencyTransaction>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Amount = amount,
|
||||
UserId = userId,
|
||||
Note = txData.Note,
|
||||
Type = txData.Type,
|
||||
Extra = txData.Extra,
|
||||
OtherId = txData.OtherId,
|
||||
DateAdded = DateTime.UtcNow
|
||||
});
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Amount = amount,
|
||||
UserId = userId,
|
||||
Note = txData.Note,
|
||||
Type = txData.Type,
|
||||
Extra = txData.Extra,
|
||||
OtherId = txData.OtherId,
|
||||
DateAdded = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -81,7 +81,7 @@ public class BotStrings : IBotStrings
|
||||
|
||||
return new CommandStrings()
|
||||
{
|
||||
Examples = new[] { "" },
|
||||
Examples = [""],
|
||||
Desc = "?",
|
||||
Params = []
|
||||
};
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Permissions;
|
||||
|
||||
namespace NadekoBot.Common.TypeReaders;
|
||||
|
||||
public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
|
||||
@@ -34,7 +36,7 @@ public sealed class ModuleOrExprTypeReader : NadekoTypeReader<ModuleOrExpr>
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
||||
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
||||
?.Key;
|
||||
if (module is null && input != "ACTUALEXPRESSIONS")
|
||||
if (module is null && input != "ACTUALEXPRESSIONS" && input != CleverBotResponseStr.CLEVERBOT_RESPONSE)
|
||||
return new(TypeReaderResult.FromError<ModuleOrExpr>(CommandError.ParseFailed, "No such module found."));
|
||||
|
||||
return new(TypeReaderResult.FromSuccess(new ModuleOrExpr
|
||||
|
@@ -19,7 +19,7 @@ public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
|
||||
var value = eventInfo.Source.Value as string;
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
var isMultiLine = value.IndexOfAny(new[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
|
||||
var isMultiLine = value.IndexOfAny(['\r', '\n', '\x85', '\x2028', '\x2029']) >= 0;
|
||||
if (isMultiLine)
|
||||
{
|
||||
eventInfo = new(eventInfo.Source)
|
||||
|
@@ -10,7 +10,12 @@ public static class UserExtensions
|
||||
|
||||
// This method is only used for the xp card
|
||||
public static Uri? RealAvatarUrl(this DiscordUser usr)
|
||||
=> Uri.TryCreate(CDN.GetDefaultUserAvatarUrl(usr.UserId), UriKind.Absolute, out var uri)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(usr.AvatarId))
|
||||
return new Uri(CDN.GetUserAvatarUrl(usr.UserId, usr.AvatarId, 128, ImageFormat.Png));
|
||||
|
||||
return Uri.TryCreate(CDN.GetDefaultUserAvatarUrl(usr.UserId), UriKind.Absolute, out var uri)
|
||||
? uri
|
||||
: null;
|
||||
}
|
||||
}
|
@@ -187,12 +187,19 @@ repeatinvoke:
|
||||
- index:
|
||||
desc: "The index at which to display the repeat message."
|
||||
repeat:
|
||||
desc: Repeat a message once every specified amount of time in the current channel. You can instead specify time of day for the message to be repeated daily (make sure you've set your server's timezone). If you've specified time of day, you can still override the default daily interval with your own interval. You can have up to 5 repeating messages on the server in total.
|
||||
desc: |-
|
||||
Repeat a message once every specified amount of time in the current channel.
|
||||
You can specify a different channel as the first argument.
|
||||
You can instead specify time of day for the message to be repeated daily (make sure you've set your server's timezone).
|
||||
If you've specified time of day, you can still override the default daily interval with your own interval.
|
||||
You can have up to 5 repeating messages on the server in total.
|
||||
ex:
|
||||
- Hello there
|
||||
- 1h5m Hello @erryone
|
||||
- 10:00 Daily have a nice day! This will execute once every 24h.
|
||||
- 21:00 30m Starting at 21 and every 30 minutes after that i will send this message!
|
||||
- '#other-channel hello there'
|
||||
- '1h5m Hello @erryone'
|
||||
- '10:00 Daily have a nice day! This will execute once every 24h.'
|
||||
- '#other-channel 10:00 Daily have a nice day! This will execute once every 24h.'
|
||||
- '21:00 30m Starting at 21 and every 30 minutes after that i will send this message!'
|
||||
params:
|
||||
- message:
|
||||
desc: "The text to be repeated at the specified intervals or times."
|
||||
|
@@ -230,7 +230,7 @@
|
||||
"user_unbanned": "User unbanned",
|
||||
"presence_updates": "Presence updates",
|
||||
"sb_user": "User soft-banned",
|
||||
"awarded": "has awarded {0} to {1}",
|
||||
"awarded": "{2} has awarded {0} to {1}",
|
||||
"better_luck": "Better luck next time ^_^",
|
||||
"br_win": "Congratulations! You won {0} for rolling above {1}",
|
||||
"deck_reshuffled": "Deck reshuffled.",
|
||||
@@ -241,7 +241,7 @@
|
||||
"cards_left": "{0} cards left in the deck.",
|
||||
"cards": "Cards",
|
||||
"hand_value": "Hand value",
|
||||
"gifted": "has gifted {0} to {1}",
|
||||
"gifted": "{2} has gifted {0} to {1}",
|
||||
"has": "{0} has {1}",
|
||||
"heads": "Head",
|
||||
"mass_award": "Awarded {0} to {1} users from {2} role.",
|
||||
@@ -366,7 +366,7 @@
|
||||
"ttt_against_yourself": "You can't play against yourself.",
|
||||
"ttt_already_running": "TicTacToe Game is already running in this channel.",
|
||||
"ttt_a_draw": "A draw!",
|
||||
"ttt_created": "has created a game of TicTacToe.",
|
||||
"ttt_created": "{0} has created a game of TicTacToe.",
|
||||
"ttt_has_won": "{0} has won!",
|
||||
"ttt_matched_three": "Matched three",
|
||||
"ttt_no_moves": "No moves left!",
|
||||
@@ -507,7 +507,7 @@
|
||||
"city_not_found": "City not found.",
|
||||
"magicitems_not_loaded": "Magic Items not loaded.",
|
||||
"mal_profile": "{0}'s MAL profile",
|
||||
"mashape_api_missing": "Bot owner didn't specify MashapeApiKey. You can't use this functionality.",
|
||||
"mashape_api_missing": "Bot owner didn't specify RapidApi api key. You can't use this functionality.",
|
||||
"min_max": "Min/Max",
|
||||
"no_channel_found": "No channel found.",
|
||||
"on_hold": "On-hold",
|
||||
@@ -960,8 +960,8 @@
|
||||
"perm_override_all_confirm": "Are you sure that you want to remove **ALL** discord permission overrides on this server? This action is irreversible.",
|
||||
"perm_overrides": "Discord Permission Overrides",
|
||||
"perm_override_reset": "Discord Permission Overrides for this command have been cleared.",
|
||||
"bj_created": "has created a new BlackJack game in this channel.",
|
||||
"bj_joined": "has joined the BlackJack game",
|
||||
"bj_created": "{0} has created a new BlackJack game in this channel.",
|
||||
"bj_joined": "{0} has joined the BlackJack game",
|
||||
"reset": "Xp Reset",
|
||||
"reset_server_confirm": "Are you sure that you want to reset the XP of all users on the server?",
|
||||
"reset_user_confirm": "Are you sure that you want to reset specified user's XP on this server?",
|
||||
|
Reference in New Issue
Block a user