mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a52a246982 | ||
|
803fe5db2f | ||
|
39980a1374 | ||
|
d2c4af273b | ||
|
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 |
31
CHANGELOG.md
31
CHANGELOG.md
@@ -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
|
||||
|
||||
|
22
Dockerfile
22
Dockerfile
@@ -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"
|
@@ -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
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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);
|
||||
}
|
@@ -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.
|
||||
|
@@ -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);
|
||||
}
|
@@ -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)
|
||||
|
@@ -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; }
|
||||
|
@@ -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]
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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();
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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]
|
||||
|
@@ -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();
|
||||
|
||||
@@ -1194,7 +1195,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 +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>()
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Version>5.0.3</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>
|
||||
|
||||
|
@@ -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();
|
||||
@@ -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;
|
||||
|
@@ -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 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,13 +412,20 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
|
||||
public SourcedPaginatedResponseBuilder<T> Items(IReadOnlyCollection<T> col)
|
||||
{
|
||||
items = col;
|
||||
TotalElements = col.Count;
|
||||
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
|
||||
Elems = col.Count;
|
||||
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage).ToArray() as IReadOnlyCollection<T>);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IEnumerable<T>>> func)
|
||||
public SourcedPaginatedResponseBuilder<T> TotalElements(int i)
|
||||
{
|
||||
Elems = i;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IReadOnlyCollection<T>>> func)
|
||||
{
|
||||
Elems = null;
|
||||
ItemsFunc = func;
|
||||
return this;
|
||||
}
|
||||
|
@@ -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 = []
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user