mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
793a49fc64 | ||
|
8b6be656b3 | ||
|
89a88304dc | ||
|
a7fe9ae08f | ||
|
0469705037 | ||
|
dc568fe0e2 | ||
|
eb01bb6c08 | ||
|
71a3539d0e | ||
|
c896a0cdb8 | ||
|
8effe817ad | ||
|
eedf6998b6 | ||
|
e6b7c31a72 | ||
|
2f77fd57b0 | ||
|
fda3d92134 | ||
|
fe6f28143b | ||
|
df3909fc55 | ||
|
9ffa701742 | ||
|
a4f7df8aee | ||
|
15e6cff14a | ||
|
e63ac07a52 | ||
|
a294e3bf8f | ||
|
a5b2fac69c | ||
|
a8e06a5ae4 | ||
|
761bdd8610 | ||
|
b5904889b0 | ||
|
5b4517cf5c | ||
|
72158c0a2c | ||
|
1ca6f6dc5c | ||
|
005fd7b8c6 | ||
|
3fc53b0609 | ||
|
62ec2241e4 | ||
|
75f8254a8e | ||
|
f23ffe0c67 | ||
|
d1a818542c | ||
|
15f67e3a51 | ||
|
412f346ac8 | ||
|
8107a80c4c |
@@ -104,7 +104,7 @@ publish-medusa-package:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
|
||||
script:
|
||||
- LAST_TAG=$(git describe --tags --abbrev=0)
|
||||
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHA"; fi
|
||||
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHORT_SHA"; fi
|
||||
- cd src/Nadeko.Medusa/
|
||||
- dotnet pack -c Release /p:Version=$MEDUSA_VERSION -o bin/Release/packed
|
||||
- dotnet nuget push bin/Release/packed/ --source https://www.myget.org/F/nadeko/api/v2/package --api-key "$MYGET_API_KEY"
|
||||
|
59
CHANGELOG.md
59
CHANGELOG.md
@@ -2,6 +2,65 @@
|
||||
|
||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## [4.3.9] - 12.10.2022
|
||||
|
||||
### Added
|
||||
|
||||
- `.betstats` shows sum of all bets, payouts and the payout rate in %. Updates once an hour
|
||||
|
||||
### Changed
|
||||
|
||||
- `.betstats` looks way better (except on Mac)
|
||||
- `.feedadd` errors clarified and separated in individual error messages for each issue.
|
||||
- `.clubban` and `.clubunban` errors clarified and separated in individual error messages for each issue.
|
||||
- `.clubapply` better error messages
|
||||
|
||||
### Fixed
|
||||
|
||||
- `.timely` 'Remind' button fixed in DMs
|
||||
- `.cmdcd` database bugs fixed
|
||||
- Fixed bugged mysql and postgresql migrations
|
||||
- Fixed issues with lodaing medusae due to strict versioning
|
||||
|
||||
### Removed
|
||||
|
||||
- `.slotstats` Superseded by `.betstats`
|
||||
|
||||
## [4.3.8] - 02.10.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.autopublish` command which will automatically publish messages posted in the channel.
|
||||
- Added `--after <messageid>` option to prune which will make prune only delete messages after the specified message id.
|
||||
|
||||
### Changed
|
||||
|
||||
- `.prune` options `--after` and `--safe` are now proper command options, and will show in .h help
|
||||
- `.cmdcd` code mostly rewritten, slight QoL improvements.
|
||||
- Clarified `.remind` permission requirements in help text
|
||||
- `.cmdcds` looks a little better, and is paginated
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed trivia bugs
|
||||
- Fixed `.yun` not working with channels with underscore in the name
|
||||
|
||||
## [4.3.7] - 14.09.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added `.exprdelserv` (.exds) to completement .exas. Deletes an expression on the current server and is susceptible to .dpo, unlike .exd
|
||||
- Added `.shopreq` which lets you set role requirement for specific shop items
|
||||
- Added `.shopbuy` alias to `.buy`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.convertlist` showing currencies twice (this may not apply to existing users and it may require you to manually remove all currencies from units.json)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `Viewer` field from stream online notification as it is (almost?) always 0.
|
||||
|
||||
## [4.3.6] - 08.09.2022
|
||||
|
||||
### Added
|
||||
|
@@ -17,7 +17,7 @@ It is recommended that you use **Ubuntu 20.04**, as there have been nearly no pr
|
||||
|
||||
##### Compatible operating systems:
|
||||
|
||||
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10 22.04
|
||||
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10, 22.04
|
||||
- Mint: 19, 20
|
||||
- Debian: 9, 10
|
||||
- CentOS: 7
|
||||
@@ -316,6 +316,26 @@ This method is similar to the one above, but requires one extra step, with the a
|
||||
|
||||
If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean](http://m.do.co/c/46b4d3d44795/) (by using this link, you will get **$10 credit** and also support Nadeko)
|
||||
|
||||
To set up the VPS, please select the options below
|
||||
```
|
||||
These are the min requirements you must follow:
|
||||
|
||||
OS: Any between Ubuntu, Fedora, and Debian
|
||||
|
||||
Plan: Basic
|
||||
|
||||
CPU options: regular with SSD
|
||||
1 GB / 1 CPU
|
||||
25 GB SSD Disk
|
||||
1000 GB transfer
|
||||
|
||||
Note: You can select the cheapest option with 512 MB /1 CPU but this has been a hit or miss.
|
||||
|
||||
Datacenter region: Choose one depending on where you are located.
|
||||
|
||||
Authentication: Password or SSH
|
||||
(Select SSH if you know what you are doing, otherwise choose password)
|
||||
```
|
||||
**Setting up NadekoBot**
|
||||
Assuming you have followed the link above to setup an account and a Droplet with a 64-bit operational system on Digital Ocean and got the `IP address and root password (in your e-mail)` to login, it's time to get started.
|
||||
|
||||
|
@@ -17,4 +17,7 @@
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Version)' == '' ">
|
||||
<Version>5.0.0</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@@ -116,10 +116,6 @@ public sealed class Bot
|
||||
// cache
|
||||
.AddCache(_creds);
|
||||
|
||||
// admin
|
||||
#if GLOBAL_NADEKO
|
||||
svcs.AddSingleton<ILogCommandService, DummyLogCommandService>();
|
||||
#endif
|
||||
|
||||
svcs.AddHttpClient();
|
||||
svcs.AddHttpClient("memelist")
|
||||
|
33
src/NadekoBot/Common/TypeReaders/GuildUserTypeReader.cs
Normal file
33
src/NadekoBot/Common/TypeReaders/GuildUserTypeReader.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace NadekoBot.Common.TypeReaders;
|
||||
|
||||
public sealed class GuildUserTypeReader : NadekoTypeReader<IGuildUser>
|
||||
{
|
||||
public override async ValueTask<TypeReaderResult<IGuildUser>> ReadAsync(ICommandContext ctx, string input)
|
||||
{
|
||||
if (ctx.Guild is null)
|
||||
return TypeReaderResult.FromError<IGuildUser>(CommandError.Unsuccessful, "Must be in a guild.");
|
||||
|
||||
input = input.Trim();
|
||||
IGuildUser? user = null;
|
||||
if (MentionUtils.TryParseUser(input, out var id))
|
||||
user = await ctx.Guild.GetUserAsync(id, CacheMode.AllowDownload);
|
||||
|
||||
if (ulong.TryParse(input, out id))
|
||||
user = await ctx.Guild.GetUserAsync(id, CacheMode.AllowDownload);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
var users = await ctx.Guild.GetUsersAsync(CacheMode.CacheOnly);
|
||||
user = users.FirstOrDefault(x => x.Username == input)
|
||||
?? users.FirstOrDefault(x =>
|
||||
string.Equals(x.ToString(), input, StringComparison.InvariantCultureIgnoreCase))
|
||||
?? users.FirstOrDefault(x =>
|
||||
string.Equals(x.Username, input, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
if (user is null)
|
||||
return TypeReaderResult.FromError<IGuildUser>(CommandError.ObjectNotFound, "User not found.");
|
||||
|
||||
return TypeReaderResult.FromSuccess(user);
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
#nullable disable
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
@@ -6,12 +7,14 @@ namespace NadekoBot.Db;
|
||||
|
||||
public static class CurrencyTransactionExtensions
|
||||
{
|
||||
public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
|
||||
=> set.AsQueryable()
|
||||
.AsNoTracking()
|
||||
public static Task<List<CurrencyTransaction>> GetPageFor(
|
||||
this DbSet<CurrencyTransaction> set,
|
||||
ulong userId,
|
||||
int page)
|
||||
=> set.ToLinqToDBTable()
|
||||
.Where(x => x.UserId == userId)
|
||||
.OrderByDescending(x => x.DateAdded)
|
||||
.Skip(15 * page)
|
||||
.Take(15)
|
||||
.ToList();
|
||||
.ToListAsyncLinqToDB();
|
||||
}
|
9
src/NadekoBot/Db/Models/AutoPublishChannel.cs
Normal file
9
src/NadekoBot/Db/Models/AutoPublishChannel.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
public class AutoPublishChannel : DbEntity
|
||||
{
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
}
|
9
src/NadekoBot/Db/Models/GamblingStats.cs
Normal file
9
src/NadekoBot/Db/Models/GamblingStats.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Services.Database.Models;
|
||||
|
||||
public class GamblingStats : DbEntity
|
||||
{
|
||||
public string Feature { get; set; }
|
||||
public decimal Bet { get; set; }
|
||||
public decimal PaidOut { get; set; }
|
||||
}
|
@@ -24,6 +24,7 @@ public class ShopEntry : DbEntity, IIndexed
|
||||
|
||||
//list
|
||||
public HashSet<ShopEntryItem> Items { get; set; } = new();
|
||||
public ulong? RoleRequirement { get; set; }
|
||||
}
|
||||
|
||||
public class ShopEntryItem : DbEntity
|
||||
|
@@ -464,6 +464,22 @@ public abstract class NadekoContext : DbContext
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region AutoPublish
|
||||
|
||||
modelBuilder.Entity<AutoPublishChannel>(apc => apc
|
||||
.HasIndex(x => x.GuildId)
|
||||
.IsUnique());
|
||||
|
||||
#endregion
|
||||
|
||||
#region GamblingStats
|
||||
|
||||
modelBuilder.Entity<GamblingStats>(gs => gs
|
||||
.HasIndex(x => x.Feature)
|
||||
.IsUnique());
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
3550
src/NadekoBot/Migrations/Mysql/20220913192520_shop-role-req.Designer.cs
generated
Normal file
3550
src/NadekoBot/Migrations/Mysql/20220913192520_shop-role-req.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class shoprolereq : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<ulong>(
|
||||
name: "rolerequirement",
|
||||
table: "shopentry",
|
||||
type: "bigint unsigned",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "rolerequirement",
|
||||
table: "shopentry");
|
||||
}
|
||||
}
|
||||
}
|
3579
src/NadekoBot/Migrations/Mysql/20220916194514_autopub.Designer.cs
generated
Normal file
3579
src/NadekoBot/Migrations/Mysql/20220916194514_autopub.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
src/NadekoBot/Migrations/Mysql/20220916194514_autopub.cs
Normal file
42
src/NadekoBot/Migrations/Mysql/20220916194514_autopub.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class autopub : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "autopublishchannel",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_autopublishchannel", x => x.id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_autopublishchannel_guildid",
|
||||
table: "autopublishchannel",
|
||||
column: "guildid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "autopublishchannel");
|
||||
}
|
||||
}
|
||||
}
|
3612
src/NadekoBot/Migrations/Mysql/20221003175743_gambling-stats.Designer.cs
generated
Normal file
3612
src/NadekoBot/Migrations/Mysql/20221003175743_gambling-stats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class gamblingstats : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "gamblingstats",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
feature = table.Column<string>(type: "varchar(255)", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
bet = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
paidout = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_gamblingstats", x => x.id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_gamblingstats_feature",
|
||||
table: "gamblingstats",
|
||||
column: "feature",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "gamblingstats");
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,6 +19,35 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasAnnotation("ProductVersion", "6.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("channelid");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_autopublishchannel");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_autopublishchannel_guildid");
|
||||
|
||||
b.ToTable("autopublishchannel", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1072,6 +1101,39 @@ namespace NadekoBot.Migrations.Mysql
|
||||
b.ToTable("filterwordschannelid", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<decimal>("Bet")
|
||||
.HasColumnType("decimal(65,30)")
|
||||
.HasColumnName("bet");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<string>("Feature")
|
||||
.HasColumnType("varchar(255)")
|
||||
.HasColumnName("feature");
|
||||
|
||||
b.Property<decimal>("PaidOut")
|
||||
.HasColumnType("decimal(65,30)")
|
||||
.HasColumnName("paidout");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_gamblingstats");
|
||||
|
||||
b.HasIndex("Feature")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_gamblingstats_feature");
|
||||
|
||||
b.ToTable("gamblingstats", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2236,6 +2298,10 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("rolename");
|
||||
|
||||
b.Property<ulong?>("RoleRequirement")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("rolerequirement");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("type");
|
3694
src/NadekoBot/Migrations/PostgreSql/20220913192529_shop-role-req.Designer.cs
generated
Normal file
3694
src/NadekoBot/Migrations/PostgreSql/20220913192529_shop-role-req.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class shoprolereq : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "rolerequirement",
|
||||
table: "shopentry",
|
||||
type: "numeric(20,0)",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "rolerequirement",
|
||||
table: "shopentry");
|
||||
}
|
||||
}
|
||||
}
|
3725
src/NadekoBot/Migrations/PostgreSql/20220916194523_autopub.Designer.cs
generated
Normal file
3725
src/NadekoBot/Migrations/PostgreSql/20220916194523_autopub.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class autopub : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "rolerequirement",
|
||||
table: "shopentry",
|
||||
type: "numeric(20,0)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "autopublishchannel",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_autopublishchannel", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_autopublishchannel_guildid",
|
||||
table: "autopublishchannel",
|
||||
column: "guildid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "autopublishchannel");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "rolerequirement",
|
||||
table: "shopentry");
|
||||
}
|
||||
}
|
||||
}
|
3760
src/NadekoBot/Migrations/PostgreSql/20221003175752_gambling-stats.Designer.cs
generated
Normal file
3760
src/NadekoBot/Migrations/PostgreSql/20221003175752_gambling-stats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class gamblingstats : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "gamblingstats",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
feature = table.Column<string>(type: "text", nullable: true),
|
||||
bet = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
paidout = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_gamblingstats", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_gamblingstats_feature",
|
||||
table: "gamblingstats",
|
||||
column: "feature",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "gamblingstats");
|
||||
}
|
||||
}
|
||||
}
|
@@ -22,6 +22,37 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<decimal>("ChannelId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("channelid");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp without time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<decimal>("GuildId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_autopublishchannel");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_autopublishchannel_guildid");
|
||||
|
||||
b.ToTable("autopublishchannel", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1126,6 +1157,41 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.ToTable("filterwordschannelid", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<decimal>("Bet")
|
||||
.HasColumnType("numeric")
|
||||
.HasColumnName("bet");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp without time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<string>("Feature")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("feature");
|
||||
|
||||
b.Property<decimal>("PaidOut")
|
||||
.HasColumnType("numeric")
|
||||
.HasColumnName("paidout");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_gamblingstats");
|
||||
|
||||
b.HasIndex("Feature")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_gamblingstats_feature");
|
||||
|
||||
b.ToTable("gamblingstats", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2342,6 +2408,10 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("rolename");
|
||||
|
||||
b.Property<decimal?>("RoleRequirement")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("rolerequirement");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
2849
src/NadekoBot/Migrations/Sqlite/20220913190532_shop-role-req.Designer.cs
generated
Normal file
2849
src/NadekoBot/Migrations/Sqlite/20220913190532_shop-role-req.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class shoprolereq : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<ulong>(
|
||||
name: "RoleRequirement",
|
||||
table: "ShopEntry",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RoleRequirement",
|
||||
table: "ShopEntry");
|
||||
}
|
||||
}
|
||||
}
|
2872
src/NadekoBot/Migrations/Sqlite/20220916191702_autopub.Designer.cs
generated
Normal file
2872
src/NadekoBot/Migrations/Sqlite/20220916191702_autopub.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
src/NadekoBot/Migrations/Sqlite/20220916191702_autopub.cs
Normal file
40
src/NadekoBot/Migrations/Sqlite/20220916191702_autopub.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class autopub : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AutoPublishChannel",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AutoPublishChannel", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AutoPublishChannel_GuildId",
|
||||
table: "AutoPublishChannel",
|
||||
column: "GuildId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AutoPublishChannel");
|
||||
}
|
||||
}
|
||||
}
|
2898
src/NadekoBot/Migrations/Sqlite/20221003111019_gambling-stats.Designer.cs
generated
Normal file
2898
src/NadekoBot/Migrations/Sqlite/20221003111019_gambling-stats.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class gamblingstats : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GamblingStats",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Feature = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Bet = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GamblingStats", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GamblingStats_Feature",
|
||||
table: "GamblingStats",
|
||||
column: "Feature",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GamblingStats");
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,6 +17,29 @@ namespace NadekoBot.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("ChannelId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AutoPublishChannel");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -841,6 +864,32 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("FilterWordsChannelId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<decimal>("Bet")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Feature")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<decimal>("PaidOut")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Feature")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("GamblingStats");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1745,6 +1794,9 @@ namespace NadekoBot.Migrations
|
||||
b.Property<string>("RoleName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong?>("RoleRequirement")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@@ -33,9 +33,13 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
}
|
||||
|
||||
private readonly SomethingOnlyChannelService _somethingOnly;
|
||||
private readonly AutoPublishService _autoPubService;
|
||||
|
||||
public Administration(SomethingOnlyChannelService somethingOnly)
|
||||
=> _somethingOnly = somethingOnly;
|
||||
public Administration(SomethingOnlyChannelService somethingOnly, AutoPublishService autoPubService)
|
||||
{
|
||||
_somethingOnly = somethingOnly;
|
||||
_autoPubService = autoPubService;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -376,4 +380,26 @@ public partial class Administration : NadekoModule<AdministrationService>
|
||||
await t.DeleteAsync();
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
public async Task AutoPublish()
|
||||
{
|
||||
if (ctx.Channel.GetChannelType() != ChannelType.News)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.req_announcement_channel);
|
||||
return;
|
||||
}
|
||||
|
||||
var newState = await _autoPubService.ToggleAutoPublish(ctx.Guild.Id, ctx.Channel.Id);
|
||||
|
||||
if (newState)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.autopublish_enable);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.autopublish_disable);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Nadeko.Common;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
|
87
src/NadekoBot/Modules/Administration/AutoPublishService.cs
Normal file
87
src/NadekoBot/Modules/Administration/AutoPublishService.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class AutoPublishService : IExecNoCommand, IReadyExecutor, INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredsProvider _creds;
|
||||
private ConcurrentDictionary<ulong, ulong> _enabled;
|
||||
|
||||
public AutoPublishService(DbService db, DiscordSocketClient client, IBotCredsProvider creds)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||
{
|
||||
if (guild is null)
|
||||
return;
|
||||
|
||||
if (msg.Channel.GetChannelType() != ChannelType.News)
|
||||
return;
|
||||
|
||||
if (!_enabled.TryGetValue(guild.Id, out var cid) || cid != msg.Channel.Id)
|
||||
return;
|
||||
|
||||
await msg.CrosspostAsync(new RequestOptions()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysFail
|
||||
});
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
var creds = _creds.GetCreds();
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var items = await ctx.GetTable<AutoPublishChannel>()
|
||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, creds.TotalShards, _client.ShardId))
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
_enabled = items
|
||||
.ToDictionary(x => x.GuildId, x => x.ChannelId)
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleAutoPublish(ulong guildId, ulong channelId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var deleted = await ctx.GetTable<AutoPublishChannel>()
|
||||
.DeleteAsync(x => x.GuildId == guildId && x.ChannelId == channelId);
|
||||
|
||||
if (deleted != 0)
|
||||
{
|
||||
_enabled.TryRemove(guildId, out _);
|
||||
return false;
|
||||
}
|
||||
|
||||
await ctx.GetTable<AutoPublishChannel>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
},
|
||||
old => new()
|
||||
{
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
|
||||
_enabled[guildId] = channelId;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
#nullable disable
|
||||
using CommandLine;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
@@ -10,17 +11,34 @@ public partial class Administration
|
||||
{
|
||||
private static readonly TimeSpan _twoWeeks = TimeSpan.FromDays(14);
|
||||
|
||||
//delets her own messages, no perm required
|
||||
public sealed class PruneOptions : INadekoCommandOptions
|
||||
{
|
||||
[Option(shortName: 's', longName: "safe", Default = false, HelpText = "Whether pinned messages should be deleted.", Required = false)]
|
||||
public bool Safe { get; set; }
|
||||
|
||||
[Option(shortName: 'a', longName: "after", Default = null, HelpText = "Prune only messages after the specified message ID.", Required = false)]
|
||||
public ulong? After { get; set; }
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
//deletes her own messages, no perm required
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Prune(string parameter = null)
|
||||
[NadekoOptions(typeof(PruneOptions))]
|
||||
public async Task Prune(params string[] args)
|
||||
{
|
||||
var (opts, _) = OptionsParser.ParseFrom<PruneOptions>(new PruneOptions(), args);
|
||||
|
||||
var user = await ctx.Guild.GetCurrentUserAsync();
|
||||
|
||||
if (parameter is "-s" or "--safe")
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id && !x.IsPinned);
|
||||
if (opts.Safe)
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id && !x.IsPinned, opts.After);
|
||||
else
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id);
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id, opts.After);
|
||||
|
||||
ctx.Message.DeleteAfter(3);
|
||||
}
|
||||
|
||||
@@ -29,8 +47,9 @@ public partial class Administration
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
[NadekoOptions(typeof(PruneOptions))]
|
||||
[Priority(1)]
|
||||
public async Task Prune(int count, string parameter = null)
|
||||
public async Task Prune(int count, params string[] args)
|
||||
{
|
||||
count++;
|
||||
if (count < 1)
|
||||
@@ -38,10 +57,12 @@ public partial class Administration
|
||||
if (count > 1000)
|
||||
count = 1000;
|
||||
|
||||
if (parameter is "-s" or "--safe")
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, count, x => !x.IsPinned);
|
||||
var (opts, _) = OptionsParser.ParseFrom<PruneOptions>(new PruneOptions(), args);
|
||||
|
||||
if (opts.Safe)
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, count, x => !x.IsPinned, opts.After);
|
||||
else
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, count, _ => true);
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel, count, _ => true, opts.After);
|
||||
}
|
||||
|
||||
//prune @user [x]
|
||||
@@ -49,17 +70,19 @@ public partial class Administration
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
[NadekoOptions(typeof(PruneOptions))]
|
||||
[Priority(0)]
|
||||
public Task Prune(IGuildUser user, int count = 100, string parameter = null)
|
||||
=> Prune(user.Id, count, parameter);
|
||||
public Task Prune(IGuildUser user, int count = 100, string args = null)
|
||||
=> Prune(user.Id, count, args);
|
||||
|
||||
//prune userid [x]
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(ChannelPerm.ManageMessages)]
|
||||
[BotPerm(ChannelPerm.ManageMessages)]
|
||||
[NadekoOptions(typeof(PruneOptions))]
|
||||
[Priority(0)]
|
||||
public async Task Prune(ulong userId, int count = 100, string parameter = null)
|
||||
public async Task Prune(ulong userId, int count = 100, params string[] args)
|
||||
{
|
||||
if (userId == ctx.User.Id)
|
||||
count++;
|
||||
@@ -70,17 +93,21 @@ public partial class Administration
|
||||
if (count > 1000)
|
||||
count = 1000;
|
||||
|
||||
if (parameter is "-s" or "--safe")
|
||||
var (opts, _) = OptionsParser.ParseFrom<PruneOptions>(new PruneOptions(), args);
|
||||
|
||||
if (opts.Safe)
|
||||
{
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
count,
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks && !m.IsPinned);
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks && !m.IsPinned,
|
||||
opts.After);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
count,
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks);
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks,
|
||||
opts.After);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,4 @@
|
||||
#nullable disable
|
||||
using Nadeko.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class PruneService : INService
|
||||
@@ -13,7 +11,7 @@ public class PruneService : INService
|
||||
public PruneService(ILogCommandService logService)
|
||||
=> _logService = logService;
|
||||
|
||||
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate)
|
||||
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate, ulong? after = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel, nameof(channel));
|
||||
|
||||
@@ -28,7 +26,14 @@ public class PruneService : INService
|
||||
var now = DateTime.UtcNow;
|
||||
IMessage[] msgs;
|
||||
IMessage lastMessage = null;
|
||||
msgs = (await channel.GetMessagesAsync(50).FlattenAsync()).Where(predicate).Take(amount).ToArray();
|
||||
var dled = await channel.GetMessagesAsync(50).FlattenAsync();
|
||||
|
||||
msgs = dled
|
||||
.Where(predicate)
|
||||
.Where(x => after is ulong a ? x.Id > a : true)
|
||||
.Take(amount)
|
||||
.ToArray();
|
||||
|
||||
while (amount > 0 && msgs.Any())
|
||||
{
|
||||
lastMessage = msgs[^1];
|
||||
@@ -62,8 +67,11 @@ public class PruneService : INService
|
||||
amount -= 50;
|
||||
if (amount > 0)
|
||||
{
|
||||
msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync())
|
||||
dled = await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync();
|
||||
|
||||
msgs = dled
|
||||
.Where(predicate)
|
||||
.Where(x => after is ulong a ? x.Id > a : true)
|
||||
.Take(amount)
|
||||
.ToArray();
|
||||
}
|
||||
|
@@ -67,15 +67,23 @@ public partial class Administration
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task ReactionRolesList()
|
||||
public async Task ReactionRolesList(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
var reros = await _rero.GetReactionRolesAsync(ctx.Guild.Id);
|
||||
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var embed = _eb.Create(ctx)
|
||||
.WithOkColor();
|
||||
|
||||
var content = string.Empty;
|
||||
foreach (var g in reros.GroupBy(x => x.MessageId).OrderBy(x => x.Key))
|
||||
foreach (var g in reros.OrderBy(x => x.Group)
|
||||
.Skip(curPage * 10)
|
||||
.GroupBy(x => x.MessageId)
|
||||
.OrderBy(x => x.Key))
|
||||
{
|
||||
var messageId = g.Key;
|
||||
content +=
|
||||
@@ -89,7 +97,8 @@ public partial class Administration
|
||||
|
||||
foreach (var rero in ggs)
|
||||
{
|
||||
content += $"\t{rero.Emote} -> {(ctx.Guild.GetRole(rero.RoleId)?.Mention ?? "<missing role>")}";
|
||||
content +=
|
||||
$"\t{rero.Emote} -> {(ctx.Guild.GetRole(rero.RoleId)?.Mention ?? "<missing role>")}";
|
||||
if (rero.LevelReq > 0)
|
||||
content += $" (lvl {rero.LevelReq}+)";
|
||||
content += '\n';
|
||||
@@ -102,7 +111,8 @@ public partial class Administration
|
||||
? "There are no reaction roles on this server"
|
||||
: content);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
return embed;
|
||||
}, reros.Count, 10);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@@ -3,6 +3,9 @@
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
public sealed class DummyLogCommandService : ILogCommandService
|
||||
#if GLOBAL_NADEKO
|
||||
, INService
|
||||
#endif
|
||||
{
|
||||
public void AddDeleteIgnore(ulong xId)
|
||||
{
|
||||
|
@@ -160,15 +160,8 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
|
||||
found.Response.TrimTo(1000).Replace("](", "]\\(")));
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ExprDelete(kwum id)
|
||||
public async Task ExprDeleteInternalAsync(kwum id)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
|
||||
return;
|
||||
}
|
||||
|
||||
var ex = await _service.DeleteAsync(ctx.Guild?.Id, id);
|
||||
|
||||
if (ex is not null)
|
||||
@@ -186,6 +179,24 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ExprDeleteServer(kwum id)
|
||||
=> await ExprDeleteInternalAsync(id);
|
||||
|
||||
[Cmd]
|
||||
public async Task ExprDelete(kwum id)
|
||||
{
|
||||
if (!AdminInGuildOrOwnerInDm())
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.expr_insuff_perms);
|
||||
return;
|
||||
}
|
||||
|
||||
await ExprDeleteInternalAsync(id);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task ExprReact(kwum id, params string[] emojiStrs)
|
||||
{
|
||||
|
@@ -145,6 +145,9 @@ public partial class Gambling
|
||||
|
||||
public async Task BetDrawInternal(long amount, InputValueGuess? val, InputColorGuess? col)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
var res = await _service.BetDrawAsync(ctx.User.Id,
|
||||
amount,
|
||||
(byte?)val,
|
||||
|
@@ -29,6 +29,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
private readonly IBankService _bank;
|
||||
private readonly IPatronageService _ps;
|
||||
private readonly RemindService _remind;
|
||||
private readonly GamblingTxTracker _gamblingTxTracker;
|
||||
|
||||
private IUserMessage rdMsg;
|
||||
|
||||
@@ -41,7 +42,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
GamblingConfigService configService,
|
||||
IBankService bank,
|
||||
IPatronageService ps,
|
||||
RemindService remind)
|
||||
RemindService remind,
|
||||
GamblingTxTracker gamblingTxTracker)
|
||||
: base(configService)
|
||||
{
|
||||
_gs = gs;
|
||||
@@ -51,6 +53,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
_bank = bank;
|
||||
_ps = ps;
|
||||
_remind = remind;
|
||||
_gamblingTxTracker = gamblingTxTracker;
|
||||
|
||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||
_enUsCulture.NumberDecimalDigits = 0;
|
||||
@@ -65,6 +68,43 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return N(bal);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task BetStats()
|
||||
{
|
||||
var stats = await _gamblingTxTracker.GetAllAsync();
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor();
|
||||
|
||||
var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n";
|
||||
str += "――――――――――――――――――――\n";
|
||||
foreach (var stat in stats)
|
||||
{
|
||||
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
|
||||
str += $"`{stat.Feature.PadBoth(9)}`" +
|
||||
$"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`" +
|
||||
$"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`" +
|
||||
$"|`{perc.PadLeft(6, ' ')}`\n";
|
||||
}
|
||||
|
||||
var bet = stats.Sum(x => x.Bet);
|
||||
var paidOut = stats.Sum(x => x.PaidOut);
|
||||
|
||||
if (bet == 0)
|
||||
bet = 1;
|
||||
|
||||
var tPerc = (paidOut / bet).ToString("P2", Culture);
|
||||
str += "――――――――――――――――――――\n";
|
||||
str += $"` {("TOTAL").PadBoth(7)}` " +
|
||||
$"|**{N(bet).PadLeft(8, ' ')}**" +
|
||||
$"|**{N(paidOut).PadLeft(8, ' ')}**" +
|
||||
$"|`{tPerc.PadLeft(6, ' ')}`";
|
||||
|
||||
eb.WithDescription(str);
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task Economy()
|
||||
{
|
||||
@@ -105,7 +145,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
await _remind.AddReminderAsync(ctx.User.Id,
|
||||
ctx.User.Id,
|
||||
ctx.Guild.Id,
|
||||
ctx.Guild?.Id,
|
||||
true,
|
||||
when,
|
||||
GetText(strs.timely_time));
|
||||
@@ -249,7 +289,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
List<CurrencyTransaction> trs;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
trs = uow.CurrencyTransactions.GetPageFor(userId, page);
|
||||
trs = await uow.CurrencyTransactions.GetPageFor(userId, page);
|
||||
}
|
||||
|
||||
var embed = _eb.Create()
|
||||
@@ -957,5 +997,4 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
sb.ToString(),
|
||||
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
|
||||
}
|
||||
|
||||
}
|
@@ -78,7 +78,7 @@ public class CurrencyConfig
|
||||
[Comment(@"What is the name of the currency")]
|
||||
public string Name { get; set; } = "Nadeko Flower";
|
||||
|
||||
[Comment(@"For how long will the transactions be kept in the database (curtrs)
|
||||
[Comment(@"For how long (in days) will the transactions be kept in the database (curtrs)
|
||||
Set 0 to disable cleanup (keep transactions forever)")]
|
||||
public int TransactionsLifetime { get; set; } = 0;
|
||||
}
|
||||
|
@@ -38,4 +38,6 @@ public interface IShopService
|
||||
/// <param name="toIndex">Destination index of the entry</param>
|
||||
/// <returns>Whether swap was successful</returns>
|
||||
Task<bool> MoveEntryAsync(ulong guildId, int fromIndex, int toIndex);
|
||||
|
||||
Task<bool> SetItemRoleRequirementAsync(ulong guildId, int index, ulong? roleId);
|
||||
}
|
@@ -98,6 +98,23 @@ public partial class Gambling
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.RoleRequirement is ulong reqRoleId)
|
||||
{
|
||||
var role = ctx.Guild.GetRole(reqRoleId);
|
||||
if (role is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_req_role_not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
if (!guser.RoleIds.Contains(reqRoleId))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_req_role_unfulfilled(Format.Bold(role.ToString())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
{
|
||||
var guser = (IGuildUser)ctx.User;
|
||||
@@ -412,6 +429,27 @@ public partial class Gambling
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async Task ShopReq(int itemIndex, [Leftover] IRole role = null)
|
||||
{
|
||||
if (--itemIndex < 0)
|
||||
return;
|
||||
|
||||
var succ = await _service.SetItemRoleRequirementAsync(ctx.Guild.Id, itemIndex, role?.Id);
|
||||
if (!succ)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.shop_item_not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
if (role is null)
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_item_role_no_req(itemIndex));
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.shop_item_role_req(itemIndex + 1, role));
|
||||
}
|
||||
|
||||
public IEmbedBuilder EntryToEmbed(ShopEntry entry)
|
||||
{
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
@@ -443,11 +481,17 @@ public partial class Gambling
|
||||
|
||||
public string EntryToString(ShopEntry entry)
|
||||
{
|
||||
var prepend = string.Empty;
|
||||
if (entry.RoleRequirement is not null)
|
||||
prepend = Format.Italics(GetText(strs.shop_item_requires_role($"<@&{entry.RoleRequirement}>")))
|
||||
+ Environment.NewLine;
|
||||
|
||||
if (entry.Type == ShopEntryType.Role)
|
||||
return GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")));
|
||||
return prepend
|
||||
+ GetText(strs.shop_role(Format.Bold(ctx.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")));
|
||||
if (entry.Type == ShopEntryType.List)
|
||||
return GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
|
||||
return "";
|
||||
return prepend + GetText(strs.unique_items_left(entry.Items.Count)) + "\n" + entry.Name;
|
||||
return prepend;
|
||||
}
|
||||
}
|
||||
}
|
@@ -94,4 +94,20 @@ public class ShopService : IShopService, INService
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> SetItemRoleRequirementAsync(ulong guildId, int index, ulong? roleId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var entries = GetEntriesInternal(uow, guildId);
|
||||
|
||||
if (index >= entries.Count)
|
||||
return false;
|
||||
|
||||
var entry = entries[index];
|
||||
|
||||
entry.RoleRequirement = roleId;
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -47,27 +47,6 @@ public partial class Gambling
|
||||
public Task Test()
|
||||
=> Task.CompletedTask;
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async Task SlotStats()
|
||||
{
|
||||
//i remembered to not be a moron
|
||||
var paid = totalPaidOut;
|
||||
var bet = totalBet;
|
||||
|
||||
if (bet <= 0)
|
||||
bet = 1;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("Slot Stats")
|
||||
.AddField("Total Bet", N(bet), true)
|
||||
.AddField("Paid Out", N(paid), true)
|
||||
.WithFooter($"Payout Rate: {paid * 1.0M / bet * 100:f4}%");
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task Slot(ShmartNumber amount)
|
||||
{
|
||||
|
@@ -112,25 +112,25 @@ public partial class Games
|
||||
|
||||
private void RegisterEvents(TriviaGame trivia)
|
||||
{
|
||||
trivia.OnQuestion += OnTriviaOnOnQuestion;
|
||||
trivia.OnHint += OnTriviaOnOnHint;
|
||||
trivia.OnGuess += OnTriviaOnOnGuess;
|
||||
trivia.OnEnded += OnTriviaOnOnEnded;
|
||||
trivia.OnStats += OnTriviaOnOnStats;
|
||||
trivia.OnTimeout += OnTriviaOnOnTimeout;
|
||||
trivia.OnQuestion += OnTriviaQuestion;
|
||||
trivia.OnHint += OnTriviaHint;
|
||||
trivia.OnGuess += OnTriviaGuess;
|
||||
trivia.OnEnded += OnTriviaEnded;
|
||||
trivia.OnStats += OnTriviaStats;
|
||||
trivia.OnTimeout += OnTriviaTimeout;
|
||||
}
|
||||
|
||||
private void UnregisterEvents(TriviaGame trivia)
|
||||
{
|
||||
trivia.OnQuestion -= OnTriviaOnOnQuestion;
|
||||
trivia.OnHint -= OnTriviaOnOnHint;
|
||||
trivia.OnGuess -= OnTriviaOnOnGuess;
|
||||
trivia.OnEnded -= OnTriviaOnOnEnded;
|
||||
trivia.OnStats -= OnTriviaOnOnStats;
|
||||
trivia.OnTimeout -= OnTriviaOnOnTimeout;
|
||||
trivia.OnQuestion -= OnTriviaQuestion;
|
||||
trivia.OnHint -= OnTriviaHint;
|
||||
trivia.OnGuess -= OnTriviaGuess;
|
||||
trivia.OnEnded -= OnTriviaEnded;
|
||||
trivia.OnStats -= OnTriviaStats;
|
||||
trivia.OnTimeout -= OnTriviaTimeout;
|
||||
}
|
||||
|
||||
private async Task OnTriviaOnOnHint(TriviaGame game, TriviaQuestion question)
|
||||
private async Task OnTriviaHint(TriviaGame game, TriviaQuestion question)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -150,11 +150,11 @@ public partial class Games
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error editing triva message");
|
||||
Log.Warning(ex, "Error editing trivia message");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnTriviaOnOnQuestion(TriviaGame game, TriviaQuestion question)
|
||||
private async Task OnTriviaQuestion(TriviaGame game, TriviaQuestion question)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -173,19 +173,16 @@ public partial class Games
|
||||
|
||||
questionMessage = await ctx.Channel.EmbedAsync(questionEmbed);
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound or HttpStatusCode.Forbidden or HttpStatusCode.BadRequest)
|
||||
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound or HttpStatusCode.Forbidden
|
||||
or HttpStatusCode.BadRequest)
|
||||
{
|
||||
Log.Warning("Unable to send trivia questions. Stopping immediately");
|
||||
game.Stop();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error sending trivia embed");
|
||||
await Task.Delay(2000);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnTriviaOnOnTimeout(TriviaGame _, TriviaQuestion question)
|
||||
private async Task OnTriviaTimeout(TriviaGame _, TriviaQuestion question)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -205,7 +202,7 @@ public partial class Games
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnTriviaOnOnStats(TriviaGame game)
|
||||
private async Task OnTriviaStats(TriviaGame game)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -217,7 +214,7 @@ public partial class Games
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnTriviaOnOnEnded(TriviaGame game)
|
||||
private async Task OnTriviaEnded(TriviaGame game)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -237,10 +234,9 @@ public partial class Games
|
||||
}
|
||||
|
||||
UnregisterEvents(game);
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
private async Task OnTriviaOnOnGuess(TriviaGame _, TriviaUser user, TriviaQuestion question, bool isWin)
|
||||
private async Task OnTriviaGuess(TriviaGame _, TriviaUser user, TriviaQuestion question, bool isWin)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Channels;
|
||||
using Exception = System.Exception;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common.Trivia;
|
||||
|
||||
@@ -64,7 +65,6 @@ public sealed class TriviaGame
|
||||
if (errorCount >= 5)
|
||||
{
|
||||
Log.Warning("Trivia errored 5 times and will quit");
|
||||
await OnEnded(this);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public sealed class TriviaGame
|
||||
|
||||
var maybeQuestion = await _questionPool.GetQuestionAsync();
|
||||
|
||||
if (!(maybeQuestion is TriviaQuestion question))
|
||||
if (maybeQuestion is not { } question)
|
||||
{
|
||||
// if question is null (ran out of question, or other bugg ) - stop
|
||||
break;
|
||||
@@ -110,7 +110,8 @@ public sealed class TriviaGame
|
||||
var guessed = false;
|
||||
while (true)
|
||||
{
|
||||
var readTask = _inputs.Reader.ReadAsync().AsTask();
|
||||
using var readCancel = new CancellationTokenSource();
|
||||
var readTask = _inputs.Reader.ReadAsync(readCancel.Token).AsTask();
|
||||
|
||||
// wait for either someone to attempt to guess
|
||||
// or for timeout
|
||||
@@ -119,6 +120,8 @@ public sealed class TriviaGame
|
||||
// if the task which completed is the timeout task
|
||||
if (task == halfGuessTimerTask)
|
||||
{
|
||||
readCancel.Cancel();
|
||||
|
||||
// if hint is already sent, means time expired
|
||||
// break (end the round)
|
||||
if (hintSent)
|
||||
@@ -147,6 +150,7 @@ public sealed class TriviaGame
|
||||
|
||||
// reset inactivity counter
|
||||
inactivity = 0;
|
||||
errorCount = 0;
|
||||
|
||||
var isWin = false;
|
||||
// if user won the game, tell the game to stop
|
||||
@@ -174,9 +178,9 @@ public sealed class TriviaGame
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Log.Error(ex, "Fatal error in trivia game: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@@ -1,68 +1,142 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Db;
|
||||
|
||||
namespace NadekoBot.Modules.Permissions.Services;
|
||||
|
||||
public class CmdCdService : IExecPreCommand, INService
|
||||
public sealed class CmdCdService : IExecPreCommand, IReadyExecutor, INService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> CommandCooldowns { get; }
|
||||
public ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>> ActiveCooldowns { get; } = new();
|
||||
private readonly DbService _db;
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, int>> _settings = new();
|
||||
|
||||
public int Priority { get; } = 0;
|
||||
private readonly ConcurrentDictionary<(ulong, string), ConcurrentDictionary<ulong, DateTime>> _activeCooldowns =
|
||||
new();
|
||||
|
||||
public CmdCdService(Bot bot)
|
||||
=> CommandCooldowns = new(bot.AllGuildConfigs.ToDictionary(k => k.GuildId,
|
||||
v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns)));
|
||||
public int Priority => 0;
|
||||
|
||||
public CmdCdService(Bot bot, DbService db)
|
||||
{
|
||||
_db = db;
|
||||
_settings = bot
|
||||
.AllGuildConfigs
|
||||
.ToDictionary(x => x.GuildId, x => x.CommandCooldowns
|
||||
.DistinctBy(x => x.CommandName.ToLowerInvariant())
|
||||
.ToDictionary(c => c.CommandName, c => c.Seconds)
|
||||
.ToConcurrent())
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public Task<bool> ExecPreCommandAsync(ICommandContext context, string moduleName, CommandInfo command)
|
||||
=> TryBlock(context.Guild, context.User, command.Name.ToLowerInvariant());
|
||||
|
||||
public Task<bool> TryBlock(IGuild guild, IUser user, string commandName)
|
||||
{
|
||||
if (guild is null)
|
||||
if (!_settings.TryGetValue(guild.Id, out var cooldownSettings))
|
||||
return Task.FromResult(false);
|
||||
|
||||
var cmdcds = CommandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<CommandCooldown>());
|
||||
CommandCooldown cdRule;
|
||||
if ((cdRule = cmdcds.FirstOrDefault(cc => cc.CommandName == commandName)) is not null)
|
||||
if (!cooldownSettings.TryGetValue(commandName, out var cdSeconds))
|
||||
return Task.FromResult(false);
|
||||
|
||||
var cooldowns = _activeCooldowns.GetOrAdd(
|
||||
(guild.Id, commandName),
|
||||
static _ => new());
|
||||
|
||||
// if user is not already on cooldown, add
|
||||
if (cooldowns.TryAdd(user.Id, DateTime.UtcNow))
|
||||
{
|
||||
var activeCdsForGuild = ActiveCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<ActiveCooldown>());
|
||||
if (activeCdsForGuild.FirstOrDefault(ac => ac.UserId == user.Id && ac.Command == commandName) is not null)
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
// if there is an entry, maybe it expired. Try to check if it expired and don't fail if it did
|
||||
// - just update
|
||||
if (cooldowns.TryGetValue(user.Id, out var oldValue))
|
||||
{
|
||||
var diff = DateTime.UtcNow - oldValue;
|
||||
if (diff.Seconds > cdSeconds)
|
||||
{
|
||||
if (cooldowns.TryUpdate(user.Id, DateTime.UtcNow, oldValue))
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
activeCdsForGuild.Add(new()
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
UserId = user.Id,
|
||||
Command = commandName
|
||||
});
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(cdRule.Seconds * 1000);
|
||||
activeCdsForGuild.RemoveWhere(ac => ac.Command == commandName && ac.UserId == user.Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
// once per hour delete expired entries
|
||||
foreach (var ((guildId, commandName), dict) in _activeCooldowns)
|
||||
{
|
||||
// if this pair no longer has associated config, that means it has been removed.
|
||||
// remove all cooldowns
|
||||
if (!_settings.TryGetValue(guildId, out var inner)
|
||||
|| !inner.TryGetValue(commandName, out var cdSeconds))
|
||||
{
|
||||
_activeCooldowns.Remove((guildId, commandName), out _);
|
||||
continue;
|
||||
}
|
||||
|
||||
Cleanup(dict, cdSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Cleanup(ConcurrentDictionary<ulong, DateTime> dict, int cdSeconds)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var (key, _) in dict.Where(x => (now - x.Value).Seconds > cdSeconds).ToArray())
|
||||
{
|
||||
dict.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCooldowns(ulong guildId, string cmdName)
|
||||
{
|
||||
if (_settings.TryGetValue(guildId, out var dict))
|
||||
dict.TryRemove(cmdName, out _);
|
||||
|
||||
_activeCooldowns.TryRemove((guildId, cmdName), out _);
|
||||
|
||||
using var ctx = _db.GetDbContext();
|
||||
var gc = ctx.GuildConfigsForId(guildId, x => x.Include(x => x.CommandCooldowns));
|
||||
gc.CommandCooldowns.RemoveWhere(x => x.CommandName == cmdName);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
public void AddCooldown(ulong guildId, string name, int secs)
|
||||
{
|
||||
if (secs <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(secs));
|
||||
|
||||
var sett = _settings.GetOrAdd(guildId, static _ => new());
|
||||
sett[name] = secs;
|
||||
|
||||
// force cleanup
|
||||
if (_activeCooldowns.TryGetValue((guildId, name), out var dict))
|
||||
Cleanup(dict, secs);
|
||||
|
||||
using var ctx = _db.GetDbContext();
|
||||
var gc = ctx.GuildConfigsForId(guildId, x => x.Include(x => x.CommandCooldowns));
|
||||
gc.CommandCooldowns.RemoveWhere(x => x.CommandName == name);
|
||||
gc.CommandCooldowns.Add(new()
|
||||
{
|
||||
Seconds = secs,
|
||||
CommandName = name
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public Task<bool> ExecPreCommandAsync(ICommandContext ctx, string moduleName, CommandInfo command)
|
||||
public IReadOnlyCollection<(string CommandName, int Seconds)> GetCommandCooldowns(ulong guildId)
|
||||
{
|
||||
var guild = ctx.Guild;
|
||||
var user = ctx.User;
|
||||
var commandName = command.Name.ToLowerInvariant();
|
||||
if (!_settings.TryGetValue(guildId, out var dict))
|
||||
return Array.Empty<(string, int)>();
|
||||
|
||||
return TryBlock(guild, user, commandName);
|
||||
return dict.Select(x => (x.Key, x.Value)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class ActiveCooldown
|
||||
{
|
||||
public string Command { get; set; }
|
||||
public ulong UserId { get; set; }
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
#nullable disable
|
||||
using Humanizer.Localisation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Db;
|
||||
@@ -12,12 +13,6 @@ public partial class Permissions
|
||||
[Group]
|
||||
public partial class CmdCdsCommands : NadekoModule
|
||||
{
|
||||
private ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> CommandCooldowns
|
||||
=> _service.CommandCooldowns;
|
||||
|
||||
private ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>> ActiveCooldowns
|
||||
=> _service.ActiveCooldowns;
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly CmdCdService _service;
|
||||
|
||||
@@ -40,12 +35,10 @@ public partial class Permissions
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var config = uow.GuildConfigsForId(channel.Guild.Id, set => set.Include(gc => gc.CommandCooldowns));
|
||||
var localSet = CommandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
|
||||
|
||||
var toDelete = config.CommandCooldowns.FirstOrDefault(cc => cc.CommandName == name);
|
||||
if (toDelete is not null)
|
||||
uow.Set<CommandCooldown>().Remove(toDelete);
|
||||
localSet.RemoveWhere(cc => cc.CommandName == name);
|
||||
if (secs != 0)
|
||||
{
|
||||
var cc = new CommandCooldown
|
||||
@@ -54,7 +47,7 @@ public partial class Permissions
|
||||
Seconds = secs
|
||||
};
|
||||
config.CommandCooldowns.Add(cc);
|
||||
localSet.Add(cc);
|
||||
_service.AddCooldown(channel.Guild.Id, name, secs);
|
||||
}
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
@@ -62,8 +55,7 @@ public partial class Permissions
|
||||
|
||||
if (secs == 0)
|
||||
{
|
||||
var activeCds = ActiveCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<ActiveCooldown>());
|
||||
activeCds.RemoveWhere(ac => ac.Command == name);
|
||||
_service.ClearCooldowns(ctx.Guild.Id, cmdName);
|
||||
await ReplyConfirmLocalizedAsync(strs.cmdcd_cleared(Format.Bold(name)));
|
||||
}
|
||||
else
|
||||
@@ -84,19 +76,29 @@ public partial class Permissions
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AllCmdCooldowns()
|
||||
public async Task AllCmdCooldowns(int page = 1)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
var localSet = CommandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
|
||||
var localSet = _service.GetCommandCooldowns(ctx.Guild.Id);
|
||||
|
||||
if (!localSet.Any())
|
||||
await ReplyConfirmLocalizedAsync(strs.cmdcd_none);
|
||||
else
|
||||
{
|
||||
await channel.SendTableAsync("",
|
||||
localSet.Select(c => c.CommandName + ": " + c.Seconds + GetText(strs.sec)),
|
||||
s => $"{s,-30}",
|
||||
2);
|
||||
await ctx.SendPaginatedConfirmAsync(page, curPage =>
|
||||
{
|
||||
var items = localSet.Skip(curPage * 15)
|
||||
.Take(15)
|
||||
.Select(x => $"{Format.Code(x.CommandName)}: {x.Seconds.Seconds().Humanize(maxUnit: TimeUnit.Second, culture: Culture)}");
|
||||
|
||||
return _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithDescription(items.Join("\n"));
|
||||
|
||||
}, localSet.Count, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ public partial class Searches
|
||||
public partial class FeedCommands : NadekoModule<FeedsService>
|
||||
{
|
||||
private static readonly Regex _ytChannelRegex =
|
||||
new(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-]{1,})");
|
||||
new(@"youtube\.com\/(?:c\/|channel\/|user\/)?(?<channelid>[a-zA-Z0-9\-_]{1,})");
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -32,10 +32,13 @@ public partial class Searches
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task Feed(string url, [Leftover] ITextChannel channel = null)
|
||||
{
|
||||
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
if (success)
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.feed_invalid_url);
|
||||
return;
|
||||
}
|
||||
|
||||
channel ??= (ITextChannel)ctx.Channel;
|
||||
try
|
||||
{
|
||||
@@ -44,21 +47,28 @@ public partial class Searches
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Unable to get feeds from that url");
|
||||
success = false;
|
||||
}
|
||||
await ReplyErrorLocalizedAsync(strs.feed_cant_parse);
|
||||
return;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
success = _service.AddFeed(ctx.Guild.Id, channel.Id, url);
|
||||
if (success)
|
||||
var result = _service.AddFeed(ctx.Guild.Id, channel.Id, url);
|
||||
if (result == FeedAddResult.Success)
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_added);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == FeedAddResult.Duplicate)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.feed_duplicate);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.feed_not_valid);
|
||||
if (result == FeedAddResult.LimitReached)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.feed_limit_reached);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@@ -207,7 +207,7 @@ public class FeedsService : INService
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
|
||||
public FeedAddResult AddFeed(ulong guildId, ulong channelId, string rssFeed)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rssFeed, nameof(rssFeed));
|
||||
|
||||
@@ -221,9 +221,9 @@ public class FeedsService : INService
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs));
|
||||
|
||||
if (gc.FeedSubs.Any(x => x.Url.ToLower() == fs.Url.ToLower()))
|
||||
return false;
|
||||
return FeedAddResult.Duplicate;
|
||||
if (gc.FeedSubs.Count >= 10)
|
||||
return false;
|
||||
return FeedAddResult.LimitReached;
|
||||
|
||||
gc.FeedSubs.Add(fs);
|
||||
uow.SaveChanges();
|
||||
@@ -242,7 +242,7 @@ public class FeedsService : INService
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
return FeedAddResult.Success;
|
||||
}
|
||||
|
||||
public bool RemoveFeed(ulong guildId, int index)
|
||||
@@ -271,3 +271,11 @@ public class FeedsService : INService
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FeedAddResult
|
||||
{
|
||||
Success,
|
||||
LimitReached,
|
||||
Invalid,
|
||||
Duplicate,
|
||||
}
|
@@ -1,6 +1,4 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Nadeko.Common;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
@@ -12,95 +10,6 @@ using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public sealed class StreamOnlineMessageDeleterService : INService, IReadyExecutor
|
||||
{
|
||||
private readonly StreamNotificationService _notifService;
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IPubSub _pubSub;
|
||||
|
||||
public StreamOnlineMessageDeleterService(
|
||||
StreamNotificationService notifService,
|
||||
DbService db,
|
||||
IPubSub pubSub,
|
||||
DiscordSocketClient client)
|
||||
{
|
||||
_notifService = notifService;
|
||||
_db = db;
|
||||
_client = client;
|
||||
_pubSub = pubSub;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
_notifService.OnlineMessagesSent += OnOnlineMessagesSent;
|
||||
|
||||
if(_client.ShardId == 0)
|
||||
await _pubSub.Sub(_notifService.StreamsOfflineKey, OnStreamsOffline);
|
||||
}
|
||||
|
||||
private async Task OnOnlineMessagesSent(FollowedStream.FType type, string name, IReadOnlyCollection<(ulong, ulong)> pairs)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
foreach (var (channelId, messageId) in pairs)
|
||||
{
|
||||
await ctx.GetTable<StreamOnlineMessage>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Name = name,
|
||||
Type = type,
|
||||
MessageId = messageId,
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask OnStreamsOffline(List<StreamData> streamDatas)
|
||||
{
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
var pairs = await GetMessagesToDelete(streamDatas);
|
||||
|
||||
foreach (var (channelId, messageId) in pairs)
|
||||
{
|
||||
try
|
||||
{
|
||||
var textChannel = await _client.GetChannelAsync(channelId) as ITextChannel;
|
||||
if (textChannel is null)
|
||||
continue;
|
||||
|
||||
await textChannel.DeleteMessageAsync(messageId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<(ulong, ulong)>> GetMessagesToDelete(List<StreamData> streamDatas)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var toReturn = new List<(ulong, ulong)>();
|
||||
foreach (var sd in streamDatas)
|
||||
{
|
||||
var key = sd.CreateKey();
|
||||
var toDelete = await ctx.GetTable<StreamOnlineMessage>()
|
||||
.Where(x => (x.Type == key.Type && x.Name == key.Name)
|
||||
|| Sql.DateDiff(Sql.DateParts.Day, x.DateAdded, DateTime.UtcNow) > 1)
|
||||
.DeleteWithOutputAsync();
|
||||
|
||||
toReturn.AddRange(toDelete.Select(x => (x.ChannelId, x.MessageId)));
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
{
|
||||
private readonly DbService _db;
|
||||
@@ -370,7 +279,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
|
||||
var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message);
|
||||
|
||||
var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream), message);
|
||||
var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream, false), message);
|
||||
|
||||
// only cache the ids of channel/message pairs
|
||||
if(_deleteOnOfflineServers.Contains(fs.GuildId))
|
||||
@@ -563,18 +472,22 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
return data;
|
||||
}
|
||||
|
||||
public IEmbedBuilder GetEmbed(ulong guildId, StreamData status)
|
||||
public IEmbedBuilder GetEmbed(ulong guildId, StreamData status, bool showViewers = true)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(status.Name)
|
||||
.WithUrl(status.StreamUrl)
|
||||
.WithDescription(status.StreamUrl)
|
||||
.AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true)
|
||||
.AddField(GetText(guildId, strs.viewers),
|
||||
.AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true);
|
||||
|
||||
if (showViewers)
|
||||
{
|
||||
embed.AddField(GetText(guildId, strs.viewers),
|
||||
status.Viewers == 0 && !status.IsLive
|
||||
? "-"
|
||||
: status.Viewers,
|
||||
true);
|
||||
}
|
||||
|
||||
if (status.IsLive)
|
||||
embed = embed.WithOkColor();
|
||||
|
@@ -0,0 +1,99 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Searches.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Services;
|
||||
|
||||
public sealed class StreamOnlineMessageDeleterService : INService, IReadyExecutor
|
||||
{
|
||||
private readonly StreamNotificationService _notifService;
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IPubSub _pubSub;
|
||||
|
||||
public StreamOnlineMessageDeleterService(
|
||||
StreamNotificationService notifService,
|
||||
DbService db,
|
||||
IPubSub pubSub,
|
||||
DiscordSocketClient client)
|
||||
{
|
||||
_notifService = notifService;
|
||||
_db = db;
|
||||
_client = client;
|
||||
_pubSub = pubSub;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
_notifService.OnlineMessagesSent += OnOnlineMessagesSent;
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
await _pubSub.Sub(_notifService.StreamsOfflineKey, OnStreamsOffline);
|
||||
}
|
||||
|
||||
private async Task OnOnlineMessagesSent(
|
||||
FollowedStream.FType type,
|
||||
string name,
|
||||
IReadOnlyCollection<(ulong, ulong)> pairs)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
foreach (var (channelId, messageId) in pairs)
|
||||
{
|
||||
await ctx.GetTable<StreamOnlineMessage>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Name = name,
|
||||
Type = type,
|
||||
MessageId = messageId,
|
||||
ChannelId = channelId,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask OnStreamsOffline(List<StreamData> streamDatas)
|
||||
{
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
var pairs = await GetMessagesToDelete(streamDatas);
|
||||
|
||||
foreach (var (channelId, messageId) in pairs)
|
||||
{
|
||||
try
|
||||
{
|
||||
var textChannel = await _client.GetChannelAsync(channelId) as ITextChannel;
|
||||
if (textChannel is null)
|
||||
continue;
|
||||
|
||||
await textChannel.DeleteMessageAsync(messageId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<(ulong, ulong)>> GetMessagesToDelete(List<StreamData> streamDatas)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var toReturn = new List<(ulong, ulong)>();
|
||||
foreach (var sd in streamDatas)
|
||||
{
|
||||
var key = sd.CreateKey();
|
||||
var toDelete = await ctx.GetTable<StreamOnlineMessage>()
|
||||
.Where(x => (x.Type == key.Type && x.Name == key.Name)
|
||||
|| Sql.DateDiff(Sql.DateParts.Day, x.DateAdded, DateTime.UtcNow) > 1)
|
||||
.DeleteWithOutputAsync();
|
||||
|
||||
toReturn.AddRange(toDelete.Select(x => (x.ChannelId, x.MessageId)));
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
}
|
@@ -286,8 +286,7 @@ public sealed class PatronageService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExecPreCommandAsync(
|
||||
ICommandContext ctx,
|
||||
public async Task<bool> ExecPreCommandAsync(ICommandContext ctx,
|
||||
string moduleName,
|
||||
CommandInfo command)
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user