Compare commits

...

28 Commits
5.0.4 ... 5.0.7

Author SHA1 Message Date
Kwoth
a52a246982 fix: xplb and xpglb pagination fixed, closes #430
fix: Page number when there is an unknown number of items while paginating is now correct
fix: .stm and .stma fixed and can now mention everyone as long as the user executing the command also can
dev: Cleaned up/improved some code
2024-05-15 13:44:37 +00:00
Kwoth
803fe5db2f Merge branch 'v5' into 'v5'
Updating Dockerfile

See merge request Kwoth/nadekobot!322
2024-05-15 07:29:08 +00:00
Plarpoon
39980a1374 Updating Dockerfile 2024-05-15 07:29:08 +00:00
Kwoth
d2c4af273b dev: Version upped 2024-05-15 07:13:28 +00:00
Kwoth
d51dfa88f1 fix: Fixed .ttt and gifted strings
docs: Updated changelog
dev: Updated some packages
2024-05-14 18:36:33 +00:00
Kwoth
869b9d3b9d change: .hearthstone command will no longer show text
dev: removed html2markdown as it was only used for a fluff text of the hearthstone command
2024-05-13 19:09:35 +00:00
Kwoth
f4b26c5b40 dev: removed docker_compose.yml as you don't need redis.
dev: Removed aws package as we don't need gencmdlist to upload commands anymore, have to find a new way though.
2024-05-13 17:34:06 +00:00
Kwoth
15f629ec53 dev: Using built in rng.Shuffle, using some new .net types, removed some transactions 2024-05-13 17:14:35 +00:00
Kwoth
9406a9cc34 dev: Using collection expressions, no functional change 2024-05-13 14:54:24 +00:00
Kwoth
52438f45e1 dev: Added argumentoutofrange static methods, no functional change 2024-05-13 14:50:55 +00:00
Kwoth
7b2ce072ee dev: removed some unused code 2024-05-13 14:26:45 +00:00
Kwoth
89d93dcffb docs: updated command strings for .repeat command to include targetting channels 2024-05-13 14:18:56 +00:00
Kwoth
0fb34b1c61 docs: Updated changelog 2024-05-13 10:53:47 +00:00
Kwoth
980a6b0af8 dev: removed crowdin.yml 2024-05-13 10:48:55 +00:00
Kwoth
5c7a467caa Merge branch 'v5' of https://gitlab.com/kwoth/nadekobot into v5 2024-05-13 10:48:18 +00:00
Kwoth
35fd5c415d fix: Blackjack and .pick command responses will no longer reply as the original message will always be deleted 2024-05-13 10:48:05 +00:00
Kwoth
2762108986 Update Crowdin configuration file 2024-05-13 06:49:15 +00:00
Kwoth
876d63fd8b fix: Fixed a blackjack string usage 2024-05-13 06:32:46 +00:00
Kwoth
263ef4b47f fix: Fixed blackjack strings 2024-05-13 06:22:48 +00:00
Kwoth
1c540476d3 dev: Fixed a build warning and small cleanup 2024-05-12 19:00:16 +00:00
Kwoth
1d0f3d3fd6 docs: Removed ubuntu 16.04 and 18.04 from supoported version as there are issues installing .net8 there due to glibc version 2024-05-11 10:49:53 +00:00
Kwoth
c7cec25a29 change: .greet / .bye will get properly disabled if the bot can't write to the specified channel 2024-05-11 08:08:48 +00:00
Kwoth
23c8dc00e8 dev: Upped version to 5.0.5 2024-05-11 08:02:59 +00:00
Kwoth
0da8190637 fix: repeat, greet, bye, boost messages can now once again mention anyone
fix: .say #channel fixed
2024-05-11 08:00:48 +00:00
Kwoth
d766295286 docs: Updated supported distros (thx cata) 2024-05-11 05:57:31 +00:00
Kwoth
21e3c64e01 Merge branch 'v5' of https://gitlab.com/kwoth/nadekobot into v5 2024-05-11 02:30:40 +00:00
Kwoth
9ddcd6d89e docs: Updated changelog 2024-05-11 02:05:56 +00:00
Kwoth
a154d5881c docs: Updated changelog 2024-05-10 15:36:12 +00:00
98 changed files with 594 additions and 837 deletions

View File

@@ -2,7 +2,36 @@
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.7] - 15.05.2024
### Fixed
- `.streammessage` will once again be able to mention anyone (as long as the user setting the message has the permission to mention everyone)
- `.streammsgall` fixed
- `.xplb` and `.xpglb` pagination fixed
- Fixed page number when the total number of elements is unknown
## [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

View File

@@ -1,16 +1,25 @@
# Use the .NET 8.0 SDK as the base image for the build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /source
# Copy the .csproj files for each project
COPY src/Nadeko.Medusa/*.csproj src/Nadeko.Medusa/
COPY src/NadekoBot/*.csproj src/NadekoBot/
COPY src/NadekoBot.Coordinator/*.csproj src/NadekoBot.Coordinator/
COPY src/NadekoBot.Generators/*.csproj src/NadekoBot.Generators/
COPY src/NadekoBot.Voice/*.csproj src/NadekoBot.Voice/
COPY NuGet.Config ./
# Restore the dependencies for the NadekoBot project
RUN dotnet restore src/NadekoBot/
# Copy the rest of the source code
COPY . .
# Set the working directory to the NadekoBot project
WORKDIR /source/src/NadekoBot
# Build and publish the NadekoBot project, then clean up unnecessary files
RUN set -xe; \
dotnet --version; \
dotnet publish -c Release -o /app --no-restore; \
@@ -19,28 +28,33 @@ RUN set -xe; \
find /app -type f -exec chmod -x {} \; ;\
chmod +x /app/NadekoBot
# final stage/image
# Use the .NET 8.0 runtime as the base image for the final stage
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
# Create a new user, install dependencies, and set up sudoers file
RUN set -xe; \
useradd -m nadeko; \
apt-get update; \
apt-get install -y --no-install-recommends libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 sudo; \
update-alternatives --install /usr/local/bin/python python /usr/bin/python3.9 1; \
apt-get install -y --no-install-recommends libsqlite3-0 curl ffmpeg sudo python3; \
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
curl -Lo /usr/local/bin/yt-dlp https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp; \
chmod a+rx /usr/local/bin/yt-dlp; \
apt-get autoremove -y; \
apt-get autoclean -y
# Copy the built application and the entrypoint script from the build stage
COPY --from=build /app ./
COPY docker-entrypoint.sh /usr/local/sbin
# Set environment variables
ENV shard_id=0
ENV total_shards=1
ENV NadekoBot__creds=/app/data/creds.yml
# Define the data directory as a volume
VOLUME [ "/app/data" ]
# Set the entrypoint and default command
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
CMD dotnet NadekoBot.dll "$shard_id" "$total_shards"
CMD dotnet NadekoBot.dll "$shard_id" "$total_shards"

View File

@@ -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

View File

@@ -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

View File

@@ -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`

View File

@@ -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)' == '' ">

View File

@@ -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>

View File

@@ -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];
}

View File

@@ -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
];
}

View File

@@ -20,48 +20,48 @@ public static class DiscordUserExtensions
string discrim,
string avatarId)
=> ctx.GetTable<DiscordUser>()
.InsertOrUpdate(
() => new()
{
UserId = userId,
Username = username,
Discriminator = discrim,
AvatarId = avatarId,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
Username = username,
Discriminator = discrim,
AvatarId = avatarId
},
() => new()
{
UserId = userId
});
.InsertOrUpdate(
() => new()
{
UserId = userId,
Username = username,
Discriminator = discrim,
AvatarId = avatarId,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
Username = username,
Discriminator = discrim,
AvatarId = avatarId
},
() => new()
{
UserId = userId
});
public static Task EnsureUserCreatedAsync(
this DbContext ctx,
ulong userId)
=> ctx.GetTable<DiscordUser>()
.InsertOrUpdateAsync(
() => new()
{
UserId = userId,
Username = "Unknown",
Discriminator = "????",
AvatarId = string.Empty,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
},
() => new()
{
UserId = userId
});
.InsertOrUpdateAsync(
() => new()
{
UserId = userId,
Username = "Unknown",
Discriminator = "????",
AvatarId = string.Empty,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
},
() => new()
{
UserId = userId
});
//temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown
public static DiscordUser GetOrCreateUser(
@@ -83,25 +83,29 @@ public static class DiscordUserExtensions
public static int GetUserGlobalRank(this DbSet<DiscordUser> users, ulong id)
=> users.AsQueryable()
.Where(x => x.TotalXp
> users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault())
.Count()
.Where(x => x.TotalXp
> users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault())
.Count()
+ 1;
public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page, int perPage)
=> users.AsQueryable().OrderByDescending(x => x.TotalXp).Skip(page * perPage).Take(perPage).AsEnumerable()
.ToArray();
public static async Task<IReadOnlyCollection<DiscordUser>> GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page, int perPage)
=> await users.ToLinqToDBTable()
.OrderByDescending(x => x.TotalXp)
.Skip(page * perPage)
.Take(perPage)
.ToArrayAsyncLinqToDB();
public static Task<List<DiscordUser>> GetTopRichest(
this DbSet<DiscordUser> users,
ulong botId,
int page = 0, int perPage = 9)
int page = 0,
int perPage = 9)
=> users.AsQueryable()
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
.Skip(page * perPage)
.Take(perPage)
.ToListAsyncLinqToDB();
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
.Skip(page * perPage)
.Take(perPage)
.ToListAsyncLinqToDB();
public static async Task<long> GetUserCurrencyAsync(this DbSet<DiscordUser> users, ulong userId)
=> (await users.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId))?.CurrencyAmount ?? 0;
@@ -118,8 +122,8 @@ public static class DiscordUserExtensions
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
=> users.AsQueryable()
.Where(x => x.UserId != botId)
.OrderByDescending(x => x.CurrencyAmount)
.Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100)
.Sum(x => x.CurrencyAmount);
.Where(x => x.UserId != botId)
.OrderByDescending(x => x.CurrencyAmount)
.Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100)
.Sum(x => x.CurrencyAmount);
}

View File

@@ -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.

View File

@@ -2,7 +2,6 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
namespace NadekoBot.Db;
@@ -27,33 +26,33 @@ public static class UserXpExtensions
return usr;
}
public static List<UserXpStats> GetUsersFor(this DbSet<UserXpStats> xps, ulong guildId, int page)
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Skip(page * 9)
.Take(9)
.ToList();
public static async Task<IReadOnlyCollection<UserXpStats>> GetUsersFor(
this DbSet<UserXpStats> xps,
ulong guildId,
int page)
=> await xps.ToLinqToDBTable()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Skip(page * 9)
.Take(9)
.ToArrayAsyncLinqToDB();
public static List<UserXpStats> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Take(count)
.ToList();
public static async Task<List<UserXpStats>> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
=> await xps.ToLinqToDBTable()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Take(count)
.ToListAsyncLinqToDB();
public static int GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId
&& x.Xp + x.AwardedXp
> xps.AsQueryable()
.Where(y => y.UserId == userId && y.GuildId == guildId)
.Select(y => y.Xp + y.AwardedXp)
.FirstOrDefault())
.Count()
public static async Task<int> GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
=> await xps.ToLinqToDBTable()
.Where(x => x.GuildId == guildId
&& x.Xp + x.AwardedXp
> xps.AsQueryable()
.Where(y => y.UserId == userId && y.GuildId == guildId)
.Select(y => y.Xp + y.AwardedXp)
.FirstOrDefault())
.CountAsyncLinqToDB()
+ 1;
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
@@ -61,12 +60,11 @@ public static class UserXpExtensions
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
=> xps.Delete(x => x.GuildId == guildId);
public static async Task<LevelStats> GetLevelDataFor(this ITable<UserXpStats> userXp, ulong guildId, ulong userId)
=> await userXp
.Where(x => x.GuildId == guildId && x.UserId == userId)
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
? new(uxs.Xp + uxs.AwardedXp)
: new(0);
=> await userXp
.Where(x => x.GuildId == guildId && x.UserId == userId)
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
? new(uxs.Xp + uxs.AwardedXp)
: new(0);
}

View File

@@ -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)

View File

@@ -3,6 +3,7 @@ namespace NadekoBot.Db.Models;
public class GuildConfig : DbEntity
{
// public bool Keep { get; set; }
public ulong GuildId { get; set; }
public string Prefix { get; set; }

View File

@@ -33,10 +33,7 @@ public class Permissionv2 : DbEntity, IIndexed
};
public static List<Permissionv2> GetDefaultPermlist
=> new()
{
AllowAllPerm
};
=> [AllowAllPerm];
}
public enum PrimaryPermissionType

View File

@@ -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;

View File

@@ -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";

View File

@@ -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();

View File

@@ -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))

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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>());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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()

View File

@@ -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",

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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)

View File

@@ -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]
@@ -766,7 +766,7 @@ public partial class Gambling : GamblingModule<GamblingService>
}
async Task<IEnumerable<DiscordUser>> GetTopRichest(int curPage)
async Task<IReadOnlyCollection<DiscordUser>> GetTopRichest(int curPage)
{
if (opts.Clean)
{

View File

@@ -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
{

View File

@@ -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();

View File

@@ -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()

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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.

View File

@@ -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)

View File

@@ -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;

View File

@@ -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 += _ =>
{

View File

@@ -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;

View File

@@ -1,12 +1,9 @@
#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;
@@ -249,13 +246,16 @@ public sealed partial 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
@@ -424,90 +424,8 @@ public sealed partial 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));
}

View File

@@ -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"
}
];
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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+))");

View File

@@ -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();
}

View File

@@ -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>()

View File

@@ -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)

View File

@@ -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;

View File

@@ -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");

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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();
// }

View File

@@ -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,
// });
// }
// }

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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);

View File

@@ -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; }
}

View File

@@ -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();

View File

@@ -143,6 +143,10 @@ public partial class Searches
if (--index < 0)
return;
var canMentionEveryone = (ctx.User as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
if (!canMentionEveryone)
message = message?.SanitizeAllMentions();
if (!_service.SetStreamMessage(ctx.Guild.Id, index, message, out var fs))
{
await Response().Confirm(strs.stream_not_following).SendAsync();
@@ -160,6 +164,10 @@ public partial class Searches
[UserPerm(GuildPerm.ManageMessages)]
public async Task StreamMessageAll([Leftover] string message)
{
var canMentionEveryone = (ctx.User as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
if (!canMentionEveryone)
message = message?.SanitizeAllMentions();
var count = _service.SetStreamMessageForAll(ctx.Guild.Id, message);
if (count == 0)

View File

@@ -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];
}
}
@@ -297,6 +294,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
var msg = await _sender.Response(textChannel)
.Embed(GetEmbed(fs.GuildId, stream, false))
.Text(message)
.Sanitize(false)
.SendAsync();
// only cache the ids of channel/message pairs
@@ -572,12 +570,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];
}
@@ -618,7 +616,9 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
{
using var uow = _db.GetDbContext();
var all = uow.Set<FollowedStream>().ToList();
var all = uow.Set<FollowedStream>()
.Where(x => x.GuildId == guildId)
.ToList();
if (all.Count == 0)
return 0;
@@ -626,6 +626,19 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
all.ForEach(x => x.Message = message);
uow.SaveChanges();
lock (_shardLock)
{
foreach (var fs in all)
{
var streams = GetLocalGuildStreams(fs.CreateKey(), guildId);
// message doesn't participate in equality checking
// removing and adding = update
streams.Remove(fs);
streams.Add(fs);
}
}
return all.Count;
}

View File

@@ -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>();

View File

@@ -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 [];
}
}

View File

@@ -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>()

View File

@@ -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
})

View File

@@ -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,

View File

@@ -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);

View File

@@ -1,7 +1,6 @@
#nullable disable warnings
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Db.Models;
using NadekoBot.Db;
using NadekoBot.Modules.Patronage;
namespace NadekoBot.Modules.Xp;
@@ -191,21 +190,22 @@ public partial class Xp : NadekoModule<XpService>
await ctx.Channel.TriggerTypingAsync();
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
allCleanUsers = _service.GetTopUserXps(ctx.Guild.Id, 1000)
.Where(user => socketGuild.GetUser(user.UserId) is not null)
.ToList();
allCleanUsers = (await _service.GetTopUserXps(ctx.Guild.Id, 1000))
.Where(user => socketGuild.GetUser(user.UserId) is not null)
.ToList();
}
await Response()
var res = opts.Clean
? Response()
.Paginated()
.PageItems<UserXpStats>(opts.Clean
? (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(allCleanUsers.Skip(curPage * 9)
.Take(9)
.ToList())
: (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(_service.GetUserXps(ctx.Guild.Id, curPage)))
.Items(allCleanUsers)
: Response()
.Paginated()
.PageItems((curPage) => _service.GetUserXps(ctx.Guild.Id, curPage));
await res
.PageSize(9)
.CurrentPage(page)
.AddFooter(false)
.Page((users, curPage) =>
{
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
@@ -241,23 +241,33 @@ public partial class Xp : NadekoModule<XpService>
{
if (--page < 0 || page > 99)
return;
var users = _service.GetUserXps(page);
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.global_leaderboard)).WithOkColor();
await Response()
.Paginated()
.PageItems(async curPage => await _service.GetUserXps(curPage))
.PageSize(9)
.Page((users, curPage) =>
{
var embed = _sender.CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.global_leaderboard));
if (!users.Any())
embed.WithDescription("-");
else
{
for (var i = 0; i < users.Length; i++)
{
var user = users[i];
embed.AddField($"#{i + 1 + (page * 9)} {user.ToString()}",
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
}
}
if (!users.Any())
{
embed.WithDescription("-");
return embed;
}
await Response().Embed(embed).SendAsync();
for (var i = 0; i < users.Count; i++)
{
var user = users[i];
embed.AddField($"#{i + 1 + (curPage * 9)} {user}",
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
}
return embed;
})
.SendAsync();
}
[Cmd]

View File

@@ -563,22 +563,23 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
uow.SaveChanges();
}
public List<UserXpStats> GetUserXps(ulong guildId, int page)
public async Task<IReadOnlyCollection<UserXpStats>> GetUserXps(ulong guildId, int page)
{
using var uow = _db.GetDbContext();
return uow.Set<UserXpStats>().GetUsersFor(guildId, page);
await using var uow = _db.GetDbContext();
return await uow.Set<UserXpStats>().GetUsersFor(guildId, page);
}
public List<UserXpStats> GetTopUserXps(ulong guildId, int count)
public async Task<IReadOnlyCollection<UserXpStats>> GetTopUserXps(ulong guildId, int count)
{
using var uow = _db.GetDbContext();
return uow.Set<UserXpStats>().GetTopUserXps(guildId, count);
await using var uow = _db.GetDbContext();
return await uow.Set<UserXpStats>().GetTopUserXps(guildId, count);
}
public DiscordUser[] GetUserXps(int page, int perPage = 9)
public Task<IReadOnlyCollection<DiscordUser>> GetUserXps(int page, int perPage = 9)
{
using var uow = _db.GetDbContext();
return uow.Set<DiscordUser>().GetUsersXpLeaderboardFor(page, perPage);
return uow.Set<DiscordUser>()
.GetUsersXpLeaderboardFor(page, perPage);
}
public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationLocation type)
@@ -884,7 +885,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
var du = uow.GetOrCreateUser(user, set => set.Include(x => x.Club));
var totalXp = du.TotalXp;
var globalRank = uow.Set<DiscordUser>().GetUserGlobalRank(user.Id);
var guildRank = uow.Set<UserXpStats>().GetUserGuildRanking(user.Id, user.GuildId);
var guildRank = await uow.Set<UserXpStats>().GetUserGuildRanking(user.Id, user.GuildId);
var stats = uow.GetOrCreateUserXpStats(user.GuildId, user.Id);
await uow.SaveChangesAsync();
@@ -1469,7 +1470,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>()

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<Version>5.0.4</Version>
<Version>5.0.7</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>

View File

@@ -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;

View File

@@ -112,7 +112,7 @@ public class Localization : ILocalization
{
Cmd = key,
Desc = key,
Usage = new[] { key }
Usage = [key]
};
}

View File

@@ -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()
{

View File

@@ -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);
}

View File

@@ -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];

View File

@@ -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)];

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 = [];
}
}

View File

@@ -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();
}

View File

@@ -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)
{

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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());
}

View File

@@ -58,7 +58,7 @@ public partial class ResponseBuilder
cb.WithButton(new ButtonBuilder()
.WithStyle(ButtonStyle.Primary)
.WithCustomId(BUTTON_RIGHT)
.WithDisabled(lastPage == 0 || currentPage >= lastPage)
.WithDisabled(lastPage is not null && (lastPage == 0 || currentPage >= lastPage))
.WithEmote(InteractionHelpers.ArrowRight));
return cb;

View File

@@ -1,5 +1,6 @@
using NadekoBot.Common.Configs;
using NadekoBot.Db.Models;
using System.Collections.ObjectModel;
namespace NadekoBot.Extensions;
@@ -375,7 +376,7 @@ public class PaginatedResponseBuilder
=> new SourcedPaginatedResponseBuilder<T>(_builder)
.Items(items);
public SourcedPaginatedResponseBuilder<T> PageItems<T>(Func<int, Task<IEnumerable<T>>> items)
public SourcedPaginatedResponseBuilder<T> PageItems<T>(Func<int, Task<IReadOnlyCollection<T>>> items)
=> new SourcedPaginatedResponseBuilder<T>(_builder)
.PageItems(items);
}
@@ -389,14 +390,14 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
return Task.FromResult<EmbedBuilder>(new());
};
public Func<int, Task<IEnumerable<T>>> ItemsFunc { get; set; } = static delegate
public Func<int, Task<IReadOnlyCollection<T>>> ItemsFunc { get; set; } = static delegate
{
return Task.FromResult(Enumerable.Empty<T>());
return Task.FromResult<IReadOnlyCollection<T>>(ReadOnlyCollection<T>.Empty);
};
public Func<int, Task<SimpleInteractionBase>>? InteractionFunc { get; private set; }
public int Elems { 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; }
@@ -412,18 +413,19 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
{
items = col;
Elems = col.Count;
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage).ToArray() as IReadOnlyCollection<T>);
return this;
}
public SourcedPaginatedResponseBuilder<T> TotalElements(int i)
{
Elems = i;
return this;
}
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IEnumerable<T>>> func)
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IReadOnlyCollection<T>>> func)
{
Elems = null;
ItemsFunc = func;
return this;
}

View File

@@ -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())
{

View File

@@ -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
});
}
}
}

View File

@@ -81,7 +81,7 @@ public class BotStrings : IBotStrings
return new CommandStrings()
{
Examples = new[] { "" },
Examples = [""],
Desc = "?",
Params = []
};

View File

@@ -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)

View File

@@ -167,7 +167,8 @@ public static class Extensions
{
if (lastPage is not null)
return embed.WithFooter($"{curPage + 1} / {lastPage + 1}");
return embed.WithFooter(curPage.ToString());
return embed.WithFooter((curPage + 1).ToString());
}
// public static EmbedBuilder WithOkColor(this EmbedBuilder eb)

View File

@@ -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."

View File

@@ -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?",