Compare commits

...

35 Commits
4.0.3 ... 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
Kwoth
cc522ef872 Updated CHANGELOG.md 2022-03-04 04:55:39 +01:00
Kwoth
0e192ee7f0 Removed some lingering strings calling Expressions 'CustomReactions'. Id which shows up when you add a new expr is now correct. 2022-03-04 04:51:02 +01:00
Kwoth
ddd0592b30 Changed the intent error message to say 'all intents' 2022-03-04 02:23:48 +01:00
59 changed files with 882 additions and 771 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,12 +7,37 @@ 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
- Fixed the `id` which shows up when you add a new Expression
- Fixed some strings which were still referring to "CustomReaction(s)" instead of "Expression(s)"
## [4.0.3] - 04.03.2022
### Fixed
- Console should no longer spam numbers when `.antispam` is enabled
## [4.0.2] - 03.03.2022
### Fixed
- Fixed `.rero` not working due to a bug introduced in 4.0
## [4.0.1] - 03.03.2022
### Added
- Added `usePrivilegedIntents` to creds.yml if you don't have or don't want (?) to use them
- Added a human-readable, detailed error message if logging in fails due to missing privileged intents

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();
@@ -358,7 +356,7 @@ How to enable privileged intents:
1. Head over to the Discord Developer Portal https://discord.com/developers/applications/
2. Select your Application.
3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section.
4. Enable both intents.
4. Enable all intents.
5. Restart your bot.
Read this only if your bot is in 100 or more servers:

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

@@ -31,7 +31,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -39,8 +39,8 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.new_cust_react))
.WithDescription($"#{ex.Id}")
.WithTitle(GetText(strs.expr_new))
.WithDescription($"#{new kwum(ex.Id)}")
.AddField(GetText(strs.trigger), key)
.AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
@@ -56,7 +56,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if ((channel is null && !_creds.IsOwner(ctx.User))
|| (channel is not null && !((IGuildUser)ctx.User).GuildPermissions.Administrator))
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -65,14 +65,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.edited_cust_react))
.WithTitle(GetText(strs.expr_edited))
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger)
.AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
}
else
await ReplyErrorLocalizedAsync(strs.edit_fail);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
}
[Cmd]
@@ -86,7 +86,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (expressions is null || !expressions.Any())
{
await ReplyErrorLocalizedAsync(strs.no_found);
await ReplyErrorLocalizedAsync(strs.expr_no_found);
return;
}
@@ -105,7 +105,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
: " // " + string.Join(" ", ex.GetReactions())))
.Join('\n');
return _eb.Create().WithOkColor().WithTitle(GetText(strs.custom_reactions)).WithDescription(desc);
return _eb.Create().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
},
expressions.Length,
20);
@@ -118,7 +118,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (found is null)
{
await ReplyErrorLocalizedAsync(strs.no_found_id);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
return;
}
@@ -135,7 +135,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -145,13 +145,13 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.deleted))
.WithTitle(GetText(strs.expr_deleted))
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
.AddField(GetText(strs.response), ex.Response.TrimTo(1024)));
}
else
await ReplyErrorLocalizedAsync(strs.no_found_id);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
}
[Cmd]
@@ -159,21 +159,21 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
var ex = _service.GetExpression(ctx.Guild?.Id, id);
if (ex is null)
{
await ReplyErrorLocalizedAsync(strs.no_found);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
return;
}
if (emojiStrs.Length == 0)
{
await _service.ResetExprReactions(ctx.Guild?.Id, id);
await ReplyConfirmLocalizedAsync(strs.crr_reset(Format.Bold(id.ToString())));
await ReplyConfirmLocalizedAsync(strs.expr_reset(Format.Bold(id.ToString())));
return;
}
@@ -204,7 +204,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
await _service.SetExprReactions(ctx.Guild?.Id, id, succ);
await ReplyConfirmLocalizedAsync(strs.crr_set(Format.Bold(id.ToString()),
await ReplyConfirmLocalizedAsync(strs.expr_set(Format.Bold(id.ToString()),
succ.Select(static x => x.ToString()).Join(", ")));
}
@@ -237,14 +237,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
var (success, newVal) = await _service.ToggleExprOptionAsync(id, option);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.no_found_id);
await ReplyErrorLocalizedAsync(strs.expr_no_found_id);
return;
}
@@ -270,7 +270,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
.WithDescription("This will delete all custom reactions on this server.")))
{
var count = _service.DeleteAllExpressions(ctx.Guild.Id);
await ReplyConfirmLocalizedAsync(strs.cleared(count));
await ReplyConfirmLocalizedAsync(strs.exprs_cleared(count));
}
}
@@ -279,7 +279,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}
@@ -298,7 +298,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{
if (!AdminInGuildOrOwnerInDm())
{
await ReplyErrorLocalizedAsync(strs.insuff_perms);
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
return;
}

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:
- ""
- ""

View File

@@ -1,13 +1,13 @@
{
"deleted": "Custom Reaction deleted",
"insuff_perms": "Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for server custom reactions.",
"custom_reactions": "Custom Reactions",
"new_cust_react": "New Custom Reaction",
"no_found": "No custom reaction found.",
"no_found_id": "No custom reaction found with that id.",
"cleared": "All {0} custom reactions on this server have been removed.",
"crr_reset": "Custom reaction with id {0} will no longer add reactions.",
"crr_set": "Custom reaction with id {0} will add following reactions to the response message: {1}",
"expr_deleted": "Expression deleted",
"expr_insuff_perms": "Insufficient permissions. Requires Bot ownership for global expressions, and Administrator for server expressions.",
"expressions": "Expressions",
"expr_new": "New Expression",
"expr_no_found": "No expression found.",
"expr_no_found_id": "No expression found with that id.",
"exprs_cleared": "All {0} expressions on this server have been removed.",
"expr_reset": "Expression with id {0} will no longer add reactions.",
"expr_set": "Expression with id {0} will add following reactions to the response message: {1}",
"invalid_emojis": "All emojis you've specified are invalid.",
"invalid_emoji_link": "Specified link is either not an image or exceeds 256KB.",
"emoji_add_error": "Error adding emoji. You either ran out of emoji slots, or image size is inadequate.",
@@ -636,8 +636,8 @@
"vcrole_added": "Users who join {0} voice channel will get {1} role.",
"vcrole_removed": "Users who join {0} voice channel will no longer get a role.",
"vc_role_list": "Voice channel roles",
"option_disabled": "{0} option is now disabled for custom reaction with id {1}.",
"option_enabled": "{0} option is now enabled for custom reaction with id {1}.",
"option_disabled": "{0} option is now disabled for the expression with id {1}.",
"option_enabled": "{0} option is now enabled for the expression with id {1}.",
"aliases_none": "No alias found",
"alias_added": "Typing {0} will now be an alias of {1}.",
"alias_list": "List of aliases",
@@ -847,7 +847,7 @@
"club_apps_for": "Applicants for {0} club",
"club_leaderboard": "Club leaderboard - page {0}",
"template_reloaded": "Xp template has been reloaded.",
"edited_cust_react": "Custom Reaction Edited",
"expr_edited": "Expression Edited",
"self_assign_are_exclusive": "You can only choose 1 role from each group.",
"self_assign_are_not_exclusive": "You can choose any number of roles from any group.",
"self_assign_group": "Group {0}",
@@ -870,7 +870,6 @@
"feed_no_feed": "You haven't subscribed to any feeds on this server.",
"restart_fail": "You must setup RestartCommand in your creds.yml",
"restarting": "Restarting.",
"edit_fail": "Custom reaction with that ID does not exist.",
"streaming": "Streaming",
"rafflecur": "{0} Currency Raffle",
"rafflecur_joined": "User {0} joined the raffle",