mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
59cea6ee38 | ||
|
cbcfa77a34 | ||
|
cfb202cc95 | ||
|
b7d1fd1b47 | ||
|
4cf3bdb53a | ||
|
aab5bc9744 | ||
|
1f14c9066e | ||
|
9ade3c9537 | ||
|
1dc393d2b1 | ||
|
798b66db9b | ||
|
57e65e5515 | ||
|
86e728b753 | ||
|
fd032d3e91 | ||
|
eab16865cd | ||
|
a34a86bbfa | ||
|
a016b3546f | ||
|
e09435da37 | ||
|
416f3d604c | ||
|
d9f371f994 | ||
|
c1c22d0477 | ||
|
339f13d31a | ||
|
4190f07d9c | ||
|
a9bdf36c53 | ||
|
1913cd4309 | ||
|
624439f684 | ||
|
3e14dc22cb | ||
|
f4178aeacd | ||
|
aaf3c9cfe9 | ||
|
f6ee012b15 | ||
|
363ef42923 | ||
|
cc522ef872 | ||
|
0e192ee7f0 | ||
|
ddd0592b30 |
@@ -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
|
||||
|
25
CHANGELOG.md
25
CHANGELOG.md
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
@@ -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`
|
||||
|
@@ -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}")
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -358,7 +358,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:
|
||||
|
@@ -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));
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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; }
|
||||
|
@@ -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; }
|
||||
|
@@ -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 =>
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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)))
|
||||
|
@@ -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())
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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; }
|
||||
|
@@ -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]
|
||||
|
@@ -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)
|
||||
|
@@ -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"
|
||||
|
@@ -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")
|
||||
|
@@ -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 = "",
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
// }
|
@@ -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();
|
||||
|
||||
|
@@ -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");
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 -->
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
29
src/NadekoBot/Services/Currency/CurrencyServiceExtensions.cs
Normal file
29
src/NadekoBot/Services/Currency/CurrencyServiceExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
namespace NadekoBot.Services.Currency;
|
||||
|
||||
public interface IWallet : IDisposable, IAsyncDisposable
|
||||
public interface IWallet
|
||||
{
|
||||
public ulong UserId { get; }
|
||||
|
||||
|
@@ -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");
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
// }
|
||||
// }
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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:
|
||||
- ""
|
||||
- ""
|
||||
|
@@ -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",
|
||||
|
Reference in New Issue
Block a user