Fixed xprate presentation and reworked it internally to support voice, text and image xp
This commit is contained in:
parent
5e95abadc8
commit
11b3705939
25 changed files with 595 additions and 740 deletions
src/EllieBot
Db
Extensions
Models/xp
Migrations
PostgreSql
20250226215159_xp-rate-rework.sql20250226215222_init.Designer.cs20250226215222_init.csPostgreSqlContextModelSnapshot.cs
Sqlite
Modules/Xp
Services/GrpcApi
_common
strings
|
@ -121,7 +121,6 @@ public static class GuildConfigExtensions
|
|||
.InsertWithOutputAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ServerExcluded = false,
|
||||
});
|
||||
|
||||
return srs;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#nullable disable
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class UserXpStats : DbEntity
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
#nullable disable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
public class XpSettings : DbEntity
|
||||
{
|
||||
public ulong GuildId { get; set; }
|
||||
public bool ServerExcluded { get; set; }
|
||||
public HashSet<ExcludedItem> ExclusionList { get; set; } = new();
|
||||
|
||||
public HashSet<XpRoleReward> RoleRewards { get; set; } = new();
|
||||
public HashSet<XpCurrencyReward> CurrencyRewards { get; set; } = new();
|
||||
|
|
|
@ -15,8 +15,5 @@ public class XpSettingsEntityConfiguration : IEntityTypeConfiguration<XpSettings
|
|||
|
||||
builder.HasMany(x => x.RoleRewards)
|
||||
.WithOne();
|
||||
|
||||
builder.HasMany(x => x.ExclusionList)
|
||||
.WithOne();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
START TRANSACTION;
|
||||
DROP TABLE excludeditem;
|
||||
|
||||
ALTER TABLE guildxpconfig DROP CONSTRAINT pk_guildxpconfig;
|
||||
|
||||
ALTER TABLE channelxpconfig DROP CONSTRAINT ak_channelxpconfig_guildid_channelid;
|
||||
|
||||
ALTER TABLE xpsettings DROP COLUMN serverexcluded;
|
||||
|
||||
ALTER TABLE guildxpconfig ALTER COLUMN xpamount TYPE bigint;
|
||||
|
||||
ALTER TABLE guildxpconfig ALTER COLUMN cooldown TYPE real;
|
||||
|
||||
ALTER TABLE guildxpconfig ADD id integer GENERATED BY DEFAULT AS IDENTITY;
|
||||
|
||||
ALTER TABLE guildxpconfig ADD ratetype integer NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE channelxpconfig ALTER COLUMN xpamount TYPE bigint;
|
||||
|
||||
ALTER TABLE channelxpconfig ADD ratetype integer NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE guildxpconfig ADD CONSTRAINT ak_guildxpconfig_guildid_ratetype UNIQUE (guildid, ratetype);
|
||||
|
||||
ALTER TABLE guildxpconfig ADD CONSTRAINT pk_guildxpconfig PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE channelxpconfig ADD CONSTRAINT ak_channelxpconfig_guildid_channelid_ratetype UNIQUE (guildid, channelid, ratetype);
|
||||
|
||||
INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
|
||||
VALUES ('20250226215159_xp-rate-rework', '9.0.1');
|
||||
|
||||
COMMIT;
|
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||
namespace EllieBot.Migrations.PostgreSql
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlContext))]
|
||||
[Migration("20250225212209_init")]
|
||||
[Migration("20250226215222_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
@ -877,40 +877,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
b.ToTable("discorduser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp without time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<decimal>("ItemId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("itemid");
|
||||
|
||||
b.Property<int>("ItemType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("itemtype");
|
||||
|
||||
b.Property<int?>("XpSettingsId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("xpsettingsid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_excludeditem");
|
||||
|
||||
b.HasIndex("XpSettingsId")
|
||||
.HasDatabaseName("ix_excludeditem_xpsettingsid");
|
||||
|
||||
b.ToTable("excludeditem", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FeedSub", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -3359,10 +3325,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<bool>("ServerExcluded")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("serverexcluded");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_xpsettings");
|
||||
|
||||
|
@ -3503,41 +3465,58 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("ratetype");
|
||||
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("xpamount");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_channelxpconfig");
|
||||
|
||||
b.HasAlternateKey("GuildId", "ChannelId")
|
||||
.HasName("ak_channelxpconfig_guildid_channelid");
|
||||
b.HasAlternateKey("GuildId", "ChannelId", "RateType")
|
||||
.HasName("ak_channelxpconfig_guildid_channelid_ratetype");
|
||||
|
||||
b.ToTable("channelxpconfig", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
|
||||
{
|
||||
b.Property<decimal>("GuildId")
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<float>("Cooldown")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("cooldown");
|
||||
|
||||
b.Property<decimal>("GuildId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("Cooldown")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("cooldown");
|
||||
.HasColumnName("ratetype");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
.HasColumnType("integer")
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("xpamount");
|
||||
|
||||
b.Property<string>("XpTemplateUrl")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("xptemplateurl");
|
||||
|
||||
b.HasKey("GuildId")
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_guildxpconfig");
|
||||
|
||||
b.HasAlternateKey("GuildId", "RateType")
|
||||
.HasName("ak_guildxpconfig_guildid_ratetype");
|
||||
|
||||
b.ToTable("guildxpconfig", (string)null);
|
||||
});
|
||||
|
||||
|
@ -3744,14 +3723,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
b.Navigation("Club");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.XpSettings", null)
|
||||
.WithMany("ExclusionList")
|
||||
.HasForeignKey("XpSettingsId")
|
||||
.HasConstraintName("fk_excludeditem_xpsettings_xpsettingsid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FilterChannelId", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.GuildFilterConfig", null)
|
||||
|
@ -4038,8 +4009,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
{
|
||||
b.Navigation("CurrencyRewards");
|
||||
|
||||
b.Navigation("ExclusionList");
|
||||
|
||||
b.Navigation("RoleRewards");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
|
@ -193,15 +193,16 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ratetype = table.Column<int>(type: "integer", nullable: false),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
xpamount = table.Column<int>(type: "integer", nullable: false),
|
||||
xpamount = table.Column<long>(type: "bigint", nullable: false),
|
||||
cooldown = table.Column<float>(type: "real", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_channelxpconfig", x => x.id);
|
||||
table.UniqueConstraint("ak_channelxpconfig_guildid_channelid", x => new { x.guildid, x.channelid });
|
||||
table.UniqueConstraint("ak_channelxpconfig_guildid_channelid_ratetype", x => new { x.guildid, x.channelid, x.ratetype });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
|
@ -508,14 +509,18 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
name: "guildxpconfig",
|
||||
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),
|
||||
xpamount = table.Column<int>(type: "integer", nullable: false),
|
||||
cooldown = table.Column<int>(type: "integer", nullable: false),
|
||||
ratetype = table.Column<int>(type: "integer", nullable: false),
|
||||
xpamount = table.Column<long>(type: "bigint", nullable: false),
|
||||
cooldown = table.Column<float>(type: "real", nullable: false),
|
||||
xptemplateurl = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_guildxpconfig", x => x.guildid);
|
||||
table.PrimaryKey("pk_guildxpconfig", x => x.id);
|
||||
table.UniqueConstraint("ak_guildxpconfig_guildid_ratetype", x => new { x.guildid, x.ratetype });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
|
@ -1151,7 +1156,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
serverexcluded = table.Column<bool>(type: "boolean", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
|
@ -1506,27 +1510,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "excludeditem",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
xpsettingsid = table.Column<int>(type: "integer", nullable: true),
|
||||
itemid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
itemtype = table.Column<int>(type: "integer", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_excludeditem", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_excludeditem_xpsettings_xpsettingsid",
|
||||
column: x => x.xpsettingsid,
|
||||
principalTable: "xpsettings",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "xpcurrencyreward",
|
||||
columns: table => new
|
||||
|
@ -1860,11 +1843,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
table: "discorduser",
|
||||
column: "username");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_excludeditem_xpsettingsid",
|
||||
table: "excludeditem",
|
||||
column: "xpsettingsid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_feedsub_guildid_url",
|
||||
table: "feedsub",
|
||||
|
@ -2367,9 +2345,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
migrationBuilder.DropTable(
|
||||
name: "discordpermoverrides");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "excludeditem");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "expressions");
|
||||
|
|
@ -874,40 +874,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
b.ToTable("discorduser", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp without time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<decimal>("ItemId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("itemid");
|
||||
|
||||
b.Property<int>("ItemType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("itemtype");
|
||||
|
||||
b.Property<int?>("XpSettingsId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("xpsettingsid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_excludeditem");
|
||||
|
||||
b.HasIndex("XpSettingsId")
|
||||
.HasDatabaseName("ix_excludeditem_xpsettingsid");
|
||||
|
||||
b.ToTable("excludeditem", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FeedSub", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -3356,10 +3322,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<bool>("ServerExcluded")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("serverexcluded");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_xpsettings");
|
||||
|
||||
|
@ -3500,41 +3462,58 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("ratetype");
|
||||
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("xpamount");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_channelxpconfig");
|
||||
|
||||
b.HasAlternateKey("GuildId", "ChannelId")
|
||||
.HasName("ak_channelxpconfig_guildid_channelid");
|
||||
b.HasAlternateKey("GuildId", "ChannelId", "RateType")
|
||||
.HasName("ak_channelxpconfig_guildid_channelid_ratetype");
|
||||
|
||||
b.ToTable("channelxpconfig", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
|
||||
{
|
||||
b.Property<decimal>("GuildId")
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<float>("Cooldown")
|
||||
.HasColumnType("real")
|
||||
.HasColumnName("cooldown");
|
||||
|
||||
b.Property<decimal>("GuildId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("Cooldown")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("cooldown");
|
||||
.HasColumnName("ratetype");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
.HasColumnType("integer")
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("xpamount");
|
||||
|
||||
b.Property<string>("XpTemplateUrl")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("xptemplateurl");
|
||||
|
||||
b.HasKey("GuildId")
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_guildxpconfig");
|
||||
|
||||
b.HasAlternateKey("GuildId", "RateType")
|
||||
.HasName("ak_guildxpconfig_guildid_ratetype");
|
||||
|
||||
b.ToTable("guildxpconfig", (string)null);
|
||||
});
|
||||
|
||||
|
@ -3741,14 +3720,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
b.Navigation("Club");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.XpSettings", null)
|
||||
.WithMany("ExclusionList")
|
||||
.HasForeignKey("XpSettingsId")
|
||||
.HasConstraintName("fk_excludeditem_xpsettings_xpsettingsid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FilterChannelId", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.GuildFilterConfig", null)
|
||||
|
@ -4035,8 +4006,6 @@ namespace EllieBot.Migrations.PostgreSql
|
|||
{
|
||||
b.Navigation("CurrencyRewards");
|
||||
|
||||
b.Navigation("ExclusionList");
|
||||
|
||||
b.Navigation("RoleRewards");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
BEGIN TRANSACTION;
|
||||
DROP TABLE "ExcludedItem";
|
||||
|
||||
ALTER TABLE "GuildXpConfig" ADD "Id" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "GuildXpConfig" ADD "RateType" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE "ChannelXpConfig" ADD "RateType" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE TABLE "ef_temp_GuildXpConfig" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_GuildXpConfig" PRIMARY KEY AUTOINCREMENT,
|
||||
"Cooldown" REAL NOT NULL,
|
||||
"GuildId" INTEGER NOT NULL,
|
||||
"RateType" INTEGER NOT NULL,
|
||||
"XpAmount" INTEGER NOT NULL,
|
||||
"XpTemplateUrl" TEXT NULL,
|
||||
CONSTRAINT "AK_GuildXpConfig_GuildId_RateType" UNIQUE ("GuildId", "RateType")
|
||||
);
|
||||
|
||||
INSERT INTO "ef_temp_GuildXpConfig" ("Id", "Cooldown", "GuildId", "RateType", "XpAmount", "XpTemplateUrl")
|
||||
SELECT "Id", "Cooldown", "GuildId", "RateType", "XpAmount", "XpTemplateUrl"
|
||||
FROM "GuildXpConfig";
|
||||
|
||||
CREATE TABLE "ef_temp_ChannelXpConfig" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_ChannelXpConfig" PRIMARY KEY AUTOINCREMENT,
|
||||
"ChannelId" INTEGER NOT NULL,
|
||||
"Cooldown" REAL NOT NULL,
|
||||
"GuildId" INTEGER NOT NULL,
|
||||
"RateType" INTEGER NOT NULL,
|
||||
"XpAmount" INTEGER NOT NULL,
|
||||
CONSTRAINT "AK_ChannelXpConfig_GuildId_ChannelId_RateType" UNIQUE ("GuildId", "ChannelId", "RateType")
|
||||
);
|
||||
|
||||
INSERT INTO "ef_temp_ChannelXpConfig" ("Id", "ChannelId", "Cooldown", "GuildId", "RateType", "XpAmount")
|
||||
SELECT "Id", "ChannelId", "Cooldown", "GuildId", "RateType", "XpAmount"
|
||||
FROM "ChannelXpConfig";
|
||||
|
||||
CREATE TABLE "ef_temp_XpSettings" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_XpSettings" PRIMARY KEY AUTOINCREMENT,
|
||||
"DateAdded" TEXT NULL,
|
||||
"GuildId" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO "ef_temp_XpSettings" ("Id", "DateAdded", "GuildId")
|
||||
SELECT "Id", "DateAdded", "GuildId"
|
||||
FROM "XpSettings";
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys = 0;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
DROP TABLE "GuildXpConfig";
|
||||
|
||||
ALTER TABLE "ef_temp_GuildXpConfig" RENAME TO "GuildXpConfig";
|
||||
|
||||
DROP TABLE "ChannelXpConfig";
|
||||
|
||||
ALTER TABLE "ef_temp_ChannelXpConfig" RENAME TO "ChannelXpConfig";
|
||||
|
||||
DROP TABLE "XpSettings";
|
||||
|
||||
ALTER TABLE "ef_temp_XpSettings" RENAME TO "XpSettings";
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys = 1;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
CREATE UNIQUE INDEX "IX_XpSettings_GuildId" ON "XpSettings" ("GuildId");
|
||||
|
||||
COMMIT;
|
||||
|
||||
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||
VALUES ('20250226215156_xp-rate-rework', '9.0.1');
|
|
@ -11,7 +11,7 @@ using EllieBot.Db;
|
|||
namespace EllieBot.Migrations.Sqlite
|
||||
{
|
||||
[DbContext(typeof(SqliteContext))]
|
||||
[Migration("20250225212206_init")]
|
||||
[Migration("20250226215220_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
@ -657,31 +657,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.ToTable("DiscordUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("XpSettingsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("XpSettingsId");
|
||||
|
||||
b.ToTable("ExcludedItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FeedSub", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -2499,9 +2474,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ServerExcluded")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
|
@ -2606,32 +2578,43 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasAlternateKey("GuildId", "ChannelId");
|
||||
b.HasAlternateKey("GuildId", "ChannelId", "RateType");
|
||||
|
||||
b.ToTable("ChannelXpConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
|
||||
{
|
||||
b.Property<ulong>("GuildId")
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Cooldown")
|
||||
b.Property<float>("Cooldown")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("XpTemplateUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GuildId");
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasAlternateKey("GuildId", "RateType");
|
||||
|
||||
b.ToTable("GuildXpConfig");
|
||||
});
|
||||
|
@ -2803,13 +2786,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.Navigation("Club");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.XpSettings", null)
|
||||
.WithMany("ExclusionList")
|
||||
.HasForeignKey("XpSettingsId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FilterChannelId", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.GuildFilterConfig", null)
|
||||
|
@ -3074,8 +3050,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
{
|
||||
b.Navigation("CurrencyRewards");
|
||||
|
||||
b.Navigation("ExclusionList");
|
||||
|
||||
b.Navigation("RoleRewards");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
|
@ -192,15 +192,16 @@ namespace EllieBot.Migrations.Sqlite
|
|||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
RateType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
XpAmount = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
XpAmount = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
Cooldown = table.Column<float>(type: "REAL", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChannelXpConfig", x => x.Id);
|
||||
table.UniqueConstraint("AK_ChannelXpConfig_GuildId_ChannelId", x => new { x.GuildId, x.ChannelId });
|
||||
table.UniqueConstraint("AK_ChannelXpConfig_GuildId_ChannelId_RateType", x => new { x.GuildId, x.ChannelId, x.RateType });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
|
@ -507,15 +508,18 @@ namespace EllieBot.Migrations.Sqlite
|
|||
name: "GuildXpConfig",
|
||||
columns: table => new
|
||||
{
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
XpAmount = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Cooldown = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
RateType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
XpAmount = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
Cooldown = table.Column<float>(type: "REAL", nullable: false),
|
||||
XpTemplateUrl = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GuildXpConfig", x => x.GuildId);
|
||||
table.PrimaryKey("PK_GuildXpConfig", x => x.Id);
|
||||
table.UniqueConstraint("AK_GuildXpConfig_GuildId_RateType", x => new { x.GuildId, x.RateType });
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
|
@ -1154,7 +1158,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ServerExcluded = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
|
@ -1509,27 +1512,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExcludedItem",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
XpSettingsId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
ItemId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ItemType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExcludedItem", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExcludedItem_XpSettings_XpSettingsId",
|
||||
column: x => x.XpSettingsId,
|
||||
principalTable: "XpSettings",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "XpCurrencyReward",
|
||||
columns: table => new
|
||||
|
@ -1863,11 +1845,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
table: "DiscordUser",
|
||||
column: "Username");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExcludedItem_XpSettingsId",
|
||||
table: "ExcludedItem",
|
||||
column: "XpSettingsId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_FeedSub_GuildId_Url",
|
||||
table: "FeedSub",
|
||||
|
@ -2370,9 +2347,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
migrationBuilder.DropTable(
|
||||
name: "DiscordPermOverrides");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExcludedItem");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Expressions");
|
||||
|
|
@ -654,31 +654,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.ToTable("DiscordUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("ItemId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ItemType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("XpSettingsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("XpSettingsId");
|
||||
|
||||
b.ToTable("ExcludedItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FeedSub", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -2496,9 +2471,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ServerExcluded")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
|
@ -2603,32 +2575,43 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasAlternateKey("GuildId", "ChannelId");
|
||||
b.HasAlternateKey("GuildId", "ChannelId", "RateType");
|
||||
|
||||
b.ToTable("ChannelXpConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Modules.Xp.GuildXpConfig", b =>
|
||||
{
|
||||
b.Property<ulong>("GuildId")
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Cooldown")
|
||||
b.Property<float>("Cooldown")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("XpAmount")
|
||||
b.Property<int>("RateType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("XpAmount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("XpTemplateUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GuildId");
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasAlternateKey("GuildId", "RateType");
|
||||
|
||||
b.ToTable("GuildXpConfig");
|
||||
});
|
||||
|
@ -2800,13 +2783,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
b.Navigation("Club");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.ExcludedItem", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.XpSettings", null)
|
||||
.WithMany("ExclusionList")
|
||||
.HasForeignKey("XpSettingsId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("EllieBot.Db.Models.FilterChannelId", b =>
|
||||
{
|
||||
b.HasOne("EllieBot.Db.Models.GuildFilterConfig", null)
|
||||
|
@ -3071,8 +3047,6 @@ namespace EllieBot.Migrations.Sqlite
|
|||
{
|
||||
b.Navigation("CurrencyRewards");
|
||||
|
||||
b.Navigation("ExclusionList");
|
||||
|
||||
b.Navigation("RoleRewards");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
|
|
|
@ -11,4 +11,5 @@ public sealed class UserXpBatch
|
|||
public ulong GuildId { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string AvatarId { get; set; } = string.Empty;
|
||||
public long XpToGain { get; set; } = 0;
|
||||
}
|
172
src/EllieBot/Modules/Xp/XpRate/GuildConfigXpService.cs
Normal file
172
src/EllieBot/Modules/Xp/XpRate/GuildConfigXpService.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Modules.Xp.Services;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
using GuildXpRates = (IReadOnlyList<GuildXpConfig> GuildRates, IReadOnlyList<ChannelXpConfig> ChannelRates);
|
||||
|
||||
public class GuildConfigXpService(DbService db, ShardData shardData, XpConfigService xcs) : IReadyExecutor, IEService
|
||||
{
|
||||
private ConcurrentDictionary<(XpRateType RateType, ulong GuildId), XpRate> _guildRates = new();
|
||||
private ConcurrentDictionary<ulong, ConcurrentDictionary<(XpRateType, ulong), XpRate>> _channelRates = new();
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
await using var uow = db.GetDbContext();
|
||||
_guildRates = await uow.GetTable<GuildXpConfig>()
|
||||
.AsNoTracking()
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, shardData.TotalShards, shardData.ShardId))
|
||||
.ToListAsyncLinqToDB()
|
||||
.Fmap(list =>
|
||||
list
|
||||
.ToDictionary(
|
||||
x => (x.RateType, x.GuildId),
|
||||
x => new XpRate(x.RateType, x.XpAmount, x.Cooldown))
|
||||
.ToConcurrent());
|
||||
|
||||
_channelRates = await uow.GetTable<ChannelXpConfig>()
|
||||
.AsNoTracking()
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, shardData.TotalShards, shardData.ShardId))
|
||||
.ToListAsyncLinqToDB()
|
||||
.Fmap(x =>
|
||||
x.GroupBy(x => x.GuildId)
|
||||
.ToDictionary(
|
||||
x => x.Key,
|
||||
x => x.ToDictionary(
|
||||
y => (y.RateType, y.ChannelId),
|
||||
y => new XpRate(y.RateType, y.XpAmount, y.Cooldown))
|
||||
.ToConcurrent())
|
||||
.ToConcurrent());
|
||||
}
|
||||
|
||||
public async Task<GuildXpRates> GetGuildXpRatesAsync(ulong guildId)
|
||||
{
|
||||
await using var uow = db.GetDbContext();
|
||||
var guildConfig = await uow.GetTable<GuildXpConfig>()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
var channelRates = await uow.GetTable<ChannelXpConfig>()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
return (guildConfig, channelRates);
|
||||
}
|
||||
|
||||
public async Task SetGuildXpRateAsync(ulong guildId, XpRateType type, long amount, float cooldown)
|
||||
{
|
||||
AmountAndCooldownChecks(amount, cooldown);
|
||||
|
||||
if (type == XpRateType.Voice)
|
||||
cooldown = 1.0f;
|
||||
|
||||
await using var uow = db.GetDbContext();
|
||||
await uow.GetTable<GuildXpConfig>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
RateType = type,
|
||||
XpAmount = amount,
|
||||
Cooldown = cooldown,
|
||||
},
|
||||
(_) => new()
|
||||
{
|
||||
Cooldown = cooldown,
|
||||
XpAmount = amount,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
RateType = type,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SetChannelXpRateAsync(ulong guildId,
|
||||
XpRateType type,
|
||||
ulong channelId,
|
||||
long amount,
|
||||
float cooldown)
|
||||
{
|
||||
AmountAndCooldownChecks(amount, cooldown);
|
||||
|
||||
if (type == XpRateType.Voice)
|
||||
cooldown = 1.0f;
|
||||
|
||||
await using var uow = db.GetDbContext();
|
||||
await uow.GetTable<ChannelXpConfig>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
XpAmount = amount,
|
||||
Cooldown = cooldown,
|
||||
RateType = type
|
||||
},
|
||||
(_) => new()
|
||||
{
|
||||
Cooldown = cooldown,
|
||||
XpAmount = amount,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
RateType = type,
|
||||
});
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AmountAndCooldownChecks(long amount, float cooldown)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(amount, nameof(amount));
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(amount, 1000, nameof(amount));
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(cooldown, nameof(cooldown));
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(cooldown, 1440, nameof(cooldown));
|
||||
}
|
||||
|
||||
public async Task<bool> ResetGuildXpRateAsync(ulong guildId)
|
||||
{
|
||||
await using var uow = db.GetDbContext();
|
||||
var deleted = await uow.GetTable<GuildXpConfig>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.DeleteAsync();
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> ResetChannelXpRateAsync(ulong guildId, ulong channelId)
|
||||
{
|
||||
await using var uow = db.GetDbContext();
|
||||
var deleted = await uow.GetTable<ChannelXpConfig>()
|
||||
.Where(x => x.GuildId == guildId && x.ChannelId == channelId)
|
||||
.DeleteAsync();
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
public XpRate GetXpRate(XpRateType type, ulong guildId, ulong channelId)
|
||||
{
|
||||
if (_channelRates.TryGetValue(guildId, out var guildChannelRates))
|
||||
{
|
||||
if (guildChannelRates.TryGetValue((type, channelId), out var rate))
|
||||
return rate;
|
||||
}
|
||||
|
||||
if (_guildRates.TryGetValue((type, guildId), out var guildRate))
|
||||
return guildRate;
|
||||
|
||||
var conf = xcs.Data;
|
||||
|
||||
return type switch
|
||||
{
|
||||
XpRateType.Image => new XpRate(XpRateType.Image, conf.TextXpFromImage, conf.TextXpCooldown / 60.0f),
|
||||
XpRateType.Voice => new XpRate(XpRateType.Voice, conf.VoiceXpPerMinute, 1.0f),
|
||||
_ => new XpRate(XpRateType.Text, conf.TextXpPerMessage, conf.TextXpCooldown / 60.0f),
|
||||
};
|
||||
}
|
||||
}
|
7
src/EllieBot/Modules/Xp/XpRate/XpRate.cs
Normal file
7
src/EllieBot/Modules/Xp/XpRate/XpRate.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public readonly record struct XpRate(XpRateType Type, long Amount, float Cooldown)
|
||||
{
|
||||
public bool IsExcluded()
|
||||
=> Amount == 0 || Cooldown == 0;
|
||||
}
|
|
@ -1,10 +1,3 @@
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public partial class Xp
|
||||
|
@ -17,45 +10,49 @@ public partial class Xp
|
|||
public async Task XpRate()
|
||||
{
|
||||
var rates = await _service.GetGuildXpRatesAsync(ctx.Guild.Id);
|
||||
if (rates.GuildConfig is null && !rates.ChannelRates.Any())
|
||||
if (!rates.GuildRates.Any() && !rates.ChannelRates.Any())
|
||||
{
|
||||
await Response().Pending(strs.xp_rate_none).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor();
|
||||
if (rates.GuildConfig is not null)
|
||||
{
|
||||
eb.AddField(GetText(strs.xp_rate_server),
|
||||
strs.xp_rate_amount_cooldown(
|
||||
rates.GuildConfig.XpAmount,
|
||||
rates.GuildConfig.Cooldown));
|
||||
}
|
||||
await Response()
|
||||
.Paginated()
|
||||
.Items(rates.ChannelRates.GroupBy(x => x.ChannelId).ToList())
|
||||
.PageSize(5)
|
||||
.Page((items, _) =>
|
||||
{
|
||||
var eb = CreateEmbed()
|
||||
.WithOkColor();
|
||||
|
||||
if (rates.ChannelRates.Any())
|
||||
{
|
||||
var channelRates = rates.ChannelRates
|
||||
.Select(c => $"<#{c.ChannelId}>: {GetRateString(c.XpAmount, c.Cooldown)}")
|
||||
.Join('\n');
|
||||
if (rates.GuildRates is not { Count: <= 0 })
|
||||
{
|
||||
eb.AddField(GetText(strs.xp_rate_server),
|
||||
rates.GuildRates
|
||||
.Select(x => GetText(strs.xp_rate_str(x.RateType, x.XpAmount, x.Cooldown)))
|
||||
.Join('\n'));
|
||||
}
|
||||
|
||||
eb.AddField(GetText(strs.xp_rate_channels), channelRates);
|
||||
}
|
||||
if (items.Any())
|
||||
{
|
||||
var channelRates = items
|
||||
.Select(x => $"""
|
||||
<#{x.Key}>
|
||||
{x.Select(c => $"- {GetText(strs.xp_rate_str(c.RateType, c.XpAmount, c.Cooldown))}").Join('\n')}
|
||||
""")
|
||||
.Join('\n');
|
||||
|
||||
await Response().Embed(eb).SendAsync();
|
||||
}
|
||||
eb.AddField(GetText(strs.xp_rate_channels), channelRates);
|
||||
}
|
||||
|
||||
private string GetRateString(int argXpAmount, float cd)
|
||||
{
|
||||
if (argXpAmount == 0 || cd == 0)
|
||||
return GetText(strs.xp_rate_no_gain);
|
||||
|
||||
return GetText(strs.xp_rate_amount_cooldown(argXpAmount, Math.Round(cd, 1).ToString(Culture)));
|
||||
return eb;
|
||||
})
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task XpRate(int amount, float minutes)
|
||||
public async Task XpRate(XpRateType type, int amount, float minutes)
|
||||
{
|
||||
if (amount is < 0 or > 1000)
|
||||
{
|
||||
|
@ -69,13 +66,13 @@ public partial class Xp
|
|||
return;
|
||||
}
|
||||
|
||||
await _service.SetGuildXpRateAsync(ctx.Guild.Id, amount, (int)Math.Ceiling(minutes));
|
||||
await _service.SetGuildXpRateAsync(ctx.Guild.Id, type, amount, (int)Math.Ceiling(minutes));
|
||||
await Response().Confirm(strs.xp_rate_server_set(amount, minutes)).SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task XpRate(IMessageChannel channel, int amount, float minutes)
|
||||
public async Task XpRate(IMessageChannel channel, XpRateType type, int amount, float minutes)
|
||||
{
|
||||
if (amount is < 0 or > 1000)
|
||||
{
|
||||
|
@ -89,10 +86,10 @@ public partial class Xp
|
|||
return;
|
||||
}
|
||||
|
||||
await _service.SetChannelXpRateAsync(ctx.Guild.Id, channel.Id, amount, (int)Math.Ceiling(minutes));
|
||||
await _service.SetChannelXpRateAsync(ctx.Guild.Id, type, channel.Id, amount, (int)Math.Ceiling(minutes));
|
||||
await Response()
|
||||
.Confirm(strs.xp_rate_channel_set(Format.Bold(channel.ToString()), amount, minutes))
|
||||
.SendAsync();
|
||||
.Confirm(strs.xp_rate_channel_set(Format.Bold(channel.ToString()), amount, minutes))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -116,139 +113,4 @@ public partial class Xp
|
|||
await Response().Confirm(strs.xp_rate_channel_reset($"<#{channelId}>")).SendAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GuildConfigXpService : IReadyExecutor, IEService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
|
||||
public GuildConfigXpService(DbService db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<(GuildXpConfig? GuildConfig, List<ChannelXpConfig> ChannelRates)> GetGuildXpRatesAsync(
|
||||
ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var guildConfig =
|
||||
await AsyncExtensions.FirstOrDefaultAsync(uow.GetTable<GuildXpConfig>(), x => x.GuildId == guildId);
|
||||
|
||||
var channelRates = await AsyncExtensions.ToListAsync(uow.GetTable<ChannelXpConfig>()
|
||||
.Where(x => x.GuildId == guildId));
|
||||
|
||||
return (guildConfig, channelRates);
|
||||
}
|
||||
|
||||
public async Task SetGuildXpRateAsync(ulong guildId, int amount, int cooldown)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<GuildXpConfig>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
XpAmount = amount,
|
||||
Cooldown = cooldown
|
||||
},
|
||||
(_) => new()
|
||||
{
|
||||
Cooldown = cooldown,
|
||||
XpAmount = amount,
|
||||
GuildId = guildId
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SetChannelXpRateAsync(
|
||||
ulong guildId,
|
||||
ulong channelId,
|
||||
int amount,
|
||||
int cooldown)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<ChannelXpConfig>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId,
|
||||
XpAmount = amount,
|
||||
Cooldown = cooldown
|
||||
},
|
||||
(_) => new()
|
||||
{
|
||||
Cooldown = cooldown,
|
||||
XpAmount = amount,
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channelId
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> ResetGuildXpRateAsync(ulong guildId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var deleted = await uow.GetTable<GuildXpConfig>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.DeleteAsync();
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> ResetChannelXpRateAsync(ulong guildId, ulong channelId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var deleted = await uow.GetTable<ChannelXpConfig>()
|
||||
.Where(x => x.GuildId == guildId && x.ChannelId == channelId)
|
||||
.DeleteAsync();
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
public Task OnReadyAsync()
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
||||
public class GuildXpConfig
|
||||
{
|
||||
[Key]
|
||||
public ulong GuildId { get; set; }
|
||||
|
||||
public int XpAmount { get; set; }
|
||||
public int Cooldown { get; set; }
|
||||
public string? XpTemplateUrl { get; set; }
|
||||
}
|
||||
|
||||
public sealed class GuildXpConfigEntity : IEntityTypeConfiguration<GuildXpConfig>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<GuildXpConfig> builder)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelXpConfig
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
public int XpAmount { get; set; }
|
||||
public float Cooldown { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ChannelXpConfigEntity : IEntityTypeConfiguration<ChannelXpConfig>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ChannelXpConfig> builder)
|
||||
{
|
||||
builder.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
x.ChannelId
|
||||
});
|
||||
}
|
||||
}
|
8
src/EllieBot/Modules/Xp/XpRate/XpRateType.cs
Normal file
8
src/EllieBot/Modules/Xp/XpRate/XpRateType.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public enum XpRateType
|
||||
{
|
||||
Text,
|
||||
Voice,
|
||||
Image,
|
||||
}
|
30
src/EllieBot/Modules/Xp/XpRate/db/ChannelXpConfig.cs
Normal file
30
src/EllieBot/Modules/Xp/XpRate/db/ChannelXpConfig.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public class ChannelXpConfig
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public XpRateType RateType { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
public long XpAmount { get; set; }
|
||||
public float Cooldown { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ChannelXpConfigEntity : IEntityTypeConfiguration<ChannelXpConfig>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ChannelXpConfig> builder)
|
||||
{
|
||||
builder.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
x.ChannelId,
|
||||
x.RateType,
|
||||
});
|
||||
}
|
||||
}
|
30
src/EllieBot/Modules/Xp/XpRate/db/GuildXpConfig.cs
Normal file
30
src/EllieBot/Modules/Xp/XpRate/db/GuildXpConfig.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace EllieBot.Modules.Xp;
|
||||
|
||||
public class GuildXpConfig
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public XpRateType RateType { get; set; }
|
||||
|
||||
public long XpAmount { get; set; }
|
||||
public float Cooldown { get; set; }
|
||||
public string? XpTemplateUrl { get; set; }
|
||||
}
|
||||
|
||||
public sealed class GuildXpConfigEntity : IEntityTypeConfiguration<GuildXpConfig>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<GuildXpConfig> builder)
|
||||
{
|
||||
builder.HasAlternateKey(x => new
|
||||
{
|
||||
x.GuildId,
|
||||
x.RateType,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -32,11 +32,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
private readonly ICurrencyService _cs;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly XpConfigService _xpConfig;
|
||||
private readonly IPubSub _pubSub;
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles = new();
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedChannels = new();
|
||||
private readonly ConcurrentHashSet<ulong> _excludedServers;
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
|
@ -45,8 +40,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
|
||||
private readonly INotifySubscriber _notifySub;
|
||||
private readonly IMemoryCache _memCache;
|
||||
private readonly ShardData _shardData;
|
||||
private readonly XpTemplateService _templateService;
|
||||
private readonly GuildConfigXpService _xpRateService;
|
||||
|
||||
private readonly QueueRunner _levelUpQueue = new QueueRunner(0, 100);
|
||||
|
||||
|
@ -65,7 +60,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
INotifySubscriber notifySub,
|
||||
IMemoryCache memCache,
|
||||
ShardData shardData,
|
||||
XpTemplateService templateService)
|
||||
XpTemplateService templateService,
|
||||
GuildConfigXpService xpRateService)
|
||||
{
|
||||
_db = db;
|
||||
_images = images;
|
||||
|
@ -74,47 +70,17 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
_cs = cs;
|
||||
_httpFactory = http;
|
||||
_xpConfig = xpConfig;
|
||||
_pubSub = pubSub;
|
||||
_notifySub = notifySub;
|
||||
_memCache = memCache;
|
||||
_shardData = shardData;
|
||||
_templateService = templateService;
|
||||
_excludedServers = [];
|
||||
_excludedChannels = new();
|
||||
_xpRateService = xpRateService;
|
||||
_client = client;
|
||||
_ps = ps;
|
||||
_c = c;
|
||||
|
||||
if (client.ShardId == 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
// initialize ignored
|
||||
await using (var ctx = _db.GetDbContext())
|
||||
{
|
||||
var xps = await ctx.GetTable<XpSettings>()
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId))
|
||||
.LoadWith(x => x.ExclusionList)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
foreach (var xp in xps)
|
||||
{
|
||||
if (xp.ServerExcluded)
|
||||
_excludedServers.Add(xp.GuildId);
|
||||
|
||||
foreach (var item in xp.ExclusionList)
|
||||
{
|
||||
if (item.ItemType == ExcludedItemType.Channel)
|
||||
_excludedChannels.GetOrAdd(xp.GuildId, static _ => []).Add(item.ItemId);
|
||||
else if (item.ItemType == ExcludedItemType.Role)
|
||||
_excludedRoles.GetOrAdd(xp.GuildId, static _ => []).Add(item.ItemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(UpdateTimer(), VoiceUpdateTimer(), _levelUpQueue.RunAsync());
|
||||
|
||||
return;
|
||||
|
@ -156,65 +122,66 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
/// <summary>
|
||||
/// The current batch of users that will gain xp
|
||||
/// </summary>
|
||||
private readonly ConcurrentHashSet<IGuildUser> _usersBatch = [];
|
||||
private readonly ConcurrentHashSet<XpQueueEntry> _usersBatch = [];
|
||||
|
||||
/// <summary>
|
||||
/// The current batch of users that will gain voice xp
|
||||
/// </summary>
|
||||
private readonly ConcurrentHashSet<IGuildUser> _voiceXpBatch = [];
|
||||
private readonly HashSet<IGuildUser> _voiceXpBatch = [];
|
||||
|
||||
private async Task UpdateVoiceXp()
|
||||
{
|
||||
var xpAmount = _xpConfig.Data.VoiceXpPerMinute;
|
||||
|
||||
if (xpAmount <= 0)
|
||||
return;
|
||||
|
||||
var oldBatch = _voiceXpBatch.ToArray();
|
||||
var oldBatch = _voiceXpBatch.ToHashSet();
|
||||
_voiceXpBatch.Clear();
|
||||
var validUsers = new HashSet<IGuildUser>();
|
||||
var validUsers = new List<XpQueueEntry>(oldBatch.Count);
|
||||
|
||||
var guilds = _client.Guilds;
|
||||
|
||||
foreach (var g in guilds)
|
||||
{
|
||||
if (IsServerExcluded(g.Id))
|
||||
continue;
|
||||
|
||||
foreach (var vc in g.VoiceChannels)
|
||||
{
|
||||
if (!IsVoiceChannelActive(vc))
|
||||
continue;
|
||||
|
||||
if (IsChannelExcluded(vc))
|
||||
var rate = _xpRateService.GetXpRate(XpRateType.Voice, g.Id, vc.Id);
|
||||
|
||||
if (rate.IsExcluded())
|
||||
continue;
|
||||
|
||||
foreach (var u in vc.ConnectedUsers)
|
||||
{
|
||||
if (IsServerOrRoleExcluded(u) || !UserParticipatingInVoiceChannel(u))
|
||||
if (!UserParticipatingInVoiceChannel(u))
|
||||
continue;
|
||||
|
||||
if (oldBatch.Contains(u))
|
||||
validUsers.Add(u);
|
||||
{
|
||||
validUsers.Add(new(u, rate.Amount));
|
||||
}
|
||||
|
||||
_voiceXpBatch.Add(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await UpdateXpInternalAsync(validUsers.DistinctBy(x => x.Id).ToArray(), xpAmount);
|
||||
await UpdateXpInternalAsync(validUsers.ToArray());
|
||||
}
|
||||
|
||||
private async Task UpdateXp()
|
||||
{
|
||||
var xpAmount = _xpConfig.Data.TextXpPerMessage;
|
||||
// might want to lock this, but it's not a big deal
|
||||
|
||||
// or do something like this
|
||||
// foreach (var item in currentBatch)
|
||||
// _usersBatch.TryRemove(item);
|
||||
|
||||
var currentBatch = _usersBatch.ToArray();
|
||||
_usersBatch.Clear();
|
||||
|
||||
await UpdateXpInternalAsync(currentBatch, xpAmount);
|
||||
await UpdateXpInternalAsync(currentBatch);
|
||||
}
|
||||
|
||||
private async Task UpdateXpInternalAsync(IGuildUser[] currentBatch, int xpAmount)
|
||||
private async Task UpdateXpInternalAsync(XpQueueEntry[] currentBatch)
|
||||
{
|
||||
if (currentBatch.Length == 0)
|
||||
return;
|
||||
|
@ -227,16 +194,17 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
|
||||
await batchTable.BulkCopyAsync(currentBatch.Select(x => new UserXpBatch()
|
||||
{
|
||||
GuildId = x.GuildId,
|
||||
UserId = x.Id,
|
||||
Username = x.Username,
|
||||
AvatarId = x.DisplayAvatarId
|
||||
GuildId = x.User.GuildId,
|
||||
UserId = x.User.Id,
|
||||
Username = x.User.Username,
|
||||
AvatarId = x.User.DisplayAvatarId,
|
||||
XpToGain = x.Xp
|
||||
}));
|
||||
|
||||
await lctx.ExecuteAsync(
|
||||
$"""
|
||||
INSERT INTO UserXpStats (GuildId, UserId, Xp)
|
||||
SELECT "{tempTableName}"."GuildId", "{tempTableName}"."UserId", {xpAmount}
|
||||
SELECT "{tempTableName}"."GuildId", "{tempTableName}"."UserId", "XpToGain"
|
||||
FROM {tempTableName}
|
||||
WHERE TRUE
|
||||
ON CONFLICT (GuildId, UserId) DO UPDATE
|
||||
|
@ -251,9 +219,13 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
(batch, stats) => stats)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
var userToXp = currentBatch.ToDictionary(x => x.User.Id, x => x.Xp);
|
||||
foreach (var u in updated)
|
||||
{
|
||||
var oldStats = new LevelStats(u.Xp - xpAmount);
|
||||
if (!userToXp.TryGetValue(u.UserId, out var xpGained))
|
||||
continue;
|
||||
|
||||
var oldStats = new LevelStats(u.Xp - xpGained);
|
||||
var newStats = new LevelStats(u.Xp);
|
||||
|
||||
Log.Information("User {User} xp updated from {OldLevel} to {NewLevel}",
|
||||
|
@ -531,8 +503,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
if (UserParticipatingInVoiceChannel(user))
|
||||
{
|
||||
count++;
|
||||
if (count >= 2)
|
||||
if (++count >= 2)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -543,27 +514,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
private static bool UserParticipatingInVoiceChannel(SocketGuildUser user)
|
||||
=> !user.IsDeafened && !user.IsMuted && !user.IsSelfDeafened && !user.IsSelfMuted;
|
||||
|
||||
private bool IsServerOrRoleExcluded(SocketGuildUser user)
|
||||
{
|
||||
if (_excludedServers.Contains(user.Guild.Id))
|
||||
return true;
|
||||
|
||||
if (_excludedRoles.TryGetValue(user.Guild.Id, out var roles) && user.Roles.Any(x => roles.Contains(x.Id)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsChannelExcluded(IGuildChannel channel)
|
||||
{
|
||||
if (_excludedChannels.TryGetValue(channel.Guild.Id, out var chans)
|
||||
&& (chans.Contains(channel.Id)
|
||||
|| (channel is SocketThreadChannel tc && chans.Contains(tc.ParentChannel.Id))))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task ExecOnNoCommandAsync(IGuild guild, IUserMessage arg)
|
||||
{
|
||||
if (arg.Author is not SocketGuildUser user || user.IsBot)
|
||||
|
@ -574,27 +524,36 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (IsChannelExcluded(gc))
|
||||
var isImage = arg.Attachments.Any(a => a.Height >= 16 && a.Width >= 16);
|
||||
var isText = arg.Content.Contains(' ') || arg.Content.Length >= 5;
|
||||
|
||||
var textRate = _xpRateService.GetXpRate(XpRateType.Text, guild.Id, gc.Id);
|
||||
|
||||
XpRate rate;
|
||||
if (isImage)
|
||||
{
|
||||
var imageRate = _xpRateService.GetXpRate(XpRateType.Image, guild.Id, gc.Id);
|
||||
if (imageRate.IsExcluded())
|
||||
return;
|
||||
|
||||
rate = imageRate;
|
||||
}
|
||||
else if (isText)
|
||||
{
|
||||
if (textRate.IsExcluded())
|
||||
return;
|
||||
|
||||
rate = textRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await TryAddUserGainedXpAsync(user.Id, rate.Cooldown))
|
||||
return;
|
||||
|
||||
if (IsServerOrRoleExcluded(user))
|
||||
return;
|
||||
|
||||
var xpConf = _xpConfig.Data;
|
||||
var xp = 0;
|
||||
if (arg.Attachments.Any(a => a.Height >= 128 && a.Width >= 128))
|
||||
xp = xpConf.TextXpFromImage;
|
||||
|
||||
if (arg.Content.Contains(' ') || arg.Content.Length >= 5)
|
||||
xp = Math.Max(xp, xpConf.TextXpPerMessage);
|
||||
|
||||
if (xp <= 0)
|
||||
return;
|
||||
|
||||
if (!await TryAddUserGainedXpAsync(user.Id, xpConf.TextXpCooldown))
|
||||
return;
|
||||
|
||||
_usersBatch.Add(user);
|
||||
_usersBatch.Add(new(user, rate.Amount));
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -621,28 +580,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public bool IsServerExcluded(ulong id)
|
||||
=> _excludedServers.Contains(id);
|
||||
|
||||
public IEnumerable<ulong> GetExcludedRoles(ulong id)
|
||||
private Task<bool> TryAddUserGainedXpAsync(ulong userId, float cdInMinutes)
|
||||
{
|
||||
if (_excludedRoles.TryGetValue(id, out var val))
|
||||
return val.ToArray();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public IEnumerable<ulong> GetExcludedChannels(ulong id)
|
||||
{
|
||||
if (_excludedChannels.TryGetValue(id, out var val))
|
||||
return val.ToArray();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private Task<bool> TryAddUserGainedXpAsync(ulong userId, int cdInSeconds)
|
||||
{
|
||||
if (cdInSeconds <= 0)
|
||||
if (cdInMinutes <= float.Epsilon)
|
||||
return Task.FromResult(true);
|
||||
|
||||
if (_memCache.TryGetValue(userId, out _))
|
||||
|
@ -651,7 +591,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
using var entry = _memCache.CreateEntry(userId);
|
||||
entry.Value = true;
|
||||
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(cdInSeconds);
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(cdInMinutes);
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
@ -674,81 +614,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
guildRank);
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleExcludeServerAsync(ulong id)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var xpSetting = await uow.XpSettingsFor(id);
|
||||
if (_excludedServers.Add(id))
|
||||
{
|
||||
xpSetting.ServerExcluded = true;
|
||||
await uow.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
_excludedServers.TryRemove(id);
|
||||
xpSetting.ServerExcluded = false;
|
||||
await uow.SaveChangesAsync();
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleExcludeRoleAsync(ulong guildId, ulong rId)
|
||||
{
|
||||
var roles = _excludedRoles.GetOrAdd(guildId, _ => []);
|
||||
await using var uow = _db.GetDbContext();
|
||||
var xpSetting = await uow.XpSettingsFor(guildId, set => set.LoadWith(x => x.ExclusionList));
|
||||
var excludeObj = new ExcludedItem
|
||||
{
|
||||
ItemId = rId,
|
||||
ItemType = ExcludedItemType.Role
|
||||
};
|
||||
|
||||
if (roles.Add(rId))
|
||||
{
|
||||
if (xpSetting.ExclusionList.Add(excludeObj))
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
roles.TryRemove(rId);
|
||||
|
||||
var toDelete = xpSetting.ExclusionList.FirstOrDefault(x => x.Equals(excludeObj));
|
||||
if (toDelete is not null)
|
||||
{
|
||||
uow.Remove(toDelete);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleExcludeChannelAsync(ulong guildId, ulong chId)
|
||||
{
|
||||
var channels = _excludedChannels.GetOrAdd(guildId, _ => []);
|
||||
await using var uow = _db.GetDbContext();
|
||||
var xpSetting = await uow.XpSettingsFor(guildId, set => set.LoadWith(x => x.ExclusionList));
|
||||
var excludeObj = new ExcludedItem
|
||||
{
|
||||
ItemId = chId,
|
||||
ItemType = ExcludedItemType.Channel
|
||||
};
|
||||
|
||||
if (channels.Add(chId))
|
||||
{
|
||||
if (xpSetting.ExclusionList.Add(excludeObj))
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
channels.TryRemove(chId);
|
||||
|
||||
if (xpSetting.ExclusionList.Remove(excludeObj))
|
||||
await uow.SaveChangesAsync();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<(Stream Image, IImageFormat Format)> GenerateXpImageAsync(IGuildUser user)
|
||||
{
|
||||
var stats = await GetUserStatsAsync(user);
|
||||
|
@ -1224,8 +1089,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
if (item is null || item.Price < 0)
|
||||
return BuyResult.UnknownItem;
|
||||
|
||||
if (item.Price > 0 &&
|
||||
!await _cs.RemoveAsync(userId, item.Price, new("xpshop", "buy", $"Background {key}")))
|
||||
if (item.Price > 0 && !await _cs.RemoveAsync(userId, item.Price, new("xpshop", "buy", $"Background {key}")))
|
||||
return BuyResult.InsufficientFunds;
|
||||
|
||||
|
||||
|
@ -1438,4 +1302,13 @@ public sealed class XpTemplateService : IEService, IReadyExecutor
|
|||
|
||||
public XpTemplate GetTemplate()
|
||||
=> _template;
|
||||
}
|
||||
|
||||
public readonly record struct XpQueueEntry(IGuildUser User, long Xp)
|
||||
{
|
||||
public bool Equals(XpQueueEntry? other)
|
||||
=> other?.User == User;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> User.GetHashCode();
|
||||
}
|
|
@ -35,9 +35,9 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
|||
if (guild is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||
|
||||
var excludedChannels = _xp.GetExcludedChannels(request.GuildId);
|
||||
var excludedRoles = _xp.GetExcludedRoles(request.GuildId);
|
||||
var isServerExcluded = _xp.IsServerExcluded(request.GuildId);
|
||||
var excludedChannels = new List<ulong>();
|
||||
var excludedRoles = new List<ulong>();
|
||||
var isServerExcluded = false;
|
||||
|
||||
var reply = new GetXpSettingsReply();
|
||||
|
||||
|
@ -82,59 +82,6 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
|||
return reply;
|
||||
}
|
||||
|
||||
public override async Task<AddExclusionReply> AddExclusion(AddExclusionRequest request, ServerCallContext context)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var success = false;
|
||||
var guild = _client.GetGuild(request.GuildId);
|
||||
|
||||
if (guild is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||
|
||||
if (request.Type == "Role")
|
||||
{
|
||||
if (guild.GetRole(request.Id) is null)
|
||||
return new()
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
|
||||
success = await _xp.ToggleExcludeRoleAsync(request.GuildId, request.Id);
|
||||
}
|
||||
else if (request.Type == "Channel")
|
||||
{
|
||||
if (guild.GetTextChannel(request.Id) is null)
|
||||
return new()
|
||||
{
|
||||
Success = false
|
||||
};
|
||||
|
||||
success = await _xp.ToggleExcludeChannelAsync(request.GuildId, request.Id);
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Success = success
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<DeleteExclusionReply> DeleteExclusion(
|
||||
DeleteExclusionRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var success = false;
|
||||
if (request.Type == "Role")
|
||||
success = await _xp.ToggleExcludeRoleAsync(request.GuildId, request.Id);
|
||||
else
|
||||
success = await _xp.ToggleExcludeChannelAsync(request.GuildId, request.Id);
|
||||
|
||||
return new DeleteExclusionReply
|
||||
{
|
||||
Success = success
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<AddRewardReply> AddReward(AddRewardRequest request, ServerCallContext context)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
@ -267,15 +214,4 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
|||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public override async Task<SetServerExclusionReply> SetServerExclusion(
|
||||
SetServerExclusionRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var newValue = await _xp.ToggleExcludeServerAsync(request.GuildId);
|
||||
return new()
|
||||
{
|
||||
Success = newValue
|
||||
};
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ public abstract class EllieInteractionBase
|
|||
message = msg;
|
||||
|
||||
Client.InteractionCreated += OnInteraction;
|
||||
await Task.WhenAny(Task.Delay(30_000), _interactionCompletedSource.Task);
|
||||
await Task.WhenAny(Task.Delay(600_000), _interactionCompletedSource.Task);
|
||||
Client.InteractionCreated -= OnInteraction;
|
||||
|
||||
if (_clearAfter)
|
||||
|
@ -130,7 +130,7 @@ public sealed class EllieModalSubmitHandler
|
|||
message = msg;
|
||||
|
||||
Client.ModalSubmitted += OnInteraction;
|
||||
await Task.WhenAny(Task.Delay(300_000), _interactionCompletedSource.Task);
|
||||
await Task.WhenAny(Task.Delay(600_000), _interactionCompletedSource.Task);
|
||||
Client.ModalSubmitted -= OnInteraction;
|
||||
|
||||
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
||||
|
|
|
@ -159,7 +159,7 @@ public partial class ResponseBuilder
|
|||
|
||||
await Task.WhenAll(left.RunAsync(msg), extra?.RunAsync(msg) ?? Task.CompletedTask, right.RunAsync(msg));
|
||||
|
||||
await Task.Delay(30_000);
|
||||
await Task.Delay(600_000);
|
||||
|
||||
await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
|
||||
}
|
||||
|
|
|
@ -4935,20 +4935,25 @@ fishspot:
|
|||
xprate:
|
||||
desc: |-
|
||||
Sets the xp rate for the server or the specified channel.
|
||||
First specify the amount, and then the cooldown in minutes.
|
||||
First specify the type, amount, and then the cooldown in minutes.
|
||||
Provide no parameters to see the current rates.
|
||||
Cooldown has no effect on voice xp, as any amount is gained per minute.
|
||||
ex:
|
||||
- ''
|
||||
- '3 5'
|
||||
- '#channel 50 1'
|
||||
- 'text 3 5'
|
||||
- '#channel voice 50 1'
|
||||
params:
|
||||
- { }
|
||||
- amount:
|
||||
- type:
|
||||
desc: "The type of rate to set. One of: text, voice or image."
|
||||
amount:
|
||||
desc: "The amount of xp to give per message."
|
||||
minutes:
|
||||
desc: "The cooldown in minutes. Allows decimal values."
|
||||
- channel:
|
||||
desc: "The channel to set the rate for."
|
||||
type:
|
||||
desc: "The type of rate to set. One of: text, voice or image."
|
||||
amount:
|
||||
desc: "The amount of xp to give per message."
|
||||
minutes:
|
||||
|
|
|
@ -1170,12 +1170,12 @@
|
|||
"fish_list_title": "Fishing",
|
||||
"xp_rate_none": "No xp rate overrides on this server.",
|
||||
"xp_rate_amount_invalid": "Amount must be between 0 and 1000.",
|
||||
"xp_rate_cooldown_invalid": "Cooldown must be between 0 and 1440 minutes.",
|
||||
"xp_rate_server": "Server xp Rate",
|
||||
"xp_rate_amount_cooldown": "{0} xp per every {1} minutes",
|
||||
"xp_rate_channels": "Channel XP Rates",
|
||||
"xp_rate_server_set": "Server xp rate set to {0} xp per every {1} minutes.",
|
||||
"xp_rate_channel_set": "Channel {0} xp rate set to {1} xp per every {2} minutes.",
|
||||
"xp_rate_cooldown_invalid": "Cooldown must be between 0 and 1440.",
|
||||
"xp_rate_server": "Server xp rates",
|
||||
"xp_rate_str": "`{0}:` {1} xp every {2} min.",
|
||||
"xp_rate_channels": "Channel xp rates",
|
||||
"xp_rate_server_set": "Server xp rate set to **{0}** xp per every **{1}** min.",
|
||||
"xp_rate_channel_set": "Channel **{0}** xp rate set to **{1}** xp per every **{2}** min.",
|
||||
"xp_rate_server_reset": "Server xp rate has been reset to global defaults.",
|
||||
"xp_rate_channel_reset": "Channel {0} xp rate has been reset.",
|
||||
"xp_rate_no_gain": "No xp gain"
|
||||
|
|
Loading…
Add table
Reference in a new issue