mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-12 18:28:27 -04:00
Compare commits
28 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 |
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
|
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
|
### Added
|
||||||
|
|
||||||
|
20
Dockerfile
20
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
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
WORKDIR /source
|
WORKDIR /source
|
||||||
|
|
||||||
|
# Copy the .csproj files for each project
|
||||||
COPY src/Nadeko.Medusa/*.csproj src/Nadeko.Medusa/
|
COPY src/Nadeko.Medusa/*.csproj src/Nadeko.Medusa/
|
||||||
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
COPY src/NadekoBot/*.csproj src/NadekoBot/
|
||||||
COPY src/NadekoBot.Coordinator/*.csproj src/NadekoBot.Coordinator/
|
COPY src/NadekoBot.Coordinator/*.csproj src/NadekoBot.Coordinator/
|
||||||
COPY src/NadekoBot.Generators/*.csproj src/NadekoBot.Generators/
|
COPY src/NadekoBot.Generators/*.csproj src/NadekoBot.Generators/
|
||||||
COPY src/NadekoBot.Voice/*.csproj src/NadekoBot.Voice/
|
COPY src/NadekoBot.Voice/*.csproj src/NadekoBot.Voice/
|
||||||
COPY NuGet.Config ./
|
COPY NuGet.Config ./
|
||||||
|
|
||||||
|
# Restore the dependencies for the NadekoBot project
|
||||||
RUN dotnet restore src/NadekoBot/
|
RUN dotnet restore src/NadekoBot/
|
||||||
|
|
||||||
|
# Copy the rest of the source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Set the working directory to the NadekoBot project
|
||||||
WORKDIR /source/src/NadekoBot
|
WORKDIR /source/src/NadekoBot
|
||||||
|
|
||||||
|
# Build and publish the NadekoBot project, then clean up unnecessary files
|
||||||
RUN set -xe; \
|
RUN set -xe; \
|
||||||
dotnet --version; \
|
dotnet --version; \
|
||||||
dotnet publish -c Release -o /app --no-restore; \
|
dotnet publish -c Release -o /app --no-restore; \
|
||||||
@@ -19,28 +28,33 @@ RUN set -xe; \
|
|||||||
find /app -type f -exec chmod -x {} \; ;\
|
find /app -type f -exec chmod -x {} \; ;\
|
||||||
chmod +x /app/NadekoBot
|
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
|
FROM mcr.microsoft.com/dotnet/runtime:8.0
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Create a new user, install dependencies, and set up sudoers file
|
||||||
RUN set -xe; \
|
RUN set -xe; \
|
||||||
useradd -m nadeko; \
|
useradd -m nadeko; \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
apt-get install -y --no-install-recommends libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 sudo; \
|
apt-get install -y --no-install-recommends libsqlite3-0 curl ffmpeg sudo python3; \
|
||||||
update-alternatives --install /usr/local/bin/python python /usr/bin/python3.9 1; \
|
|
||||||
echo 'Defaults>nadeko env_keep+="ASPNETCORE_* DOTNET_* NadekoBot_* shard_id total_shards TZ"' > /etc/sudoers.d/nadeko; \
|
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; \
|
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; \
|
chmod a+rx /usr/local/bin/yt-dlp; \
|
||||||
apt-get autoremove -y; \
|
apt-get autoremove -y; \
|
||||||
apt-get autoclean -y
|
apt-get autoclean -y
|
||||||
|
|
||||||
|
# Copy the built application and the entrypoint script from the build stage
|
||||||
COPY --from=build /app ./
|
COPY --from=build /app ./
|
||||||
COPY docker-entrypoint.sh /usr/local/sbin
|
COPY docker-entrypoint.sh /usr/local/sbin
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
ENV shard_id=0
|
ENV shard_id=0
|
||||||
ENV total_shards=1
|
ENV total_shards=1
|
||||||
ENV NadekoBot__creds=/app/data/creds.yml
|
ENV NadekoBot__creds=/app/data/creds.yml
|
||||||
|
|
||||||
|
# Define the data directory as a volume
|
||||||
VOLUME [ "/app/data" ]
|
VOLUME [ "/app/data" ]
|
||||||
|
|
||||||
|
# Set the entrypoint and default command
|
||||||
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
|
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:
|
##### Compatible operating systems:
|
||||||
|
|
||||||
- Ubuntu: 20.04, 22.04, 22.10 +
|
- Ubuntu: 20.04, 21.04, 21.10, 22.04
|
||||||
- Debian: 11 +
|
- Mint: 19, 20
|
||||||
- CentOS: 7
|
- Debian: 10, 11, 12
|
||||||
- openSUSE 15
|
- RockyLinux: 8, 9
|
||||||
- Fedora: 33, 34, 35
|
- AlmaLinux: 8, 9
|
||||||
|
- openSUSE Leap: 15.5, 15.6 & Tumbleweed
|
||||||
|
- Fedora: 38, 39, 40, 41, 42
|
||||||
|
|
||||||
## Linux From Source
|
## Linux From Source
|
||||||
|
|
||||||
|
@@ -93,7 +93,11 @@ 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
|
- ⚠️ 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:
|
2. Navigate to your bot's folder, example:
|
||||||
- `cd ~/Desktop/nadekobot`
|
- `cd ~/Desktop/nadekobot`
|
||||||
3. Pull the new version
|
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`
|
- `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
|
- ⚠️ 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
|
4. **Backup** old output in case your data is overwritten
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Discord.Net.Core" Version="3.204.0" />
|
<PackageReference Include="Discord.Net.Core" Version="3.204.0" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
<PackageReference Include="YamlDotNet" Version="15.1.2" />
|
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Version)' == '' ">
|
<PropertyGroup Condition=" '$(Version)' == '' ">
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="YamlDotNet" Version="15.1.2" />
|
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -417,8 +417,7 @@ namespace NadekoBot.Coordinator
|
|||||||
{
|
{
|
||||||
lock (locker)
|
lock (locker)
|
||||||
{
|
{
|
||||||
if (shardId >= _shardStatuses.Length)
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(shardId, _shardStatuses.Length);
|
||||||
throw new ArgumentOutOfRangeException(nameof(shardId));
|
|
||||||
|
|
||||||
return _shardStatuses[shardId];
|
return _shardStatuses[shardId];
|
||||||
}
|
}
|
||||||
|
@@ -80,10 +80,10 @@ public sealed class Bot : IBot
|
|||||||
// _interactionService = new(Client.Rest);
|
// _interactionService = new(Client.Rest);
|
||||||
|
|
||||||
Client.Log += Client_Log;
|
Client.Log += Client_Log;
|
||||||
_loadedAssemblies = new[]
|
_loadedAssemblies =
|
||||||
{
|
[
|
||||||
typeof(Bot).Assembly, // bot
|
typeof(Bot).Assembly // bot
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -88,14 +88,18 @@ public static class DiscordUserExtensions
|
|||||||
.Count()
|
.Count()
|
||||||
+ 1;
|
+ 1;
|
||||||
|
|
||||||
public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page, int perPage)
|
public static async Task<IReadOnlyCollection<DiscordUser>> GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page, int perPage)
|
||||||
=> users.AsQueryable().OrderByDescending(x => x.TotalXp).Skip(page * perPage).Take(perPage).AsEnumerable()
|
=> await users.ToLinqToDBTable()
|
||||||
.ToArray();
|
.OrderByDescending(x => x.TotalXp)
|
||||||
|
.Skip(page * perPage)
|
||||||
|
.Take(perPage)
|
||||||
|
.ToArrayAsyncLinqToDB();
|
||||||
|
|
||||||
public static Task<List<DiscordUser>> GetTopRichest(
|
public static Task<List<DiscordUser>> GetTopRichest(
|
||||||
this DbSet<DiscordUser> users,
|
this DbSet<DiscordUser> users,
|
||||||
ulong botId,
|
ulong botId,
|
||||||
int page = 0, int perPage = 9)
|
int page = 0,
|
||||||
|
int perPage = 9)
|
||||||
=> users.AsQueryable()
|
=> users.AsQueryable()
|
||||||
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
|
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
|
||||||
.OrderByDescending(c => c.CurrencyAmount)
|
.OrderByDescending(c => c.CurrencyAmount)
|
||||||
|
@@ -7,19 +7,20 @@ namespace NadekoBot.Db;
|
|||||||
public static class GuildConfigExtensions
|
public static class GuildConfigExtensions
|
||||||
{
|
{
|
||||||
private static List<WarningPunishment> DefaultWarnPunishments
|
private static List<WarningPunishment> DefaultWarnPunishments
|
||||||
=> new()
|
=>
|
||||||
{
|
[
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Count = 3,
|
Count = 3,
|
||||||
Punishment = PunishmentAction.Kick
|
Punishment = PunishmentAction.Kick
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Count = 5,
|
Count = 5,
|
||||||
Punishment = PunishmentAction.Ban
|
Punishment = PunishmentAction.Ban
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets full stream role settings for the guild with the specified id.
|
/// Gets full stream role settings for the guild with the specified id.
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db;
|
namespace NadekoBot.Db;
|
||||||
@@ -27,33 +26,33 @@ public static class UserXpExtensions
|
|||||||
return usr;
|
return usr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<UserXpStats> GetUsersFor(this DbSet<UserXpStats> xps, ulong guildId, int page)
|
public static async Task<IReadOnlyCollection<UserXpStats>> GetUsersFor(
|
||||||
=> xps.AsQueryable()
|
this DbSet<UserXpStats> xps,
|
||||||
.AsNoTracking()
|
ulong guildId,
|
||||||
|
int page)
|
||||||
|
=> await xps.ToLinqToDBTable()
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
||||||
.Skip(page * 9)
|
.Skip(page * 9)
|
||||||
.Take(9)
|
.Take(9)
|
||||||
.ToList();
|
.ToArrayAsyncLinqToDB();
|
||||||
|
|
||||||
public static List<UserXpStats> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
|
public static async Task<List<UserXpStats>> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
|
||||||
=> xps.AsQueryable()
|
=> await xps.ToLinqToDBTable()
|
||||||
.AsNoTracking()
|
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
.OrderByDescending(x => x.Xp + x.AwardedXp)
|
||||||
.Take(count)
|
.Take(count)
|
||||||
.ToList();
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
public static int GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
public static async Task<int> GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
||||||
=> xps.AsQueryable()
|
=> await xps.ToLinqToDBTable()
|
||||||
.AsNoTracking()
|
|
||||||
.Where(x => x.GuildId == guildId
|
.Where(x => x.GuildId == guildId
|
||||||
&& x.Xp + x.AwardedXp
|
&& x.Xp + x.AwardedXp
|
||||||
> xps.AsQueryable()
|
> xps.AsQueryable()
|
||||||
.Where(y => y.UserId == userId && y.GuildId == guildId)
|
.Where(y => y.UserId == userId && y.GuildId == guildId)
|
||||||
.Select(y => y.Xp + y.AwardedXp)
|
.Select(y => y.Xp + y.AwardedXp)
|
||||||
.FirstOrDefault())
|
.FirstOrDefault())
|
||||||
.Count()
|
.CountAsyncLinqToDB()
|
||||||
+ 1;
|
+ 1;
|
||||||
|
|
||||||
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
|
||||||
@@ -68,5 +67,4 @@ public static class UserXpExtensions
|
|||||||
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
|
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
|
||||||
? new(uxs.Xp + uxs.AwardedXp)
|
? new(uxs.Xp + uxs.AwardedXp)
|
||||||
: new(0);
|
: new(0);
|
||||||
|
|
||||||
}
|
}
|
@@ -22,8 +22,7 @@ public static class WarningExtensions
|
|||||||
string mod,
|
string mod,
|
||||||
int index)
|
int index)
|
||||||
{
|
{
|
||||||
if (index < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
|
|
||||||
var warn = warnings.AsQueryable()
|
var warn = warnings.AsQueryable()
|
||||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||||
|
@@ -3,6 +3,7 @@ namespace NadekoBot.Db.Models;
|
|||||||
|
|
||||||
public class GuildConfig : DbEntity
|
public class GuildConfig : DbEntity
|
||||||
{
|
{
|
||||||
|
// public bool Keep { get; set; }
|
||||||
public ulong GuildId { get; set; }
|
public ulong GuildId { get; set; }
|
||||||
|
|
||||||
public string Prefix { get; set; }
|
public string Prefix { get; set; }
|
||||||
|
@@ -33,10 +33,7 @@ public class Permissionv2 : DbEntity, IIndexed
|
|||||||
};
|
};
|
||||||
|
|
||||||
public static List<Permissionv2> GetDefaultPermlist
|
public static List<Permissionv2> GetDefaultPermlist
|
||||||
=> new()
|
=> [AllowAllPerm];
|
||||||
{
|
|
||||||
AllowAllPerm
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PrimaryPermissionType
|
public enum PrimaryPermissionType
|
||||||
|
@@ -97,7 +97,7 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
{
|
{
|
||||||
var newContent = await _repSvc.ReplaceAsync(toSend,
|
var newContent = await _repSvc.ReplaceAsync(toSend,
|
||||||
new(client: _client, guild: user.Guild, channel: channel, users: user));
|
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)
|
if (conf.BoostMessageDeleteAfter > 0)
|
||||||
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
|
||||||
|
|
||||||
@@ -202,12 +202,6 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
if (!users.Any())
|
if (!users.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// var rep = new ReplacementBuilder().WithChannel(channel)
|
|
||||||
// .WithClient(_client)
|
|
||||||
// .WithServer(_client, (SocketGuild)channel.Guild)
|
|
||||||
// .WithManyUsers(users)
|
|
||||||
// .Build();
|
|
||||||
|
|
||||||
var repCtx = new ReplacementContext(client: _client,
|
var repCtx = new ReplacementContext(client: _client,
|
||||||
guild: channel.Guild,
|
guild: channel.Guild,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -217,11 +211,12 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
text = await _repSvc.ReplaceAsync(text, repCtx);
|
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||||
try
|
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)
|
if (conf.AutoDeleteByeMessagesTimer > 0)
|
||||||
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
||||||
}
|
}
|
||||||
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|
||||||
|
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions
|
||||||
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
||||||
{
|
{
|
||||||
Log.Warning(ex,
|
Log.Warning(ex,
|
||||||
@@ -243,26 +238,21 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
if (users.Count == 0)
|
if (users.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// var rep = new ReplacementBuilder()
|
|
||||||
// .WithChannel(channel)
|
|
||||||
// .WithClient(_client)
|
|
||||||
// .WithServer(_client, (SocketGuild)channel.Guild)
|
|
||||||
// .WithManyUsers(users)
|
|
||||||
// .Build();
|
|
||||||
|
|
||||||
var repCtx = new ReplacementContext(client: _client,
|
var repCtx = new ReplacementContext(client: _client,
|
||||||
guild: channel.Guild,
|
guild: channel.Guild,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
users: users.ToArray());
|
users: users.ToArray());
|
||||||
|
|
||||||
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
|
var text = SmartText.CreateFrom(conf.ChannelGreetMessageText);
|
||||||
text = await _repSvc.ReplaceAsync(text, repCtx);
|
text = await _repSvc.ReplaceAsync(text, repCtx);
|
||||||
try
|
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)
|
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
||||||
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
||||||
}
|
}
|
||||||
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
|
||||||
|
|| ex.DiscordCode == DiscordErrorCode.MissingPermissions
|
||||||
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
|| ex.DiscordCode == DiscordErrorCode.UnknownChannel)
|
||||||
{
|
{
|
||||||
Log.Warning(ex,
|
Log.Warning(ex,
|
||||||
@@ -329,13 +319,13 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
// if there are no embeds, add an embed with the footer
|
// if there are no embeds, add an embed with the footer
|
||||||
smartText = seta with
|
smartText = seta with
|
||||||
{
|
{
|
||||||
Embeds = new[]
|
Embeds =
|
||||||
{
|
[
|
||||||
new SmartEmbedArrayElementText()
|
new SmartEmbedArrayElementText()
|
||||||
{
|
{
|
||||||
Footer = CreateFooterSource(user)
|
Footer = CreateFooterSource(user)
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
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
|
catch
|
||||||
{
|
{
|
||||||
@@ -573,8 +563,6 @@ public class GreetService : INService, IReadyExecutor
|
|||||||
|
|
||||||
public bool SetBoostMessage(ulong guildId, ref string message)
|
public bool SetBoostMessage(ulong guildId, ref string message)
|
||||||
{
|
{
|
||||||
message = message.SanitizeMentions();
|
|
||||||
|
|
||||||
using var uow = _db.GetDbContext();
|
using var uow = _db.GetDbContext();
|
||||||
var conf = uow.GuildConfigsForId(guildId, set => set);
|
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||||
conf.BoostMessage = message;
|
conf.BoostMessage = message;
|
||||||
|
@@ -291,8 +291,7 @@ public class MuteService : INService
|
|||||||
|
|
||||||
public async Task<IRole> GetMuteRole(IGuild guild)
|
public async Task<IRole> GetMuteRole(IGuild guild)
|
||||||
{
|
{
|
||||||
if (guild is null)
|
ArgumentNullException.ThrowIfNull(guild);
|
||||||
throw new ArgumentNullException(nameof(guild));
|
|
||||||
|
|
||||||
const string defaultMuteRoleName = "nadeko-mute";
|
const string defaultMuteRoleName = "nadeko-mute";
|
||||||
|
|
||||||
|
@@ -69,8 +69,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
|
|||||||
|
|
||||||
public async Task<string> RemovePlayingAsync(int index)
|
public async Task<string> RemovePlayingAsync(int index)
|
||||||
{
|
{
|
||||||
if (index < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var toRemove = await uow.Set<RotatingPlayingStatus>().AsQueryable().AsNoTracking().Skip(index).FirstOrDefaultAsync();
|
var toRemove = await uow.Set<RotatingPlayingStatus>().AsQueryable().AsNoTracking().Skip(index).FirstOrDefaultAsync();
|
||||||
|
@@ -22,8 +22,7 @@ public class PruneService : INService
|
|||||||
ArgumentNullException.ThrowIfNull(channel, nameof(channel));
|
ArgumentNullException.ThrowIfNull(channel, nameof(channel));
|
||||||
|
|
||||||
var originalAmount = amount;
|
var originalAmount = amount;
|
||||||
if (amount <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
using var cancelSource = new CancellationTokenSource();
|
using var cancelSource = new CancellationTokenSource();
|
||||||
if (!_pruningGuilds.TryAdd(channel.GuildId, cancelSource))
|
if (!_pruningGuilds.TryAdd(channel.GuildId, cancelSource))
|
||||||
|
@@ -250,11 +250,9 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
int group = 0,
|
int group = 0,
|
||||||
int levelReq = 0)
|
int levelReq = 0)
|
||||||
{
|
{
|
||||||
if (group < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(group);
|
||||||
throw new ArgumentOutOfRangeException(nameof(group));
|
|
||||||
|
|
||||||
if (levelReq < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(levelReq);
|
||||||
throw new ArgumentOutOfRangeException(nameof(group));
|
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
|
|
||||||
@@ -307,10 +305,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
|
|||||||
lock (_cacheLock)
|
lock (_cacheLock)
|
||||||
{
|
{
|
||||||
_cache.AddOrUpdate(msg.Id,
|
_cache.AddOrUpdate(msg.Id,
|
||||||
_ => new()
|
_ => [obj],
|
||||||
{
|
|
||||||
obj
|
|
||||||
},
|
|
||||||
(_, list) =>
|
(_, list) =>
|
||||||
{
|
{
|
||||||
list.RemoveAll(x => x.Emote == emote);
|
list.RemoveAll(x => x.Emote == emote);
|
||||||
|
@@ -24,7 +24,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
|||||||
private readonly GuildTimezoneService _tz;
|
private readonly GuildTimezoneService _tz;
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
|
||||||
private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = new();
|
private readonly ConcurrentHashSet<ulong> _ignoreMessageIds = [];
|
||||||
private readonly UserPunishService _punishService;
|
private readonly UserPunishService _punishService;
|
||||||
private readonly IMessageSenderService _sender;
|
private readonly IMessageSenderService _sender;
|
||||||
|
|
||||||
@@ -115,10 +115,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
|||||||
strs.user_status_change("👤" + Format.Bold(gu.Username),
|
strs.user_status_change("👤" + Format.Bold(gu.Username),
|
||||||
Format.Bold(after.Status.ToString())));
|
Format.Bold(after.Status.ToString())));
|
||||||
PresenceUpdates.AddOrUpdate(logChannel,
|
PresenceUpdates.AddOrUpdate(logChannel,
|
||||||
new List<string>
|
[str],
|
||||||
{
|
|
||||||
str
|
|
||||||
},
|
|
||||||
(_, list) =>
|
(_, list) =>
|
||||||
{
|
{
|
||||||
list.Add(str);
|
list.Add(str);
|
||||||
@@ -130,10 +127,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
|||||||
var str =
|
var str =
|
||||||
$"👾`{PrettyCurrentTime(gu.Guild)}`👤__**{gu.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**.";
|
$"👾`{PrettyCurrentTime(gu.Guild)}`👤__**{gu.Username}**__ is now playing **{after.Activities.FirstOrDefault()?.Name ?? "-"}**.";
|
||||||
PresenceUpdates.AddOrUpdate(logChannel,
|
PresenceUpdates.AddOrUpdate(logChannel,
|
||||||
new List<string>
|
[str],
|
||||||
{
|
|
||||||
str
|
|
||||||
},
|
|
||||||
(_, list) =>
|
(_, list) =>
|
||||||
{
|
{
|
||||||
list.Add(str);
|
list.Add(str);
|
||||||
@@ -881,10 +875,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
|
|||||||
if (!string.IsNullOrWhiteSpace(str))
|
if (!string.IsNullOrWhiteSpace(str))
|
||||||
{
|
{
|
||||||
PresenceUpdates.AddOrUpdate(logChannel,
|
PresenceUpdates.AddOrUpdate(logChannel,
|
||||||
new List<string>
|
[str],
|
||||||
{
|
|
||||||
str
|
|
||||||
},
|
|
||||||
(_, list) =>
|
(_, list) =>
|
||||||
{
|
{
|
||||||
list.Add(str);
|
list.Add(str);
|
||||||
|
@@ -64,8 +64,7 @@ public class UserPunishService : INService, IReadyExecutor
|
|||||||
long weight,
|
long weight,
|
||||||
string reason)
|
string reason)
|
||||||
{
|
{
|
||||||
if (weight <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(weight);
|
||||||
throw new ArgumentOutOfRangeException(nameof(weight));
|
|
||||||
|
|
||||||
var modName = mod.ToString();
|
var modName = mod.ToString();
|
||||||
|
|
||||||
|
@@ -130,8 +130,7 @@ public class VcRoleService : INService
|
|||||||
|
|
||||||
public void AddVcRole(ulong guildId, IRole role, ulong vcId)
|
public void AddVcRole(ulong guildId, IRole role, ulong vcId)
|
||||||
{
|
{
|
||||||
if (role is null)
|
ArgumentNullException.ThrowIfNull(role);
|
||||||
throw new ArgumentNullException(nameof(role));
|
|
||||||
|
|
||||||
var guildVcRoles = VcRoles.GetOrAdd(guildId, new ConcurrentDictionary<ulong, IRole>());
|
var guildVcRoles = VcRoles.GetOrAdd(guildId, new ConcurrentDictionary<ulong, IRole>());
|
||||||
|
|
||||||
|
@@ -356,7 +356,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
|||||||
if (maybeGuildId is { } guildId)
|
if (maybeGuildId is { } guildId)
|
||||||
{
|
{
|
||||||
newguildExpressions.AddOrUpdate(guildId,
|
newguildExpressions.AddOrUpdate(guildId,
|
||||||
new[] { expr },
|
[expr],
|
||||||
(_, old) =>
|
(_, old) =>
|
||||||
{
|
{
|
||||||
var newArray = old.ToArray();
|
var newArray = old.ToArray();
|
||||||
@@ -389,7 +389,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
|||||||
expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
|
expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
|
||||||
|
|
||||||
if (maybeGuildId is { } guildId)
|
if (maybeGuildId is { } guildId)
|
||||||
newguildExpressions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
|
newguildExpressions.AddOrUpdate(guildId, [expr], (_, old) => old.With(expr));
|
||||||
else
|
else
|
||||||
return _pubSub.Pub(_gexprAddedKey, expr);
|
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)
|
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
||||||
{
|
{
|
||||||
if (bet < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(bet);
|
||||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
|
||||||
|
|
||||||
var user = new AnimalRacingUser(userName, userId, 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)
|
public async Task<bool> AwardAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
if (amount <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
await ctx.GetTable<BankUser>()
|
await ctx.GetTable<BankUser>()
|
||||||
@@ -41,8 +40,7 @@ public sealed class BankService : IBankService, INService
|
|||||||
|
|
||||||
public async Task<bool> TakeAsync(ulong userId, long amount)
|
public async Task<bool> TakeAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
if (amount <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
var rows = await ctx.Set<BankUser>()
|
var rows = await ctx.Set<BankUser>()
|
||||||
@@ -58,8 +56,7 @@ public sealed class BankService : IBankService, INService
|
|||||||
|
|
||||||
public async Task<bool> DepositAsync(ulong userId, long amount)
|
public async Task<bool> DepositAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
if (amount <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
if (!await _cur.RemoveAsync(userId, amount, new("bank", "deposit")))
|
if (!await _cur.RemoveAsync(userId, amount, new("bank", "deposit")))
|
||||||
return false;
|
return false;
|
||||||
@@ -86,8 +83,7 @@ public sealed class BankService : IBankService, INService
|
|||||||
|
|
||||||
public async Task<bool> WithdrawAsync(ulong userId, long amount)
|
public async Task<bool> WithdrawAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
if (amount <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
var rows = await ctx.Set<BankUser>()
|
var rows = await ctx.Set<BankUser>()
|
||||||
|
@@ -50,12 +50,12 @@ public partial class Gambling
|
|||||||
bj.GameEnded += Bj_GameEnded;
|
bj.GameEnded += Bj_GameEnded;
|
||||||
bj.Start();
|
bj.Start();
|
||||||
|
|
||||||
await Response().Confirm(strs.bj_created).SendAsync();
|
await Response().NoReply().Confirm(strs.bj_created(ctx.User.ToString())).SendAsync();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (await bj.Join(ctx.User, amount))
|
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
|
else
|
||||||
{
|
{
|
||||||
Log.Information("{User} can't join a blackjack game as it's in {BlackjackState} state already",
|
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)
|
public User(IUser user, long bet)
|
||||||
{
|
{
|
||||||
if (bet <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bet);
|
||||||
throw new ArgumentOutOfRangeException(nameof(bet));
|
|
||||||
|
|
||||||
Bet = bet;
|
Bet = bet;
|
||||||
DiscordUser = user;
|
DiscordUser = user;
|
||||||
|
@@ -12,9 +12,9 @@ public partial class Gambling
|
|||||||
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
|
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
|
||||||
{
|
{
|
||||||
private static readonly string[] _numbers =
|
private static readonly string[] _numbers =
|
||||||
{
|
[
|
||||||
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"
|
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"
|
||||||
};
|
];
|
||||||
|
|
||||||
private int RepostCounter
|
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 Regex _fudgeRegex = new(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly char[] _fateRolls = { '-', ' ', '+' };
|
private static readonly char[] _fateRolls = ['-', ' ', '+'];
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
|
|
||||||
public DiceRollCommands(IImageCache images)
|
public DiceRollCommands(IImageCache images)
|
||||||
|
@@ -466,7 +466,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
return;
|
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]
|
[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 _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]
|
[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)
|
if (opts.Clean)
|
||||||
{
|
{
|
||||||
|
@@ -131,8 +131,8 @@ public partial class BetRollConfig
|
|||||||
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
|
public BetRollPair[] Pairs { get; set; } = Array.Empty<BetRollPair>();
|
||||||
|
|
||||||
public BetRollConfig()
|
public BetRollConfig()
|
||||||
=> Pairs = new BetRollPair[]
|
=> Pairs =
|
||||||
{
|
[
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
WhenAbove = 99,
|
WhenAbove = 99,
|
||||||
@@ -148,7 +148,7 @@ public partial class BetRollConfig
|
|||||||
WhenAbove = 66,
|
WhenAbove = 66,
|
||||||
MultiplyBy = 2
|
MultiplyBy = 2
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
@@ -207,7 +207,7 @@ public partial class LuckyLadderSettings
|
|||||||
public decimal[] Multipliers { get; set; }
|
public decimal[] Multipliers { get; set; }
|
||||||
|
|
||||||
public LuckyLadderSettings()
|
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]
|
[Cloneable]
|
||||||
@@ -228,11 +228,11 @@ public sealed partial class WaifuConfig
|
|||||||
List of items available for gifting.
|
List of items available for gifting.
|
||||||
If negative is true, gift will instead reduce waifu value.
|
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()
|
public WaifuConfig()
|
||||||
=> Items = new()
|
=> Items =
|
||||||
{
|
[
|
||||||
new("🥔", 5, "Potato"),
|
new("🥔", 5, "Potato"),
|
||||||
new("🍪", 10, "Cookie"),
|
new("🍪", 10, "Cookie"),
|
||||||
new("🥖", 20, "Bread"),
|
new("🥖", 20, "Bread"),
|
||||||
@@ -269,7 +269,7 @@ public sealed partial class WaifuConfig
|
|||||||
new("🚁", 20000, "Helicopter"),
|
new("🚁", 20000, "Helicopter"),
|
||||||
new("🚀", 30000, "Spaceship"),
|
new("🚀", 30000, "Spaceship"),
|
||||||
new("🌕", 50000, "Moon")
|
new("🌕", 50000, "Moon")
|
||||||
};
|
];
|
||||||
|
|
||||||
public class WaifuDecayConfig
|
public class WaifuDecayConfig
|
||||||
{
|
{
|
||||||
|
@@ -24,6 +24,7 @@ public abstract class GamblingModule<TService> : NadekoModule<TService>
|
|||||||
{
|
{
|
||||||
if (amount < 1)
|
if (amount < 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (amount < Config.MinBet)
|
if (amount < Config.MinBet)
|
||||||
{
|
{
|
||||||
await Response().Error(strs.min_bet_limit(Format.Bold(Config.MinBet.ToString()) + CurrencySign)).SendAsync();
|
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)
|
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);
|
msg.DeleteAfter(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,10 +20,8 @@ public class ShopService : IShopService, INService
|
|||||||
|
|
||||||
public async Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice)
|
public async Task<bool> ChangeEntryPriceAsync(ulong guildId, int index, int newPrice)
|
||||||
{
|
{
|
||||||
if (index < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(newPrice);
|
||||||
if (newPrice <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(newPrice));
|
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var entries = GetEntriesInternal(uow, guildId);
|
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)
|
public async Task<bool> ChangeEntryNameAsync(ulong guildId, int index, string newName)
|
||||||
{
|
{
|
||||||
if (index < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
if (string.IsNullOrWhiteSpace(newName))
|
if (string.IsNullOrWhiteSpace(newName))
|
||||||
throw new ArgumentNullException(nameof(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)
|
public async Task<bool> SwapEntriesAsync(ulong guildId, int index1, int index2)
|
||||||
{
|
{
|
||||||
if (index1 < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(index1);
|
||||||
throw new ArgumentOutOfRangeException(nameof(index1));
|
ArgumentOutOfRangeException.ThrowIfNegative(index2);
|
||||||
if (index2 < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(index2));
|
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var entries = GetEntriesInternal(uow, guildId);
|
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)
|
public async Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex)
|
||||||
{
|
{
|
||||||
if (fromIndex < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(fromIndex);
|
||||||
throw new ArgumentOutOfRangeException(nameof(fromIndex));
|
ArgumentOutOfRangeException.ThrowIfNegative(toIndex);
|
||||||
if (toIndex < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(toIndex));
|
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var entries = GetEntriesInternal(uow, guildId);
|
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)
|
public static IEnumerable<WaifuLbResult> GetTop(this DbSet<WaifuInfo> waifus, int count, int skip = 0)
|
||||||
{
|
{
|
||||||
if (count < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(count);
|
||||||
throw new ArgumentOutOfRangeException(nameof(count));
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
return new List<WaifuLbResult>();
|
return [];
|
||||||
|
|
||||||
return waifus.Include(wi => wi.Waifu)
|
return waifus.Include(wi => wi.Waifu)
|
||||||
.Include(wi => wi.Affinity)
|
.Include(wi => wi.Affinity)
|
||||||
|
@@ -14,5 +14,5 @@ public interface IGamblingService
|
|||||||
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
||||||
Task<FlipResult[]> FlipAsync(int count);
|
Task<FlipResult[]> FlipAsync(int count);
|
||||||
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
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,8 +20,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
|
public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
if (amount < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
@@ -47,8 +46,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
public async Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount)
|
public async Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
if (amount < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
if (amount > 0)
|
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)
|
public async Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess)
|
||||||
{
|
{
|
||||||
if (amount < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
if (guess > 1)
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(guess, 1);
|
||||||
throw new ArgumentOutOfRangeException(nameof(guess));
|
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
@@ -105,19 +101,18 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
return result;
|
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)
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
if (guessColor is null && guessValue is null)
|
if (maybeGuessColor is null && maybeGuessValue is null)
|
||||||
throw new ArgumentNullException();
|
throw new ArgumentNullException();
|
||||||
|
|
||||||
if (guessColor > 1)
|
if (maybeGuessColor > 1)
|
||||||
throw new ArgumentOutOfRangeException(nameof(guessColor));
|
throw new ArgumentOutOfRangeException(nameof(maybeGuessColor));
|
||||||
|
|
||||||
if (guessValue > 1)
|
if (maybeGuessValue > 1)
|
||||||
throw new ArgumentOutOfRangeException(nameof(guessValue));
|
throw new ArgumentOutOfRangeException(nameof(maybeGuessValue));
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
@@ -130,7 +125,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var game = new BetdrawGame();
|
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;
|
var won = (long)result.Won;
|
||||||
if (won > 0)
|
if (won > 0)
|
||||||
@@ -143,8 +138,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
|
public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
if (amount < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
@@ -170,8 +164,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
public Task<FlipResult[]> FlipAsync(int count)
|
public Task<FlipResult[]> FlipAsync(int count)
|
||||||
{
|
{
|
||||||
if (count < 1)
|
ArgumentOutOfRangeException.ThrowIfLessThan(count, 1);
|
||||||
throw new ArgumentOutOfRangeException(nameof(count));
|
|
||||||
|
|
||||||
var game = new BetflipGame(0);
|
var game = new BetflipGame(0);
|
||||||
|
|
||||||
@@ -242,11 +235,8 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
public async Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick)
|
public async Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick)
|
||||||
{
|
{
|
||||||
if (amount < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(amount);
|
||||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(pick, 2);
|
||||||
|
|
||||||
if (pick > 2)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(pick));
|
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
|
@@ -42,7 +42,7 @@ public sealed class AcrophobiaGame : IDisposable
|
|||||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||||
private readonly NadekoRandom _rng;
|
private readonly NadekoRandom _rng;
|
||||||
|
|
||||||
private readonly HashSet<ulong> _usersWhoVoted = new();
|
private readonly HashSet<ulong> _usersWhoVoted = [];
|
||||||
|
|
||||||
public AcrophobiaGame(Options options)
|
public AcrophobiaGame(Options options)
|
||||||
{
|
{
|
||||||
|
@@ -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")]
|
[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.",
|
"Most definitely yes.",
|
||||||
"For sure.",
|
"For sure.",
|
||||||
"Totally!",
|
"Totally!",
|
||||||
@@ -49,52 +49,59 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
|||||||
"Don't even think about it.",
|
"Don't even think about it.",
|
||||||
"Definitely no.",
|
"Definitely no.",
|
||||||
"NO - It may cause disease contraction!"
|
"NO - It may cause disease contraction!"
|
||||||
};
|
];
|
||||||
|
|
||||||
[Comment("List of animals which will be used for the animal race game (.race)")]
|
[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()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🐼",
|
Icon = "🐼",
|
||||||
Name = "Panda"
|
Name = "Panda"
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🐻",
|
Icon = "🐻",
|
||||||
Name = "Bear"
|
Name = "Bear"
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🐧",
|
Icon = "🐧",
|
||||||
Name = "Pengu"
|
Name = "Pengu"
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🐨",
|
Icon = "🐨",
|
||||||
Name = "Koala"
|
Name = "Koala"
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🐬",
|
Icon = "🐬",
|
||||||
Name = "Dolphin"
|
Name = "Dolphin"
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🐞",
|
Icon = "🐞",
|
||||||
Name = "Ladybird"
|
Name = "Ladybird"
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🦀",
|
Icon = "🦀",
|
||||||
Name = "Crab"
|
Name = "Crab"
|
||||||
},
|
},
|
||||||
|
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Icon = "🦄",
|
Icon = "🦄",
|
||||||
Name = "Unicorn"
|
Name = "Unicorn"
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
|
|
||||||
[Comment(@"Which chatbot API should bot use.
|
[Comment(@"Which chatbot API should bot use.
|
||||||
'cleverbot' - bot will use Cleverbot API.
|
'cleverbot' - bot will use Cleverbot API.
|
||||||
|
@@ -33,8 +33,8 @@ public sealed class NunchiGame : IDisposable
|
|||||||
|
|
||||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||||
|
|
||||||
private HashSet<(ulong Id, string Name)> participants = new();
|
private HashSet<(ulong Id, string Name)> participants = [];
|
||||||
private readonly HashSet<(ulong Id, string Name)> _passed = new();
|
private readonly HashSet<(ulong Id, string Name)> _passed = [];
|
||||||
private Timer killTimer;
|
private Timer killTimer;
|
||||||
|
|
||||||
public NunchiGame(ulong creatorId, string creatorName)
|
public NunchiGame(ulong creatorId, string creatorName)
|
||||||
|
@@ -17,9 +17,9 @@ public class TicTacToe
|
|||||||
private IGuildUser winner;
|
private IGuildUser winner;
|
||||||
|
|
||||||
private readonly string[] _numbers =
|
private readonly string[] _numbers =
|
||||||
{
|
[
|
||||||
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:"
|
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:"
|
||||||
};
|
];
|
||||||
|
|
||||||
private IUserMessage previousMessage;
|
private IUserMessage previousMessage;
|
||||||
private Timer timeoutTimer;
|
private Timer timeoutTimer;
|
||||||
@@ -42,7 +42,7 @@ public class TicTacToe
|
|||||||
_options = options;
|
_options = options;
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
|
|
||||||
_users = new[] { firstUser, null };
|
_users = [firstUser, null];
|
||||||
_state = new int?[,] { { null, null, null }, { null, null, null }, { null, null, null } };
|
_state = new int?[,] { { null, null, null }, { null, null, null }, { null, null, null } };
|
||||||
|
|
||||||
phase = Phase.Starting;
|
phase = Phase.Starting;
|
||||||
|
@@ -37,7 +37,7 @@ public partial class Games
|
|||||||
|
|
||||||
game = new(Strings, _client, channel, (IGuildUser)ctx.User, options, _sender);
|
game = new(Strings, _client, channel, (IGuildUser)ctx.User, options, _sender);
|
||||||
_service.TicTacToeGames.Add(channel.Id, game);
|
_service.TicTacToeGames.Add(channel.Id, game);
|
||||||
await Response().Confirm(strs.ttt_created).SendAsync();
|
await Response().Confirm(strs.ttt_created(ctx.User)).SendAsync();
|
||||||
|
|
||||||
game.OnEnded += _ =>
|
game.OnEnded += _ =>
|
||||||
{
|
{
|
||||||
|
@@ -9,13 +9,13 @@ public class TriviaQuestion
|
|||||||
public const int MAX_STRING_LENGTH = 22;
|
public const int MAX_STRING_LENGTH = 22;
|
||||||
|
|
||||||
//represents the min size to judge levDistance with
|
//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(9, 0),
|
||||||
new(14, 1),
|
new(14, 1),
|
||||||
new(19, 2),
|
new(19, 2),
|
||||||
new(22, 3)
|
new(22, 3)
|
||||||
};
|
];
|
||||||
|
|
||||||
public string Category
|
public string Category
|
||||||
=> _qModel.Category;
|
=> _qModel.Category;
|
||||||
|
@@ -1,12 +1,9 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Amazon.S3;
|
|
||||||
using NadekoBot.Modules.Help.Common;
|
using NadekoBot.Modules.Help.Common;
|
||||||
using NadekoBot.Modules.Help.Services;
|
using NadekoBot.Modules.Help.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
|
||||||
using Nadeko.Common.Medusa;
|
using Nadeko.Common.Medusa;
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Help;
|
namespace NadekoBot.Modules.Help;
|
||||||
|
|
||||||
@@ -249,13 +246,16 @@ public sealed partial class Help : NadekoModule<HelpService>
|
|||||||
var succ = new HashSet<CommandInfo>();
|
var succ = new HashSet<CommandInfo>();
|
||||||
if (opts.View != CommandsOptions.ViewType.All)
|
if (opts.View != CommandsOptions.ViewType.All)
|
||||||
{
|
{
|
||||||
succ = new((await cmds.Select(async x =>
|
succ =
|
||||||
|
[
|
||||||
|
..(await cmds.Select(async x =>
|
||||||
{
|
{
|
||||||
var pre = await x.CheckPreconditionsAsync(Context, _services);
|
var pre = await x.CheckPreconditionsAsync(Context, _services);
|
||||||
return (Cmd: x, Succ: pre.IsSuccess);
|
return (Cmd: x, Succ: pre.IsSuccess);
|
||||||
})
|
})
|
||||||
.WhenAll()).Where(x => x.Succ)
|
.WhenAll()).Where(x => x.Succ)
|
||||||
.Select(x => x.Cmd));
|
.Select(x => x.Cmd)
|
||||||
|
];
|
||||||
|
|
||||||
if (opts.View == CommandsOptions.ViewType.Hide)
|
if (opts.View == CommandsOptions.ViewType.Hide)
|
||||||
// if hidden is specified, completely remove these commands from the list
|
// if hidden is specified, completely remove these commands from the list
|
||||||
@@ -424,90 +424,8 @@ public sealed partial class Help : NadekoModule<HelpService>
|
|||||||
.ToList());
|
.ToList());
|
||||||
|
|
||||||
var readableData = JsonConvert.SerializeObject(cmdData, Formatting.Indented);
|
var readableData = JsonConvert.SerializeObject(cmdData, Formatting.Indented);
|
||||||
var uploadData = JsonConvert.SerializeObject(cmdData, Formatting.None);
|
|
||||||
|
|
||||||
// for example https://nyc.digitaloceanspaces.com (without your space name)
|
// send the indented file to chat
|
||||||
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
|
|
||||||
await using var rDataStream = new MemoryStream(Encoding.ASCII.GetBytes(readableData));
|
await using var rDataStream = new MemoryStream(Encoding.ASCII.GetBytes(readableData));
|
||||||
await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen));
|
await ctx.Channel.SendFileAsync(rDataStream, "cmds.json", GetText(strs.commandlist_regen));
|
||||||
}
|
}
|
||||||
|
@@ -6,17 +6,62 @@ public class MedusaeRepositoryService : IMedusaeRepositoryService, INService
|
|||||||
{
|
{
|
||||||
// Simulate retrieving data from a database or API
|
// Simulate retrieving data from a database or API
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
return new List<ModuleItem>
|
return
|
||||||
|
[
|
||||||
|
new()
|
||||||
{
|
{
|
||||||
new ModuleItem { Name = "RSS Reader", Description = "Keep up to date with your favorite websites", Command = ".meinstall rss" },
|
Name = "RSS Reader",
|
||||||
new ModuleItem { Name = "Password Manager", Description = "Safely store and manage all your passwords", Command = ".meinstall passwordmanager" },
|
Description = "Keep up to date with your favorite websites",
|
||||||
new ModuleItem { Name = "Browser Extension", Description = "Enhance your browsing experience with useful tools", Command = ".meinstall browserextension" },
|
Command = ".meinstall rss"
|
||||||
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()
|
||||||
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" },
|
Name = "Password Manager",
|
||||||
new ModuleItem { Name = "Social Media Manager", Description = "Manage all your social media accounts in one place", Command = ".meinstall socialmediamanager" },
|
Description = "Safely store and manage all your passwords",
|
||||||
new ModuleItem { Name = "Code Editor", Description = "Write and edit code online", Command = ".meinstall codeeditor" }
|
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);
|
_dnetApiClient = prop.GetValue(_client, null);
|
||||||
_sendVoiceStateUpdateMethodInfo = _dnetApiClient.GetType()
|
_sendVoiceStateUpdateMethodInfo = _dnetApiClient.GetType()
|
||||||
.GetMethod("SendVoiceStateUpdateAsync",
|
.GetMethod("SendVoiceStateUpdateAsync",
|
||||||
new[]
|
[
|
||||||
{
|
|
||||||
typeof(ulong), typeof(ulong?), typeof(bool),
|
typeof(ulong), typeof(ulong?), typeof(bool),
|
||||||
typeof(bool), typeof(RequestOptions)
|
typeof(bool), typeof(RequestOptions)
|
||||||
});
|
]);
|
||||||
|
|
||||||
_client.LeftGuild += ClientOnLeftGuild;
|
_client.LeftGuild += ClientOnLeftGuild;
|
||||||
}
|
}
|
||||||
@@ -55,7 +54,7 @@ public sealed class AyuVoiceStateService : INService
|
|||||||
bool isMuted = false)
|
bool isMuted = false)
|
||||||
// return _voiceStateUpdate(guildId, channelId, isDeafened, isMuted);
|
// return _voiceStateUpdate(guildId, channelId, isDeafened, isMuted);
|
||||||
=> (Task)_sendVoiceStateUpdateMethodInfo.Invoke(_dnetApiClient,
|
=> (Task)_sendVoiceStateUpdateMethodInfo.Invoke(_dnetApiClient,
|
||||||
new object[] { guildId, channelId, isMuted, isDeafened, null });
|
[guildId, channelId, isMuted, isDeafened, null]);
|
||||||
|
|
||||||
private Task SendLeaveVoiceChannelInternalAsync(ulong guildId)
|
private Task SendLeaveVoiceChannelInternalAsync(ulong guildId)
|
||||||
=> InvokeSendVoiceStateUpdateAsync(guildId);
|
=> InvokeSendVoiceStateUpdateAsync(guildId);
|
||||||
|
@@ -11,9 +11,9 @@ public sealed partial class YtLoader : INService
|
|||||||
private static readonly byte[] _ytResultJsonEnd = Encoding.UTF8.GetBytes(";<");
|
private static readonly byte[] _ytResultJsonEnd = Encoding.UTF8.GetBytes(";<");
|
||||||
|
|
||||||
private static readonly string[] _durationFormats =
|
private static readonly string[] _durationFormats =
|
||||||
{
|
[
|
||||||
@"m\:ss", @"mm\:ss", @"h\:mm\:ss", @"hh\:mm\:ss", @"hhh\:mm\:ss"
|
@"m\:ss", @"mm\:ss", @"h\:mm\:ss", @"hh\:mm\:ss", @"hhh\:mm\:ss"
|
||||||
};
|
];
|
||||||
|
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
|
|
||||||
|
@@ -201,12 +201,9 @@ public sealed partial class MusicQueue : IMusicQueue
|
|||||||
|
|
||||||
public IQueuedTrackInfo? MoveTrack(int from, int to)
|
public IQueuedTrackInfo? MoveTrack(int from, int to)
|
||||||
{
|
{
|
||||||
if (from < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(from);
|
||||||
throw new ArgumentOutOfRangeException(nameof(from));
|
ArgumentOutOfRangeException.ThrowIfNegative(to);
|
||||||
if (to < 0)
|
ArgumentOutOfRangeException.ThrowIfEqual(to, from);
|
||||||
throw new ArgumentOutOfRangeException(nameof(to));
|
|
||||||
if (to == from)
|
|
||||||
throw new ArgumentException($"{nameof(from)} and {nameof(to)} must be different");
|
|
||||||
|
|
||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
@@ -270,20 +267,8 @@ public sealed partial class MusicQueue : IMusicQueue
|
|||||||
{
|
{
|
||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
var list = tracks.ToList();
|
var list = tracks.ToArray();
|
||||||
|
rng.Shuffle(list);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
tracks = new(list);
|
tracks = new(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,9 +7,9 @@ namespace NadekoBot.Modules.Music;
|
|||||||
public sealed class YtdlYoutubeResolver : IYoutubeResolver
|
public sealed class YtdlYoutubeResolver : IYoutubeResolver
|
||||||
{
|
{
|
||||||
private static readonly string[] _durationFormats =
|
private static readonly string[] _durationFormats =
|
||||||
{
|
[
|
||||||
"ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss"
|
"ss", "m\\:ss", "mm\\:ss", "h\\:mm\\:ss", "hh\\:mm\\:ss", "hhh\\:mm\\:ss"
|
||||||
};
|
];
|
||||||
|
|
||||||
private static readonly Regex _expiryRegex = new(@"(?:[\?\&]expire\=(?<timestamp>\d+))");
|
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)
|
public static List<MusicPlaylist> GetPlaylistsOnPage(this DbSet<MusicPlaylist> playlists, int num)
|
||||||
{
|
{
|
||||||
if (num < 1)
|
ArgumentOutOfRangeException.ThrowIfLessThan(num, 1);
|
||||||
throw new ArgumentOutOfRangeException(nameof(num));
|
|
||||||
|
|
||||||
return playlists.AsQueryable().Skip((num - 1) * 20).Take(20).Include(pl => pl.Songs).ToList();
|
return playlists.AsQueryable().Skip((num - 1) * 20).Take(20).Include(pl => pl.Songs).ToList();
|
||||||
}
|
}
|
||||||
|
@@ -111,7 +111,6 @@ public sealed class PatronageService
|
|||||||
var lastDate = lastRun.ToDateOnly();
|
var lastDate = lastRun.ToDateOnly();
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
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)
|
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
|
// assumes that the code above runs in less than an hour
|
||||||
await _cache.AddAsync(_quotaKey, now.ToBinary());
|
await _cache.AddAsync(_quotaKey, now.ToBinary());
|
||||||
await tran.CommitAsync();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -171,7 +169,6 @@ public sealed class PatronageService
|
|||||||
|
|
||||||
var lastChargeUtc = subscriber.LastCharge.Value.ToUniversalTime();
|
var lastChargeUtc = subscriber.LastCharge.Value.ToUniversalTime();
|
||||||
var dateInOneMonth = lastChargeUtc.Date.AddMonths(1);
|
var dateInOneMonth = lastChargeUtc.Date.AddMonths(1);
|
||||||
// await using var tran = await ctx.Database.BeginTransactionAsync();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dbPatron = await ctx.GetTable<PatronUser>()
|
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)
|
private async Task ListBlacklistInternal(string title, BlacklistType type, int page = 0)
|
||||||
{
|
{
|
||||||
if (page < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
throw new ArgumentOutOfRangeException(nameof(page));
|
|
||||||
|
|
||||||
var list = _service.GetBlacklist();
|
var list = _service.GetBlacklist();
|
||||||
var allItems = await list.Where(x => x.Type == type)
|
var allItems = await list.Where(x => x.Type == type)
|
||||||
|
@@ -112,8 +112,7 @@ public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
|||||||
|
|
||||||
public void AddCooldown(ulong guildId, string name, int secs)
|
public void AddCooldown(ulong guildId, string name, int secs)
|
||||||
{
|
{
|
||||||
if (secs <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(secs);
|
||||||
throw new ArgumentOutOfRangeException(nameof(secs));
|
|
||||||
|
|
||||||
var sett = _settings.GetOrAdd(guildId, static _ => new());
|
var sett = _settings.GetOrAdd(guildId, static _ => new());
|
||||||
sett[name] = secs;
|
sett[name] = secs;
|
||||||
|
@@ -162,7 +162,7 @@ public partial class Searches
|
|||||||
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
.AddField(GetText(strs.episodes), animeData.TotalEpisodes.ToString(), true)
|
||||||
.AddField(GetText(strs.status), animeData.AiringStatus, true)
|
.AddField(GetText(strs.status), animeData.AiringStatus, true)
|
||||||
.AddField(GetText(strs.genres),
|
.AddField(GetText(strs.genres),
|
||||||
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : new[] { "none" }),
|
string.Join(",\n", animeData.Genres.Any() ? animeData.Genres : ["none"]),
|
||||||
true)
|
true)
|
||||||
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
.WithFooter($"{GetText(strs.score)} {animeData.AverageScore} / 100");
|
||||||
await Response().Embed(embed).SendAsync();
|
await Response().Embed(embed).SendAsync();
|
||||||
@@ -194,7 +194,7 @@ public partial class Searches
|
|||||||
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
.AddField(GetText(strs.chapters), mangaData.TotalChapters.ToString(), true)
|
||||||
.AddField(GetText(strs.status), mangaData.PublishingStatus, true)
|
.AddField(GetText(strs.status), mangaData.PublishingStatus, true)
|
||||||
.AddField(GetText(strs.genres),
|
.AddField(GetText(strs.genres),
|
||||||
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : new[] { "none" }),
|
string.Join(",\n", mangaData.Genres.Any() ? mangaData.Genres : ["none"]),
|
||||||
true)
|
true)
|
||||||
.WithFooter($"{GetText(strs.score)} {mangaData.AverageScore} / 100");
|
.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)
|
foreach (var feed in gc.FeedSubs)
|
||||||
{
|
{
|
||||||
_subs.AddOrUpdate(feed.Url.ToLower(),
|
_subs.AddOrUpdate(feed.Url.ToLower(),
|
||||||
new List<FeedSub>
|
[feed],
|
||||||
{
|
|
||||||
feed
|
|
||||||
},
|
|
||||||
(_, old) =>
|
(_, old) =>
|
||||||
{
|
{
|
||||||
old.Add(feed);
|
old.Add(feed);
|
||||||
@@ -275,7 +272,7 @@ public class FeedsService : INService
|
|||||||
return false;
|
return false;
|
||||||
var toRemove = items[index];
|
var toRemove = items[index];
|
||||||
_subs.AddOrUpdate(toRemove.Url.ToLower(),
|
_subs.AddOrUpdate(toRemove.Url.ToLower(),
|
||||||
new List<FeedSub>(),
|
[],
|
||||||
(_, old) =>
|
(_, old) =>
|
||||||
{
|
{
|
||||||
old.Remove(toRemove);
|
old.Remove(toRemove);
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
public class PlainGoogleScrapeSearchResult : ISearchResult
|
public class PlainGoogleScrapeSearchResult : ISearchResult
|
||||||
{
|
{
|
||||||
public string? Answer { get; init; } = null!;
|
public required string? Answer { get; init; }
|
||||||
public IReadOnlyCollection<ISearchResultEntry> Entries { get; init; } = null!;
|
public required IReadOnlyCollection<ISearchResultEntry> Entries { get; init; }
|
||||||
public ISearchResultInformation Info { get; init; } = null!;
|
public required ISearchResultInformation Info { get; init; }
|
||||||
}
|
}
|
@@ -1,5 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Html2Markdown;
|
|
||||||
using NadekoBot.Modules.Searches.Common;
|
using NadekoBot.Modules.Searches.Common;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@@ -23,8 +22,8 @@ public class SearchesService : INService
|
|||||||
Birds
|
Birds
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<WoWJoke> WowJokes { get; } = new();
|
public List<WoWJoke> WowJokes { get; } = [];
|
||||||
public List<MagicItem> MagicItems { get; } = new();
|
public List<MagicItem> MagicItems { get; } = [];
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
private readonly IGoogleApiService _google;
|
private readonly IGoogleApiService _google;
|
||||||
private readonly IImageCache _imgs;
|
private readonly IImageCache _imgs;
|
||||||
@@ -68,7 +67,7 @@ public class SearchesService : INService
|
|||||||
_yomamaJokes = File.ReadAllLines("data/yomama.txt").Shuffle().ToList();
|
_yomamaJokes = File.ReadAllLines("data/yomama.txt").Shuffle().ToList();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_yomamaJokes = new();
|
_yomamaJokes = [];
|
||||||
Log.Warning("data/yomama.txt is missing. .yomama command won't work");
|
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();
|
var subpath = tag.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
int max;
|
var max = tag switch
|
||||||
switch (tag)
|
|
||||||
{
|
{
|
||||||
case ImageTag.Food:
|
ImageTag.Food => 773,
|
||||||
max = 773;
|
ImageTag.Dogs => 750,
|
||||||
break;
|
ImageTag.Cats => 773,
|
||||||
case ImageTag.Dogs:
|
ImageTag.Birds => 578,
|
||||||
max = 750;
|
_ => 100,
|
||||||
break;
|
};
|
||||||
case ImageTag.Cats:
|
|
||||||
max = 773;
|
|
||||||
break;
|
|
||||||
case ImageTag.Birds:
|
|
||||||
max = 578;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
max = 100;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"https://nadeko-pictures.nyc3.digitaloceanspaces.com/{subpath}/"
|
return $"https://nadeko-pictures.nyc3.digitaloceanspaces.com/{subpath}/"
|
||||||
+ _rng.Next(1, max).ToString("000")
|
+ _rng.Next(1, max).ToString("000")
|
||||||
@@ -380,11 +369,11 @@ public class SearchesService : INService
|
|||||||
return null;
|
return null;
|
||||||
if (!string.IsNullOrWhiteSpace(data.Img))
|
if (!string.IsNullOrWhiteSpace(data.Img))
|
||||||
data.Img = await _google.ShortenUrl(data.Img);
|
data.Img = await _google.ShortenUrl(data.Img);
|
||||||
if (!string.IsNullOrWhiteSpace(data.Text))
|
// if (!string.IsNullOrWhiteSpace(data.Text))
|
||||||
{
|
// {
|
||||||
var converter = new Converter();
|
// var converter = new Converter();
|
||||||
data.Text = converter.Convert(data.Text);
|
// data.Text = converter.Convert(data.Text);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@@ -143,6 +143,10 @@ public partial class Searches
|
|||||||
if (--index < 0)
|
if (--index < 0)
|
||||||
return;
|
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))
|
if (!_service.SetStreamMessage(ctx.Guild.Id, index, message, out var fs))
|
||||||
{
|
{
|
||||||
await Response().Confirm(strs.stream_not_following).SendAsync();
|
await Response().Confirm(strs.stream_not_following).SendAsync();
|
||||||
@@ -160,6 +164,10 @@ public partial class Searches
|
|||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
public async Task StreamMessageAll([Leftover] string message)
|
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);
|
var count = _service.SetStreamMessageForAll(ctx.Guild.Id, message);
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
|
@@ -202,10 +202,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
_trackCounter[key].Add(info.GuildId);
|
_trackCounter[key].Add(info.GuildId);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_trackCounter[key] = new()
|
_trackCounter[key] = [info.GuildId];
|
||||||
{
|
|
||||||
info.GuildId
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,6 +294,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
var msg = await _sender.Response(textChannel)
|
var msg = await _sender.Response(textChannel)
|
||||||
.Embed(GetEmbed(fs.GuildId, stream, false))
|
.Embed(GetEmbed(fs.GuildId, stream, false))
|
||||||
.Text(message)
|
.Text(message)
|
||||||
|
.Sanitize(false)
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
|
|
||||||
// only cache the ids of channel/message pairs
|
// 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))
|
if (map.TryGetValue(guildId, out var set))
|
||||||
return set;
|
return set;
|
||||||
return map[guildId] = new();
|
return map[guildId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_shardTrackedStreams[key] = new()
|
_shardTrackedStreams[key] = new()
|
||||||
{
|
{
|
||||||
{ guildId, new() }
|
{ guildId, [] }
|
||||||
};
|
};
|
||||||
return _shardTrackedStreams[key][guildId];
|
return _shardTrackedStreams[key][guildId];
|
||||||
}
|
}
|
||||||
@@ -618,7 +616,9 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
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)
|
if (all.Count == 0)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -627,6 +627,19 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
|||||||
|
|
||||||
uow.SaveChanges();
|
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;
|
return all.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,10 +41,7 @@ public class PicartoProvider : Provider
|
|||||||
|
|
||||||
public override async Task<StreamData?> GetStreamDataAsync(string login)
|
public override async Task<StreamData?> GetStreamDataAsync(string login)
|
||||||
{
|
{
|
||||||
var data = await GetStreamDataAsync(new List<string>
|
var data = await GetStreamDataAsync([login]);
|
||||||
{
|
|
||||||
login
|
|
||||||
});
|
|
||||||
|
|
||||||
return data.FirstOrDefault();
|
return data.FirstOrDefault();
|
||||||
}
|
}
|
||||||
@@ -52,7 +49,7 @@ public class PicartoProvider : Provider
|
|||||||
public override async Task<IReadOnlyCollection<StreamData>> GetStreamDataAsync(List<string> logins)
|
public override async Task<IReadOnlyCollection<StreamData>> GetStreamDataAsync(List<string> logins)
|
||||||
{
|
{
|
||||||
if (logins.Count == 0)
|
if (logins.Count == 0)
|
||||||
return new List<StreamData>();
|
return [];
|
||||||
|
|
||||||
using var http = _httpClientFactory.CreateClient();
|
using var http = _httpClientFactory.CreateClient();
|
||||||
var toReturn = new List<StreamData>();
|
var toReturn = new List<StreamData>();
|
||||||
|
@@ -66,10 +66,7 @@ public sealed class TwitchHelixProvider : Provider
|
|||||||
|
|
||||||
public override async Task<StreamData?> GetStreamDataAsync(string login)
|
public override async Task<StreamData?> GetStreamDataAsync(string login)
|
||||||
{
|
{
|
||||||
var data = await GetStreamDataAsync(new List<string>
|
var data = await GetStreamDataAsync([login]);
|
||||||
{
|
|
||||||
login
|
|
||||||
});
|
|
||||||
|
|
||||||
return data.FirstOrDefault();
|
return data.FirstOrDefault();
|
||||||
}
|
}
|
||||||
@@ -125,7 +122,7 @@ public sealed class TwitchHelixProvider : Provider
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Something went wrong retreiving {StreamPlatform} streams", Platform);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Something went wrong retreiving {StreamPlatform} streams", Platform);
|
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);
|
var text = SmartText.CreateFrom(repeater.Message);
|
||||||
text = await _repSvc.ReplaceAsync(text, repCtx);
|
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("🔄"));
|
_ = newMsg.AddReactionAsync(new Emoji("🔄"));
|
||||||
|
|
||||||
if (_noRedundant.Contains(repeater.Id))
|
if (_noRedundant.Contains(repeater.Id))
|
||||||
@@ -359,8 +363,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
|||||||
|
|
||||||
public async Task<RunningRepeater?> RemoveByIndexAsync(ulong guildId, int index)
|
public async Task<RunningRepeater?> RemoveByIndexAsync(ulong guildId, int index)
|
||||||
{
|
{
|
||||||
if (index > MAX_REPEATERS * 2)
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(index, MAX_REPEATERS * 2);
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var toRemove = await uow.Set<Repeater>()
|
var toRemove = await uow.Set<Repeater>()
|
||||||
|
@@ -58,13 +58,13 @@ public class ConverterService : INService, IReadyExecutor
|
|||||||
var currencyRates = await GetCurrencyRates();
|
var currencyRates = await GetCurrencyRates();
|
||||||
var baseType = new ConvertUnit
|
var baseType = new ConvertUnit
|
||||||
{
|
{
|
||||||
Triggers = new[] { currencyRates.Base },
|
Triggers = [currencyRates.Base],
|
||||||
Modifier = decimal.One,
|
Modifier = decimal.One,
|
||||||
UnitType = unitTypeString
|
UnitType = unitTypeString
|
||||||
};
|
};
|
||||||
var units = currencyRates.ConversionRates.Select(u => new ConvertUnit
|
var units = currencyRates.ConversionRates.Select(u => new ConvertUnit
|
||||||
{
|
{
|
||||||
Triggers = new[] { u.Key },
|
Triggers = [u.Key],
|
||||||
Modifier = u.Value,
|
Modifier = u.Value,
|
||||||
UnitType = unitTypeString
|
UnitType = unitTypeString
|
||||||
})
|
})
|
||||||
|
@@ -85,6 +85,7 @@ public partial class Utility : NadekoModule
|
|||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Text(message)
|
.Text(message)
|
||||||
|
.Channel(channel)
|
||||||
.UserBasedMentions()
|
.UserBasedMentions()
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
@@ -479,7 +480,7 @@ public partial class Utility : NadekoModule
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (tags.Length == 0)
|
if (tags.Length == 0)
|
||||||
tags = new[] { name };
|
tags = [name];
|
||||||
|
|
||||||
await ctx.Guild.CreateStickerAsync(
|
await ctx.Guild.CreateStickerAsync(
|
||||||
name,
|
name,
|
||||||
|
@@ -332,8 +332,7 @@ public class ClubService : INService, IClubService
|
|||||||
|
|
||||||
public List<ClubInfo> GetClubLeaderboardPage(int page)
|
public List<ClubInfo> GetClubLeaderboardPage(int page)
|
||||||
{
|
{
|
||||||
if (page < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(page);
|
||||||
throw new ArgumentOutOfRangeException(nameof(page));
|
|
||||||
|
|
||||||
using var uow = _db.GetDbContext();
|
using var uow = _db.GetDbContext();
|
||||||
return uow.Set<ClubInfo>().GetClubLeaderboardPage(page);
|
return uow.Set<ClubInfo>().GetClubLeaderboardPage(page);
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
using NadekoBot.Modules.Xp.Services;
|
using NadekoBot.Modules.Xp.Services;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Db;
|
|
||||||
using NadekoBot.Modules.Patronage;
|
using NadekoBot.Modules.Patronage;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Xp;
|
namespace NadekoBot.Modules.Xp;
|
||||||
@@ -191,21 +190,22 @@ public partial class Xp : NadekoModule<XpService>
|
|||||||
await ctx.Channel.TriggerTypingAsync();
|
await ctx.Channel.TriggerTypingAsync();
|
||||||
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
|
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
|
||||||
|
|
||||||
allCleanUsers = _service.GetTopUserXps(ctx.Guild.Id, 1000)
|
allCleanUsers = (await _service.GetTopUserXps(ctx.Guild.Id, 1000))
|
||||||
.Where(user => socketGuild.GetUser(user.UserId) is not null)
|
.Where(user => socketGuild.GetUser(user.UserId) is not null)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Response()
|
var res = opts.Clean
|
||||||
|
? Response()
|
||||||
.Paginated()
|
.Paginated()
|
||||||
.PageItems<UserXpStats>(opts.Clean
|
.Items(allCleanUsers)
|
||||||
? (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(allCleanUsers.Skip(curPage * 9)
|
: Response()
|
||||||
.Take(9)
|
.Paginated()
|
||||||
.ToList())
|
.PageItems((curPage) => _service.GetUserXps(ctx.Guild.Id, curPage));
|
||||||
: (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(_service.GetUserXps(ctx.Guild.Id, curPage)))
|
|
||||||
|
await res
|
||||||
.PageSize(9)
|
.PageSize(9)
|
||||||
.CurrentPage(page)
|
.CurrentPage(page)
|
||||||
.AddFooter(false)
|
|
||||||
.Page((users, curPage) =>
|
.Page((users, curPage) =>
|
||||||
{
|
{
|
||||||
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
|
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)
|
if (--page < 0 || page > 99)
|
||||||
return;
|
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())
|
if (!users.Any())
|
||||||
|
{
|
||||||
embed.WithDescription("-");
|
embed.WithDescription("-");
|
||||||
else
|
return embed;
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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]
|
[Cmd]
|
||||||
|
@@ -563,22 +563,23 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
uow.SaveChanges();
|
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();
|
await using var uow = _db.GetDbContext();
|
||||||
return uow.Set<UserXpStats>().GetUsersFor(guildId, page);
|
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();
|
await using var uow = _db.GetDbContext();
|
||||||
return uow.Set<UserXpStats>().GetTopUserXps(guildId, count);
|
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();
|
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)
|
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 du = uow.GetOrCreateUser(user, set => set.Include(x => x.Club));
|
||||||
var totalXp = du.TotalXp;
|
var totalXp = du.TotalXp;
|
||||||
var globalRank = uow.Set<DiscordUser>().GetUserGlobalRank(user.Id);
|
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);
|
var stats = uow.GetOrCreateUserXpStats(user.GuildId, user.Id);
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -1469,7 +1470,6 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
// await using var tran = await ctx.Database.BeginTransactionAsync();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (await ctx.GetTable<XpShopOwnedItem>()
|
if (await ctx.GetTable<XpShopOwnedItem>()
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.0.4</Version>
|
<Version>5.0.7</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
@@ -28,22 +28,20 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<Publish>True</Publish>
|
<Publish>True</Publish>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="AWSSDK.S3" Version="3.7.307"/>
|
|
||||||
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
|
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
||||||
<PackageReference Include="Discord.Net" Version="3.204.0"/>
|
<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.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="Google.Apis.Customsearch.v1" Version="1.49.0.2084"/>
|
||||||
<!-- <PackageReference Include="Grpc.AspNetCore" Version="2.62.0" />-->
|
<!-- <PackageReference Include="Grpc.AspNetCore" Version="2.62.0" />-->
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.26.1"/>
|
<PackageReference Include="Google.Protobuf" Version="3.26.1"/>
|
||||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.62.0"/>
|
<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>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Html2Markdown" Version="6.2.0.3"/>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.5.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.5.0"/>
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
|
||||||
@@ -70,14 +68,14 @@
|
|||||||
<PackageReference Include="OneOf" Version="3.0.263"/>
|
<PackageReference Include="OneOf" Version="3.0.263"/>
|
||||||
<PackageReference Include="OneOf.SourceGenerator" 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.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.Fonts" Version="1.0.0-beta17"/>
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.8"/>
|
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.8"/>
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14"/>
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14"/>
|
||||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009"/>
|
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009"/>
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.7.33"/>
|
<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="SharpToken" Version="2.0.2"/>
|
||||||
|
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
|
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
|
||||||
@@ -93,7 +91,7 @@
|
|||||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="8.1.0"/>
|
<PackageReference Include="linq2db.EntityFrameworkCore" Version="8.1.0"/>
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4"/>
|
<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="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
|
||||||
|
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
|
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
|
||||||
@@ -102,7 +100,7 @@
|
|||||||
<PackageReference Include="TwitchLib.Api" Version="3.4.1"/>
|
<PackageReference Include="TwitchLib.Api" Version="3.4.1"/>
|
||||||
|
|
||||||
<!-- sqlselectcsv and stock -->
|
<!-- sqlselectcsv and stock -->
|
||||||
<PackageReference Include="CsvHelper" Version="28.0.1"/>
|
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -43,8 +43,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
if (string.IsNullOrWhiteSpace(keywords))
|
if (string.IsNullOrWhiteSpace(keywords))
|
||||||
throw new ArgumentNullException(nameof(keywords));
|
throw new ArgumentNullException(nameof(keywords));
|
||||||
|
|
||||||
if (count <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||||
throw new ArgumentOutOfRangeException(nameof(count));
|
|
||||||
|
|
||||||
var match = _plRegex.Match(keywords);
|
var match = _plRegex.Match(keywords);
|
||||||
if (match.Length > 1)
|
if (match.Length > 1)
|
||||||
@@ -62,8 +61,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
if (string.IsNullOrWhiteSpace(id))
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
throw new ArgumentNullException(nameof(id));
|
throw new ArgumentNullException(nameof(id));
|
||||||
|
|
||||||
if (count <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||||
throw new ArgumentOutOfRangeException(nameof(count));
|
|
||||||
|
|
||||||
var query = _yt.Search.List("snippet");
|
var query = _yt.Search.List("snippet");
|
||||||
query.MaxResults = count;
|
query.MaxResults = count;
|
||||||
@@ -82,8 +80,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
if (string.IsNullOrWhiteSpace(keywords))
|
if (string.IsNullOrWhiteSpace(keywords))
|
||||||
throw new ArgumentNullException(nameof(keywords));
|
throw new ArgumentNullException(nameof(keywords));
|
||||||
|
|
||||||
if (count <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||||
throw new ArgumentOutOfRangeException(nameof(count));
|
|
||||||
|
|
||||||
var query = _yt.Search.List("snippet");
|
var query = _yt.Search.List("snippet");
|
||||||
query.MaxResults = count;
|
query.MaxResults = count;
|
||||||
@@ -100,8 +97,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
if (string.IsNullOrWhiteSpace(keywords))
|
if (string.IsNullOrWhiteSpace(keywords))
|
||||||
throw new ArgumentNullException(nameof(keywords));
|
throw new ArgumentNullException(nameof(keywords));
|
||||||
|
|
||||||
if (count <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||||
throw new ArgumentOutOfRangeException(nameof(count));
|
|
||||||
|
|
||||||
var query = _yt.Search.List("snippet");
|
var query = _yt.Search.List("snippet");
|
||||||
query.MaxResults = count;
|
query.MaxResults = count;
|
||||||
@@ -150,8 +146,7 @@ public sealed partial class GoogleApiService : IGoogleApiService, INService
|
|||||||
if (string.IsNullOrWhiteSpace(playlistId))
|
if (string.IsNullOrWhiteSpace(playlistId))
|
||||||
throw new ArgumentNullException(nameof(playlistId));
|
throw new ArgumentNullException(nameof(playlistId));
|
||||||
|
|
||||||
if (count <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);
|
||||||
throw new ArgumentOutOfRangeException(nameof(count));
|
|
||||||
|
|
||||||
string nextPageToken = null;
|
string nextPageToken = null;
|
||||||
|
|
||||||
|
@@ -112,7 +112,7 @@ public class Localization : ILocalization
|
|||||||
{
|
{
|
||||||
Cmd = key,
|
Cmd = key,
|
||||||
Desc = key,
|
Desc = key,
|
||||||
Usage = new[] { key }
|
Usage = [key]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,13 +8,13 @@ namespace NadekoBot.Common;
|
|||||||
|
|
||||||
public sealed class RedisBotCache : IBotCache
|
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(bool), typeof(int), typeof(uint), typeof(long),
|
||||||
typeof(ulong), typeof(float), typeof(double),
|
typeof(ulong), typeof(float), typeof(double),
|
||||||
typeof(string), typeof(byte[]), typeof(ReadOnlyMemory<byte>), typeof(Memory<byte>),
|
typeof(string), typeof(byte[]), typeof(ReadOnlyMemory<byte>), typeof(Memory<byte>),
|
||||||
typeof(RedisValue),
|
typeof(RedisValue)
|
||||||
};
|
];
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions _opts = new()
|
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)
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(array);
|
ArgumentNullException.ThrowIfNull(array);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||||
if (arrayIndex < 0)
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(arrayIndex, array.Length);
|
||||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
|
||||||
|
|
||||||
if (arrayIndex >= array.Length)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
|
||||||
|
|
||||||
CopyToInternal(array, arrayIndex);
|
CopyToInternal(array, arrayIndex);
|
||||||
}
|
}
|
||||||
|
@@ -6,10 +6,13 @@ namespace NadekoBot.Extensions;
|
|||||||
|
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
private static readonly HashSet<char> _lettersAndDigits = new(Enumerable.Range(48, 10)
|
private static readonly HashSet<char> _lettersAndDigits =
|
||||||
|
[
|
||||||
|
..Enumerable.Range(48, 10)
|
||||||
.Concat(Enumerable.Range(65, 26))
|
.Concat(Enumerable.Range(65, 26))
|
||||||
.Concat(Enumerable.Range(97, 26))
|
.Concat(Enumerable.Range(97, 26))
|
||||||
.Select(x => (char)x));
|
.Select(x => (char)x)
|
||||||
|
];
|
||||||
|
|
||||||
private static readonly Regex _filterRegex = new(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)",
|
private static readonly Regex _filterRegex = new(@"discord(?:\.gg|\.io|\.me|\.li|(?:app)?\.com\/invite)\/(\w+)",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
@@ -43,7 +46,7 @@ public static class StringExtensions
|
|||||||
|
|
||||||
public static string ToTitleCase(this string str)
|
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++)
|
for (var i = 0; i < tokens.Length; i++)
|
||||||
{
|
{
|
||||||
var token = tokens[i];
|
var token = tokens[i];
|
||||||
|
@@ -38,8 +38,7 @@ public sealed class NadekoRandom : Random
|
|||||||
|
|
||||||
public long NextLong(long minValue, long maxValue)
|
public long NextLong(long minValue, long maxValue)
|
||||||
{
|
{
|
||||||
if (minValue > maxValue)
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(minValue, maxValue);
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
|
||||||
if (minValue == maxValue)
|
if (minValue == maxValue)
|
||||||
return minValue;
|
return minValue;
|
||||||
var bytes = new byte[sizeof(long)];
|
var bytes = new byte[sizeof(long)];
|
||||||
|
@@ -9,8 +9,7 @@ public sealed class QueueRunner
|
|||||||
|
|
||||||
public QueueRunner(int delayMs = 0, int maxCapacity = -1)
|
public QueueRunner(int delayMs = 0, int maxCapacity = -1)
|
||||||
{
|
{
|
||||||
if (delayMs < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(delayMs);
|
||||||
throw new ArgumentOutOfRangeException(nameof(delayMs));
|
|
||||||
|
|
||||||
_delayMs = delayMs;
|
_delayMs = delayMs;
|
||||||
_channel = maxCapacity switch
|
_channel = maxCapacity switch
|
||||||
|
@@ -9,8 +9,7 @@ public sealed class RatelimitAttribute : PreconditionAttribute
|
|||||||
|
|
||||||
public RatelimitAttribute(int seconds)
|
public RatelimitAttribute(int seconds)
|
||||||
{
|
{
|
||||||
if (seconds <= 0)
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(seconds);
|
||||||
throw new ArgumentOutOfRangeException(nameof(seconds));
|
|
||||||
|
|
||||||
Seconds = seconds;
|
Seconds = seconds;
|
||||||
}
|
}
|
||||||
|
@@ -146,14 +146,14 @@ public sealed partial class BotConfig : ICloneable<BotConfig>
|
|||||||
Prefix = ".";
|
Prefix = ".";
|
||||||
RotateStatuses = false;
|
RotateStatuses = false;
|
||||||
GroupGreets = false;
|
GroupGreets = false;
|
||||||
DmHelpTextKeywords = new()
|
DmHelpTextKeywords =
|
||||||
{
|
[
|
||||||
"help",
|
"help",
|
||||||
"commands",
|
"commands",
|
||||||
"cmds",
|
"cmds",
|
||||||
"module",
|
"module",
|
||||||
"can you do"
|
"can you do"
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
||||||
@@ -178,8 +178,8 @@ public sealed partial class BlockedConfig
|
|||||||
|
|
||||||
public BlockedConfig()
|
public BlockedConfig()
|
||||||
{
|
{
|
||||||
Modules = new();
|
Modules = [];
|
||||||
Commands = new();
|
Commands = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,5 +12,5 @@ public abstract class DbService
|
|||||||
public abstract Task SetupAsync();
|
public abstract Task SetupAsync();
|
||||||
|
|
||||||
public abstract DbContext CreateRawDbContext(string dbType, string connString);
|
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 =
|
private readonly string[] _regIndicators =
|
||||||
{
|
[
|
||||||
"🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:",
|
"🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:",
|
||||||
"🇯", "🇶", "🇰"
|
"🇯", "🇶", "🇰"
|
||||||
};
|
];
|
||||||
|
|
||||||
public Card(CardSuit s, int cardNum)
|
public Card(CardSuit s, int cardNum)
|
||||||
{
|
{
|
||||||
|
@@ -64,7 +64,7 @@ public sealed class MedusaNinjectIocModule : IIocModule, IDisposable
|
|||||||
var assembly = typeof(JsonSerializerOptions).Assembly;
|
var assembly = typeof(JsonSerializerOptions).Assembly;
|
||||||
var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler");
|
var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler");
|
||||||
var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public);
|
var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public);
|
||||||
clearCacheMethod?.Invoke(null, new object?[] { null });
|
clearCacheMethod?.Invoke(null, [null]);
|
||||||
|
|
||||||
isLoaded = false;
|
isLoaded = false;
|
||||||
}
|
}
|
||||||
|
@@ -140,7 +140,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
|||||||
=> alias.Equals(commandName, StringComparison.InvariantCultureIgnoreCase)))
|
=> alias.Equals(commandName, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
?.OptionalStrings
|
?.OptionalStrings
|
||||||
.Args
|
.Args
|
||||||
?? new[] { string.Empty };
|
?? [string.Empty];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ReloadStrings()
|
public Task ReloadStrings()
|
||||||
@@ -375,7 +375,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
|||||||
var targetType = parserType.BaseType!.GetGenericArguments()[0];
|
var targetType = parserType.BaseType!.GetGenericArguments()[0];
|
||||||
var typeReaderInstance = (TypeReader)Activator.CreateInstance(
|
var typeReaderInstance = (TypeReader)Activator.CreateInstance(
|
||||||
typeof(ParamParserAdapter<>).MakeGenericType(targetType),
|
typeof(ParamParserAdapter<>).MakeGenericType(targetType),
|
||||||
args: new[] { parserObj, strings, _cont })!;
|
args: [parserObj, strings, _cont])!;
|
||||||
|
|
||||||
typeReaders.Add(targetType, typeReaderInstance);
|
typeReaders.Add(targetType, typeReaderInstance);
|
||||||
}
|
}
|
||||||
@@ -888,7 +888,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
|||||||
var cmdAttribute = method.GetCustomAttribute<cmdAttribute>(true)!;
|
var cmdAttribute = method.GetCustomAttribute<cmdAttribute>(true)!;
|
||||||
var aliases = cmdAttribute.Aliases;
|
var aliases = cmdAttribute.Aliases;
|
||||||
if (aliases.Length == 0)
|
if (aliases.Length == 0)
|
||||||
aliases = new[] { method.Name.ToLowerInvariant() };
|
aliases = [method.Name.ToLowerInvariant()];
|
||||||
|
|
||||||
cmds.Add(new(
|
cmds.Add(new(
|
||||||
aliases,
|
aliases,
|
||||||
|
@@ -40,7 +40,7 @@ public sealed partial class ReplacementPatternStore
|
|||||||
Register("%server.id%", static (IGuild g) => g.Id.ToString());
|
Register("%server.id%", static (IGuild g) => g.Id.ToString());
|
||||||
Register("%server.name%", static (IGuild g) => g.Name);
|
Register("%server.name%", static (IGuild g) => g.Name);
|
||||||
Register("%server.icon%", static (IGuild g) => g.IconUrl);
|
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.boosters%", static (IGuild g) => g.PremiumSubscriptionCount.ToString());
|
||||||
Register("%server.boost_level%", static (IGuild g) => ((int)g.PremiumTier).ToString());
|
Register("%server.boost_level%", static (IGuild g) => ((int)g.PremiumTier).ToString());
|
||||||
}
|
}
|
||||||
|
@@ -58,7 +58,7 @@ public partial class ResponseBuilder
|
|||||||
cb.WithButton(new ButtonBuilder()
|
cb.WithButton(new ButtonBuilder()
|
||||||
.WithStyle(ButtonStyle.Primary)
|
.WithStyle(ButtonStyle.Primary)
|
||||||
.WithCustomId(BUTTON_RIGHT)
|
.WithCustomId(BUTTON_RIGHT)
|
||||||
.WithDisabled(lastPage == 0 || currentPage >= lastPage)
|
.WithDisabled(lastPage is not null && (lastPage == 0 || currentPage >= lastPage))
|
||||||
.WithEmote(InteractionHelpers.ArrowRight));
|
.WithEmote(InteractionHelpers.ArrowRight));
|
||||||
|
|
||||||
return cb;
|
return cb;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using NadekoBot.Common.Configs;
|
using NadekoBot.Common.Configs;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace NadekoBot.Extensions;
|
namespace NadekoBot.Extensions;
|
||||||
|
|
||||||
@@ -375,7 +376,7 @@ public class PaginatedResponseBuilder
|
|||||||
=> new SourcedPaginatedResponseBuilder<T>(_builder)
|
=> new SourcedPaginatedResponseBuilder<T>(_builder)
|
||||||
.Items(items);
|
.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)
|
=> new SourcedPaginatedResponseBuilder<T>(_builder)
|
||||||
.PageItems(items);
|
.PageItems(items);
|
||||||
}
|
}
|
||||||
@@ -389,14 +390,14 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
|
|||||||
return Task.FromResult<EmbedBuilder>(new());
|
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 Func<int, Task<SimpleInteractionBase>>? InteractionFunc { get; private set; }
|
||||||
|
|
||||||
public int Elems { get; private set; } = 1;
|
public int? Elems { get; private set; } = 1;
|
||||||
public int ItemsPerPage { get; private set; } = 9;
|
public int ItemsPerPage { get; private set; } = 9;
|
||||||
public bool AddPaginatedFooter { get; private set; } = true;
|
public bool AddPaginatedFooter { get; private set; } = true;
|
||||||
public bool IsEphemeral { get; private set; }
|
public bool IsEphemeral { get; private set; }
|
||||||
@@ -412,7 +413,7 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
|
|||||||
{
|
{
|
||||||
items = col;
|
items = col;
|
||||||
Elems = col.Count;
|
Elems = col.Count;
|
||||||
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
|
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage).ToArray() as IReadOnlyCollection<T>);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,8 +423,9 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IEnumerable<T>>> func)
|
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IReadOnlyCollection<T>>> func)
|
||||||
{
|
{
|
||||||
|
Elems = null;
|
||||||
ItemsFunc = func;
|
ItemsFunc = func;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@@ -94,10 +94,8 @@ public class CommandHandler : INService, IReadyExecutor, ICommandHandler
|
|||||||
|
|
||||||
public string SetPrefix(IGuild guild, string prefix)
|
public string SetPrefix(IGuild guild, string prefix)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(prefix))
|
ArgumentNullException.ThrowIfNullOrWhiteSpace(prefix);
|
||||||
throw new ArgumentNullException(nameof(prefix));
|
ArgumentNullException.ThrowIfNull(guild);
|
||||||
if (guild is null)
|
|
||||||
throw new ArgumentNullException(nameof(guild));
|
|
||||||
|
|
||||||
using (var uow = _db.GetDbContext())
|
using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
|
@@ -72,30 +72,24 @@ public class DefaultWallet : IWallet
|
|||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
var userId = UserId;
|
var userId = UserId;
|
||||||
|
|
||||||
await using (var tran = await ctx.Database.BeginTransactionAsync())
|
|
||||||
|
await ctx.GetTable<DiscordUser>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
var changed = await ctx
|
UserId = userId,
|
||||||
.GetTable<DiscordUser>()
|
Username = "Unknown",
|
||||||
.Where(x => x.UserId == userId)
|
Discriminator = "????",
|
||||||
.UpdateAsync(x => new()
|
CurrencyAmount = amount,
|
||||||
|
},
|
||||||
|
(old) => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = x.CurrencyAmount + amount
|
CurrencyAmount = old.CurrencyAmount + amount
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
UserId = userId
|
||||||
});
|
});
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (txData is not null)
|
if (txData is not null)
|
||||||
{
|
{
|
||||||
await ctx.GetTable<CurrencyTransaction>()
|
await ctx.GetTable<CurrencyTransaction>()
|
||||||
|
@@ -81,7 +81,7 @@ public class BotStrings : IBotStrings
|
|||||||
|
|
||||||
return new CommandStrings()
|
return new CommandStrings()
|
||||||
{
|
{
|
||||||
Examples = new[] { "" },
|
Examples = [""],
|
||||||
Desc = "?",
|
Desc = "?",
|
||||||
Params = []
|
Params = []
|
||||||
};
|
};
|
||||||
|
@@ -19,7 +19,7 @@ public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
|
|||||||
var value = eventInfo.Source.Value as string;
|
var value = eventInfo.Source.Value as string;
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
var isMultiLine = value.IndexOfAny(new[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
|
var isMultiLine = value.IndexOfAny(['\r', '\n', '\x85', '\x2028', '\x2029']) >= 0;
|
||||||
if (isMultiLine)
|
if (isMultiLine)
|
||||||
{
|
{
|
||||||
eventInfo = new(eventInfo.Source)
|
eventInfo = new(eventInfo.Source)
|
||||||
|
@@ -167,7 +167,8 @@ public static class Extensions
|
|||||||
{
|
{
|
||||||
if (lastPage is not null)
|
if (lastPage is not null)
|
||||||
return embed.WithFooter($"{curPage + 1} / {lastPage + 1}");
|
return embed.WithFooter($"{curPage + 1} / {lastPage + 1}");
|
||||||
return embed.WithFooter(curPage.ToString());
|
|
||||||
|
return embed.WithFooter((curPage + 1).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// public static EmbedBuilder WithOkColor(this EmbedBuilder eb)
|
// public static EmbedBuilder WithOkColor(this EmbedBuilder eb)
|
||||||
|
@@ -187,12 +187,19 @@ repeatinvoke:
|
|||||||
- index:
|
- index:
|
||||||
desc: "The index at which to display the repeat message."
|
desc: "The index at which to display the repeat message."
|
||||||
repeat:
|
repeat:
|
||||||
desc: Repeat a message once every specified amount of time in the current channel. You can instead specify time of day for the message to be repeated daily (make sure you've set your server's timezone). If you've specified time of day, you can still override the default daily interval with your own interval. You can have up to 5 repeating messages on the server in total.
|
desc: |-
|
||||||
|
Repeat a message once every specified amount of time in the current channel.
|
||||||
|
You can specify a different channel as the first argument.
|
||||||
|
You can instead specify time of day for the message to be repeated daily (make sure you've set your server's timezone).
|
||||||
|
If you've specified time of day, you can still override the default daily interval with your own interval.
|
||||||
|
You can have up to 5 repeating messages on the server in total.
|
||||||
ex:
|
ex:
|
||||||
- Hello there
|
- Hello there
|
||||||
- 1h5m Hello @erryone
|
- '#other-channel hello there'
|
||||||
- 10:00 Daily have a nice day! This will execute once every 24h.
|
- '1h5m Hello @erryone'
|
||||||
- 21:00 30m Starting at 21 and every 30 minutes after that i will send this message!
|
- '10:00 Daily have a nice day! This will execute once every 24h.'
|
||||||
|
- '#other-channel 10:00 Daily have a nice day! This will execute once every 24h.'
|
||||||
|
- '21:00 30m Starting at 21 and every 30 minutes after that i will send this message!'
|
||||||
params:
|
params:
|
||||||
- message:
|
- message:
|
||||||
desc: "The text to be repeated at the specified intervals or times."
|
desc: "The text to be repeated at the specified intervals or times."
|
||||||
|
@@ -230,7 +230,7 @@
|
|||||||
"user_unbanned": "User unbanned",
|
"user_unbanned": "User unbanned",
|
||||||
"presence_updates": "Presence updates",
|
"presence_updates": "Presence updates",
|
||||||
"sb_user": "User soft-banned",
|
"sb_user": "User soft-banned",
|
||||||
"awarded": "has awarded {0} to {1}",
|
"awarded": "{2} has awarded {0} to {1}",
|
||||||
"better_luck": "Better luck next time ^_^",
|
"better_luck": "Better luck next time ^_^",
|
||||||
"br_win": "Congratulations! You won {0} for rolling above {1}",
|
"br_win": "Congratulations! You won {0} for rolling above {1}",
|
||||||
"deck_reshuffled": "Deck reshuffled.",
|
"deck_reshuffled": "Deck reshuffled.",
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
"cards_left": "{0} cards left in the deck.",
|
"cards_left": "{0} cards left in the deck.",
|
||||||
"cards": "Cards",
|
"cards": "Cards",
|
||||||
"hand_value": "Hand value",
|
"hand_value": "Hand value",
|
||||||
"gifted": "has gifted {0} to {1}",
|
"gifted": "{2} has gifted {0} to {1}",
|
||||||
"has": "{0} has {1}",
|
"has": "{0} has {1}",
|
||||||
"heads": "Head",
|
"heads": "Head",
|
||||||
"mass_award": "Awarded {0} to {1} users from {2} role.",
|
"mass_award": "Awarded {0} to {1} users from {2} role.",
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
"ttt_against_yourself": "You can't play against yourself.",
|
"ttt_against_yourself": "You can't play against yourself.",
|
||||||
"ttt_already_running": "TicTacToe Game is already running in this channel.",
|
"ttt_already_running": "TicTacToe Game is already running in this channel.",
|
||||||
"ttt_a_draw": "A draw!",
|
"ttt_a_draw": "A draw!",
|
||||||
"ttt_created": "has created a game of TicTacToe.",
|
"ttt_created": "{0} has created a game of TicTacToe.",
|
||||||
"ttt_has_won": "{0} has won!",
|
"ttt_has_won": "{0} has won!",
|
||||||
"ttt_matched_three": "Matched three",
|
"ttt_matched_three": "Matched three",
|
||||||
"ttt_no_moves": "No moves left!",
|
"ttt_no_moves": "No moves left!",
|
||||||
@@ -507,7 +507,7 @@
|
|||||||
"city_not_found": "City not found.",
|
"city_not_found": "City not found.",
|
||||||
"magicitems_not_loaded": "Magic Items not loaded.",
|
"magicitems_not_loaded": "Magic Items not loaded.",
|
||||||
"mal_profile": "{0}'s MAL profile",
|
"mal_profile": "{0}'s MAL profile",
|
||||||
"mashape_api_missing": "Bot owner didn't specify MashapeApiKey. You can't use this functionality.",
|
"mashape_api_missing": "Bot owner didn't specify RapidApi api key. You can't use this functionality.",
|
||||||
"min_max": "Min/Max",
|
"min_max": "Min/Max",
|
||||||
"no_channel_found": "No channel found.",
|
"no_channel_found": "No channel found.",
|
||||||
"on_hold": "On-hold",
|
"on_hold": "On-hold",
|
||||||
@@ -960,8 +960,8 @@
|
|||||||
"perm_override_all_confirm": "Are you sure that you want to remove **ALL** discord permission overrides on this server? This action is irreversible.",
|
"perm_override_all_confirm": "Are you sure that you want to remove **ALL** discord permission overrides on this server? This action is irreversible.",
|
||||||
"perm_overrides": "Discord Permission Overrides",
|
"perm_overrides": "Discord Permission Overrides",
|
||||||
"perm_override_reset": "Discord Permission Overrides for this command have been cleared.",
|
"perm_override_reset": "Discord Permission Overrides for this command have been cleared.",
|
||||||
"bj_created": "has created a new BlackJack game in this channel.",
|
"bj_created": "{0} has created a new BlackJack game in this channel.",
|
||||||
"bj_joined": "has joined the BlackJack game",
|
"bj_joined": "{0} has joined the BlackJack game",
|
||||||
"reset": "Xp Reset",
|
"reset": "Xp Reset",
|
||||||
"reset_server_confirm": "Are you sure that you want to reset the XP of all users on the server?",
|
"reset_server_confirm": "Are you sure that you want to reset the XP of all users on the server?",
|
||||||
"reset_user_confirm": "Are you sure that you want to reset specified user's XP on this server?",
|
"reset_user_confirm": "Are you sure that you want to reset specified user's XP on this server?",
|
||||||
|
Reference in New Issue
Block a user