Compare commits

..

32 Commits
4.0.4 ... 4.0.6

Author SHA1 Message Date
Kwoth
b079492d38 Fixed voice presence logging, closes #338 2022-03-21 19:58:36 +01:00
Kwoth
28720ebcea Fixed .clubaccept, .clubban, .clubkick and .clubunban, closes #341 2022-03-21 19:32:31 +01:00
Kwoth
59cea6ee38 Version upped to 4.0.5, updated changelog 2022-03-21 16:00:05 +01:00
Kwoth
cbcfa77a34 Updated discord.net to 3.4.1 2022-03-21 15:57:42 +01:00
Kwoth
cfb202cc95 A few more improvements and cleanup for the wallet 2022-03-21 15:55:57 +01:00
Kwoth
b7d1fd1b47 Many IDisposable fixes. GlobalNadeko won't have file watchers for creds. Wallet simplified 2022-03-21 15:33:18 +01:00
Kwoth
4cf3bdb53a Removed unneeded httpclient for searchimages and some minor cleanup 2022-03-21 13:33:43 +01:00
Kwoth
aab5bc9744 Added long delay during graceful coordinator restart, and some logging 2022-03-21 13:25:48 +01:00
Kwoth
1f14c9066e Added several missing using statements 2022-03-21 13:20:26 +01:00
Kwoth
9ade3c9537 Added some custom settings to GlobalNadeko configuration 2022-03-21 13:02:55 +01:00
Kwoth
1dc393d2b1 - Moved update loop to a separate method.
- Added optimize flag to GlobalNadeko configuration.
- Added some packages which will be needed soon
2022-03-21 12:01:07 +01:00
Kwoth
798b66db9b Fixed playing rotate nullref 2022-03-21 11:07:42 +01:00
Kwoth
57e65e5515 Coordinator fix 2022-03-21 01:52:32 +01:00
Kwoth
86e728b753 Converted many raw sql queries to their linq2db equivalents 2022-03-20 19:31:04 +01:00
Kwoth
fd032d3e91 Fixed addbulkasync method 2022-03-20 12:19:12 +01:00
Kwoth
eab16865cd Added a missing savechanges 2022-03-20 12:07:06 +01:00
Kwoth
a34a86bbfa Rewrote several raw queries to linqtodb 2022-03-20 01:14:24 +01:00
Kwoth
a016b3546f Added some missing tables to the context, no functional change 2022-03-20 01:09:22 +01:00
Kwoth
e09435da37 Moved some ICurrencyService methods to extensions to simplify use 2022-03-19 21:19:22 +01:00
Kwoth
416f3d604c Added a missing using 2022-03-19 17:01:03 +01:00
Kwoth
d9f371f994 Added logging to coordinator, downgraded framework to net5, downgraded grpc packages to 2.41 2022-03-19 11:19:08 +01:00
Kwoth
c1c22d0477 Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2022-03-18 23:50:55 +01:00
Kwoth
339f13d31a Updated gitlab-ci and docker-guide 2022-03-18 23:49:35 +01:00
Kwoth
4190f07d9c Merge branch 'eDavid-v4-patch-48565' into 'v4'
python3 folder is up to 3.9 now

See merge request Kwoth/nadekobot!241
2022-03-18 18:14:04 +00:00
David
a9bdf36c53 python3 folder is up to 3.9 now 2022-03-18 17:31:27 +00:00
Kwoth
1913cd4309 Merge branch 'hokutochen-v4-patch-69894' into 'v4'
wremoved "and when queue is empty" from .qap description.

See merge request Kwoth/nadekobot!238
2022-03-15 09:30:51 +00:00
Hokuto Chen
624439f684 wremoved "and when queue is empty" from .qap description. 2022-03-15 09:30:51 +00:00
Kwoth
3e14dc22cb Merge branch 'hokutochen-v4-patch-80313' into 'v4'
incorrect bot version (we are on 4.0.4)?

See merge request Kwoth/nadekobot!236
2022-03-15 00:00:17 +00:00
Kwoth
f4178aeacd Merge branch 'ban=patch' into 'v4'
Fix ban dms sending to mod. Closes #340

Closes #340

See merge request Kwoth/nadekobot!237
2022-03-14 23:59:55 +00:00
Alan Beatty
aaf3c9cfe9 Fix ban dms sending to mod. Closes #340 2022-03-14 11:44:15 -05:00
Kwoth
f6ee012b15 Removed some references to .cr commands from command strings 2022-03-13 06:57:22 +01:00
Hokuto Chen
363ef42923 incorrect bot version (we are on 4.0.4)? 2022-03-12 20:55:03 +00:00
57 changed files with 831 additions and 736 deletions

View File

@@ -97,29 +97,29 @@ upload-windows-updater-release:
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/$INSTALLER_FILE_NAME" --acl public-read --body "$INSTALLER_OUTPUT_DIR/$INSTALLER_FILE_NAME"
- aws --endpoint-url $AWS_SERVICE_URL s3api put-object --bucket "$AWS_BUCKET_NAME" --key "dl/bot/releases-v3.json" --acl public-read --body "releases-v3.json"
# docker-build:
# # Use the official docker image.
# image: docker:latest
# stage: build
# services:
# - docker:dind
# before_script:
# - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
# # Default branch leaves tag empty (= latest tag)
# # All other branches are tagged with the escaped branch name (commit ref slug)
# script:
# - |
# if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
# tag=""
# echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
# else
# tag=":$CI_COMMIT_REF_SLUG"
# echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
# fi
# - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
# - docker push "$CI_REGISTRY_IMAGE${tag}"
# # Run this job in a branch where a Dockerfile exists
# rules:
# - if: $CI_COMMIT_BRANCH
# exists:
# - Dockerfile
docker-build:
# Use the official docker image.
image: docker:latest
stage: build
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
# Default branch leaves tag empty (= latest tag)
# All other branches are tagged with the escaped branch name (commit ref slug)
script:
- |
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
tag=""
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
else
tag=":$CI_COMMIT_REF_SLUG"
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
fi
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
- docker push "$CI_REGISTRY_IMAGE${tag}"
# Run this job in a branch where a Dockerfile exists
rules:
- if: $CI_COMMIT_BRANCH
exists:
- Dockerfile

View File

@@ -7,6 +7,14 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- More cool stuff coming soon
## [4.0.5] - 21.03.2022
### Fixes
- Fixed several bugs in the currency code
- Fixed some potential memory leaks
- Fixed some response strings
## [4.0.4] - 04.03.2022
### Fixed

View File

@@ -25,7 +25,7 @@ RUN set -xe; \
useradd -m nadeko; \
apt-get update; \
apt-get install -y libopus0 libsodium23 libsqlite3-0 curl ffmpeg python3 python3-pip sudo; \
update-alternatives --install /usr/bin/python python /usr/bin/python3.7 1; \
update-alternatives --install /usr/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; \
pip3 install --upgrade youtube-dl; \
apt-get remove -y python3-pip; \
@@ -39,4 +39,4 @@ ENV total_shards=1
VOLUME [ "app/data" ]
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]
CMD dotnet NadekoBot.dll "$shard_id" "$total_shards"
CMD dotnet NadekoBot.dll "$shard_id" "$total_shards"

View File

@@ -1,18 +1,25 @@
# Setting up NadekoBot with Docker
# DO NOT USE YET - WORK IN PROGRESS
# WORK IN PROGRESS
### Docker Compose
### Installation
1. Create a `/srv/nadeko` folder
- `mkdir -p /srv/nadeko`
2. Create a `docker-compose.yml`
- nano `docker-compose.yml`
- copy the following contents into it:
##### docker-compose.yml
```yml
version: "3.7"
services:
nadeko:
image: registry.gitlab.com/veovis/nadekobot:v3-docker
image: registry.gitlab.com/kwoth/nadekobot:latest
depends_on:
- redis
environment:
TZ: Europe/Paris
#NadekoBot_RedisOptions: redis,name=nadeko
NadekoBot_RedisOptions: redis,name=nadeko
#NadekoBot_ShardRunCommand: dotnet
#NadekoBot_ShardRunArguments: /app/NadekoBot.dll {0} {1}
volumes:
@@ -27,6 +34,12 @@ services:
volumes:
- /srv/nadeko/redis-data:/data
```
3. Save your file and run docker compose
- `docker-compose up`
4. Edit creds in `/srv/nadeko/conf/creds.yml`
5. Run it again with
- `docker-compose up`
### Updating
- `cd /srv/nadeko`
- `docker-compose pull`

View File

@@ -15,6 +15,9 @@ namespace NadekoBot.Services
.MinimumLevel.Override("System", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.File("coord.log", LogEventLevel.Information,
rollOnFileSizeLimit: true,
fileSizeLimitBytes: 10_000_000)
.WriteTo.Console(LogEventLevel.Information,
theme: GetTheme(),
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] | #{LogSource} | {Message:lj}{NewLine}{Exception}")

View File

@@ -9,9 +9,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.42.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.44.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup>

View File

@@ -114,14 +114,6 @@ namespace NadekoBot.Coordinator
StartShard(shardId);
break;
}
if (status.Process is null or {HasExited: true})
{
Log.Warning("Shard {ShardId} is starting (process)...", shardId);
hadAction = true;
StartShard(shardId);
break;
}
if (DateTime.UtcNow - status.LastUpdate >
TimeSpan.FromSeconds(_config.UnresponsiveSec))
@@ -139,6 +131,24 @@ namespace NadekoBot.Coordinator
StartShard(shardId);
break;
}
try
{
if (status.Process is null or { HasExited: true })
{
Log.Warning("Shard {ShardId} is starting (process)...", shardId);
hadAction = true;
StartShard(shardId);
break;
}
}
catch (InvalidOperationException)
{
Log.Warning("Process for shard {ShardId} is bugged... ", shardId);
hadAction = true;
StartShard(shardId);
break;
}
}
}
@@ -161,17 +171,13 @@ namespace NadekoBot.Coordinator
var status = _shardStatuses[shardId];
try
{
if (status.Process is { HasExited: false } p)
{
try
{
p.Kill(true);
}
catch
{
}
}
status.Process?.Kill(true);
}
catch
{
}
try
{
status.Process?.Dispose();
}
catch
@@ -280,10 +286,10 @@ namespace NadekoBot.Coordinator
for (var shardId = 0; shardId < _shardStatuses.Length; shardId++)
{
var status = _shardStatuses[shardId];
if (status.Process is { } p)
if (status.Process is Process p)
{
p.Kill();
p.Dispose();
try{p.Kill();} catch {}
try{p.Dispose();} catch {}
_shardStatuses[shardId] = status with
{
Process = null,

View File

@@ -119,8 +119,6 @@ public sealed class Bot
// admin
#if GLOBAL_NADEKO
svcs.AddSingleton<ILogCommandService, DummyLogCommandService>();
#else
svcs.AddSingleton<ILogCommandService, LogCommandService>();
#endif
svcs.AddHttpClient();

View File

@@ -84,77 +84,6 @@ public static class DiscordUserExtensions
item.CurrencyAmount = 0;
}
public static bool TryUpdateCurrencyState(
this NadekoContext ctx,
ulong userId,
string name,
string discrim,
string avatarId,
long amount,
bool allowNegative = false)
{
if (amount == 0)
return true;
// if remove - try to remove if he has more or equal than the amount
// and return number of rows > 0 (was there a change)
if (amount < 0 && !allowNegative)
{
var rows = ctx.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId} AND CurrencyAmount>={-amount};");
return rows > 0;
}
// if remove and negative is allowed, just remove without any condition
if (amount < 0 && allowNegative)
{
var rows = ctx.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId};");
return rows > 0;
}
// if add - create a new user with default values if it doesn't exist
// if it exists, sum current amount with the new one, if it doesn't
// he just has the new amount
var updatedUserData = !string.IsNullOrWhiteSpace(name);
name ??= "Unknown";
discrim ??= "????";
avatarId ??= "";
// just update the amount, there is no new user data
if (!updatedUserData)
{
ctx.Database.ExecuteSqlInterpolated($@"
UPDATE OR IGNORE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId};
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
");
}
else
{
ctx.Database.ExecuteSqlInterpolated($@"
UPDATE OR IGNORE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount},
Username={name},
Discriminator={discrim},
AvatarId={avatarId}
WHERE UserId={userId};
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
");
}
return true;
}
public static decimal GetTotalCurrency(this DbSet<DiscordUser> users)
=> users.Sum((Func<DiscordUser, decimal>)(x => x.CurrencyAmount));

View File

@@ -73,6 +73,7 @@ public static class GuildConfigExtensions
{
GuildConfig config;
// todo linq2db
if (includes is null)
config = ctx.GuildConfigs.IncludeEverything().FirstOrDefault(c => c.GuildId == guildId);
else
@@ -100,6 +101,26 @@ public static class GuildConfigExtensions
}
return config;
// ctx.GuildConfigs
// .ToLinqToDBTable()
// .InsertOrUpdate(() => new()
// {
// GuildId = guildId,
// Permissions = Permissionv2.GetDefaultPermlist,
// WarningsInitialized = true,
// WarnPunishments = DefaultWarnPunishments
// },
// _ => new(),
// () => new()
// {
// GuildId = guildId
// });
//
// if(includes is null)
// return ctx.GuildConfigs
// .ToLinqToDBTable()
// .First(x => x.GuildId == guildId);
}
public static LogSetting LogSettingsFor(this NadekoContext ctx, ulong guildId)

View File

@@ -1,4 +1,6 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
using NadekoBot.Services.Database;
@@ -75,11 +77,22 @@ public static class WaifuExtensions
.Select(x => x.Waifu.UserId)
.FirstOrDefault();
public static WaifuInfoStats GetWaifuInfo(this NadekoContext ctx, ulong userId)
public static async Task<WaifuInfoStats> GetWaifuInfoAsync(this NadekoContext ctx, ulong userId)
{
ctx.Database.ExecuteSqlInterpolated($@"
INSERT OR IGNORE INTO WaifuInfo (AffinityId, ClaimerId, Price, WaifuId)
VALUES ({null}, {null}, {1}, (SELECT Id FROM DiscordUser WHERE UserId={userId}));");
await ctx.WaifuInfo
.ToLinqToDBTable()
.InsertOrUpdateAsync(() => new()
{
AffinityId = null,
ClaimerId = null,
Price = 1,
WaifuId = ctx.DiscordUser.Where(x => x.UserId == userId).Select(x => x.Id).First()
},
_ => new(),
() => new()
{
WaifuId = ctx.DiscordUser.Where(x => x.UserId == userId).Select(x => x.Id).First()
});
var toReturn = ctx.WaifuInfo.AsQueryable()
.Where(w => w.WaifuId

View File

@@ -3,6 +3,7 @@ namespace NadekoBot.Services.Database.Models;
public class WaifuItem : DbEntity
{
public WaifuInfo WaifuInfo { get; set; }
public int? WaifuInfoId { get; set; }
public string ItemEmoji { get; set; }
public string Name { get; set; }

View File

@@ -37,9 +37,13 @@ public class NadekoContext : DbContext
public DbSet<NadekoExpression> Expressions { get; set; }
public DbSet<CurrencyTransaction> CurrencyTransactions { get; set; }
public DbSet<WaifuUpdate> WaifuUpdates { get; set; }
public DbSet<WaifuItem> WaifuItem { get; set; }
public DbSet<Warning> Warnings { get; set; }
public DbSet<UserXpStats> UserXpStats { get; set; }
public DbSet<ClubInfo> Clubs { get; set; }
public DbSet<ClubBans> ClubBans { get; set; }
public DbSet<ClubApplicants> ClubApplicants { get; set; }
//logging
public DbSet<LogSetting> LogSettings { get; set; }

View File

@@ -29,7 +29,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("ClubApplicants");
b.ToTable("ClubApplicants", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b =>
@@ -44,7 +44,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("ClubBans");
b.ToTable("ClubBans", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b =>
@@ -86,7 +86,7 @@ namespace NadekoBot.Migrations
b.HasIndex("OwnerId")
.IsUnique();
b.ToTable("Clubs");
b.ToTable("Clubs", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b =>
@@ -155,7 +155,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("DiscordUser");
b.ToTable("DiscordUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
@@ -189,7 +189,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FollowedStream");
b.ToTable("FollowedStream", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
@@ -218,7 +218,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiAltSetting");
b.ToTable("AntiAltSetting", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
@@ -250,7 +250,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiRaidSetting");
b.ToTable("AntiRaidSetting", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b =>
@@ -272,7 +272,7 @@ namespace NadekoBot.Migrations
b.HasIndex("AntiSpamSettingId");
b.ToTable("AntiSpamIgnore");
b.ToTable("AntiSpamIgnore", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b =>
@@ -304,7 +304,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiSpamSetting");
b.ToTable("AntiSpamSetting", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoCommand", b =>
@@ -342,7 +342,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("AutoCommands");
b.ToTable("AutoCommands", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
@@ -370,7 +370,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId");
b.ToTable("AutoTranslateChannels");
b.ToTable("AutoTranslateChannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
@@ -398,7 +398,7 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("ChannelId", "UserId");
b.ToTable("AutoTranslateUsers");
b.ToTable("AutoTranslateUsers", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b =>
@@ -421,7 +421,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("BanTemplates");
b.ToTable("BanTemplates", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistEntry", b =>
@@ -441,7 +441,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Blacklist");
b.ToTable("Blacklist", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
@@ -466,7 +466,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("CommandAlias");
b.ToTable("CommandAlias", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
@@ -491,7 +491,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("CommandCooldown");
b.ToTable("CommandCooldown", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b =>
@@ -529,7 +529,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("CurrencyTransactions");
b.ToTable("CurrencyTransactions", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b =>
@@ -554,7 +554,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("DelMsgOnCmdChannel");
b.ToTable("DelMsgOnCmdChannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordPermOverride", b =>
@@ -580,7 +580,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId", "Command")
.IsUnique();
b.ToTable("DiscordPermOverrides");
b.ToTable("DiscordPermOverrides", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b =>
@@ -605,7 +605,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId");
b.ToTable("ExcludedItem");
b.ToTable("ExcludedItem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b =>
@@ -631,7 +631,7 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("GuildConfigId", "Url");
b.ToTable("FeedSub");
b.ToTable("FeedSub", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
@@ -653,7 +653,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterChannelId");
b.ToTable("FilterChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
@@ -675,7 +675,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilteredWord");
b.ToTable("FilteredWord", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b =>
@@ -697,7 +697,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterLinksChannelId");
b.ToTable("FilterLinksChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b =>
@@ -719,7 +719,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterWordsChannelId");
b.ToTable("FilterWordsChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
@@ -741,7 +741,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("GCChannelId");
b.ToTable("GCChannelId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b =>
@@ -767,7 +767,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId", "Number")
.IsUnique();
b.ToTable("GroupName");
b.ToTable("GroupName", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
@@ -891,7 +891,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WarnExpireHours");
b.ToTable("GuildConfigs");
b.ToTable("GuildConfigs", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
@@ -917,7 +917,7 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId", "LogItemId", "ItemType")
.IsUnique();
b.ToTable("IgnoredLogChannels");
b.ToTable("IgnoredLogChannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
@@ -939,7 +939,7 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId");
b.ToTable("IgnoredVoicePresenceCHannels");
b.ToTable("IgnoredVoicePresenceCHannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b =>
@@ -962,7 +962,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ChannelId")
.IsUnique();
b.ToTable("ImageOnlyChannels");
b.ToTable("ImageOnlyChannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
@@ -1027,7 +1027,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("LogSettings");
b.ToTable("LogSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlayerSettings", b =>
@@ -1064,7 +1064,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("MusicPlayerSettings");
b.ToTable("MusicPlayerSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
@@ -1087,7 +1087,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("MusicPlaylists");
b.ToTable("MusicPlaylists", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b =>
@@ -1109,7 +1109,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("MutedUserId");
b.ToTable("MutedUserId", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.NadekoExpression", b =>
@@ -1147,7 +1147,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Expressions");
b.ToTable("Expressions", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b =>
@@ -1169,7 +1169,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId");
b.ToTable("NsfwBlacklistedTags");
b.ToTable("NsfwBlacklistedTags", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
@@ -1209,7 +1209,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("Permissions");
b.ToTable("Permissions", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlantedCurrency", b =>
@@ -1246,7 +1246,7 @@ namespace NadekoBot.Migrations
b.HasIndex("MessageId")
.IsUnique();
b.ToTable("PlantedCurrency");
b.ToTable("PlantedCurrency", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b =>
@@ -1280,7 +1280,7 @@ namespace NadekoBot.Migrations
b.HasIndex("MusicPlaylistId");
b.ToTable("PlaylistSong");
b.ToTable("PlaylistSong", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b =>
@@ -1306,7 +1306,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("Poll");
b.ToTable("Poll", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b =>
@@ -1331,7 +1331,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PollId");
b.ToTable("PollAnswer");
b.ToTable("PollAnswer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b =>
@@ -1356,7 +1356,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PollId");
b.ToTable("PollVote");
b.ToTable("PollVote", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b =>
@@ -1392,7 +1392,7 @@ namespace NadekoBot.Migrations
b.HasIndex("Keyword");
b.ToTable("Quotes");
b.ToTable("Quotes", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
@@ -1417,7 +1417,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ReactionRoleMessageId");
b.ToTable("ReactionRole");
b.ToTable("ReactionRole", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
@@ -1448,7 +1448,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("ReactionRoleMessage");
b.ToTable("ReactionRoleMessage", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
@@ -1482,7 +1482,7 @@ namespace NadekoBot.Migrations
b.HasIndex("When");
b.ToTable("Reminders");
b.ToTable("Reminders", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b =>
@@ -1517,7 +1517,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Repeaters");
b.ToTable("Repeaters", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b =>
@@ -1546,7 +1546,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PatreonUserId")
.IsUnique();
b.ToTable("RewardedUsers");
b.ToTable("RewardedUsers", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RotatingPlayingStatus", b =>
@@ -1566,7 +1566,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("RotatingStatus");
b.ToTable("RotatingStatus", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
@@ -1597,7 +1597,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId", "RoleId")
.IsUnique();
b.ToTable("SelfAssignableRoles");
b.ToTable("SelfAssignableRoles", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
@@ -1637,7 +1637,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("ShopEntry");
b.ToTable("ShopEntry", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b =>
@@ -1659,7 +1659,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ShopEntryId");
b.ToTable("ShopEntryItem");
b.ToTable("ShopEntryItem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b =>
@@ -1681,7 +1681,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredRole");
b.ToTable("SlowmodeIgnoredRole", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b =>
@@ -1703,7 +1703,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredUser");
b.ToTable("SlowmodeIgnoredUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b =>
@@ -1728,7 +1728,7 @@ namespace NadekoBot.Migrations
b.HasIndex("StreamRoleSettingsId");
b.ToTable("StreamRoleBlacklistedUser");
b.ToTable("StreamRoleBlacklistedUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b =>
@@ -1760,7 +1760,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("StreamRoleSettings");
b.ToTable("StreamRoleSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b =>
@@ -1785,7 +1785,7 @@ namespace NadekoBot.Migrations
b.HasIndex("StreamRoleSettingsId");
b.ToTable("StreamRoleWhitelistedUser");
b.ToTable("StreamRoleWhitelistedUser", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b =>
@@ -1810,7 +1810,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnbanTimer");
b.ToTable("UnbanTimer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b =>
@@ -1835,7 +1835,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnmuteTimer");
b.ToTable("UnmuteTimer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b =>
@@ -1863,7 +1863,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnroleTimer");
b.ToTable("UnroleTimer", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b =>
@@ -1908,7 +1908,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId", "GuildId")
.IsUnique();
b.ToTable("UserXpStats");
b.ToTable("UserXpStats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b =>
@@ -1933,7 +1933,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("VcRoleInfo");
b.ToTable("VcRoleInfo", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b =>
@@ -1968,7 +1968,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WaifuId")
.IsUnique();
b.ToTable("WaifuInfo");
b.ToTable("WaifuInfo", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b =>
@@ -1993,7 +1993,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WaifuInfoId");
b.ToTable("WaifuItem");
b.ToTable("WaifuItem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b =>
@@ -2025,7 +2025,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("WaifuUpdates");
b.ToTable("WaifuUpdates", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b =>
@@ -2068,7 +2068,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("Warnings");
b.ToTable("Warnings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b =>
@@ -2099,7 +2099,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("WarningPunishment");
b.ToTable("WarningPunishment", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b =>
@@ -2124,7 +2124,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId");
b.ToTable("XpCurrencyReward");
b.ToTable("XpCurrencyReward", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b =>
@@ -2153,7 +2153,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId", "Level")
.IsUnique();
b.ToTable("XpRoleReward");
b.ToTable("XpRoleReward", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b =>
@@ -2176,7 +2176,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("XpSettings");
b.ToTable("XpSettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>

View File

@@ -10,27 +10,26 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public partial class DangerousCommands : NadekoModule<DangerousCommandsService>
{
private async Task InternalExecSql(string sql, params object[] reps)
private async Task ConfirmActionInternalAsync(string name, Func<Task> action)
{
sql = string.Format(sql, reps);
try
{
var embed = _eb.Create()
.WithTitle(GetText(strs.sql_confirm_exec))
.WithDescription(Format.Code(sql));
.WithDescription(name);
if (!await PromptUserConfirmAsync(embed))
return;
var res = await _service.ExecuteSql(sql);
await SendConfirmAsync(res.ToString());
await action();
await ctx.OkAsync();
}
catch (Exception ex)
{
await SendErrorAsync(ex.ToString());
}
}
[Cmd]
[OwnerOnly]
public partial Task SqlSelect([Leftover] string sql)
@@ -57,38 +56,55 @@ namespace NadekoBot.Modules.Administration
[Cmd]
[OwnerOnly]
public partial Task SqlExec([Leftover] string sql)
=> InternalExecSql(sql);
public async partial Task SqlExec([Leftover] string sql)
{
try
{
var embed = _eb.Create()
.WithTitle(GetText(strs.sql_confirm_exec))
.WithDescription(Format.Code(sql));
if (!await PromptUserConfirmAsync(embed))
return;
var res = await _service.ExecuteSql(sql);
await SendConfirmAsync(res.ToString());
}
catch (Exception ex)
{
await SendErrorAsync(ex.ToString());
}
}
[Cmd]
[OwnerOnly]
public partial Task DeleteWaifus()
=> SqlExec(DangerousCommandsService.WAIFUS_DELETE_SQL);
=> ConfirmActionInternalAsync("Delete Waifus", () => _service.DeleteWaifus());
[Cmd]
[OwnerOnly]
public partial Task DeleteWaifu(IUser user)
=> DeleteWaifu(user.Id);
public async partial Task DeleteWaifu(IUser user)
=> await DeleteWaifu(user.Id);
[Cmd]
[OwnerOnly]
public partial Task DeleteWaifu(ulong userId)
=> InternalExecSql(DangerousCommandsService.WAIFU_DELETE_SQL, userId);
=> ConfirmActionInternalAsync($"Delete Waifu {userId}", () => _service.DeleteWaifu(userId));
[Cmd]
[OwnerOnly]
public partial Task DeleteCurrency()
=> SqlExec(DangerousCommandsService.CURRENCY_DELETE_SQL);
=> ConfirmActionInternalAsync("Delete Currency", () => _service.DeleteCurrency());
[Cmd]
[OwnerOnly]
public partial Task DeletePlaylists()
=> SqlExec(DangerousCommandsService.MUSIC_PLAYLIST_DELETE_SQL);
=> ConfirmActionInternalAsync("Delete Playlists", () => _service.DeletePlaylists());
[Cmd]
[OwnerOnly]
public partial Task DeleteXp()
=> SqlExec(DangerousCommandsService.XP_DELETE_SQL);
=> ConfirmActionInternalAsync("Delete Xp", () => _service.DeleteXp());
[Cmd]
[OwnerOnly]
@@ -108,10 +124,6 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public partial Task PurgeUser([Leftover] IUser user)
=> PurgeUser(user.Id);
//[NadekoCommand, Usage, Description, Aliases]
//[OwnerOnly]
//public partial Task DeleteUnusedCrnQ() =>
// SqlExec(DangerousCommandsService.DeleteUnusedExpressionsAndQuotes);
}
}
}

View File

@@ -2,46 +2,83 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration.Services;
public class DangerousCommandsService : INService
{
public const string WAIFUS_DELETE_SQL = @"DELETE FROM WaifuUpdates;
DELETE FROM WaifuItem;
DELETE FROM WaifuInfo;";
public const string WAIFU_DELETE_SQL =
@"DELETE FROM WaifuUpdates WHERE UserId=(SELECT Id FROM DiscordUser WHERE UserId={0});
DELETE FROM WaifuItem WHERE WaifuInfoId=(SELECT Id FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0}));
UPDATE WaifuInfo SET ClaimerId=NULL WHERE ClaimerId=(SELECT Id FROM DiscordUser WHERE UserId={0});
DELETE FROM WaifuInfo WHERE WaifuId=(SELECT Id FROM DiscordUser WHERE UserId={0});";
public const string CURRENCY_DELETE_SQL =
"UPDATE DiscordUser SET CurrencyAmount=0; DELETE FROM CurrencyTransactions; DELETE FROM PlantedCurrency;";
public const string MUSIC_PLAYLIST_DELETE_SQL = "DELETE FROM MusicPlaylists;";
public const string XP_DELETE_SQL = @"DELETE FROM UserXpStats;
UPDATE DiscordUser
SET ClubId=NULL,
IsClubAdmin=0,
TotalXp=0;
DELETE FROM ClubApplicants;
DELETE FROM ClubBans;
DELETE FROM Clubs;";
// public const string DeleteUnusedExpressionsAndQuotes = @"DELETE FROM Expressions
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);
//DELETE FROM Quotes
//WHERE UseCount=0 AND (DateAdded < date('now', '-7 day') OR DateAdded is null);";
private readonly DbService _db;
public DangerousCommandsService(DbService db)
=> _db = db;
public async Task DeleteXp()
{
await using var ctx = _db.GetDbContext();
await ctx.DiscordUser.UpdateAsync(_ => new DiscordUser()
{
Club = null,
IsClubAdmin = false,
TotalXp = 0
});
await ctx.ClubApplicants.DeleteAsync();
await ctx.ClubBans.DeleteAsync();
await ctx.Clubs.DeleteAsync();
await ctx.SaveChangesAsync();
}
public async Task DeleteWaifus()
{
await using var ctx = _db.GetDbContext();
await ctx.WaifuUpdates.DeleteAsync();
await ctx.WaifuItem.DeleteAsync();
await ctx.WaifuInfo.DeleteAsync();
await ctx.SaveChangesAsync();
}
public async Task DeleteWaifu(ulong userId)
{
await using var ctx = _db.GetDbContext();
await ctx.WaifuUpdates
.Where(x => x.User.UserId == userId)
.DeleteAsync();
await ctx.WaifuItem
.Where(x => x.WaifuInfo.Waifu.UserId == userId)
.DeleteAsync();
await ctx.WaifuInfo
.Where(x => x.Claimer.UserId == userId)
.UpdateAsync(old => new WaifuInfo()
{
ClaimerId = null,
});
await ctx.WaifuInfo
.Where(x => x.Waifu.UserId == userId)
.DeleteAsync();
await ctx.SaveChangesAsync();
}
public async Task DeletePlaylists()
{
await using var ctx = _db.GetDbContext();
await ctx.MusicPlaylists.DeleteAsync();
await ctx.SaveChangesAsync();
}
public async Task DeleteCurrency()
{
await using var ctx = _db.GetDbContext();
await ctx.DiscordUser.UpdateAsync(_ => new DiscordUser()
{
CurrencyAmount = 0
});
await ctx.CurrencyTransactions.DeleteAsync();
await ctx.PlantedCurrency.DeleteAsync();
await ctx.SaveChangesAsync();
}
public async Task<int> ExecuteSql(string sql)
{
int res;

View File

@@ -30,7 +30,7 @@ public sealed class ImageOnlyChannelService : IEarlyBehavior
_client = client;
_db = db;
var uow = _db.GetDbContext();
using var uow = _db.GetDbContext();
_enabledOn = uow.ImageOnlyChannels.ToList()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(y => y.ChannelId)))

View File

@@ -11,6 +11,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
private readonly SelfService _selfService;
private readonly Replacer _rep;
private readonly DbService _db;
private readonly DiscordSocketClient _client;
public PlayingRotateService(
DiscordSocketClient client,
@@ -22,6 +23,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
_db = db;
_bss = bss;
_selfService = selfService;
_client = client;
if (client.ShardId == 0)
_rep = new ReplacementBuilder().WithClient(client).WithProviders(phProviders).Build();
@@ -29,6 +31,9 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
public async Task OnReadyAsync()
{
if (_client.ShardId != 0)
return;
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
var index = 0;
while (await timer.WaitForNextTickAsync())

View File

@@ -8,6 +8,9 @@ using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration;
public sealed class LogCommandService : ILogCommandService, IReadyExecutor
#if !GLOBAL_NADEKO
, INService // don't load this service on global nadeko
#endif
{
public ConcurrentDictionary<ulong, LogSetting> GuildLogSettings { get; }
@@ -42,9 +45,6 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_mute = mute;
_prot = prot;
_tz = tz;
#if !GLOBAL_NADEKO
using (var uow = db.GetDbContext())
{
var guildIds = client.Guilds.Select(x => x.Id).ToList();
@@ -64,7 +64,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_client.UserUnbanned += _client_UserUnbanned;
_client.UserJoined += _client_UserJoined;
_client.UserLeft += _client_UserLeft;
//_client.UserPresenceUpdated += _client_UserPresenceUpdated;
// _client.PresenceUpdated += _client_UserPresenceUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS;
_client.GuildMemberUpdated += _client_GuildUserUpdated;
@@ -78,12 +78,10 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
_mute.UserUnmuted += MuteCommands_UserUnmuted;
_prot.OnAntiProtectionTriggered += TriggeredAntiProtection;
#endif
}
public Task OnReadyAsync()
=> Task.WhenAll(PresenceUpdateTask(), IgnoreMessageIdsClearTask());
public async Task OnReadyAsync()
=> await Task.WhenAll(PresenceUpdateTask(), IgnoreMessageIdsClearTask());
private async Task IgnoreMessageIdsClearTask()
{
@@ -94,7 +92,6 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
private async Task PresenceUpdateTask()
{
#if !GLOBAL_NADEKO
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(15));
while (await timer.WaitForNextTickAsync())
{
@@ -120,7 +117,6 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
}
catch { }
}
#endif
}
public LogSetting? GetGuildLogSettings(ulong guildId)

View File

@@ -478,7 +478,7 @@ public partial class Administration
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
var embed = _service.GetBanUserDmEmbed(Context, user, defaultMessage, msg, null);
if (embed is not null)
await ctx.User.SendAsync(embed);
await user.SendAsync(embed);
}
catch
{

View File

@@ -1,4 +1,5 @@
#nullable disable
using LinqToDB;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Common.TypeReaders.Models;
@@ -224,16 +225,34 @@ public class UserPunishService : INService, IReadyExecutor
public async Task CheckAllWarnExpiresAsync()
{
await using var uow = _db.GetDbContext();
var cleared = await uow.Database.ExecuteSqlRawAsync(@"UPDATE Warnings
SET Forgiven = 1,
ForgivenBy = 'Expiry'
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 0)
AND Forgiven = 0
AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
var cleared = await uow.Warnings
.Where(x => uow.GuildConfigs
.Any(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Clear)
&& x.Forgiven == false
&& x.DateAdded
< DateTime.UtcNow.AddHours(-uow.GuildConfigs
.Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours)
.First()))
.UpdateAsync(_ => new()
{
Forgiven = true,
ForgivenBy = "expiry"
});
var deleted = await uow.Database.ExecuteSqlRawAsync(@"DELETE FROM Warnings
WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND WarnExpireAction = 1)
AND DateAdded < datetime('now', (SELECT '-' || WarnExpireHours || ' hours' FROM GuildConfigs as gc WHERE gc.GuildId = Warnings.GuildId));");
var deleted = await uow.Warnings
.Where(x => uow.GuildConfigs
.Any(y => y.GuildId == x.GuildId
&& y.WarnExpireHours > 0
&& y.WarnExpireAction == WarnExpireAction.Delete)
&& x.DateAdded
< DateTime.UtcNow.AddHours(-uow.GuildConfigs
.Where(y => x.GuildId == y.GuildId)
.Select(y => y.WarnExpireHours)
.First()))
.DeleteAsync();
if (cleared > 0 || deleted > 0)
{
@@ -241,6 +260,8 @@ WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND
cleared,
deleted);
}
await uow.SaveChangesAsync();
}
public async Task CheckWarnExpiresAsync(ulong guildId)
@@ -251,21 +272,24 @@ WHERE GuildId in (SELECT GuildId FROM GuildConfigs WHERE WarnExpireHours > 0 AND
if (config.WarnExpireHours == 0)
return;
var hours = $"{-config.WarnExpireHours} hours";
if (config.WarnExpireAction == WarnExpireAction.Clear)
{
await uow.Database.ExecuteSqlInterpolatedAsync($@"UPDATE warnings
SET Forgiven = 1,
ForgivenBy = 'Expiry'
WHERE GuildId={guildId}
AND Forgiven = 0
AND DateAdded < datetime('now', {hours})");
await uow.Warnings
.Where(x => x.GuildId == guildId
&& x.Forgiven == false
&& x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
.UpdateAsync(_ => new()
{
Forgiven = true,
ForgivenBy = "expiry"
});
}
else if (config.WarnExpireAction == WarnExpireAction.Delete)
{
await uow.Database.ExecuteSqlInterpolatedAsync($@"DELETE FROM warnings
WHERE GuildId={guildId}
AND DateAdded < datetime('now', {hours})");
await uow.Warnings
.Where(x => x.GuildId == guildId
&& x.DateAdded < DateTime.UtcNow.AddHours(-config.WarnExpireHours))
.DeleteAsync();
}
await uow.SaveChangesAsync();

View File

@@ -76,6 +76,7 @@ public partial class Gambling
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
}
ar.Dispose();
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
}
@@ -127,6 +128,7 @@ public partial class Gambling
private Task Ar_OnStartingFailed(AnimalRace race)
{
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
race.Dispose();
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
}

View File

@@ -65,8 +65,7 @@ public partial class Gambling : GamblingModule<GamblingService>
public async Task<string> GetBalanceStringAsync(ulong userId)
{
var wallet = await _cs.GetWalletAsync(userId);
var bal = await wallet.GetBalance();
var bal = await _cs.GetBalanceAsync(userId);
return N(bal);
}

View File

@@ -60,7 +60,6 @@ public class GamblingService : INService, IReadyExecutor
await using var uow = _db.GetDbContext();
await uow.CurrencyTransactions
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
await uow.SaveChangesAsync();
}
catch (Exception ex)
{
@@ -102,17 +101,16 @@ public class GamblingService : INService, IReadyExecutor
if (maxDecay == 0)
maxDecay = int.MaxValue;
await uow.Database.ExecuteSqlInterpolatedAsync($@"
UPDATE DiscordUser
SET CurrencyAmount=
CASE WHEN
{maxDecay} > ROUND(CurrencyAmount * {config.Decay.Percent} - 0.5)
THEN
CurrencyAmount - ROUND(CurrencyAmount * {config.Decay.Percent} - 0.5)
ELSE
CurrencyAmount - {maxDecay}
END
WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};");
var decay = (double)config.Decay.Percent;
await uow.DiscordUser
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
.UpdateAsync(old => new()
{
CurrencyAmount =
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
: old.CurrencyAmount - maxDecay
});
_cache.SetLastCurrencyDecay();
await uow.SaveChangesAsync();

View File

@@ -35,7 +35,7 @@ public class VoteRewardService : INService, IReadyExecutor
if (_client.ShardId != 0)
return;
var http = new HttpClient(new HttpClientHandler
using var http = new HttpClient(new HttpClientHandler
{
AllowAutoRedirect = false,
ServerCertificateCustomValidationCallback = delegate { return true; }

View File

@@ -236,9 +236,9 @@ public partial class Gambling
public partial Task WaifuInfo(ulong targetId)
=> InternalWaifuInfo(targetId);
private Task InternalWaifuInfo(ulong targetId, string name = null)
private async Task InternalWaifuInfo(ulong targetId, string name = null)
{
var wi = _service.GetFullWaifuInfoAsync(targetId);
var wi = await _service.GetFullWaifuInfoAsync(targetId);
var affInfo = _service.GetAffinityTitle(wi.AffinityCount);
var waifuItems = _service.GetWaifuItems().ToDictionary(x => x.ItemEmoji, x => x);
@@ -280,7 +280,7 @@ public partial class Gambling
true)
.AddField(GetText(strs.gifts), itemsStr, true);
return ctx.Channel.EmbedAsync(embed);
await ctx.Channel.EmbedAsync(embed);
}
[Cmd]

View File

@@ -385,10 +385,10 @@ public class WaifuService : INService, IReadyExecutor
return true;
}
public WaifuInfoStats GetFullWaifuInfoAsync(ulong targetId)
public async Task<WaifuInfoStats> GetFullWaifuInfoAsync(ulong targetId)
{
using var uow = _db.GetDbContext();
var wi = uow.GetWaifuInfo(targetId);
await using var uow = _db.GetDbContext();
var wi = await uow.GetWaifuInfoAsync(targetId);
if (wi is null)
{
wi = new()
@@ -409,12 +409,12 @@ public class WaifuService : INService, IReadyExecutor
return wi;
}
public WaifuInfoStats GetFullWaifuInfoAsync(IGuildUser target)
public async Task<WaifuInfoStats> GetFullWaifuInfoAsync(IGuildUser target)
{
using var uow = _db.GetDbContext();
await using var uow = _db.GetDbContext();
_ = uow.GetOrCreateUser(target);
return GetFullWaifuInfoAsync(target.Id);
return await GetFullWaifuInfoAsync(target.Id);
}
public string GetClaimTitle(int count)

View File

@@ -359,7 +359,7 @@ public partial class Help : NadekoModule<HelpService>
};
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
var oldVersionObject = await dlClient.GetObjectAsync(new()
using var oldVersionObject = await dlClient.GetObjectAsync(new()
{
BucketName = "nadeko-pictures",
Key = "cmds/versions.json"

View File

@@ -76,7 +76,7 @@ public sealed partial class YtLoader
var mem = GetScriptResponseSpan(response);
var root = JsonDocument.Parse(mem).RootElement;
var tracksJsonItems = root
using var tracksJsonItems = root
.GetProperty("contents")
.GetProperty("twoColumnSearchResultsRenderer")
.GetProperty("primaryContents")

View File

@@ -7,12 +7,6 @@ using Newtonsoft.Json.Linq;
namespace NadekoBot.Modules.Nsfw;
public record TagRequest(
ulong GuildId,
bool ForceExplicit,
Booru SearchType,
params string[] Tags);
public record UrlReply
{
public string Error { get; init; }
@@ -30,7 +24,6 @@ public class SearchImagesService : ISearchImagesService, INService
public ConcurrentDictionary<ulong, Timer> AutoBoobTimers { get; } = new();
public ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; } = new();
private readonly Random _rng;
private readonly HttpClient _http;
private readonly SearchImageCacher _cache;
private readonly IHttpClientFactory _httpFactory;
private readonly DbService _db;
@@ -39,14 +32,11 @@ public class SearchImagesService : ISearchImagesService, INService
public SearchImagesService(
DbService db,
IHttpClientFactory http,
SearchImageCacher cacher,
IHttpClientFactory httpFactory)
{
_db = db;
_rng = new NadekoRandom();
_http = http.CreateClient();
_http.AddFakeHeaders();
_cache = cacher;
_httpFactory = httpFactory;
@@ -205,8 +195,10 @@ public class SearchImagesService : ISearchImagesService, INService
{
try
{
using var http = _httpFactory.CreateClient();
http.AddFakeHeaders();
JToken obj;
obj = JArray.Parse(await _http.GetStringAsync($"http://api.oboobs.ru/boobs/{_rng.Next(0, 12000)}"))[0];
obj = JArray.Parse(await http.GetStringAsync($"http://api.oboobs.ru/boobs/{_rng.Next(0, 12000)}"))[0];
return new()
{
Error = "",
@@ -269,8 +261,10 @@ public class SearchImagesService : ISearchImagesService, INService
{
try
{
using var http = _httpFactory.CreateClient();
http.AddFakeHeaders();
JToken obj;
obj = JArray.Parse(await _http.GetStringAsync($"http://api.obutts.ru/butts/{_rng.Next(0, 6100)}"))[0];
obj = JArray.Parse(await http.GetStringAsync($"http://api.obutts.ru/butts/{_rng.Next(0, 6100)}"))[0];
return new()
{
Error = "",

View File

@@ -98,15 +98,16 @@ public partial class Searches
return;
var fileName = $"{query}-sparkline.{imageData.Extension}";
using var attachment = new FileAttachment(
imageData.FileData,
fileName
);
await message.ModifyAsync(mp =>
{
mp.Attachments =
new(new[]
{
new FileAttachment(
imageData.FileData,
fileName
)
attachment
});
mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build();

View File

@@ -1,55 +1,55 @@
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();
}
// using System.Net.Http.Json;
//
// namespace NadekoBot.Modules.Searches;
//
// public sealed class PolygonApiClient : IDisposable
// {
// private const string BASE_URL = "https://api.polygon.io/v3";
//
// private readonly HttpClient _httpClient;
// private readonly string _apiKey;
//
// public PolygonApiClient(HttpClient httpClient, string apiKey)
// {
// _httpClient = httpClient;
// _apiKey = apiKey;
// }
//
// public async Task<IReadOnlyCollection<PolygonTickerData>> TickersAsync(string? ticker = null, string? query = null)
// {
// if (string.IsNullOrWhiteSpace(query))
// query = null;
//
// if(query is not null)
// query = Uri.EscapeDataString(query);
//
// var requestString = $"{BASE_URL}/reference/tickers"
// + "?type=CS"
// + "&active=true"
// + "&order=asc"
// + "&limit=1000"
// + $"&apiKey={_apiKey}";
//
// if (!string.IsNullOrWhiteSpace(ticker))
// requestString += $"&ticker={ticker}";
//
// if (!string.IsNullOrWhiteSpace(query))
// requestString += $"&search={query}";
//
//
// var response = await _httpClient.GetFromJsonAsync<PolygonTickerResponse>(requestString);
//
// if (response is null)
// return Array.Empty<PolygonTickerData>();
//
// return response.Results;
// }
//
// // public async Task<PolygonTickerDetailsV3> TickerDetailsV3Async(string ticker)
// // {
// // return new();
// // }
//
// public void Dispose()
// => _httpClient.Dispose();
// }

View File

@@ -34,7 +34,7 @@ public partial class Searches
return;
using var http = _httpFactory.CreateClient("memelist");
var res = await http.GetAsync("https://api.memegen.link/templates/");
using var res = await http.GetAsync("https://api.memegen.link/templates/");
var rawJson = await res.Content.ReadAsStringAsync();

View File

@@ -109,7 +109,8 @@ public class SearchesService : INService
using (var avatarImg = Image.Load<Rgba32>(data))
{
avatarImg.Mutate(x => x.Resize(85, 85).ApplyRoundedCorners(42));
data = avatarImg.ToStream().ToArray();
await using var avStream = avatarImg.ToStream();
data = avStream.ToArray();
DrawAvatar(bg, avatarImg);
}
@@ -141,7 +142,8 @@ public class SearchesService : INService
bg.Mutate(x => x.DrawImage(flowers, new(0, 0), new GraphicsOptions()));
}
return bg.ToStream().ToArray();
await using var stream = bg.ToStream();
return stream.ToArray();
}
public Task<WeatherData> GetWeatherDataAsync(string query)
@@ -532,7 +534,7 @@ public class SearchesService : INService
http.DefaultRequestHeaders.Clear();
using var response = await http.SendAsync(msg);
var content = await response.Content.ReadAsStreamAsync();
await using var content = await response.Content.ReadAsStreamAsync();
using var document = await _googleParser.ParseDocumentAsync(content);
var elems = document.QuerySelectorAll("div.g > div > div");

View File

@@ -3,6 +3,7 @@ using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services.Database.Models;
using System.Net;
namespace NadekoBot.Modules.Searches;
@@ -31,12 +32,14 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE
public async Task OnReadyAsync()
{
var ctx = _db.GetDbContext();
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
var cs = await ctx.AutoTranslateChannels.Include(x => x.Users)
List<AutoTranslateChannel> cs;
await using (var ctx = _db.GetDbContext())
{
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
cs = await ctx.AutoTranslateChannels.Include(x => x.Users)
.Where(x => guilds.Contains(x.GuildId))
.ToListAsyncEF();
}
foreach (var c in cs)
{
@@ -103,7 +106,7 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE
public async Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete)
{
var ctx = _db.GetDbContext();
await using var ctx = _db.GetDbContext();
var old = await ctx.AutoTranslateChannels.ToLinqToDBTable()
.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
@@ -164,7 +167,7 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE
if (!_google.Languages.ContainsKey(from) || !_google.Languages.ContainsKey(to))
return null;
var ctx = _db.GetDbContext();
await using var ctx = _db.GetDbContext();
var ch = await ctx.AutoTranslateChannels.GetByChannelId(channelId);
if (ch is null)
@@ -206,14 +209,13 @@ public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyE
public async Task<bool> UnregisterUser(ulong channelId, ulong userId)
{
var ctx = _db.GetDbContext();
await using var ctx = _db.GetDbContext();
var rows = await ctx.AutoTranslateUsers.ToLinqToDBTable()
.DeleteAsync(x => x.UserId == userId && x.Channel.ChannelId == channelId);
if (_users.TryGetValue(channelId, out var inner))
inner.TryRemove(userId, out _);
await ctx.SaveChangesAsync();
return rows > 0;
}

View File

@@ -62,7 +62,7 @@ public class PicartoProvider : Provider
{
http.DefaultRequestHeaders.Accept.Add(new("application/json"));
// get id based on the username
var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
using var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
if (!res.IsSuccessStatusCode)
continue;

View File

@@ -59,7 +59,7 @@ If you are experiencing ratelimits, you should create your own application at: h
// so there is no need for ratelimit checks atm
try
{
var res = await http.PostAsJsonAsync(
using var res = await http.PostAsJsonAsync(
$"https://open-api.trovo.live/openplatform/channels/id",
new TrovoRequestData()
{

View File

@@ -84,12 +84,13 @@ public class PatreonRewardsService : INService, IReadyExecutor
try
{
using var http = _httpFactory.CreateClient();
var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token"
using var content = new StringContent(string.Empty);
using var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token"
+ "?grant_type=refresh_token"
+ $"&refresh_token={creds.Patreon.RefreshToken}"
+ $"&client_id={creds.Patreon.ClientId}"
+ $"&client_secret={creds.Patreon.ClientSecret}",
new StringContent(string.Empty));
content);
res.EnsureSuccessStatusCode();

View File

@@ -78,8 +78,6 @@ public class RemindService : INService
.ToLinqToDBTable()
.Where(x => x.ServerId / 4194304 % (ulong)_creds.TotalShards == (ulong)_client.ShardId
&& x.When < now)
// .FromSqlInterpolated(
// $"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};")
.ToListAsyncLinqToDB();
}

View File

@@ -27,7 +27,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
_creds = creds;
_client = client;
var uow = _db.GetDbContext();
using var uow = _db.GetDbContext();
var shardRepeaters = uow.Set<Repeater>()
.Where(x => (int)(x.GuildId / Math.Pow(2, 22)) % _creds.TotalShards
== _client.ShardId)

View File

@@ -336,7 +336,7 @@ public partial class Utility : NadekoModule
return;
using var http = _httpFactory.CreateClient();
var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!res.IsImage() || res.GetImageSize() is null or > 262_144)
{
await ReplyErrorLocalizedAsync(strs.invalid_emoji_link);

View File

@@ -235,7 +235,7 @@ public partial class Xp
[Cmd]
[Priority(1)]
public partial Task ClubAccept(IUser user)
=> ClubAccept(user.ToString());
=> ClubAccept($"{user.Username}#{user.Discriminator}");
[Cmd]
[Priority(0)]
@@ -277,7 +277,7 @@ public partial class Xp
[Cmd]
[Priority(1)]
public partial Task ClubBan([Leftover] IUser user)
=> ClubBan(user.ToString());
=> ClubBan($"{user.Username}#{user.Discriminator}");
[Cmd]
[Priority(0)]
@@ -295,7 +295,7 @@ public partial class Xp
[Cmd]
[Priority(1)]
public partial Task ClubUnBan([Leftover] IUser user)
=> ClubUnBan(user.ToString());
=> ClubUnBan($"{user.Username}#{user.Discriminator}");
[Cmd]
[Priority(0)]

View File

@@ -121,153 +121,155 @@ public class XpService : INService, IReadyExecutor
#endif
}
public Task OnReadyAsync()
=> UpdateLoop();
private async Task UpdateLoop()
public async Task OnReadyAsync()
{
using var timer = new PeriodicTimer(5.Seconds());
while (await timer.WaitForNextTickAsync())
{
try
await UpdateLoop();
}
}
private async Task UpdateLoop()
{
try
{
var toNotify =
new List<(IGuild Guild, IMessageChannel MessageChannel, IUser User, int Level,
XpNotificationLocation NotifyType, NotifOf NotifOf)>();
var roleRewards = new Dictionary<ulong, List<XpRoleReward>>();
var curRewards = new Dictionary<ulong, List<XpCurrencyReward>>();
var toAddTo = new List<UserCacheItem>();
while (_addMessageXp.TryDequeue(out var usr))
toAddTo.Add(usr);
var group = toAddTo.GroupBy(x => (GuildId: x.Guild.Id, x.User));
if (toAddTo.Count == 0)
return;
await using (var uow = _db.GetDbContext())
{
var toNotify =
new List<(IGuild Guild, IMessageChannel MessageChannel, IUser User, int Level,
XpNotificationLocation NotifyType, NotifOf NotifOf)>();
var roleRewards = new Dictionary<ulong, List<XpRoleReward>>();
var curRewards = new Dictionary<ulong, List<XpCurrencyReward>>();
var toAddTo = new List<UserCacheItem>();
while (_addMessageXp.TryDequeue(out var usr))
toAddTo.Add(usr);
var group = toAddTo.GroupBy(x => (GuildId: x.Guild.Id, x.User));
if (toAddTo.Count == 0)
continue;
await using (var uow = _db.GetDbContext())
foreach (var item in group)
{
foreach (var item in group)
var xp = item.Sum(x => x.XpAmount);
var usr = uow.GetOrCreateUserXpStats(item.Key.GuildId, item.Key.User.Id);
var du = uow.GetOrCreateUser(item.Key.User);
var globalXp = du.TotalXp;
var oldGlobalLevelData = new LevelStats(globalXp);
var newGlobalLevelData = new LevelStats(globalXp + xp);
var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
usr.Xp += xp;
du.TotalXp += xp;
if (du.Club is not null)
du.Club.Xp += xp;
var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
if (oldGlobalLevelData.Level < newGlobalLevelData.Level)
{
var xp = item.Sum(x => x.XpAmount);
var usr = uow.GetOrCreateUserXpStats(item.Key.GuildId, item.Key.User.Id);
var du = uow.GetOrCreateUser(item.Key.User);
var globalXp = du.TotalXp;
var oldGlobalLevelData = new LevelStats(globalXp);
var newGlobalLevelData = new LevelStats(globalXp + xp);
var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
usr.Xp += xp;
du.TotalXp += xp;
if (du.Club is not null)
du.Club.Xp += xp;
var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
if (oldGlobalLevelData.Level < newGlobalLevelData.Level)
du.LastLevelUp = DateTime.UtcNow;
var first = item.First();
if (du.NotifyOnLevelUp != XpNotificationLocation.None)
{
du.LastLevelUp = DateTime.UtcNow;
var first = item.First();
if (du.NotifyOnLevelUp != XpNotificationLocation.None)
{
toNotify.Add((first.Guild, first.Channel, first.User, newGlobalLevelData.Level,
du.NotifyOnLevelUp, NotifOf.Global));
}
}
if (oldGuildLevelData.Level < newGuildLevelData.Level)
{
usr.LastLevelUp = DateTime.UtcNow;
//send level up notification
var first = item.First();
if (usr.NotifyOnLevelUp != XpNotificationLocation.None)
{
toNotify.Add((first.Guild, first.Channel, first.User, newGuildLevelData.Level,
usr.NotifyOnLevelUp, NotifOf.Server));
}
//give role
if (!roleRewards.TryGetValue(usr.GuildId, out var rrews))
{
rrews = uow.XpSettingsFor(usr.GuildId).RoleRewards.ToList();
roleRewards.Add(usr.GuildId, rrews);
}
if (!curRewards.TryGetValue(usr.GuildId, out var crews))
{
crews = uow.XpSettingsFor(usr.GuildId).CurrencyRewards.ToList();
curRewards.Add(usr.GuildId, crews);
}
//loop through levels since last level up, so if a high amount of xp is gained, reward are still applied.
for (var i = oldGuildLevelData.Level + 1; i <= newGuildLevelData.Level; i++)
{
var rrew = rrews.FirstOrDefault(x => x.Level == i);
if (rrew is not null)
{
var role = first.User.Guild.GetRole(rrew.RoleId);
if (role is not null)
{
if (rrew.Remove)
_ = first.User.RemoveRoleAsync(role);
else
_ = first.User.AddRoleAsync(role);
}
}
//get currency reward for this level
var crew = crews.FirstOrDefault(x => x.Level == i);
if (crew is not null)
//give the user the reward if it exists
await _cs.AddAsync(item.Key.User.Id, crew.Amount, new("xp", "level-up"));
}
toNotify.Add((first.Guild, first.Channel, first.User, newGlobalLevelData.Level,
du.NotifyOnLevelUp, NotifOf.Global));
}
}
uow.SaveChanges();
if (oldGuildLevelData.Level < newGuildLevelData.Level)
{
usr.LastLevelUp = DateTime.UtcNow;
//send level up notification
var first = item.First();
if (usr.NotifyOnLevelUp != XpNotificationLocation.None)
{
toNotify.Add((first.Guild, first.Channel, first.User, newGuildLevelData.Level,
usr.NotifyOnLevelUp, NotifOf.Server));
}
//give role
if (!roleRewards.TryGetValue(usr.GuildId, out var rrews))
{
rrews = uow.XpSettingsFor(usr.GuildId).RoleRewards.ToList();
roleRewards.Add(usr.GuildId, rrews);
}
if (!curRewards.TryGetValue(usr.GuildId, out var crews))
{
crews = uow.XpSettingsFor(usr.GuildId).CurrencyRewards.ToList();
curRewards.Add(usr.GuildId, crews);
}
//loop through levels since last level up, so if a high amount of xp is gained, reward are still applied.
for (var i = oldGuildLevelData.Level + 1; i <= newGuildLevelData.Level; i++)
{
var rrew = rrews.FirstOrDefault(x => x.Level == i);
if (rrew is not null)
{
var role = first.User.Guild.GetRole(rrew.RoleId);
if (role is not null)
{
if (rrew.Remove)
_ = first.User.RemoveRoleAsync(role);
else
_ = first.User.AddRoleAsync(role);
}
}
//get currency reward for this level
var crew = crews.FirstOrDefault(x => x.Level == i);
if (crew is not null)
//give the user the reward if it exists
await _cs.AddAsync(item.Key.User.Id, crew.Amount, new("xp", "level-up"));
}
}
}
await toNotify.Select(async x =>
{
if (x.NotifOf == NotifOf.Server)
{
if (x.NotifyType == XpNotificationLocation.Dm)
{
await x.User.SendConfirmAsync(_eb,
_strings.GetText(strs.level_up_dm(x.User.Mention,
Format.Bold(x.Level.ToString()),
Format.Bold(x.Guild.ToString() ?? "-")),
x.Guild.Id));
}
else if (x.MessageChannel is not null) // channel
{
await x.MessageChannel.SendConfirmAsync(_eb,
_strings.GetText(strs.level_up_channel(x.User.Mention,
Format.Bold(x.Level.ToString())),
x.Guild.Id));
}
}
else
{
IMessageChannel chan;
if (x.NotifyType == XpNotificationLocation.Dm)
chan = await x.User.CreateDMChannelAsync();
else // channel
chan = x.MessageChannel;
uow.SaveChanges();
}
await chan.SendConfirmAsync(_eb,
_strings.GetText(strs.level_up_global(x.User.Mention,
await toNotify.Select(async x =>
{
if (x.NotifOf == NotifOf.Server)
{
if (x.NotifyType == XpNotificationLocation.Dm)
{
await x.User.SendConfirmAsync(_eb,
_strings.GetText(strs.level_up_dm(x.User.Mention,
Format.Bold(x.Level.ToString()),
Format.Bold(x.Guild.ToString() ?? "-")),
x.Guild.Id));
}
else if (x.MessageChannel is not null) // channel
{
await x.MessageChannel.SendConfirmAsync(_eb,
_strings.GetText(strs.level_up_channel(x.User.Mention,
Format.Bold(x.Level.ToString())),
x.Guild.Id));
}
})
.WhenAll();
}
catch (Exception ex)
{
Log.Error(ex, "Error In the XP update loop");
}
}
else
{
IMessageChannel chan;
if (x.NotifyType == XpNotificationLocation.Dm)
chan = await x.User.CreateDMChannelAsync();
else // channel
chan = x.MessageChannel;
await chan.SendConfirmAsync(_eb,
_strings.GetText(strs.level_up_global(x.User.Mention,
Format.Bold(x.Level.ToString())),
x.Guild.Id));
}
})
.WhenAll();
}
catch (Exception ex)
{
Log.Error(ex, "Error In the XP update loop");
}
}

View File

@@ -23,24 +23,19 @@
<PackageReference Include="AWSSDK.S3" Version="3.7.7.21" />
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.4" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Discord.Net" Version="3.3.0" />
<PackageReference Include="Discord.Net" Version="3.4.1" />
<PackageReference Include="CoreCLR-NCalc" Version="2.2.92" />
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138" />
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.55.0.2449" />
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084" />
<PackageReference Include="Google.Protobuf" Version="3.19.4" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.42.0" />
<PackageReference Include="Grpc.Tools" Version="2.43.0">
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.41.0" />
<PackageReference Include="Grpc.Tools" Version="2.41.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Html2Markdown" Version="5.0.2.561" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
@@ -59,10 +54,22 @@
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0010" />
<PackageReference Include="StackExchange.Redis" Version="2.2.88" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
<PackageReference Include="linq2db.EntityFrameworkCore" Version="6.6.1" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" />
<!-- Db-related packages -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="linq2db.EntityFrameworkCore" Version="6.6.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
<!-- Remove this when static abstract interface members support is released -->
<PackageReference Include="System.Runtime.Experimental" Version="6.0.0" />
@@ -71,6 +78,12 @@
<!-- Used by stream notifications -->
<PackageReference Include="TwitchLib.Api" Version="3.4.1" />
<!-- Uncomment to check for disposable issues -->
<!-- <PackageReference Include="IDisposableAnalyzers" Version="4.0.2">-->
<!-- <PrivateAssets>all</PrivateAssets>-->
<!-- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
<!-- </PackageReference>-->
</ItemGroup>
<ItemGroup>
@@ -101,10 +114,15 @@
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionPrefix).$(VersionSuffix)</Version>
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'GlobalNadeko' ">
<DefineConstants>$(DefineConstants);GLOBAL_NADEKO</DefineConstants>
<!-- Define trace doesn't seem to affect the build at all so I had to remove $(DefineConstants)-->
<DefineTrace>false</DefineTrace>
<DefineConstants>GLOBAL_NADEKO</DefineConstants>
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
<Optimize>true</Optimize>
<DebugType>portable</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<!-- TODO: Remove this when the conflict issue in System.Runtime.Experimental is fixed -->

View File

@@ -14,7 +14,7 @@ public class CurrencyService : ICurrencyService, INService
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
{
if (type == CurrencyType.Default)
return Task.FromResult<IWallet>(new DefaultWallet(userId, _db.GetDbContext()));
return Task.FromResult<IWallet>(new DefaultWallet(userId, _db));
throw new ArgumentOutOfRangeException(nameof(type));
}
@@ -27,14 +27,12 @@ public class CurrencyService : ICurrencyService, INService
{
if (type == CurrencyType.Default)
{
await using var ctx = _db.GetDbContext();
foreach (var userId in userIds)
{
var wallet = new DefaultWallet(userId, ctx);
var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, txData);
}
await ctx.SaveChangesAsync();
return;
}
@@ -58,6 +56,7 @@ public class CurrencyService : ICurrencyService, INService
? du.CurrencyAmount - amount
: 0
});
await ctx.SaveChangesAsync();
return;
}
@@ -69,7 +68,7 @@ public class CurrencyService : ICurrencyService, INService
long amount,
TxData txData)
{
await using var wallet = await GetWalletAsync(userId);
var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, txData);
}
@@ -78,7 +77,7 @@ public class CurrencyService : ICurrencyService, INService
long amount,
TxData txData)
{
await using var wallet = await GetWalletAsync(user.Id);
var wallet = await GetWalletAsync(user.Id);
await wallet.Add(amount, txData);
}
@@ -87,7 +86,7 @@ public class CurrencyService : ICurrencyService, INService
long amount,
TxData txData)
{
await using var wallet = await GetWalletAsync(userId);
var wallet = await GetWalletAsync(userId);
return await wallet.Take(amount, txData);
}
@@ -96,22 +95,7 @@ public class CurrencyService : ICurrencyService, INService
long amount,
TxData txData)
{
await using var wallet = await GetWalletAsync(user.Id);
var wallet = await GetWalletAsync(user.Id);
return await wallet.Take(amount, txData);
}
public async Task<bool> TransferAsync(
ulong fromId,
ulong toId,
long amount,
string fromName,
string note)
{
await using var fromWallet = await GetWalletAsync(fromId);
await using var toWallet = await GetWalletAsync(toId);
var extra = new TxData("gift", fromName, note, fromId);
return await fromWallet.Transfer(amount, toWallet, extra);
}
}

View File

@@ -0,0 +1,29 @@
using NadekoBot.Services.Currency;
namespace NadekoBot.Services;
public static class CurrencyServiceExtensions
{
public static async Task<long> GetBalanceAsync(this ICurrencyService cs, ulong userId)
{
var wallet = await cs.GetWalletAsync(userId);
return await wallet.GetBalance();
}
// todo transfer should be a transaction
public static async Task<bool> TransferAsync(
this ICurrencyService cs,
ulong fromId,
ulong toId,
long amount,
string fromName,
string note)
{
var fromWallet = await cs.GetWalletAsync(fromId);
var toWallet = await cs.GetWalletAsync(toId);
var extra = new TxData("gift", fromName, note, fromId);
return await fromWallet.Transfer(amount, toWallet, extra);
}
}

View File

@@ -7,48 +7,54 @@ namespace NadekoBot.Services.Currency;
public class DefaultWallet : IWallet
{
private readonly DbService _db;
public ulong UserId { get; }
private readonly NadekoContext _ctx;
public DefaultWallet(ulong userId, NadekoContext ctx)
public DefaultWallet(ulong userId, DbService db)
{
UserId = userId;
_ctx = ctx;
_db = db;
}
public Task<long> GetBalance()
=> _ctx.DiscordUser
.ToLinqToDBTable()
.Where(x => x.UserId == UserId)
.Select(x => x.CurrencyAmount)
.FirstOrDefaultAsync();
public async Task<long> GetBalance()
{
await using var ctx = _db.GetDbContext();
return await ctx.DiscordUser
.ToLinqToDBTable()
.Where(x => x.UserId == UserId)
.Select(x => x.CurrencyAmount)
.FirstOrDefaultAsync();
}
public async Task<bool> Take(long amount, TxData txData)
{
if (amount < 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative.");
var changed = await _ctx.DiscordUser
.Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
.UpdateAsync(x => new()
{
CurrencyAmount = x.CurrencyAmount - amount
});
await using var ctx = _db.GetDbContext();
var changed = await ctx.DiscordUser
.Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
.UpdateAsync(x => new()
{
CurrencyAmount = x.CurrencyAmount - amount
});
if (changed == 0)
return false;
await _ctx.CreateLinqToDbContext()
.InsertAsync(new CurrencyTransaction()
{
Amount = -amount,
Note = txData.Note,
UserId = UserId,
Type = txData.Type,
Extra = txData.Extra,
OtherId = txData.OtherId
});
await ctx
.GetTable<CurrencyTransaction>()
.InsertAsync(() => new()
{
Amount = -amount,
Note = txData.Note,
UserId = UserId,
Type = txData.Type,
Extra = txData.Extra,
OtherId = txData.OtherId,
DateAdded = DateTime.UtcNow
});
return true;
}
@@ -58,9 +64,11 @@ public class DefaultWallet : IWallet
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
await using (var tran = await _ctx.Database.BeginTransactionAsync())
await using var ctx = _db.GetDbContext();
await using (var tran = await ctx.Database.BeginTransactionAsync())
{
var changed = await _ctx.DiscordUser
var changed = await ctx.DiscordUser
.Where(x => x.UserId == UserId)
.UpdateAsync(x => new()
{
@@ -69,7 +77,7 @@ public class DefaultWallet : IWallet
if (changed == 0)
{
await _ctx.DiscordUser
await ctx.DiscordUser
.ToLinqToDBTable()
.Value(x => x.UserId, UserId)
.Value(x => x.Username, "Unknown")
@@ -81,29 +89,16 @@ public class DefaultWallet : IWallet
await tran.CommitAsync();
}
var ct = new CurrencyTransaction()
{
Amount = amount,
UserId = UserId,
Note = txData.Note,
Type = txData.Type,
Extra = txData.Extra,
OtherId = txData.OtherId
};
await _ctx.CreateLinqToDbContext()
.InsertAsync(ct);
}
public void Dispose()
{
_ctx.SaveChanges();
_ctx.Dispose();
}
public async ValueTask DisposeAsync()
{
await _ctx.SaveChangesAsync();
await _ctx.DisposeAsync();
await ctx.GetTable<CurrencyTransaction>()
.InsertAsync(() => new()
{
Amount = amount,
UserId = UserId,
Note = txData.Note,
Type = txData.Type,
Extra = txData.Extra,
OtherId = txData.OtherId,
DateAdded = DateTime.UtcNow
});
}
}

View File

@@ -38,11 +38,4 @@ public interface ICurrencyService
IUser user,
long amount,
TxData txData);
Task<bool> TransferAsync(
ulong from,
ulong to,
long amount,
string fromName,
string note);
}

View File

@@ -1,6 +1,6 @@
namespace NadekoBot.Services.Currency;
public interface IWallet : IDisposable, IAsyncDisposable
public interface IWallet
{
public ulong UserId { get; }

View File

@@ -32,10 +32,9 @@ public class DbService
using var context = new NadekoContext(_options);
if (context.Database.GetPendingMigrations().Any())
{
var mContext = new NadekoContext(_migrateOptions);
using var mContext = new NadekoContext(_migrateOptions);
mContext.Database.Migrate();
mContext.SaveChanges();
mContext.Dispose();
}
context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL");

View File

@@ -32,6 +32,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
private readonly object _reloadLock = new();
private readonly IDisposable _changeToken;
public BotCredsProvider(int? totalShards = null)
{
@@ -52,9 +53,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
_config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true)
.AddEnvironmentVariables("NadekoBot_")
.Build();
ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
#if !GLOBAL_NADEKO
_changeToken = ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
#endif
Reload();
}

View File

@@ -96,7 +96,8 @@ public class RemoteGrpcCoordinator : ICoordinator, IReadyExecutor
break;
}
await Task.Delay(22500);
Log.Information("Coordinator is restarting gracefully. Waiting...");
await Task.Delay(30_000);
}
catch (Exception ex)
{

View File

@@ -5,9 +5,9 @@ using System.Diagnostics;
namespace NadekoBot.Services;
public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
public sealed class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
{
public const string BOT_VERSION = "4.0.3";
public const string BOT_VERSION = "4.0.5";
public string Author
=> "Kwoth#2452";
@@ -133,11 +133,16 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
};
}
public async Task OnReadyAsync()
private void InitializeChannelCount()
{
var guilds = _client.Guilds;
textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel));
voiceChannels = guilds.Sum(g => g.Channels.Count(cx => cx is IVoiceChannel));
textChannels = guilds.Sum(static g => g.Channels.Count(static cx => cx is ITextChannel));
voiceChannels = guilds.Sum(static g => g.Channels.Count(static cx => cx is IVoiceChannel));
}
public async Task OnReadyAsync()
{
InitializeChannelCount();
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
do
@@ -189,4 +194,4 @@ public class StatsService : IStatsService, IReadyExecutor, INService, IDisposabl
_currentProcess.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

@@ -1,87 +1,87 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace NadekoBot.Extensions;
public static class ProcessExtensions
{
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
public static void KillTree(this Process process)
=> process.KillTree(_defaultTimeout);
public static void KillTree(this Process process, TimeSpan timeout)
{
if (_isWindows)
RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _);
else
{
var children = new HashSet<int>();
GetAllChildIdsUnix(process.Id, children, timeout);
foreach (var childId in children)
KillProcessUnix(childId, timeout);
KillProcessUnix(process.Id, timeout);
}
}
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
{
var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
{
using var reader = new StringReader(stdout);
while (true)
{
var text = reader.ReadLine();
if (text is null)
return;
if (int.TryParse(text, out var id))
{
children.Add(id);
// Recursively get the children
GetAllChildIdsUnix(id, children, timeout);
}
}
}
}
private static void KillProcessUnix(int processId, TimeSpan timeout)
=> RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
private static int RunProcessAndWaitForExit(
string fileName,
string arguments,
TimeSpan timeout,
out string? stdout)
{
stdout = null;
var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
UseShellExecute = false
};
var process = Process.Start(startInfo);
if (process is null)
return -1;
if (process.WaitForExit((int)timeout.TotalMilliseconds))
stdout = process.StandardOutput.ReadToEnd();
else
process.Kill();
return process.ExitCode;
}
}
// // Copyright (c) .NET Foundation. All rights reserved.
// // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// // https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
//
// using System.Diagnostics;
// using System.Runtime.InteropServices;
//
// namespace NadekoBot.Extensions;
//
// public static class ProcessExtensions
// {
// private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
//
// public static void KillTree(this Process process)
// => process.KillTree(_defaultTimeout);
//
// public static void KillTree(this Process process, TimeSpan timeout)
// {
// if (_isWindows)
// RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _);
// else
// {
// var children = new HashSet<int>();
// GetAllChildIdsUnix(process.Id, children, timeout);
// foreach (var childId in children)
// KillProcessUnix(childId, timeout);
//
// KillProcessUnix(process.Id, timeout);
// }
// }
//
// private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
// {
// var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
//
// if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
// {
// using var reader = new StringReader(stdout);
// while (true)
// {
// var text = reader.ReadLine();
// if (text is null)
// return;
//
// if (int.TryParse(text, out var id))
// {
// children.Add(id);
// // Recursively get the children
// GetAllChildIdsUnix(id, children, timeout);
// }
// }
// }
// }
//
// private static void KillProcessUnix(int processId, TimeSpan timeout)
// => RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
//
// private static int RunProcessAndWaitForExit(
// string fileName,
// string arguments,
// TimeSpan timeout,
// out string? stdout)
// {
// stdout = null;
//
// var startInfo = new ProcessStartInfo
// {
// FileName = fileName,
// Arguments = arguments,
// RedirectStandardOutput = true,
// UseShellExecute = false
// };
//
// using var process = Process.Start(startInfo);
//
// if (process is null)
// return -1;
//
// if (process.WaitForExit((int)timeout.TotalMilliseconds))
// stdout = process.StandardOutput.ReadToEnd();
// else
// process.Kill();
//
// return process.ExitCode;
// }
// }

View File

@@ -21,7 +21,7 @@ public static class Rgba32Extensions
var xOffset = 0;
for (var i = 0; i < imgArray.Count; i++)
{
var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
using var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
var offset = xOffset;
imgFrame.Mutate(x => x.DrawImage(frame, new(offset, 0), new GraphicsOptions()));
xOffset += imgArray[i].Bounds().Width;

View File

@@ -89,7 +89,7 @@ public static class StringExtensions
public static async Task<Stream> ToStream(this string str)
{
var ms = new MemoryStream();
var sw = new StreamWriter(ms);
await using var sw = new StreamWriter(ms);
await sw.WriteAsync(str);
await sw.FlushAsync();
ms.Position = 0;

View File

@@ -202,9 +202,9 @@ exprlist:
Running the command in DM will list global custom reactions, while running it in a server will list server custom reactions.
Shows enabled settings, followed by id, followed by the trigger.
**Settings:**
• 🗯️ Triggered if trigger matches any word (`{0}h {0}crca`)
• ✉️ Response will be DMed (`{0}h {0}crdm`)
• ❌ Trigger will be deleted (`{0}h {0}crad`)
• 🗯️ Triggered if trigger matches any word (`{0}h {0}exca`)
• ✉️ Response will be DMed (`{0}h {0}exdm`)
• ❌ Trigger will be deleted (`{0}h {0}exad`)
args:
- "1"
- "all"
@@ -889,7 +889,7 @@ deleteplaylist:
args:
- "5"
queueautoplay:
desc: "Toggles autoplay - When the song is finished, automatically queue a related Youtube song. (Works only for Youtube songs and when queue is empty)"
desc: "Toggles autoplay - When the song is finished, automatically queue a related Youtube song. (Works only for Youtube songs)"
args:
- ""
streamadd:
@@ -2154,4 +2154,4 @@ showembed:
deleteemptyservers:
desc: "Deletes all servers in which the bot is the only member."
args:
- ""
- ""