Xp and clubs finished. Need a lot of testing. Version upped to 1.8-beta

This commit is contained in:
Master Kwoth 2017-09-10 03:52:34 +02:00
parent 088d95340f
commit 96d792f63b
49 changed files with 2566 additions and 648 deletions

View File

@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class xpstuff : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "XpMinutesTimeout",
table: "BotConfig",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "XpPerMessage",
table: "BotConfig",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "UserXpStats",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildId = table.Column<ulong>(nullable: false),
NotifyOnLevelUp = table.Column<bool>(nullable: false),
UserId = table.Column<ulong>(nullable: false),
Xp = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserXpStats", x => x.Id);
});
migrationBuilder.CreateTable(
name: "XpSettings",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: false),
NotifyMessage = table.Column<string>(nullable: true),
ServerExcluded = table.Column<bool>(nullable: false),
XpRoleRewardExclusive = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_XpSettings", x => x.Id);
table.ForeignKey(
name: "FK_XpSettings_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ExcludedItem",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
ItemId = table.Column<ulong>(nullable: false),
ItemType = table.Column<int>(nullable: false),
XpSettingsId = table.Column<int>(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",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "XpRoleReward",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
Level = table.Column<int>(nullable: false),
RoleId = table.Column<ulong>(nullable: false),
XpSettingsId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_XpRoleReward", x => x.Id);
table.ForeignKey(
name: "FK_XpRoleReward_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_ExcludedItem_XpSettingsId",
table: "ExcludedItem",
column: "XpSettingsId");
migrationBuilder.CreateIndex(
name: "IX_UserXpStats_UserId_GuildId",
table: "UserXpStats",
columns: new[] { "UserId", "GuildId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_XpRoleReward_XpSettingsId",
table: "XpRoleReward",
column: "XpSettingsId");
migrationBuilder.CreateIndex(
name: "IX_XpSettings_GuildConfigId",
table: "XpSettings",
column: "GuildConfigId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ExcludedItem");
migrationBuilder.DropTable(
name: "UserXpStats");
migrationBuilder.DropTable(
name: "XpRoleReward");
migrationBuilder.DropTable(
name: "XpSettings");
migrationBuilder.DropColumn(
name: "XpMinutesTimeout",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "XpPerMessage",
table: "BotConfig");
}
}
}

View File

@ -9,8 +9,8 @@ using NadekoBot.Services.Database.Models;
namespace NadekoBot.Migrations namespace NadekoBot.Migrations
{ {
[DbContext(typeof(NadekoContext))] [DbContext(typeof(NadekoContext))]
[Migration("20170821085106_xp-stuff")] [Migration("20170908230730_xp-and-clubs")]
partial class xpstuff partial class xpandclubs
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
@ -184,9 +184,13 @@ namespace NadekoBot.Migrations
b.Property<int>("TriviaCurrencyReward"); b.Property<int>("TriviaCurrencyReward");
b.Property<int>("XpMinutesTimeout"); b.Property<int>("XpMinutesTimeout")
.ValueGeneratedOnAdd()
.HasDefaultValue(5);
b.Property<int>("XpPerMessage"); b.Property<int>("XpPerMessage")
.ValueGeneratedOnAdd()
.HasDefaultValue(3);
b.HasKey("Id"); b.HasKey("Id");
@ -243,6 +247,63 @@ namespace NadekoBot.Migrations
b.ToTable("ClashOfClans"); b.ToTable("ClashOfClans");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b =>
{
b.Property<int>("ClubId");
b.Property<int>("UserId");
b.HasKey("ClubId", "UserId");
b.HasIndex("UserId");
b.ToTable("ClubApplicants");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b =>
{
b.Property<int>("ClubId");
b.Property<int>("UserId");
b.HasKey("ClubId", "UserId");
b.HasIndex("UserId");
b.ToTable("ClubBans");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int>("Discrim");
b.Property<string>("ImageUrl");
b.Property<int>("MinimumLevelReq");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20);
b.Property<int>("OwnerId");
b.Property<int>("Xp");
b.HasKey("Id");
b.HasAlternateKey("Name", "Discrim");
b.HasIndex("OwnerId")
.IsUnique();
b.ToTable("Clubs");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -396,10 +457,18 @@ namespace NadekoBot.Migrations
b.Property<string>("AvatarId"); b.Property<string>("AvatarId");
b.Property<int?>("ClubId");
b.Property<DateTime?>("DateAdded"); b.Property<DateTime?>("DateAdded");
b.Property<string>("Discriminator"); b.Property<string>("Discriminator");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 857, DateTimeKind.Local));
b.Property<int>("NotifyOnLevelUp");
b.Property<ulong>("UserId"); b.Property<ulong>("UserId");
b.Property<string>("Username"); b.Property<string>("Username");
@ -408,6 +477,8 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("UserId"); b.HasAlternateKey("UserId");
b.HasIndex("ClubId");
b.ToTable("DiscordUser"); b.ToTable("DiscordUser");
}); });
@ -1282,11 +1353,17 @@ namespace NadekoBot.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
b.Property<int>("AwardedXp");
b.Property<DateTime?>("DateAdded"); b.Property<DateTime?>("DateAdded");
b.Property<ulong>("GuildId"); b.Property<ulong>("GuildId");
b.Property<bool>("NotifyOnLevelUp"); b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local));
b.Property<int>("NotifyOnLevelUp");
b.Property<ulong>("UserId"); b.Property<ulong>("UserId");
@ -1456,6 +1533,8 @@ namespace NadekoBot.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasAlternateKey("Level");
b.HasIndex("XpSettingsId"); b.HasIndex("XpSettingsId");
b.ToTable("XpRoleReward"); b.ToTable("XpRoleReward");
@ -1533,6 +1612,40 @@ namespace NadekoBot.Migrations
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club")
.WithMany("Applicants")
.HasForeignKey("ClubId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club")
.WithMany("Bans")
.HasForeignKey("ClubId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner")
.WithOne()
.HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
@ -1554,6 +1667,13 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId"); .HasForeignKey("BotConfigId");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club")
.WithMany("Users")
.HasForeignKey("ClubId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.BotConfig") b.HasOne("NadekoBot.Services.Database.Models.BotConfig")

View File

@ -0,0 +1,296 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class xpandclubs : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "XpMinutesTimeout",
table: "BotConfig",
nullable: false,
defaultValue: 5)
.Annotation("Sqlite:Autoincrement", true);
migrationBuilder.AddColumn<int>(
name: "XpPerMessage",
table: "BotConfig",
nullable: false,
defaultValue: 3)
.Annotation("Sqlite:Autoincrement", true);
migrationBuilder.CreateTable(
name: "Clubs",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
Discrim = table.Column<int>(nullable: false),
ImageUrl = table.Column<string>(nullable: true),
MinimumLevelReq = table.Column<int>(nullable: false),
Name = table.Column<string>(maxLength: 20, nullable: false),
OwnerId = table.Column<int>(nullable: false),
Xp = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Clubs", x => x.Id);
table.UniqueConstraint("AK_Clubs_Name_Discrim", x => new { x.Name, x.Discrim });
table.ForeignKey(
name: "FK_Clubs_DiscordUser_OwnerId",
column: x => x.OwnerId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.Sql(MigrationQueries.UserClub);
migrationBuilder.AddColumn<DateTime>(
name: "LastLevelUp",
table: "DiscordUser",
nullable: false,
defaultValue: DateTime.UtcNow);
migrationBuilder.AddColumn<int>(
name: "NotifyOnLevelUp",
table: "DiscordUser",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "UserXpStats",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AwardedXp = table.Column<int>(nullable: false),
DateAdded = table.Column<DateTime>(nullable: true),
GuildId = table.Column<ulong>(nullable: false),
LastLevelUp = table.Column<DateTime>(nullable: false, defaultValue: new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local)),
NotifyOnLevelUp = table.Column<int>(nullable: false),
UserId = table.Column<ulong>(nullable: false),
Xp = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserXpStats", x => x.Id);
});
migrationBuilder.CreateTable(
name: "XpSettings",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
GuildConfigId = table.Column<int>(nullable: false),
NotifyMessage = table.Column<string>(nullable: true),
ServerExcluded = table.Column<bool>(nullable: false),
XpRoleRewardExclusive = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_XpSettings", x => x.Id);
table.ForeignKey(
name: "FK_XpSettings_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClubApplicants",
columns: table => new
{
ClubId = table.Column<int>(nullable: false),
UserId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClubApplicants", x => new { x.ClubId, x.UserId });
table.ForeignKey(
name: "FK_ClubApplicants_Clubs_ClubId",
column: x => x.ClubId,
principalTable: "Clubs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ClubApplicants_DiscordUser_UserId",
column: x => x.UserId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClubBans",
columns: table => new
{
ClubId = table.Column<int>(nullable: false),
UserId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClubBans", x => new { x.ClubId, x.UserId });
table.ForeignKey(
name: "FK_ClubBans_Clubs_ClubId",
column: x => x.ClubId,
principalTable: "Clubs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ClubBans_DiscordUser_UserId",
column: x => x.UserId,
principalTable: "DiscordUser",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ExcludedItem",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
ItemId = table.Column<ulong>(nullable: false),
ItemType = table.Column<int>(nullable: false),
XpSettingsId = table.Column<int>(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",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "XpRoleReward",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(nullable: true),
Level = table.Column<int>(nullable: false),
RoleId = table.Column<ulong>(nullable: false),
XpSettingsId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_XpRoleReward", x => x.Id);
table.UniqueConstraint("AK_XpRoleReward_Level", x => x.Level);
table.ForeignKey(
name: "FK_XpRoleReward_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_DiscordUser_ClubId",
table: "DiscordUser",
column: "ClubId");
migrationBuilder.CreateIndex(
name: "IX_ClubApplicants_UserId",
table: "ClubApplicants",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_ClubBans_UserId",
table: "ClubBans",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Clubs_OwnerId",
table: "Clubs",
column: "OwnerId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ExcludedItem_XpSettingsId",
table: "ExcludedItem",
column: "XpSettingsId");
migrationBuilder.CreateIndex(
name: "IX_UserXpStats_UserId_GuildId",
table: "UserXpStats",
columns: new[] { "UserId", "GuildId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_XpRoleReward_XpSettingsId",
table: "XpRoleReward",
column: "XpSettingsId");
migrationBuilder.CreateIndex(
name: "IX_XpSettings_GuildConfigId",
table: "XpSettings",
column: "GuildConfigId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_DiscordUser_Clubs_ClubId",
table: "DiscordUser");
migrationBuilder.DropTable(
name: "ClubApplicants");
migrationBuilder.DropTable(
name: "ClubBans");
migrationBuilder.DropTable(
name: "ExcludedItem");
migrationBuilder.DropTable(
name: "UserXpStats");
migrationBuilder.DropTable(
name: "XpRoleReward");
migrationBuilder.DropTable(
name: "Clubs");
migrationBuilder.DropTable(
name: "XpSettings");
migrationBuilder.DropIndex(
name: "IX_DiscordUser_ClubId",
table: "DiscordUser");
migrationBuilder.DropColumn(
name: "ClubId",
table: "DiscordUser");
migrationBuilder.DropColumn(
name: "LastLevelUp",
table: "DiscordUser");
migrationBuilder.DropColumn(
name: "NotifyOnLevelUp",
table: "DiscordUser");
migrationBuilder.DropColumn(
name: "XpMinutesTimeout",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "XpPerMessage",
table: "BotConfig");
}
}
}

View File

@ -0,0 +1,38 @@
namespace NadekoBot.Migrations
{
internal class MigrationQueries
{
public static string UserClub { get; } = @"
CREATE TABLE DiscordUser_tmp(
Id INTEGER PRIMARY KEY,
AvatarId TEXT,
Discriminator TEXT,
UserId INTEGER UNIQUE NOT NULL,
DateAdded TEXT,
Username TEXT
);
INSERT INTO DiscordUser_tmp
SELECT Id, AvatarId, Discriminator, UserId, DateAdded, Username
FROM DiscordUser;
DROP TABLE DiscordUser;
CREATE TABLE DiscordUser(
Id INTEGER PRIMARY KEY,
AvatarId TEXT,
Discriminator TEXT,
UserId INTEGER UNIQUE NOT NULL,
DateAdded TEXT,
Username TEXT,
ClubId INTEGER,
CONSTRAINT FK_DiscordUser_Clubs_ClubId FOREIGN KEY(ClubId) REFERENCES Clubs(Id) ON DELETE RESTRICT
);
INSERT INTO DiscordUser
SELECT Id, AvatarId, Discriminator, UserId, DateAdded, Username, NULL
FROM DiscordUser_tmp;
DROP TABLE DiscordUser_tmp;";
}
}

View File

@ -183,9 +183,13 @@ namespace NadekoBot.Migrations
b.Property<int>("TriviaCurrencyReward"); b.Property<int>("TriviaCurrencyReward");
b.Property<int>("XpMinutesTimeout"); b.Property<int>("XpMinutesTimeout")
.ValueGeneratedOnAdd()
.HasDefaultValue(5);
b.Property<int>("XpPerMessage"); b.Property<int>("XpPerMessage")
.ValueGeneratedOnAdd()
.HasDefaultValue(3);
b.HasKey("Id"); b.HasKey("Id");
@ -242,6 +246,63 @@ namespace NadekoBot.Migrations
b.ToTable("ClashOfClans"); b.ToTable("ClashOfClans");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b =>
{
b.Property<int>("ClubId");
b.Property<int>("UserId");
b.HasKey("ClubId", "UserId");
b.HasIndex("UserId");
b.ToTable("ClubApplicants");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b =>
{
b.Property<int>("ClubId");
b.Property<int>("UserId");
b.HasKey("ClubId", "UserId");
b.HasIndex("UserId");
b.ToTable("ClubBans");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int>("Discrim");
b.Property<string>("ImageUrl");
b.Property<int>("MinimumLevelReq");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(20);
b.Property<int>("OwnerId");
b.Property<int>("Xp");
b.HasKey("Id");
b.HasAlternateKey("Name", "Discrim");
b.HasIndex("OwnerId")
.IsUnique();
b.ToTable("Clubs");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -395,10 +456,18 @@ namespace NadekoBot.Migrations
b.Property<string>("AvatarId"); b.Property<string>("AvatarId");
b.Property<int?>("ClubId");
b.Property<DateTime?>("DateAdded"); b.Property<DateTime?>("DateAdded");
b.Property<string>("Discriminator"); b.Property<string>("Discriminator");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 857, DateTimeKind.Local));
b.Property<int>("NotifyOnLevelUp");
b.Property<ulong>("UserId"); b.Property<ulong>("UserId");
b.Property<string>("Username"); b.Property<string>("Username");
@ -407,6 +476,8 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("UserId"); b.HasAlternateKey("UserId");
b.HasIndex("ClubId");
b.ToTable("DiscordUser"); b.ToTable("DiscordUser");
}); });
@ -1281,11 +1352,17 @@ namespace NadekoBot.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
b.Property<int>("AwardedXp");
b.Property<DateTime?>("DateAdded"); b.Property<DateTime?>("DateAdded");
b.Property<ulong>("GuildId"); b.Property<ulong>("GuildId");
b.Property<bool>("NotifyOnLevelUp"); b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local));
b.Property<int>("NotifyOnLevelUp");
b.Property<ulong>("UserId"); b.Property<ulong>("UserId");
@ -1455,6 +1532,8 @@ namespace NadekoBot.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasAlternateKey("Level");
b.HasIndex("XpSettingsId"); b.HasIndex("XpSettingsId");
b.ToTable("XpRoleReward"); b.ToTable("XpRoleReward");
@ -1532,6 +1611,40 @@ namespace NadekoBot.Migrations
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club")
.WithMany("Applicants")
.HasForeignKey("ClubId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club")
.WithMany("Bans")
.HasForeignKey("ClubId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Owner")
.WithOne()
.HasForeignKey("NadekoBot.Services.Database.Models.ClubInfo", "OwnerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
@ -1553,6 +1666,13 @@ namespace NadekoBot.Migrations
.HasForeignKey("BotConfigId"); .HasForeignKey("BotConfigId");
}); });
modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.ClubInfo", "Club")
.WithMany("Users")
.HasForeignKey("ClubId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b =>
{ {
b.HasOne("NadekoBot.Services.Database.Models.BotConfig") b.HasOne("NadekoBot.Services.Database.Models.BotConfig")

View File

@ -352,54 +352,6 @@ namespace NadekoBot.Modules.Administration
oldConfig.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i })); oldConfig.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i }));
botConfig.RotatingStatusMessages = messages; botConfig.RotatingStatusMessages = messages;
//Prefix
botConfig.ModulePrefixes.Clear();
botConfig.ModulePrefixes.AddRange(new HashSet<ModulePrefix>
{
new ModulePrefix()
{
ModuleName = "Administration",
Prefix = oldConfig.CommandPrefixes.Administration
},
new ModulePrefix()
{
ModuleName = "Searches",
Prefix = oldConfig.CommandPrefixes.Searches
},
new ModulePrefix() {ModuleName = "NSFW", Prefix = oldConfig.CommandPrefixes.NSFW},
new ModulePrefix()
{
ModuleName = "Conversations",
Prefix = oldConfig.CommandPrefixes.Conversations
},
new ModulePrefix()
{
ModuleName = "ClashOfClans",
Prefix = oldConfig.CommandPrefixes.ClashOfClans
},
new ModulePrefix() {ModuleName = "Help", Prefix = oldConfig.CommandPrefixes.Help},
new ModulePrefix() {ModuleName = "Music", Prefix = oldConfig.CommandPrefixes.Music},
new ModulePrefix() {ModuleName = "Trello", Prefix = oldConfig.CommandPrefixes.Trello},
new ModulePrefix() {ModuleName = "Games", Prefix = oldConfig.CommandPrefixes.Games},
new ModulePrefix()
{
ModuleName = "Gambling",
Prefix = oldConfig.CommandPrefixes.Gambling
},
new ModulePrefix()
{
ModuleName = "Permissions",
Prefix = oldConfig.CommandPrefixes.Permissions
},
new ModulePrefix()
{
ModuleName = "Programming",
Prefix = oldConfig.CommandPrefixes.Programming
},
new ModulePrefix() {ModuleName = "Pokemon", Prefix = oldConfig.CommandPrefixes.Pokemon},
new ModulePrefix() {ModuleName = "Utility", Prefix = oldConfig.CommandPrefixes.Utility}
});
//Blacklist //Blacklist
var blacklist = new HashSet<BlacklistItem>(oldConfig.ServerBlacklist.Select(server => new BlacklistItem() { ItemId = server, Type = BlacklistType.Server })); var blacklist = new HashSet<BlacklistItem>(oldConfig.ServerBlacklist.Select(server => new BlacklistItem() { ItemId = server, Type = BlacklistType.Server }));
blacklist.AddRange(oldConfig.ChannelBlacklist.Select(channel => new BlacklistItem() { ItemId = channel, Type = BlacklistType.Channel })); blacklist.AddRange(oldConfig.ChannelBlacklist.Select(channel => new BlacklistItem() { ItemId = channel, Type = BlacklistType.Channel }));

View File

@ -153,7 +153,7 @@ namespace NadekoBot.Modules.CustomReactions
if (Context.Guild == null) // its a private one, just send back if (Context.Guild == null) // its a private one, just send back
await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false); await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false);
else else
await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false); await ((IGuildUser)Context.User).SendFileAsync(txtStream, "customreactions.txt", GetText("list_all"), false).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]

View File

@ -48,8 +48,8 @@ namespace NadekoBot.Modules.Gambling
[Group] [Group]
public class WaifuClaimCommands : NadekoSubmodule public class WaifuClaimCommands : NadekoSubmodule
{ {
private static ConcurrentDictionary<ulong, DateTime> divorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>(); private static ConcurrentDictionary<ulong, DateTime> _divorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentDictionary<ulong, DateTime> affinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>(); private static ConcurrentDictionary<ulong, DateTime> _affinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
enum WaifuClaimResult enum WaifuClaimResult
{ {
@ -219,7 +219,7 @@ namespace NadekoBot.Modules.Gambling
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id) if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id)
result = DivorceResult.NotYourWife; result = DivorceResult.NotYourWife;
else if (divorceCooldowns.AddOrUpdate(Context.User.Id, else if (_divorceCooldowns.AddOrUpdate(Context.User.Id,
now, now,
(key, old) => ((difference = now.Subtract(old)) > _divorceLimit) ? now : old) != now) (key, old) => ((difference = now.Subtract(old)) > _divorceLimit) ? now : old) != now)
{ {
@ -303,7 +303,7 @@ namespace NadekoBot.Modules.Gambling
if (w?.Affinity?.UserId == u?.Id) if (w?.Affinity?.UserId == u?.Id)
{ {
} }
else if (affinityCooldowns.AddOrUpdate(Context.User.Id, else if (_affinityCooldowns.AddOrUpdate(Context.User.Id,
now, now,
(key, old) => ((difference = now.Subtract(old)) > _affinityLimit) ? now : old) != now) (key, old) => ((difference = now.Subtract(old)) > _affinityLimit) ? now : old) != now)
{ {
@ -459,7 +459,7 @@ namespace NadekoBot.Modules.Gambling
.AddField(efb => efb.WithName(GetText("likes")).WithValue(w.Affinity?.ToString() ?? nobody).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("likes")).WithValue(w.Affinity?.ToString() ?? nobody).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("changes_of_heart")).WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true)) .AddField(efb => efb.WithName(GetText("changes_of_heart")).WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("divorces")).WithValue(divorces.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("divorces")).WithValue(divorces.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("gifts")).WithValue(!w.Items.Any() ? "-" : string.Join("\n", w.Items.OrderBy(x => x.Price).GroupBy(x => x.ItemEmoji).Select(x => $"{x.Key} x{x.Count()}"))).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("gifts")).WithValue(!w.Items.Any() ? "-" : string.Join("\n", w.Items.OrderBy(x => x.Price).GroupBy(x => x.ItemEmoji).Select(x => $"{x.Key} x{x.Count()}"))).WithIsInline(false))
.AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? nobody : string.Join("\n", claims.OrderBy(x => rng.Next()).Take(30).Select(x => x.Waifu))).WithIsInline(false)); .AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? nobody : string.Join("\n", claims.OrderBy(x => rng.Next()).Take(30).Select(x => x.Waifu))).WithIsInline(false));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);

View File

@ -11,7 +11,7 @@ using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Games namespace NadekoBot.Modules.Games
{ {
/*todo more games /* more games
- Blackjack - Blackjack
- Shiritori - Shiritori
- Simple RPG adventure - Simple RPG adventure

View File

@ -15,6 +15,7 @@ using NadekoBot.Modules.Searches.Common;
using NadekoBot.Modules.Searches.Services; using NadekoBot.Modules.Searches.Services;
using NadekoBot.Modules.NSFW.Exceptions; using NadekoBot.Modules.NSFW.Exceptions;
//todo static httpclient
namespace NadekoBot.Modules.NSFW namespace NadekoBot.Modules.NSFW
{ {
public class NSFW : NadekoTopLevelModule<SearchesService> public class NSFW : NadekoTopLevelModule<SearchesService>

View File

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using NadekoBot.Common; using NadekoBot.Common;
using NadekoBot.Common.Attributes; using NadekoBot.Common.Attributes;
//todo 50 drawing
namespace NadekoBot.Modules.Searches namespace NadekoBot.Modules.Searches
{ {
public partial class Searches public partial class Searches
@ -66,320 +65,4 @@ namespace NadekoBot.Modules.Searches
} }
} }
} }
} }
// private class CachedChampion
// {
// public System.IO.Stream ImageStream { get; set; }
// public DateTime AddedAt { get; set; }
// public string Name { get; set; }
// }
//
// private static Dictionary<string, CachedChampion> CachedChampionImages = new Dictionary<string, CachedChampion>();
// private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer();
// public LoLCommands(DiscordModule module) : base(module)
// {
// clearTimer.Interval = new TimeSpan(0, 10, 0).TotalMilliseconds;
// clearTimer.Start();
// clearTimer.Elapsed += (s, e) =>
// {
// try
// {
// CachedChampionImages = CachedChampionImages
// .Where(kvp => DateTime.UtcNow - kvp.Value.AddedAt > new TimeSpan(1, 0, 0))
// .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
// }
// catch { }
// };
// }
// public Func<CommandEventArgs, Task> DoFunc()
// {
// throw new NotImplementedException();
// }
// private class MatchupModel
// {
// public int Games { get; set; }
// public float WinRate { get; set; }
// [Newtonsoft.Json.JsonProperty("key")]
// public string Name { get; set; }
// public float StatScore { get; set; }
// }
// public override void Init(CommandGroupBuilder cgb)
// {
// cgb.CreateCommand(Module.Name + "lolchamp")
// .Description($"Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. |`{Prefix}lolchamp Riven` or `{Prefix}lolchamp Annie sup`")
// .Parameter("champ", ParameterType.Required)
// .Parameter("position", ParameterType.Unparsed)
// .Do(async e =>
// {
// try
// {
// //get role
// var role = ResolvePos(position);
// var resolvedRole = role;
// var name = champ.Replace(" ", "").ToLower();
// CachedChampion champ = null;
// if (CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ))
// if (champ != null)
// {
// champ.ImageStream.Position = 0;
// await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false);
// return;
// }
// var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={_creds.LOLAPIKey}").ConfigureAwait(false));
// JToken data = null;
// if (role != null)
// {
// for (var i = 0; i < allData.Count; i++)
// {
// if (allData[i]["role"].ToString().Equals(role))
// {
// data = allData[i];
// break;
// }
// }
// if (data == null)
// {
// await channel.SendMessageAsync("💢 Data for that role does not exist.").ConfigureAwait(false);
// return;
// }
// }
// else
// {
// data = allData[0];
// role = allData[0]["role"].ToString();
// resolvedRole = ResolvePos(role);
// }
// if (CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ))
// if (champ != null)
// {
// champ.ImageStream.Position = 0;
// await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false);
// return;
// }
// //name = data["title"].ToString();
// // get all possible roles, and "select" the shown one
// var roles = new string[allData.Count];
// for (var i = 0; i < allData.Count; i++)
// {
// roles[i] = allData[i]["role"].ToString();
// if (roles[i] == role)
// roles[i] = ">" + roles[i] + "<";
// }
// var general = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/" +
// $"champs/{name}?api_key={_creds.LOLAPIKey}")
// .ConfigureAwait(false))
// .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"];
// if (general == null)
// {
// return;
// }
// //get build data for this role
// var buildData = data["items"]["mostGames"]["items"];
// var items = new string[6];
// for (var i = 0; i < 6; i++)
// {
// items[i] = buildData[i]["id"].ToString();
// }
// //get matchup data to show counters and countered champions
// var matchupDataIE = data["matchups"].ToObject<List<MatchupModel>>();
// var matchupData = matchupDataIE.OrderBy(m => m.StatScore).ToArray();
// var countered = new[] { matchupData[0].Name, matchupData[1].Name, matchupData[2].Name };
// var counters = new[] { matchupData[matchupData.Length - 1].Name, matchupData[matchupData.Length - 2].Name, matchupData[matchupData.Length - 3].Name };
// //get runes data
// var runesJArray = data["runes"]["mostGames"]["runes"] as JArray;
// var runes = string.Join("\n", runesJArray.OrderBy(jt => int.Parse(jt["number"].ToString())).Select(jt => jt["number"].ToString() + "x" + jt["name"]));
// // get masteries data
// var masteries = (data["masteries"]["mostGames"]["masteries"] as JArray);
// //get skill order data<API_KEY>
// var orderArr = (data["skills"]["mostGames"]["order"] as JArray);
// var img = Image.FromFile("data/lol/bg.png");
// using (var g = Graphics.FromImage(img))
// {
// g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
// //g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
// const int margin = 5;
// const int imageSize = 75;
// var normalFont = new Font("Monaco", 8, FontStyle.Regular);
// var smallFont = new Font("Monaco", 7, FontStyle.Regular);
// //draw champ image
// var champName = data["key"].ToString().Replace(" ", "");
// g.DrawImage(GetImage(champName), new Rectangle(margin, margin, imageSize, imageSize));
// //draw champ name
// if (champName == "MonkeyKing")
// champName = "Wukong";
// g.DrawString($"{champName}", new Font("Times New Roman", 24, FontStyle.Regular), Brushes.WhiteSmoke, margin + imageSize + margin, margin);
// //draw champ surname
// //draw skill order
// if (orderArr.Count != 0)
// {
// float orderFormula = 120 / orderArr.Count;
// const float orderVerticalSpacing = 10;
// for (var i = 0; i < orderArr.Count; i++)
// {
// var orderX = margin + margin + imageSize + orderFormula * i + i;
// float orderY = margin + 35;
// var spellName = orderArr[i].ToString().ToLowerInvariant();
// switch (spellName)
// {
// case "w":
// orderY += orderVerticalSpacing;
// break;
// case "e":
// orderY += orderVerticalSpacing * 2;
// break;
// case "r":
// orderY += orderVerticalSpacing * 3;
// break;
// default:
// break;
// }
// g.DrawString(spellName.ToUpperInvariant(), new Font("Monaco", 7), Brushes.LimeGreen, orderX, orderY);
// }
// }
// //draw roles
// g.DrawString("Roles: " + string.Join(", ", roles), normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin);
// //draw average stats
// g.DrawString(
//$@" Average Stats
//Kills: {general["kills"]} CS: {general["minionsKilled"]}
//Deaths: {general["deaths"]} Win: {general["winPercent"]}%
//Assists: {general["assists"]} Ban: {general["banRate"]}%
//", normalFont, Brushes.WhiteSmoke, img.Width - 150, margin);
// //draw masteries
// g.DrawString($"Masteries: {string.Join(" / ", masteries?.Select(jt => jt["total"]))}", normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 20);
// //draw runes
// g.DrawString($"{runes}", smallFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 40);
// //draw counters
// g.DrawString($"Best against", smallFont, Brushes.WhiteSmoke, margin, img.Height - imageSize + margin);
// var smallImgSize = 50;
// for (var i = 0; i < counters.Length; i++)
// {
// g.DrawImage(GetImage(counters[i]),
// new Rectangle(i * (smallImgSize + margin) + margin, img.Height - smallImgSize - margin,
// smallImgSize,
// smallImgSize));
// }
// //draw countered by
// g.DrawString($"Worst against", smallFont, Brushes.WhiteSmoke, img.Width - 3 * (smallImgSize + margin), img.Height - imageSize + margin);
// for (var i = 0; i < countered.Length; i++)
// {
// var j = countered.Length - i;
// g.DrawImage(GetImage(countered[i]),
// new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), img.Height - smallImgSize - margin,
// smallImgSize,
// smallImgSize));
// }
// //draw item build
// g.DrawString("Popular build", normalFont, Brushes.WhiteSmoke, img.Width - (3 * (smallImgSize + margin) + margin), 77);
// for (var i = 0; i < 6; i++)
// {
// var inverseI = 5 - i;
// var j = inverseI % 3 + 1;
// var k = inverseI / 3;
// g.DrawImage(GetImage(items[i], GetImageType.Item),
// new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), 92 + k * (smallImgSize + margin),
// smallImgSize,
// smallImgSize));
// }
// }
// var cachedChamp = new CachedChampion { AddedAt = DateTime.UtcNow, ImageStream = img.ToStream(System.Drawing.Imaging.ImageFormat.Png), Name = name.ToLower() + "_" + resolvedRole };
// CachedChampionImages.Add(cachedChamp.Name, cachedChamp);
// await e.Channel.SendFile(data["title"] + "_stats.png", cachedChamp.ImageStream).ConfigureAwait(false);
// }
// catch (Exception ex)
// {
// await channel.SendMessageAsync("💢 Failed retreiving data for that champion.").ConfigureAwait(false);
// }
// });
// }
// private enum GetImageType
// {
// Champion,
// Item
// }
// private static Image GetImage(string id, GetImageType imageType = GetImageType.Champion)
// {
// try
// {
// switch (imageType)
// {
// case GetImageType.Champion:
// return Image.FromFile($"data/lol/champions/{id}.png");
// case GetImageType.Item:
// default:
// return Image.FromFile($"data/lol/items/{id}.png");
// }
// }
// catch (Exception)
// {
// return Image.FromFile("data/lol/_ERROR.png");
// }
// }
// private static string ResolvePos(string pos)
// {
// if (string.IsNullOrWhiteSpace(pos))
// return null;
// switch (pos.ToLowerInvariant())
// {
// case "m":
// case "mid":
// case "midorfeed":
// case "midd":
// case "middle":
// return "Middle";
// case "top":
// case "topp":
// case "t":
// case "toporfeed":
// return "Top";
// case "j":
// case "jun":
// case "jungl":
// case "jungle":
// return "Jungle";
// case "a":
// case "ad":
// case "adc":
// case "carry":
// case "ad carry":
// case "adcarry":
// case "c":
// return "ADC";
// case "s":
// case "sup":
// case "supp":
// case "support":
// return "Support";
// default:
// return pos;
// }
// }
// }
//}

View File

@ -38,6 +38,27 @@ namespace NadekoBot.Modules.Searches
} }
} }
//[NadekoCommand, Usage, Description, Aliases]
//[OwnerOnly]
//public async Task Obfuscate([Remainder] string txt)
//{
// var lastItem = "en";
// foreach (var item in _google.Languages.Except(new[] { "en" }).Where(x => x.Length < 4))
// {
// var txt2 = await _searches.Translate(lastItem + ">" + item, txt);
// await Context.Channel.EmbedAsync(new EmbedBuilder()
// .WithOkColor()
// .WithTitle(lastItem + ">" + item)
// .AddField("Input", txt)
// .AddField("Output", txt2));
// txt = txt2;
// await Task.Delay(500);
// lastItem = item;
// }
// txt = await _searches.Translate(lastItem + ">en", txt);
// await Context.Channel.SendConfirmAsync("Final output:\n\n" + txt);
//}
public enum AutoDeleteAutoTranslate public enum AutoDeleteAutoTranslate
{ {
Del, Del,

View File

@ -207,7 +207,7 @@ namespace NadekoBot.Modules.Utility.Services
if (guildSettings.TryRemove(guild.Id, out var setting) && cleanup) if (guildSettings.TryRemove(guild.Id, out var setting) && cleanup)
await RescanUsers(guild).ConfigureAwait(false); await RescanUsers(guild).ConfigureAwait(false);
} }
//todo multiple rescans at the same time?
private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null) private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
{ {
if (user.Game.HasValue && if (user.Game.HasValue &&

View File

@ -417,7 +417,7 @@ namespace NadekoBot.Modules.Utility
}) })
}); });
await Context.User.SendFileAsync( await Context.User.SendFileAsync(
await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title).ConfigureAwait(false); await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title, false).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Ping() public async Task Ping()

View File

@ -0,0 +1,311 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common;
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Xp
{
public partial class Xp
{
[Group]
public class Club : NadekoSubmodule<ClubService>
{
private readonly XpService _xps;
private readonly DiscordSocketClient _client;
public Club(XpService xps, DiscordSocketClient client)
{
_xps = xps;
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubCreate([Remainder]string clubName)
{
if (string.IsNullOrWhiteSpace(clubName) || clubName.Length > 20)
return;
if (!_service.CreateClub(Context.User, clubName, out ClubInfo club))
{
await ReplyErrorLocalized("club_create_error").ConfigureAwait(false);
return;
}
await ReplyConfirmLocalized("club_created", Format.Bold(club.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public Task ClubIcon([Remainder]string url = null)
{
if ((!Uri.IsWellFormedUriString(url, UriKind.Absolute) && url != null)
|| !_service.SetClubIcon(Context.User.Id, url))
{
return ReplyErrorLocalized("club_icon_error");
}
return ReplyConfirmLocalized("club_icon_set");
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public async Task ClubInformation(IUser user = null)
{
user = user ?? Context.User;
var club = _service.GetClubByMember(user);
if (club == null)
{
await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false);
return;
}
await ClubInformation(club.ToString()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public async Task ClubInformation([Remainder]string clubName = null)
{
if (string.IsNullOrWhiteSpace(clubName))
{
await ClubInformation(Context.User).ConfigureAwait(false);
return;
}
ClubInfo club;
if (!_service.GetClubByName(clubName, out club))
{
await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false);
return;
}
var lvl = new LevelStats(club.Xp);
await Context.Channel.SendPaginatedConfirmAsync(_client, 0, (page) =>
{
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle($"{club.ToString()}")
.WithDescription(GetText("level_x", lvl.Level) + $" ({club.Xp} xp)")
.AddField("Owner", club.Owner.ToString(), true)
.AddField("Level Req.", club.MinimumLevelReq.ToString(), true)
.AddField("Members", string.Join("\n", club.Users
.Skip(page * 10)
.Take(10)), false);
if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute))
return embed.WithThumbnailUrl(club.ImageUrl);
return embed;
}, club.Users.Count / 10);
}
[NadekoCommand, Usage, Description, Aliases]
public Task ClubBans(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
var club = _service.GetBansAndApplications(Context.User.Id);
if (club == null)
return ReplyErrorLocalized("club_not_exists");
var bans = club
.Bans
.Select(x => x.User)
.ToArray();
return Context.Channel.SendPaginatedConfirmAsync(_client, page,
curPage =>
{
var toShow = string.Join("\n", bans
.Skip(page * 10)
.Take(10)
.Select(x => x.ToString()));
return new EmbedBuilder()
.WithTitle(GetText("club_bans_for", club.ToString()))
.WithDescription(toShow);
}, bans.Length / 10);
}
[NadekoCommand, Usage, Description, Aliases]
public Task ClubApps(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
var club = _service.GetBansAndApplications(Context.User.Id);
if (club == null)
return ReplyErrorLocalized("club_not_exists");
var bans = club
.Applicants
.Select(x => x.User)
.ToArray();
return Context.Channel.SendPaginatedConfirmAsync(_client, page,
curPage =>
{
var toShow = string.Join("\n", bans
.Skip(page * 10)
.Take(10)
.Select(x => x.ToString()));
return new EmbedBuilder()
.WithTitle(GetText("club_apps_for", club.ToString()))
.WithDescription(toShow);
}, bans.Length / 10);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubApply([Remainder]string clubName)
{
if (string.IsNullOrWhiteSpace(clubName))
return;
if (!_service.GetClubByName(clubName, out ClubInfo club))
{
await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false);
return;
}
if (_service.ApplyToClub(Context.User, club))
{
await ReplyConfirmLocalized("club_applied", Format.Bold(club.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("club_apply_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public Task ClubAccept(IUser user)
=> ClubAccept(user.ToString());
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public async Task ClubAccept([Remainder]string userName)
{
if (_service.AcceptApplication(Context.User.Id, userName, out var discordUser))
{
await ReplyConfirmLocalized("club_accepted", Format.Bold(discordUser.ToString())).ConfigureAwait(false);
}
else
await ReplyErrorLocalized("club_accept_error").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Clubleave()
{
if (_service.LeaveClub(Context.User))
await ReplyConfirmLocalized("club_left").ConfigureAwait(false);
else
await ReplyErrorLocalized("club_not_in_club").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public Task ClubKick([Remainder]IUser user)
=> ClubKick(user.ToString());
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public Task ClubKick([Remainder]string userName)
{
if (_service.Kick(Context.User.Id, userName, out var club))
return ReplyConfirmLocalized("club_user_kick", Format.Bold(userName), Format.Bold(club.ToString()));
else
return ReplyErrorLocalized("club_user_kick_fail");
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public Task ClubBan([Remainder]IUser user)
=> ClubBan(user.ToString());
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public Task ClubBan([Remainder]string userName)
{
if (_service.Ban(Context.User.Id, userName, out var club))
return ReplyConfirmLocalized("club_user_banned", Format.Bold(userName), Format.Bold(club.ToString()));
else
return ReplyErrorLocalized("club_user_ban_fail");
}
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
public Task ClubUnBan([Remainder]IUser user)
=> ClubUnBan(user.ToString());
[NadekoCommand, Usage, Description, Aliases]
[Priority(0)]
public Task ClubUnBan([Remainder]string userName)
{
if (_service.UnBan(Context.User.Id, userName, out var club))
return ReplyConfirmLocalized("club_user_unbanned", Format.Bold(userName), Format.Bold(club.ToString()));
else
return ReplyErrorLocalized("club_user_unban_fail");
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubLevelReq(int level)
{
if (_service.ChangeClubLevelReq(Context.User.Id, level))
{
await ReplyConfirmLocalized("club_level_req_changed", Format.Bold(level.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("club_level_req_change_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ClubDisband()
{
if (_service.Disband(Context.User.Id, out ClubInfo club))
{
await ReplyConfirmLocalized("club_disbanded", Format.Bold(club.ToString())).ConfigureAwait(false);
}
else
{
await ReplyErrorLocalized("club_disaband_error").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public Task ClubLeaderboard(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
var clubs = _service.GetClubLeaderboardPage(page);
var embed = new EmbedBuilder()
.WithTitle(GetText("club_leaderboard", page + 1))
.WithOkColor();
var i = page * 9;
foreach (var club in clubs)
{
embed.AddField($"#{++i} " + club.ToString(), club.Xp.ToString() + " xp", false);
}
return Context.Channel.EmbedAsync(embed);
}
}
}
}

View File

@ -0,0 +1,26 @@
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Xp.Common
{
public class FullUserStats
{
public DiscordUser User { get; }
public UserXpStats FullGuildStats { get; }
public LevelStats Global { get; }
public LevelStats Guild { get; }
public int GlobalRanking { get; }
public int GuildRanking { get; }
public FullUserStats(DiscordUser usr,
UserXpStats fullGuildStats, LevelStats global,
LevelStats guild, int globalRanking, int guildRanking)
{
this.User = usr;
this.Global = global;
this.Guild = guild;
this.GlobalRanking = globalRanking;
this.GuildRanking = guildRanking;
this.FullGuildStats = fullGuildStats;
}
}
}

View File

@ -0,0 +1,43 @@
using NadekoBot.Modules.Xp.Services;
using System;
namespace NadekoBot.Modules.Xp.Common
{
public class LevelStats
{
public int Level { get; }
public int LevelXp { get; }
public int RequiredXp { get; }
public int TotalXp { get; }
public LevelStats(int xp)
{
if (xp < 0)
xp = 0;
TotalXp = xp;
const int baseXp = XpService.XP_REQUIRED_LVL_1;
var required = baseXp;
var totalXp = 0;
var lvl = 1;
while (true)
{
required = (int)(baseXp + baseXp / 4.0 * (lvl - 1));
if (required + totalXp > xp)
break;
totalXp += required;
lvl++;
}
Level = lvl - 1;
LevelXp = xp - totalXp;
RequiredXp = required;
}
public static LevelStats FromXp(int xp) => new LevelStats(xp);
}
}

View File

@ -0,0 +1,312 @@
using NadekoBot.Services;
using System;
using NadekoBot.Services.Database.Models;
using Discord;
using NadekoBot.Modules.Xp.Common;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace NadekoBot.Modules.Xp.Services
{
public class ClubService : INService
{
private readonly DbService _db;
public ClubService(DbService db)
{
_db = db;
}
public bool CreateClub(IUser user, string clubName, out ClubInfo club)
{
//must be lvl 5 and must not be in a club already
club = null;
using (var uow = _db.UnitOfWork)
{
var du = uow.DiscordUsers.GetOrCreate(user);
uow._context.SaveChanges();
var xp = new LevelStats(uow.Xp.GetTotalUserXp(user.Id));
if (xp.Level >= 5 && du.Club == null)
{
du.Club = new ClubInfo()
{
Name = clubName,
Discrim = uow.Clubs.GetNextDiscrim(clubName),
Owner = du,
};
uow.Clubs.Add(du.Club);
uow._context.SaveChanges();
}
else
return false;
uow._context.Set<ClubApplicants>()
.RemoveRange(uow._context.Set<ClubApplicants>().Where(x => x.UserId == du.Id));
club = du.Club;
uow.Complete();
}
return true;
}
public ClubInfo GetClubByMember(IUser user)
{
using (var uow = _db.UnitOfWork)
{
return uow.Clubs.GetByMember(user.Id);
}
}
public bool SetClubIcon(ulong ownerUserId, string url)
{
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(ownerUserId, set => set);
if (club == null)
return false;
club.ImageUrl = url;
uow.Complete();
}
return true;
}
public bool GetClubByName(string clubName, out ClubInfo club)
{
club = null;
var arr = clubName.Split('#');
if (arr.Length < 2 || !int.TryParse(arr[arr.Length - 1], out var discrim))
return false;
//incase club has # in it
var name = string.Concat(arr.Except(new[] { arr[arr.Length - 1] }));
if (string.IsNullOrWhiteSpace(name))
return false;
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByName(name.Trim().ToLowerInvariant(), discrim);
if (club == null)
return false;
else
return true;
}
}
public bool ApplyToClub(IUser user, ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
var du = uow.DiscordUsers.GetOrCreate(user);
uow._context.SaveChanges();
if (du.Club != null
|| new LevelStats(uow.Xp.GetTotalUserXp(user.Id)).Level < club.MinimumLevelReq
|| club.Bans.Any(x => x.UserId == du.Id)
|| club.Applicants.Any(x => x.UserId == du.Id))
{
//user banned or a member of a club, or already applied,
// or doesn't min minumum level requirement, can't apply
return false;
}
var app = new ClubApplicants
{
ClubId = club.Id,
UserId = du.Id,
};
uow._context.Set<ClubApplicants>().Add(app);
uow.Complete();
}
return true;
}
public bool AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
{
discordUser = null;
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(clubOwnerUserId,
set => set.Include(x => x.Applicants)
.ThenInclude(x => x.Club)
.Include(x => x.Applicants)
.ThenInclude(x => x.User));
if (club == null)
return false;
var applicant = club.Applicants.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant());
if (applicant == null)
return false;
applicant.User.Club = club;
club.Applicants.Remove(applicant);
//remove that user's all other applications
uow._context.Set<ClubApplicants>()
.RemoveRange(uow._context.Set<ClubApplicants>().Where(x => x.UserId == applicant.User.Id));
discordUser = applicant.User;
uow.Complete();
}
return true;
}
public ClubInfo GetBansAndApplications(ulong ownerUserId)
{
using (var uow = _db.UnitOfWork)
{
return uow.Clubs.GetByOwner(ownerUserId,
x => x.Include(y => y.Bans)
.ThenInclude(y => y.User)
.Include(y => y.Applicants)
.ThenInclude(y => y.User));
}
}
public bool LeaveClub(IUser user)
{
using (var uow = _db.UnitOfWork)
{
var du = uow.DiscordUsers.GetOrCreate(user);
if (du.Club == null || du.Club.OwnerId == du.Id)
return false;
du.Club = null;
uow.Complete();
}
return true;
}
public bool ChangeClubLevelReq(ulong userId, int level)
{
if (level < 5)
return false;
using (var uow = _db.UnitOfWork)
{
var club = uow.Clubs.GetByOwner(userId);
if (club == null)
return false;
club.MinimumLevelReq = level;
uow.Complete();
}
return true;
}
public bool Disband(ulong userId, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(userId);
if (club == null)
return false;
uow.Clubs.Remove(club);
uow.Complete();
}
return true;
}
public bool Ban(ulong ownerUserId, string userName, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(ownerUserId,
set => set.Include(x => x.Applicants)
.ThenInclude(x => x.User));
if (club == null)
return false;
var usr = club.Users.FirstOrDefault(x => x.ToString().ToLowerInvariant() == userName.ToLowerInvariant())
?? club.Applicants.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant())?.User;
if (usr == null)
return false;
if (club.OwnerId == usr.Id) // can't ban the owner kek, whew
return false;
club.Bans.Add(new ClubBans
{
Club = club,
User = usr,
});
club.Users.Remove(usr);
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
if (app != null)
club.Applicants.Remove(app);
uow.Complete();
}
return true;
}
public bool UnBan(ulong ownerUserId, string userName, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(ownerUserId,
set => set.Include(x => x.Bans)
.ThenInclude(x => x.User));
if (club == null)
return false;
var ban = club.Bans.FirstOrDefault(x => x.User.ToString().ToLowerInvariant() == userName.ToLowerInvariant());
if (ban == null)
return false;
club.Bans.Remove(ban);
uow.Complete();
}
return true;
}
public bool Kick(ulong ownerUserId, string userName, out ClubInfo club)
{
using (var uow = _db.UnitOfWork)
{
club = uow.Clubs.GetByOwner(ownerUserId);
if (club == null)
return false;
var usr = club.Users.FirstOrDefault(x => x.ToString().ToLowerInvariant() == userName.ToLowerInvariant());
if (usr == null)
return false;
if (club.OwnerId == usr.Id)
return false;
club.Users.Remove(usr);
var app = club.Applicants.FirstOrDefault(x => x.UserId == usr.Id);
if (app != null)
club.Applicants.Remove(app);
uow.Complete();
}
return true;
}
public ClubInfo[] GetClubLeaderboardPage(int page)
{
if (page < 0)
throw new ArgumentOutOfRangeException(nameof(page));
using (var uow = _db.UnitOfWork)
{
return uow.Clubs.GetClubLeaderboardPage(page);
}
}
}
}

View File

@ -1,18 +1,21 @@
namespace NadekoBot.Modules.Xp.Services using Discord;
namespace NadekoBot.Modules.Xp.Services
{ {
public class UserCacheItem public class UserCacheItem
{ {
public ulong UserId { get; set; } public IGuildUser User { get; set; }
public ulong GuildId { get; set; } public IGuild Guild { get; set; }
public IMessageChannel Channel { get; set; }
public override int GetHashCode() public override int GetHashCode()
{ {
return UserId.GetHashCode(); return User.GetHashCode();
} }
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return obj is UserCacheItem uci && uci.UserId == UserId; return obj is UserCacheItem uci && uci.User == User;
} }
} }
} }

View File

@ -1,8 +1,11 @@
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Common.Collections; using NadekoBot.Common.Collections;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Impl;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -10,57 +13,289 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageSharp;
using Image = ImageSharp.Image;
using SixLabors.Fonts;
using System.IO;
using SixLabors.Primitives;
using System.Net.Http;
using SixLabors.Shapes;
using System.Numerics;
using ImageSharp.Drawing.Pens;
using ImageSharp.Drawing.Brushes;
namespace NadekoBot.Modules.Xp.Services namespace NadekoBot.Modules.Xp.Services
{ {
public class XpService : INService public class XpService : INService
{ {
private enum NotifOf { Server, Global } // is it a server level-up or global level-up notification
private readonly DbService _db; private readonly DbService _db;
private readonly CommandHandler _cmd; private readonly CommandHandler _cmd;
private readonly IBotConfigProvider _bc; private readonly IBotConfigProvider _bc;
private readonly IImagesService _images;
private readonly Logger _log; private readonly Logger _log;
private readonly NadekoStrings _strings;
private readonly FontCollection _fonts = new FontCollection();
public const int XP_REQUIRED_LVL_1 = 36; public const int XP_REQUIRED_LVL_1 = 36;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles
= new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(); = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>();
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedChannels
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedChannels
= new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(); = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>();
private readonly ConcurrentHashSet<ulong> _excludedServers = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> _rewardedUsers = new ConcurrentHashSet<ulong>(); private readonly ConcurrentHashSet<ulong> _excludedServers
= new ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<UserCacheItem> _addMessageXp = new ConcurrentQueue<UserCacheItem>(); private readonly ConcurrentHashSet<ulong> _rewardedUsers
= new ConcurrentHashSet<ulong>();
private readonly ConcurrentQueue<UserCacheItem> _addMessageXp
= new ConcurrentQueue<UserCacheItem>();
private readonly ConcurrentDictionary<string, byte[]> _imageStreams
= new ConcurrentDictionary<string, byte[]>();
private readonly Timer updateXpTimer; private readonly Timer updateXpTimer;
private readonly Timer clearRewardedUsersTimer; private readonly HttpClient http = new HttpClient();
private FontFamily _usernameFontFamily;
private FontFamily _clubFontFamily;
private Font _levelFont;
private Font _xpFont;
private Font _awardedFont;
private Font _rankFont;
private Font _timeFont;
public XpService(CommandHandler cmd, IBotConfigProvider bc, public XpService(CommandHandler cmd, IBotConfigProvider bc,
IEnumerable<GuildConfig> allGuildConfigs, DbService db) IEnumerable<GuildConfig> allGuildConfigs, IImagesService images,
DbService db, NadekoStrings strings)
{ {
_db = db; _db = db;
_cmd = cmd; _cmd = cmd;
_bc = bc; _bc = bc;
_images = images;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_strings = strings;
//todo 60 move to font provider or somethign
_fonts = new FontCollection();
if (Directory.Exists("data/fonts"))
foreach (var file in Directory.GetFiles("data/fonts"))
{
_fonts.Install(file);
}
InitializeFonts();
_cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger; _cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger;
updateXpTimer = new Timer(_ => updateXpTimer = new Timer(async _ =>
{ {
using (var uow = _db.UnitOfWork) try
{ {
var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>();
var roleRewards = new Dictionary<ulong, List<XpRoleReward>>();
var toAddTo = new List<UserCacheItem>();
while (_addMessageXp.TryDequeue(out var usr)) while (_addMessageXp.TryDequeue(out var usr))
toAddTo.Add(usr);
var group = toAddTo.GroupBy(x => (GuildId: x.Guild.Id, User: x.User));
if (toAddTo.Count == 0)
return;
_log.Info("Adding XP to {0} users.", toAddTo.Count);
using (var uow = _db.UnitOfWork)
{ {
var usrObj = uow.Xp.GetOrCreateUser(usr.GuildId, usr.UserId); foreach (var item in group)
usrObj.Xp += _bc.BotConfig.XpPerMessage; {
var xp = item.Select(x => bc.BotConfig.XpPerMessage).Sum();
var usr = uow.Xp.GetOrCreateUser(item.Key.GuildId, item.Key.User.Id);
var du = uow.DiscordUsers.GetOrCreate(item.Key.User);
var globalXp = uow.Xp.GetTotalUserXp(item.Key.User.Id);
var oldGlobalLevelData = new LevelStats(globalXp);
var newGlobalLevelData = new LevelStats(globalXp + xp);
var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
usr.Xp += xp;
if (du.Club != null)
du.Club.Xp += xp;
var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp);
if (oldGlobalLevelData.Level < newGlobalLevelData.Level)
{
du.LastLevelUp = DateTime.UtcNow;
var first = item.First();
if (du.NotifyOnLevelUp != XpNotificationType.None)
toNotify.Add((first.Channel, first.User, newGlobalLevelData.Level, du.NotifyOnLevelUp, NotifOf.Global));
}
if (oldGuildLevelData.Level < newGuildLevelData.Level)
{
usr.LastLevelUp = DateTime.UtcNow;
//send level up notification
var first = item.First();
if (usr.NotifyOnLevelUp != XpNotificationType.None)
toNotify.Add((first.Channel, first.User, newGuildLevelData.Level, usr.NotifyOnLevelUp, NotifOf.Server));
//give role
if (!roleRewards.TryGetValue(usr.GuildId, out var rewards))
{
rewards = uow.GuildConfigs.XpSettingsFor(usr.GuildId).RoleRewards.ToList();
roleRewards.Add(usr.GuildId, rewards);
}
var rew = rewards.FirstOrDefault(x => x.Level == newGuildLevelData.Level);
if (rew != null)
{
var role = first.User.Guild.GetRole(rew.RoleId);
if (role != null)
{
var __ = first.User.AddRoleAsync(role);
}
}
}
}
uow.Complete();
} }
uow.Complete();
await Task.WhenAll(toNotify.Select(async x =>
{
if (x.NotifOf == NotifOf.Server)
{
if (x.NotifyType == XpNotificationType.Dm)
{
var chan = await x.User.GetOrCreateDMChannelAsync().ConfigureAwait(false);
if (chan != null)
await chan.SendConfirmAsync(_strings.GetText("level_up_dm",
(x.MessageChannel as ITextChannel)?.GuildId,
"xp",
x.User.Mention, Format.Bold(x.Level.ToString()),
Format.Bold((x.MessageChannel as ITextChannel)?.Guild.ToString() ?? "-")))
.ConfigureAwait(false);
}
else // channel
{
await x.MessageChannel.SendConfirmAsync(_strings.GetText("level_up_channel",
(x.MessageChannel as ITextChannel)?.GuildId,
"xp",
x.User.Mention, Format.Bold(x.Level.ToString())))
.ConfigureAwait(false);
}
}
else
{
IMessageChannel chan;
if (x.NotifyType == XpNotificationType.Dm)
{
chan = await x.User.GetOrCreateDMChannelAsync().ConfigureAwait(false);
}
else // channel
{
chan = x.MessageChannel;
}
await chan.SendConfirmAsync(_strings.GetText("level_up_global",
(x.MessageChannel as ITextChannel)?.GuildId,
"xp",
x.User.Mention, Format.Bold(x.Level.ToString())))
.ConfigureAwait(false);
}
}));
}
catch (Exception ex)
{
_log.Warn(ex);
} }
}, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)); }, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
clearRewardedUsersTimer = new Timer(_ => var clearRewardTimer = Task.Run(async () =>
{ {
_rewardedUsers.Clear(); while (true)
}, null, TimeSpan.FromSeconds(bc.BotConfig.XpMinutesTimeout), TimeSpan.FromSeconds(bc.BotConfig.XpMinutesTimeout)); {
_rewardedUsers.Clear();
await Task.Delay(TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout));
}
});
}
public IEnumerable<XpRoleReward> GetRoleRewards(ulong id)
{
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.XpSettingsFor(id)
.RoleRewards
.ToArray();
}
}
public void SetRoleReward(ulong guildId, int level, ulong? roleId)
{
using (var uow = _db.UnitOfWork)
{
var settings = uow.GuildConfigs.XpSettingsFor(guildId);
if (roleId == null)
{
settings.RoleRewards.RemoveWhere(x => x.Level == level);
}
else
{
var rew = settings.RoleRewards.FirstOrDefault(x => x.Level == level);
if (rew != null)
rew.RoleId = roleId.Value;
else
settings.RoleRewards.Add(new XpRoleReward()
{
Level = level,
RoleId = roleId.Value,
});
}
uow.Complete();
}
}
public UserXpStats[] GetUserXps(ulong guildId, int page)
{
using (var uow = _db.UnitOfWork)
{
return uow.Xp.GetUsersFor(guildId, page);
}
}
public (ulong UserId, int TotalXp)[] GetUserXps(int page)
{
using (var uow = _db.UnitOfWork)
{
return uow.Xp.GetUsersFor(page);
}
}
public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationType type)
{
using (var uow = _db.UnitOfWork)
{
var user = uow.Xp.GetOrCreateUser(guildId, userId);
user.NotifyOnLevelUp = type;
await uow.CompleteAsync().ConfigureAwait(false);
}
}
public async Task ChangeNotificationType(IUser user, XpNotificationType type)
{
using (var uow = _db.UnitOfWork)
{
var du = uow.DiscordUsers.GetOrCreate(user);
du.NotifyOnLevelUp = type;
await uow.CompleteAsync().ConfigureAwait(false);
}
} }
private Task _cmd_OnMessageNoTrigger(IUserMessage arg) private Task _cmd_OnMessageNoTrigger(IUserMessage arg)
@ -84,12 +319,25 @@ namespace NadekoBot.Modules.Xp.Services
user.Roles.Any(x => roles.Contains(x.Id))) user.Roles.Any(x => roles.Contains(x.Id)))
return; return;
_log.Info("Adding {0} xp to {1} on {2} server", _bc.BotConfig.XpPerMessage, user.ToString(), user.Guild.Name); if (!arg.Content.Contains(' ') && arg.Content.Length < 5)
_addMessageXp.Enqueue(new UserCacheItem { GuildId = user.Guild.Id, UserId = user.Id }); return;
_addMessageXp.Enqueue(new UserCacheItem { Guild = user.Guild, Channel = arg.Channel, User = user });
}); });
return Task.CompletedTask; return Task.CompletedTask;
} }
public void AddXp(ulong userId, ulong guildId, int amount)
{
using (var uow = _db.UnitOfWork)
{
var usr = uow.Xp.GetOrCreateUser(guildId, userId);
usr.AwardedXp += amount;
uow.Complete();
}
}
public bool IsServerExcluded(ulong id) public bool IsServerExcluded(ulong id)
{ {
return _excludedServers.Contains(id); return _excludedServers.Contains(id);
@ -116,25 +364,62 @@ namespace NadekoBot.Modules.Xp.Services
return _rewardedUsers.Add(userId); return _rewardedUsers.Add(userId);
} }
public UserXpStats GetUserStats(ulong guildId, ulong userId) public LevelStats GetGlobalUserStats(ulong userId)
{ {
UserXpStats user; int totalXp;
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
{ {
user = uow.Xp.GetOrCreateUser(guildId, userId); totalXp = uow.Xp.GetTotalUserXp(userId);
} }
return user; return new LevelStats(totalXp);
} }
public string GenerateXpBar(int currentXp, int requiredXp) public FullUserStats GetUserStats(IGuildUser user)
{ {
//todo DiscordUser du;
return $"{currentXp}/{requiredXp}"; UserXpStats stats;
int totalXp;
int globalRank;
int guildRank;
using (var uow = _db.UnitOfWork)
{
du = uow.DiscordUsers.GetOrCreate(user);
stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id);
totalXp = uow.Xp.GetTotalUserXp(user.Id);
globalRank = uow.Xp.GetUserGlobalRanking(user.Id);
guildRank = uow.Xp.GetUserGuildRanking(user.Id, user.GuildId);
}
return new FullUserStats(du,
stats,
new LevelStats(totalXp),
new LevelStats(stats.Xp + stats.AwardedXp),
globalRank,
guildRank);
} }
public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(UserXpStats stats)
{
var baseXp = XpService.XP_REQUIRED_LVL_1;
var required = baseXp;
var totalXp = 0;
var lvl = 1;
while (true)
{
required = (int)(baseXp + baseXp / 4.0 * (lvl - 1));
if (required + totalXp > stats.Xp)
break;
totalXp += required;
lvl++;
}
return (lvl - 1, stats.Xp - totalXp, required);
}
//todo exclude in database
public bool ToggleExcludeServer(ulong id) public bool ToggleExcludeServer(ulong id)
{ {
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
@ -225,5 +510,222 @@ namespace NadekoBot.Modules.Xp.Services
} }
} }
} }
public Task<Image<Rgba32>> GenerateImageAsync(IGuildUser user)
{
return GenerateImageAsync(GetUserStats(user));
}
private void InitializeFonts()
{
_usernameFontFamily = _fonts.Find("Whitney-Bold");
_clubFontFamily = _fonts.Find("Whitney-Bold");
_levelFont = _fonts.Find("Whitney-Bold").CreateFont(45);
_xpFont = _fonts.Find("Whitney-Bold").CreateFont(50);
_awardedFont = _fonts.Find("Whitney-Bold").CreateFont(25);
_rankFont = _fonts.Find("Uni Sans Thin CAPS").CreateFont(30);
_timeFont = _fonts.Find("Whitney-Bold").CreateFont(20);
}
public async Task<Image<Rgba32>> GenerateImageAsync(FullUserStats stats)
{
var img = Image.Load(_images.XpCard.ToArray());
var username = stats.User.ToString();
var usernameFont = _usernameFontFamily
.CreateFont(username.Length <= 6
? 50
: 50 - username.Length);
img.DrawText("@" + username, usernameFont, Rgba32.White,
new PointF(130, 5));
// level
img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 137));
img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White,
new PointF(47, 285));
//club name
var clubName = stats.User.Club?.ToString() ?? "-";
var clubFont = _clubFontFamily
.CreateFont(clubName.Length <= 8
? 35
: 35 - (clubName.Length / 2));
img.DrawText(clubName, clubFont, Rgba32.White,
new PointF(650 - clubName.Length * 10, 40));
var pen = new Pen<Rgba32>(Rgba32.Black, 1);
var brush = Brushes.Solid<Rgba32>(Rgba32.White);
var xpBgBrush = Brushes.Solid<Rgba32>(new Rgba32(0, 0, 0, 0.4f));
var global = stats.Global;
var guild = stats.Guild;
//xp bar
img.FillPolygon(xpBgBrush, new[] {
new PointF(321, 104),
new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104),
new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235),
new PointF(286, 235),
});
img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen,
new PointF(430, 130));
img.FillPolygon(xpBgBrush, new[] {
new PointF(282, 248),
new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248),
new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379),
new PointF(247, 379),
});
img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen,
new PointF(400, 270));
if (stats.FullGuildStats.AwardedXp != 0)
{
var sign = stats.FullGuildStats.AwardedXp > 0
? "+ "
: "";
img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen,
new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335));
}
//ranking
img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 170));
img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White,
new PointF(148, 317));
//time on this level
string GetTimeSpent(DateTime time)
{
var offset = DateTime.UtcNow - time;
return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m";
}
img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 197));
img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White,
new PointF(50, 344));
//avatar
if (stats.User.AvatarId != null)
{
try
{
var avatarUrl = stats.User.RealAvatarUrl();
byte[] s;
if (!_imageStreams.TryGetValue(avatarUrl, out s))
{
using (var temp = await http.GetStreamAsync(avatarUrl))
{
var tempDraw = Image.Load(temp);
tempDraw = tempDraw.Resize(69, 70);
ApplyRoundedCorners(tempDraw, 35);
s = tempDraw.ToStream().ToArray();
}
_imageStreams.AddOrUpdate(avatarUrl, s, (k, v) => s);
}
var toDraw = Image.Load(s);
img.DrawImage(toDraw,
1,
new Size(69, 70),
new Point(32, 10));
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
//club image
if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl))
{
var imgUrl = stats.User.Club.ImageUrl;
try
{
byte[] s;
if (!_imageStreams.TryGetValue(imgUrl, out s))
{
using (var temp = await http.GetStreamAsync(imgUrl))
{
var tempDraw = Image.Load(temp);
tempDraw = tempDraw.Resize(45, 45);
ApplyRoundedCorners(tempDraw, 22.5f);
s = tempDraw.ToStream().ToArray();
}
_imageStreams.AddOrUpdate(imgUrl, s, (k, v) => s);
}
var toDraw = Image.Load(s);
img.DrawImage(toDraw,
1,
new Size(45, 45),
new Point(722, 25));
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
var arr = img.ToStream().ToArray();
//_log.Info("{0:F2} KB", arr.Length * 1.0f / 1.KB());
return img;
}
// https://github.com/SixLabors/ImageSharp/tree/master/samples/AvatarWithRoundedCorner
public static void ApplyRoundedCorners(Image<Rgba32> img, float cornerRadius)
{
var corners = BuildCorners(img.Width, img.Height, cornerRadius);
// now we have our corners time to draw them
img.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true)
{
BlenderMode = ImageSharp.PixelFormats.PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background
});
}
public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius)
{
// first create a square
var rect = new RectangularePolygon(-0.5f, -0.5f, cornerRadius, cornerRadius);
// then cut out of the square a circle so we are left with a corner
var cornerToptLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius));
// corner is now a corner shape positions top left
//lets make 3 more positioned correctly, we can do that by translating the orgional around the center of the image
var center = new Vector2(imageWidth / 2, imageHeight / 2);
float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1;
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1;
// move it across the width of the image - the width of the shape
var cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0);
var cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos);
var cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos);
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
}
} }
} }

View File

@ -1,9 +1,12 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes; using NadekoBot.Common.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Extensions; using NadekoBot.Modules.Xp.Common;
using NadekoBot.Modules.Xp.Services; using NadekoBot.Modules.Xp.Services;
using NadekoBot.Services.Database.Models;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,37 +14,94 @@ namespace NadekoBot.Modules.Xp
{ {
public partial class Xp : NadekoTopLevelModule<XpService> public partial class Xp : NadekoTopLevelModule<XpService>
{ {
private readonly DiscordSocketClient _client;
public Xp(DiscordSocketClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Experience(IUser user = null) public async Task Experience([Remainder]IUser user = null)
{ {
user = user ?? Context.User; user = user ?? Context.User;
await Task.Delay(64).ConfigureAwait(false); // wait a bit in case user got XP with this message
var stats = _service.GetUserStats(Context.Guild.Id, user.Id); await Context.Channel.TriggerTypingAsync();
var img = await _service.GenerateImageAsync((IGuildUser)user);
var levelData = stats.GetLevelData();
var xpBarStr = _service.GenerateXpBar(levelData.LevelXp, levelData.LevelRequiredXp); await Context.Channel.SendFileAsync(img.ToStream(), $"{user.Id}_xp.png")
await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithTitle(user.ToString())
//.AddField(GetText("server_level"), stats.ServerLevel.ToString(), true)
.AddField(GetText("level"), levelData.Level.ToString(), true)
//.AddField(GetText("club"), stats.ClubName ?? "-", true)
.AddField(GetText("xp"), xpBarStr, false)
.WithOkColor())
.ConfigureAwait(false); .ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task XpRoleRewards(int page = 1)
{
page--;
if (page < 0)
return Task.CompletedTask;
var roles = _service.GetRoleRewards(Context.Guild.Id)
.OrderBy(x => x.Level)
.Skip(page * 9)
.Take(9);
var embed = new EmbedBuilder()
.WithTitle(GetText("role_rewards"))
.WithOkColor();
if (!roles.Any())
return Context.Channel.EmbedAsync(embed.WithDescription(GetText("no_role_rewards")));
foreach (var rolerew in roles)
{
var role = Context.Guild.GetRole(rolerew.RoleId);
if (role == null)
continue;
embed.AddField(GetText("level_x", Format.Bold(rolerew.Level.ToString())), role.ToString());
}
return Context.Channel.EmbedAsync(embed);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageRoles)]
[RequireContext(ContextType.Guild)]
public async Task XpRoleReward(int level, [Remainder] IRole role = null)
{
if (level < 1)
return;
_service.SetRoleReward(Context.Guild.Id, level, role?.Id);
if(role == null)
await ReplyConfirmLocalized("role_reward_cleared", level).ConfigureAwait(false);
else
await ReplyConfirmLocalized("role_reward_added", level, Format.Bold(role.ToString())).ConfigureAwait(false);
}
public enum NotifyPlace
{
Server = 0,
Guild = 0,
Global = 1,
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task XpNotify(NotifyPlace place = NotifyPlace.Guild, XpNotificationType type = XpNotificationType.Channel)
{
if (place == NotifyPlace.Guild)
await _service.ChangeNotificationType(Context.User.Id, Context.Guild.Id, type);
else
await _service.ChangeNotificationType(Context.User, type);
await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false);
}
public enum Server { Server }; public enum Server { Server };
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//[OwnerOnly]
//[Priority(1)]
//public async Task XpExclude(Server _, IGuild guild)
//{
//}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -101,5 +161,91 @@ namespace NadekoBot.Modules.Xp
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task XpLeaderboard(int page = 1)
{
if (--page < 0)
return Task.CompletedTask;
return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) =>
{
var users = _service.GetUserXps(Context.Guild.Id, curPage);
var embed = new EmbedBuilder()
.WithTitle(GetText("server_leaderboard"))
.WithOkColor();
if (!users.Any())
return embed.WithDescription("-");
else
{
for (int i = 0; i < users.Length; i++)
{
var levelStats = LevelStats.FromXp(users[i].Xp + users[i].AwardedXp);
var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false);
var userXpData = users[i];
var awardStr = "";
if (userXpData.AwardedXp > 0)
awardStr = $"(+{userXpData.AwardedXp})";
else if (userXpData.AwardedXp < 0)
awardStr = $"({userXpData.AwardedXp.ToString()})";
embed.AddField(
$"#{(i + 1 + curPage * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}",
$"{GetText("level_x", levelStats.Level)} - {levelStats.TotalXp}xp {awardStr}");
}
return embed;
}
}, addPaginatedFooter: false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task XpGlobalLeaderboard(int page = 1)
{
if (--page < 0)
return;
await Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) =>
{
var users = _service.GetUserXps(curPage);
var embed = new EmbedBuilder()
.WithTitle(GetText("global_leaderboard"))
.WithOkColor();
if (!users.Any())
return embed.WithDescription("-");
else
{
for (int i = 0; i < users.Length; i++)
{
var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false);
embed.AddField(
$"#{(i + 1 + curPage * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}",
$"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp");
}
return embed;
}
}, addPaginatedFooter: false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task XpAdd(int amount, [Remainder] IGuildUser user)
{
if (amount == 0)
return;
_service.AddXp(user.Id, Context.Guild.Id, amount);
await ReplyConfirmLocalized("modified", Format.Bold(user.ToString()), Format.Bold(amount.ToString())).ConfigureAwait(false);
}
} }
} }

View File

@ -58,14 +58,14 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AngleSharp" Version="0.9.9" /> <PackageReference Include="AngleSharp" Version="0.9.9" />
<PackageReference Include="Discord.Net" Version="1.0.2-build-00797" /> <PackageReference Include="Discord.Net" Version="2.0.0-alpha-build-00824" />
<PackageReference Include="libvideo" Version="1.0.1" /> <PackageReference Include="libvideo" Version="1.0.1" />
<PackageReference Include="CoreCLR-NCalc" Version="2.1.2" /> <PackageReference Include="CoreCLR-NCalc" Version="2.1.2" />
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" /> <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" />
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.20.0.701" /> <PackageReference Include="Google.Apis.YouTube.v3" Version="1.20.0.701" />
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.20.0.466" /> <PackageReference Include="Google.Apis.Customsearch.v1" Version="1.20.0.466" />
<PackageReference Include="ImageSharp" Version="1.0.0-alpha9-00173" /> <PackageReference Include="ImageSharp" Version="1.0.0-alpha9-00194" />
<PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha9-00168" /> <PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha9-00189" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
@ -87,6 +87,14 @@
<NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> <NoWarn>$(NoWarn);CS1573;CS1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />

View File

@ -3600,4 +3600,184 @@
<data name="xpexclude_desc" xml:space="preserve"> <data name="xpexclude_desc" xml:space="preserve">
<value>Exclude a user or a role from the xp system, or whole current server.</value> <value>Exclude a user or a role from the xp system, or whole current server.</value>
</data> </data>
<data name="xpnotify_cmd" xml:space="preserve">
<value>xpnotify xpn</value>
</data>
<data name="xpnotify_usage" xml:space="preserve">
<value>`{0}xpn global dm` `{0}xpn server channel`</value>
</data>
<data name="xpnotify_desc" xml:space="preserve">
<value>Sets how the bot should notify you when you get a `server` or `global` level. You can set `dm` (for the bot to send a direct message), `channel` (to get notified in the channel you sent the last message in) or `none` to disable.</value>
</data>
<data name="xprolerewards_cmd" xml:space="preserve">
<value>xprolerewards xprrs</value>
</data>
<data name="xprolerewards_usage" xml:space="preserve">
<value>`{0}xprrs`</value>
</data>
<data name="xprolerewards_desc" xml:space="preserve">
<value>Shows currently set role rewards.</value>
</data>
<data name="xprolereward_cmd" xml:space="preserve">
<value>xprolereward xprr</value>
</data>
<data name="xprolereward_usage" xml:space="preserve">
<value>`{0}xprr 3 Social`</value>
</data>
<data name="xprolereward_desc" xml:space="preserve">
<value>Sets a role reward on a specified level.</value>
</data>
<data name="xpleaderboard_cmd" xml:space="preserve">
<value>xpleaderboard xplb</value>
</data>
<data name="xpleaderboard_usage" xml:space="preserve">
<value>`{0}xplb`</value>
</data>
<data name="xpleaderboard_desc" xml:space="preserve">
<value>Shows current server's xp leaderboard.</value>
</data>
<data name="xpgloballeaderboard_cmd" xml:space="preserve">
<value>xpgleaderboard xpglb</value>
</data>
<data name="xpgloballeaderboard_usage" xml:space="preserve">
<value>`{0}xpglb`</value>
</data>
<data name="xpgloballeaderboard_desc" xml:space="preserve">
<value>Shows current server's xp leaderboard.</value>
</data>
<data name="xpadd_cmd" xml:space="preserve">
<value>xpadd</value>
</data>
<data name="xpadd_usage" xml:space="preserve">
<value>`{0}xpadd 100 @b1nzy`</value>
</data>
<data name="xpadd_desc" xml:space="preserve">
<value>Adds xp to a user on the server. This does not affect their global ranking. You can use negative values.</value>
</data>
<data name="clubcreate_cmd" xml:space="preserve">
<value>clubcreate</value>
</data>
<data name="clubcreate_usage" xml:space="preserve">
<value>`{0}clubcreate b1nzy's friends`</value>
</data>
<data name="clubcreate_desc" xml:space="preserve">
<value>Creates a club. You must be atleast level 5 and not be in the club already.</value>
</data>
<data name="clubinformation_cmd" xml:space="preserve">
<value>clubinfo</value>
</data>
<data name="clubinformation_usage" xml:space="preserve">
<value>`{0}clubinfo b1nzy's friends#123`</value>
</data>
<data name="clubinformation_desc" xml:space="preserve">
<value>Shows information about the club.</value>
</data>
<data name="clubapply_cmd" xml:space="preserve">
<value>clubapply</value>
</data>
<data name="clubapply_usage" xml:space="preserve">
<value>`{0}clubapply b1nzy's friends#123`</value>
</data>
<data name="clubapply_desc" xml:space="preserve">
<value>Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list.</value>
</data>
<data name="clubaccept_cmd" xml:space="preserve">
<value>clubaccept</value>
</data>
<data name="clubaccept_usage" xml:space="preserve">
<value>`{0}clubaccept b1nzy#1337`</value>
</data>
<data name="clubaccept_desc" xml:space="preserve">
<value>Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list.</value>
</data>
<data name="clubleave_cmd" xml:space="preserve">
<value>clubleave</value>
</data>
<data name="clubleave_usage" xml:space="preserve">
<value>`{0}clubleave`</value>
</data>
<data name="clubleave_desc" xml:space="preserve">
<value>Leaves the club you're currently in.</value>
</data>
<data name="clubdisband_cmd" xml:space="preserve">
<value>clubdisband</value>
</data>
<data name="clubdisband_usage" xml:space="preserve">
<value>`{0}clubdisband`</value>
</data>
<data name="clubdisband_desc" xml:space="preserve">
<value>Disbands the club you're the owner of. This action is irreversible.</value>
</data>
<data name="clubkick_cmd" xml:space="preserve">
<value>clubkick</value>
</data>
<data name="clubkick_usage" xml:space="preserve">
<value>`{0}clubkick b1nzy#1337`</value>
</data>
<data name="clubkick_desc" xml:space="preserve">
<value>Kicks the user from the club. You must be the club owner. They will be able to apply again.</value>
</data>
<data name="clubban_cmd" xml:space="preserve">
<value>clubban</value>
</data>
<data name="clubban_usage" xml:space="preserve">
<value>`{0}clubban b1nzy#1337`</value>
</data>
<data name="clubban_desc" xml:space="preserve">
<value>Bans the user from the club. You must be the club owner. They will not be able to apply again.</value>
</data>
<data name="clubunban_cmd" xml:space="preserve">
<value>clubunban</value>
</data>
<data name="clubunban_usage" xml:space="preserve">
<value>`{0}clubunban b1nzy#1337`</value>
</data>
<data name="clubunban_desc" xml:space="preserve">
<value>Unbans the previously banned user from the club. You must be the club owner.</value>
</data>
<data name="clublevelreq_cmd" xml:space="preserve">
<value>clublevelreq</value>
</data>
<data name="clublevelreq_usage" xml:space="preserve">
<value>`{0}clublevelreq 7`</value>
</data>
<data name="clublevelreq_desc" xml:space="preserve">
<value>Sets the club required level to apply to join the club. You must be club owner. You can't set this number below 5.</value>
</data>
<data name="clubicon_cmd" xml:space="preserve">
<value>clubicon</value>
</data>
<data name="clubicon_usage" xml:space="preserve">
<value>`{0}clubicon https://i.imgur.com/htfDMfU.png`</value>
</data>
<data name="clubicon_desc" xml:space="preserve">
<value>Sets the club icon.</value>
</data>
<data name="clubapps_cmd" xml:space="preserve">
<value>clubapps</value>
</data>
<data name="clubapps_usage" xml:space="preserve">
<value>`{0}clubapps 2`</value>
</data>
<data name="clubapps_desc" xml:space="preserve">
<value>Shows the list of users who have applied to your club. Paginated. You must be club owner to use this command.</value>
</data>
<data name="clubbans_cmd" xml:space="preserve">
<value>clubbans</value>
</data>
<data name="clubbans_usage" xml:space="preserve">
<value>`{0}clubbans 2`</value>
</data>
<data name="clubbans_desc" xml:space="preserve">
<value>Shows the list of users who have banned from your club. Paginated. You must be club owner to use this command.</value>
</data>
<data name="clubleaderboard_cmd" xml:space="preserve">
<value>clublb</value>
</data>
<data name="clubleaderboard_usage" xml:space="preserve">
<value>`{0}clublb 2`</value>
</data>
<data name="clubleaderboard_desc" xml:space="preserve">
<value>Shows club rankings on the specified page.</value>
</data>
</root> </root>

View File

@ -39,7 +39,7 @@ namespace NadekoBot.Services
public string DefaultPrefix { get; private set; } public string DefaultPrefix { get; private set; }
private ConcurrentDictionary<ulong, string> _prefixes { get; } = new ConcurrentDictionary<ulong, string>(); private ConcurrentDictionary<ulong, string> _prefixes { get; } = new ConcurrentDictionary<ulong, string>();
private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels { get; set; } = new ImmutableArray<AsyncLazy<IDMChannel>>(); private ImmutableArray<AsyncLazy<IDMChannel>> OwnerChannels { get; set; } = new ImmutableArray<AsyncLazy<IDMChannel>>();
public event Func<IUserMessage, CommandInfo, Task> CommandExecuted = delegate { return Task.CompletedTask; }; public event Func<IUserMessage, CommandInfo, Task> CommandExecuted = delegate { return Task.CompletedTask; };
public event Func<CommandInfo, ITextChannel, string, Task> CommandErrored = delegate { return Task.CompletedTask; }; public event Func<CommandInfo, ITextChannel, string, Task> CommandErrored = delegate { return Task.CompletedTask; };

View File

@ -25,6 +25,7 @@ namespace NadekoBot.Services.Database
IDiscordUserRepository DiscordUsers { get; } IDiscordUserRepository DiscordUsers { get; }
IWarningsRepository Warnings { get; } IWarningsRepository Warnings { get; }
IXpRepository Xp { get; } IXpRepository Xp { get; }
IClubRepository Clubs { get; }
int Complete(); int Complete();
Task<int> CompleteAsync(); Task<int> CompleteAsync();

View File

@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace NadekoBot.Services.Database.Models
{
public class ClubInfo : DbEntity
{
[MaxLength(20)]
public string Name { get; set; }
public int Discrim { get; set; }
public string ImageUrl { get; set; } = "";
public int MinimumLevelReq { get; set; } = 5;
public int Xp { get; set; } = 0;
public int OwnerId { get; set; }
public DiscordUser Owner { get; set; }
public List<DiscordUser> Users { get; set; } = new List<DiscordUser>();
public List<ClubApplicants> Applicants { get; set; } = new List<ClubApplicants>();
public List<ClubBans> Bans { get; set; } = new List<ClubBans>();
public override string ToString()
{
return Name + "#" + Discrim;
}
}
public class ClubApplicants
{
public int ClubId { get; set; }
public ClubInfo Club { get; set; }
public int UserId { get; set; }
public DiscordUser User { get; set; }
}
public class ClubBans
{
public int ClubId { get; set; }
public ClubInfo Club { get; set; }
public int UserId { get; set; }
public DiscordUser User { get; set; }
}
}

View File

@ -1,4 +1,6 @@
namespace NadekoBot.Services.Database.Models using System;
namespace NadekoBot.Services.Database.Models
{ {
public class DiscordUser : DbEntity public class DiscordUser : DbEntity
{ {
@ -6,6 +8,22 @@
public string Username { get; set; } public string Username { get; set; }
public string Discriminator { get; set; } public string Discriminator { get; set; }
public string AvatarId { get; set; } public string AvatarId { get; set; }
public ClubInfo Club { get; set; }
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
public XpNotificationType NotifyOnLevelUp { get; set; }
public override bool Equals(object obj)
{
return obj is DiscordUser du
? du.UserId == UserId
: false;
}
public override int GetHashCode()
{
return UserId.GetHashCode();
}
public override string ToString() => public override string ToString() =>
Username + "#" + Discriminator; Username + "#" + Discriminator;

View File

@ -1,10 +1,16 @@
namespace NadekoBot.Services.Database.Models using System;
namespace NadekoBot.Services.Database.Models
{ {
public class UserXpStats : DbEntity public class UserXpStats : DbEntity
{ {
public ulong UserId { get; set; } public ulong UserId { get; set; }
public ulong GuildId { get; set; } public ulong GuildId { get; set; }
public int Xp { get; set; } public int Xp { get; set; }
public bool NotifyOnLevelUp { get; set; } public int AwardedXp { get; set; }
public XpNotificationType NotifyOnLevelUp { get; set; }
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
} }
public enum XpNotificationType { None, Dm, Channel }
} }

View File

@ -4,6 +4,7 @@ using System.Linq;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using System;
namespace NadekoBot.Services.Database namespace NadekoBot.Services.Database
{ {
@ -44,6 +45,7 @@ namespace NadekoBot.Services.Database
public DbSet<WaifuUpdate> WaifuUpdates { get; set; } public DbSet<WaifuUpdate> WaifuUpdates { get; set; }
public DbSet<Warning> Warnings { get; set; } public DbSet<Warning> Warnings { get; set; }
public DbSet<UserXpStats> UserXpStats { get; set; } public DbSet<UserXpStats> UserXpStats { get; set; }
public DbSet<ClubInfo> Clubs { get; set; }
//logging //logging
public DbSet<LogSetting> LogSettings { get; set; } public DbSet<LogSetting> LogSettings { get; set; }
@ -71,24 +73,6 @@ namespace NadekoBot.Services.Database
{ {
var bc = new BotConfig(); var bc = new BotConfig();
bc.ModulePrefixes.AddRange(new HashSet<ModulePrefix>()
{
new ModulePrefix() { ModuleName = "Administration", Prefix = "." },
new ModulePrefix() { ModuleName = "Searches", Prefix = "~" },
new ModulePrefix() { ModuleName = "Translator", Prefix = "~" },
new ModulePrefix() { ModuleName = "NSFW", Prefix = "~" },
new ModulePrefix() { ModuleName = "ClashOfClans", Prefix = "," },
new ModulePrefix() { ModuleName = "Help", Prefix = "-" },
new ModulePrefix() { ModuleName = "Music", Prefix = "!!" },
new ModulePrefix() { ModuleName = "Trello", Prefix = "trello" },
new ModulePrefix() { ModuleName = "Games", Prefix = ">" },
new ModulePrefix() { ModuleName = "Gambling", Prefix = "$" },
new ModulePrefix() { ModuleName = "Permissions", Prefix = ";" },
new ModulePrefix() { ModuleName = "Pokemon", Prefix = ">" },
new ModulePrefix() { ModuleName = "Utility", Prefix = "." },
new ModulePrefix() { ModuleName = "CustomReactions", Prefix = "." },
new ModulePrefix() { ModuleName = "PokeGame", Prefix = ">" }
});
bc.RaceAnimals.AddRange(new HashSet<RaceAnimal> bc.RaceAnimals.AddRange(new HashSet<RaceAnimal>
{ {
new RaceAnimal { Icon = "🐼", Name = "Panda" }, new RaceAnimal { Icon = "🐼", Name = "Panda" },
@ -177,7 +161,14 @@ namespace NadekoBot.Services.Database
#endregion #endregion
#region BotConfig #region BotConfig
//var botConfigEntity = modelBuilder.Entity<BotConfig>(); var botConfigEntity = modelBuilder.Entity<BotConfig>();
botConfigEntity.Property(x => x.XpMinutesTimeout)
.HasDefaultValue(5);
botConfigEntity.Property(x => x.XpPerMessage)
.HasDefaultValue(3);
//botConfigEntity //botConfigEntity
// .HasMany(c => c.ModulePrefixes) // .HasMany(c => c.ModulePrefixes)
// .WithOne(mp => mp.BotConfig) // .WithOne(mp => mp.BotConfig)
@ -278,9 +269,19 @@ namespace NadekoBot.Services.Database
// .WithOne(); // .WithOne();
// //.HasForeignKey<WaifuInfo>(w => w.ClaimerId) // //.HasForeignKey<WaifuInfo>(w => w.ClaimerId)
// //.IsRequired(false); // //.IsRequired(false);
#endregion
#region DiscordUser
var du = modelBuilder.Entity<DiscordUser>(); var du = modelBuilder.Entity<DiscordUser>();
du.HasAlternateKey(w => w.UserId); du.HasAlternateKey(w => w.UserId);
du.HasOne(x => x.Club)
.WithMany(x => x.Users)
.IsRequired(false);
modelBuilder.Entity<DiscordUser>()
.Property(x => x.LastLevelUp)
.HasDefaultValue(DateTime.Now);
#endregion #endregion
@ -294,10 +295,14 @@ namespace NadekoBot.Services.Database
.IsUnique(); .IsUnique();
#endregion #endregion
#region XpStatas #region XpStats
modelBuilder.Entity<UserXpStats>() modelBuilder.Entity<UserXpStats>()
.HasIndex(x => new { x.UserId, x.GuildId }) .HasIndex(x => new { x.UserId, x.GuildId })
.IsUnique(); .IsUnique();
modelBuilder.Entity<UserXpStats>()
.Property(x => x.LastLevelUp)
.HasDefaultValue(DateTime.Now);
#endregion #endregion
#region XpSettings #region XpSettings
@ -305,6 +310,47 @@ namespace NadekoBot.Services.Database
.HasOne(x => x.GuildConfig) .HasOne(x => x.GuildConfig)
.WithOne(x => x.XpSettings); .WithOne(x => x.XpSettings);
#endregion #endregion
#region XpRoleReward
modelBuilder.Entity<XpRoleReward>()
.HasAlternateKey(x => x.Level);
#endregion
#region Club
var ci = modelBuilder.Entity<ClubInfo>();
ci.HasOne(x => x.Owner)
.WithOne()
.HasForeignKey<ClubInfo>(x => x.OwnerId);
ci.HasAlternateKey(x => new { x.Name, x.Discrim });
#endregion
#region ClubManytoMany
modelBuilder.Entity<ClubApplicants>()
.HasKey(t => new { t.ClubId, t.UserId });
modelBuilder.Entity<ClubApplicants>()
.HasOne(pt => pt.User)
.WithMany();
modelBuilder.Entity<ClubApplicants>()
.HasOne(pt => pt.Club)
.WithMany(x => x.Applicants);
modelBuilder.Entity<ClubBans>()
.HasKey(t => new { t.ClubId, t.UserId });
modelBuilder.Entity<ClubBans>()
.HasOne(pt => pt.User)
.WithMany();
modelBuilder.Entity<ClubBans>()
.HasOne(pt => pt.Club)
.WithMany(x => x.Bans);
#endregion
} }
} }
} }

View File

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
using System;
using System.Linq;
namespace NadekoBot.Services.Database.Repositories
{
public interface IClubRepository : IRepository<ClubInfo>
{
int GetNextDiscrim(string clubName);
ClubInfo GetByName(string v, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null);
ClubInfo GetByOwner(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null);
ClubInfo GetByMember(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null);
ClubInfo[] GetClubLeaderboardPage(int page);
}
}

View File

@ -5,5 +5,10 @@ namespace NadekoBot.Services.Database.Repositories
public interface IXpRepository : IRepository<UserXpStats> public interface IXpRepository : IRepository<UserXpStats>
{ {
UserXpStats GetOrCreateUser(ulong guildId, ulong userId); UserXpStats GetOrCreateUser(ulong guildId, ulong userId);
int GetTotalUserXp(ulong userId);
UserXpStats[] GetUsersFor(ulong guildId, int page);
(ulong UserId, int TotalXp)[] GetUsersFor(int page);
int GetUserGlobalRanking(ulong userId);
int GetUserGuildRanking(ulong userId, ulong guildId);
} }
} }

View File

@ -20,7 +20,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl
.Include(bc => bc.RaceAnimals) .Include(bc => bc.RaceAnimals)
.Include(bc => bc.Blacklist) .Include(bc => bc.Blacklist)
.Include(bc => bc.EightBallResponses) .Include(bc => bc.EightBallResponses)
.Include(bc => bc.ModulePrefixes)
.Include(bc => bc.StartupCommands) .Include(bc => bc.StartupCommands)
.Include(bc => bc.BlockedCommands) .Include(bc => bc.BlockedCommands)
.Include(bc => bc.BlockedModules) .Include(bc => bc.BlockedModules)

View File

@ -0,0 +1,65 @@
using NadekoBot.Services.Database.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System;
namespace NadekoBot.Services.Database.Repositories.Impl
{
public class ClubRepository : Repository<ClubInfo>, IClubRepository
{
public ClubRepository(DbContext context) : base(context)
{
}
public ClubInfo GetByOwner(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null)
{
if (func == null)
return _set
.Include(x => x.Bans)
.Include(x => x.Applicants)
.Include(x => x.Users)
.Include(x => x.Owner)
.FirstOrDefault(x => x.Owner.UserId == userId);
return func(_set).FirstOrDefault(x => x.Owner.UserId == userId);
}
public ClubInfo GetByName(string name, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null)
{
if (func == null)
return _set
.Include(x => x.Bans)
.Include(x => x.Applicants)
.Include(x => x.Users)
.FirstOrDefault(x => x.Name.ToLowerInvariant() == name && x.Discrim == discrim);
return func(_set).FirstOrDefault(x => x.Name == name && x.Discrim == discrim);
}
public int GetNextDiscrim(string clubName)
{
return _set
.Where(x => x.Name.ToLowerInvariant() == clubName.ToLowerInvariant())
.Max(x => x.Discrim) + 1;
}
public ClubInfo GetByMember(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null)
{
if (func == null)
return _set.Include(x => x.Users)
.Include(x => x.Bans)
.Include(x => x.Applicants)
.FirstOrDefault(x => x.Users.Any(y => y.UserId == userId));
return func(_set).FirstOrDefault(x => x.Users.Any(y => y.UserId == userId));
}
public ClubInfo[] GetClubLeaderboardPage(int page)
{
return _set.OrderBy(x => x.Xp)
.Skip(page * 9)
.Take(9)
.ToArray();
}
}
}

View File

@ -15,7 +15,15 @@ namespace NadekoBot.Services.Database.Repositories.Impl
{ {
DiscordUser toReturn; DiscordUser toReturn;
toReturn = _set.FirstOrDefault(u => u.UserId == original.Id); toReturn = _set.Include(x => x.Club)
.FirstOrDefault(u => u.UserId == original.Id);
if (toReturn != null)
{
toReturn.AvatarId = original.AvatarId;
toReturn.Username = original.Username;
toReturn.Discriminator = original.Discriminator;
}
if (toReturn == null) if (toReturn == null)
_set.Add(toReturn = new DiscordUser() _set.Add(toReturn = new DiscordUser()
@ -24,6 +32,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl
Discriminator = original.Discriminator, Discriminator = original.Discriminator,
UserId = original.Id, UserId = original.Id,
Username = original.Username, Username = original.Username,
Club = null,
}); });
return toReturn; return toReturn;

View File

@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
//todo add pagination to .lb
namespace NadekoBot.Services.Database.Repositories.Impl namespace NadekoBot.Services.Database.Repositories.Impl
{ {
public class XpRepository : Repository<UserXpStats>, IXpRepository public class XpRepository : Repository<UserXpStats>, IXpRepository
@ -12,7 +13,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl
public UserXpStats GetOrCreateUser(ulong guildId, ulong userId) public UserXpStats GetOrCreateUser(ulong guildId, ulong userId)
{ {
var usr = _set.FirstOrDefault(x => x.UserId == userId); var usr = _set.FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId);
if (usr == null) if (usr == null)
{ {
@ -20,12 +21,57 @@ namespace NadekoBot.Services.Database.Repositories.Impl
{ {
Xp = 0, Xp = 0,
UserId = userId, UserId = userId,
NotifyOnLevelUp = false, NotifyOnLevelUp = XpNotificationType.None,
GuildId = guildId, GuildId = guildId,
}); });
} }
return usr; return usr;
} }
public int GetTotalUserXp(ulong userId)
{
return _set.Where(x => x.UserId == userId).Sum(x => x.Xp);
}
public UserXpStats[] GetUsersFor(ulong guildId, int page)
{
return _set.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Skip(page * 9)
.Take(9)
.ToArray();
}
public int GetUserGlobalRanking(ulong userId)
{
return _set
.GroupBy(x => x.UserId)
.Count(x => x.Sum(y => y.Xp) > _set
.Where(y => y.UserId == userId)
.Sum(y => y.Xp)) + 1;
}
public int GetUserGuildRanking(ulong userId, ulong guildId)
{
return _set
.Where(x => x.GuildId == guildId)
.Count(x => x.Xp > (_set
.Where(y => y.UserId == userId && y.GuildId == guildId)
.Sum(y => y.Xp))) + 1;
}
public (ulong UserId, int TotalXp)[] GetUsersFor(int page)
{
return (from orduser in _set
group orduser by orduser.UserId into g
orderby g.Sum(x => x.Xp) descending
select new { UserId = g.Key, TotalXp = g.Sum(x => x.Xp) })
.Skip(page * 9)
.Take(9)
.AsEnumerable()
.Select(x => (x.UserId, x.TotalXp))
.ToArray();
}
} }
} }

View File

@ -60,6 +60,9 @@ namespace NadekoBot.Services.Database
private IXpRepository _xp; private IXpRepository _xp;
public IXpRepository Xp => _xp ?? (_xp = new XpRepository(_context)); public IXpRepository Xp => _xp ?? (_xp = new XpRepository(_context));
private IClubRepository _clubs;
public IClubRepository Clubs => _clubs ?? (_clubs = new ClubRepository(_context));
public UnitOfWork(NadekoContext context) public UnitOfWork(NadekoContext context)
{ {
_context = context; _context = context;

View File

@ -1,11 +1,13 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Database; using NadekoBot.Services.Database;
using System.Linq;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
public class DbService public class DbService
{ {
private readonly DbContextOptions options; private readonly DbContextOptions options;
private readonly DbContextOptions migrateOptions;
private readonly string _connectionString; private readonly string _connectionString;
@ -15,25 +17,23 @@ namespace NadekoBot.Services
var optionsBuilder = new DbContextOptionsBuilder(); var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlite(creds.Db.ConnectionString); optionsBuilder.UseSqlite(creds.Db.ConnectionString);
options = optionsBuilder.Options; options = optionsBuilder.Options;
//switch (_creds.Db.Type.ToUpperInvariant())
//{
// case "SQLITE":
// dbType = typeof(NadekoSqliteContext);
// break;
// //case "SQLSERVER":
// // dbType = typeof(NadekoSqlServerContext);
// // break;
// default:
// break;
//} optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlite(creds.Db.ConnectionString, x => x.SuppressForeignKeyEnforcement());
migrateOptions = optionsBuilder.Options;
} }
public NadekoContext GetDbContext() public NadekoContext GetDbContext()
{ {
var context = new NadekoContext(options); var context = new NadekoContext(options);
if (context.Database.GetPendingMigrations().Any())
{
var mContext = new NadekoContext(migrateOptions);
mContext.Database.Migrate();
mContext.SaveChanges();
mContext.Dispose();
}
context.Database.SetCommandTimeout(60); context.Database.SetCommandTimeout(60);
context.Database.Migrate();
context.EnsureSeedData(); context.EnsureSeedData();
//set important sqlite stuffs //set important sqlite stuffs

View File

@ -17,6 +17,8 @@ namespace NadekoBot.Services
ImmutableArray<byte> WifeMatrix { get; } ImmutableArray<byte> WifeMatrix { get; }
ImmutableArray<byte> RategirlDot { get; } ImmutableArray<byte> RategirlDot { get; }
ImmutableArray<byte> XpCard { get; }
void Reload(); void Reload();
} }
} }

View File

@ -377,8 +377,7 @@ namespace NadekoBot.Services.Impl
private string ConvertToLanguageCode(string language) private string ConvertToLanguageCode(string language)
{ {
string mode; _languageDictionary.TryGetValue(language, out var mode);
_languageDictionary.TryGetValue(language, out mode);
return mode; return mode;
} }
} }

View File

@ -25,6 +25,8 @@ namespace NadekoBot.Services.Impl
private const string _wifeMatrixPath = _basePath + "rategirl/wifematrix.png"; private const string _wifeMatrixPath = _basePath + "rategirl/wifematrix.png";
private const string _rategirlDot = _basePath + "rategirl/dot.png"; private const string _rategirlDot = _basePath + "rategirl/dot.png";
private const string _xpCardPath = _basePath + "xp/xp.png";
public ImmutableArray<byte> Heads { get; private set; } public ImmutableArray<byte> Heads { get; private set; }
public ImmutableArray<byte> Tails { get; private set; } public ImmutableArray<byte> Tails { get; private set; }
@ -40,6 +42,8 @@ namespace NadekoBot.Services.Impl
public ImmutableArray<byte> WifeMatrix { get; private set; } public ImmutableArray<byte> WifeMatrix { get; private set; }
public ImmutableArray<byte> RategirlDot { get; private set; } public ImmutableArray<byte> RategirlDot { get; private set; }
public ImmutableArray<byte> XpCard { get; private set; }
public ImagesService() public ImagesService()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
@ -76,6 +80,8 @@ namespace NadekoBot.Services.Impl
WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray(); WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray();
RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray(); RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray();
XpCard = File.ReadAllBytes(_xpCardPath).ToImmutableArray();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -20,7 +20,7 @@ namespace NadekoBot.Services.Impl
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly DateTime _started; private readonly DateTime _started;
public const string BotVersion = "1.7"; public const string BotVersion = "1.8";
public string Author => "Kwoth#2560"; public string Author => "Kwoth#2560";
public string Library => "Discord.Net"; public string Library => "Discord.Net";

View File

@ -132,7 +132,7 @@ namespace NadekoBot.Extensions
public static string ToJson<T>(this T any, Formatting formatting = Formatting.Indented) => public static string ToJson<T>(this T any, Formatting formatting = Formatting.Indented) =>
JsonConvert.SerializeObject(any, formatting); JsonConvert.SerializeObject(any, formatting);
public static Stream ToStream(this ImageSharp.Image<Rgba32> img) public static MemoryStream ToStream(this ImageSharp.Image<Rgba32> img)
{ {
var imageStream = new MemoryStream(); var imageStream = new MemoryStream();
img.SaveAsPng(imageStream); img.SaveAsPng(imageStream);

View File

@ -10,7 +10,7 @@ namespace NadekoBot.Extensions
public static class IMessageChannelExtensions public static class IMessageChannelExtensions
{ {
public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, EmbedBuilder embed, string msg = "") public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, EmbedBuilder embed, string msg = "")
=> ch.SendMessageAsync(msg, embed: embed); => ch.SendMessageAsync(msg, embed: embed.Build());
public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, string title, string error, string url = null, string footer = null) public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, string title, string error, string url = null, string footer = null)
{ {
@ -20,11 +20,11 @@ namespace NadekoBot.Extensions
eb.WithUrl(url); eb.WithUrl(url);
if (!string.IsNullOrWhiteSpace(footer)) if (!string.IsNullOrWhiteSpace(footer))
eb.WithFooter(efb => efb.WithText(footer)); eb.WithFooter(efb => efb.WithText(footer));
return ch.SendMessageAsync("", embed: eb); return ch.SendMessageAsync("", embed: eb.Build());
} }
public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, string error) public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, string error)
=> ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error)); => ch.SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error).Build());
public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, string title, string text, string url = null, string footer = null) public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, string title, string text, string url = null, string footer = null)
{ {
@ -34,11 +34,11 @@ namespace NadekoBot.Extensions
eb.WithUrl(url); eb.WithUrl(url);
if (!string.IsNullOrWhiteSpace(footer)) if (!string.IsNullOrWhiteSpace(footer))
eb.WithFooter(efb => efb.WithText(footer)); eb.WithFooter(efb => efb.WithText(footer));
return ch.SendMessageAsync("", embed: eb); return ch.SendMessageAsync("", embed: eb.Build());
} }
public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, string text) public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, string text)
=> ch.SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text)); => ch.SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text).Build());
public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, string seed, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3) public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, string seed, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3)
{ {

View File

@ -1,4 +1,5 @@
using Discord; using Discord;
using NadekoBot.Services.Database.Models;
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,14 +9,14 @@ namespace NadekoBot.Extensions
public static class IUserExtensions public static class IUserExtensions
{ {
public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string text) public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string text)
=> await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text)); => await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithOkColor().WithDescription(text).Build());
public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string title, string text, string url = null) public static async Task<IUserMessage> SendConfirmAsync(this IUser user, string title, string text, string url = null)
{ {
var eb = new EmbedBuilder().WithOkColor().WithDescription(text); var eb = new EmbedBuilder().WithOkColor().WithDescription(text);
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
eb.WithUrl(url); eb.WithUrl(url);
return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb); return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Build());
} }
public static async Task<IUserMessage> SendErrorAsync(this IUser user, string title, string error, string url = null) public static async Task<IUserMessage> SendErrorAsync(this IUser user, string title, string error, string url = null)
@ -23,11 +24,11 @@ namespace NadekoBot.Extensions
var eb = new EmbedBuilder().WithErrorColor().WithDescription(error); var eb = new EmbedBuilder().WithErrorColor().WithDescription(error);
if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
eb.WithUrl(url); eb.WithUrl(url);
return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb); return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Build());
} }
public static async Task<IUserMessage> SendErrorAsync(this IUser user, string error) public static async Task<IUserMessage> SendErrorAsync(this IUser user, string error)
=> await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error)); => await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: new EmbedBuilder().WithErrorColor().WithDescription(error).Build());
public static async Task<IUserMessage> SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false) => public static async Task<IUserMessage> SendFileAsync(this IUser user, string filePath, string caption = null, string text = null, bool isTTS = false) =>
await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(File.Open(filePath, FileMode.Open), caption ?? "x", text, isTTS).ConfigureAwait(false); await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(File.Open(filePath, FileMode.Open), caption ?? "x", text, isTTS).ConfigureAwait(false);
@ -39,5 +40,10 @@ namespace NadekoBot.Extensions
usr.AvatarId.StartsWith("a_") usr.AvatarId.StartsWith("a_")
? $"{DiscordConfig.CDNUrl}avatars/{usr.Id}/{usr.AvatarId}.gif" ? $"{DiscordConfig.CDNUrl}avatars/{usr.Id}/{usr.AvatarId}.gif"
: usr.GetAvatarUrl(ImageFormat.Auto); : usr.GetAvatarUrl(ImageFormat.Auto);
public static string RealAvatarUrl(this DiscordUser usr) =>
usr.AvatarId.StartsWith("a_")
? $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.gif"
: $"{DiscordConfig.CDNUrl}avatars/{usr.UserId}/{usr.AvatarId}.png";
} }
} }

View File

@ -833,5 +833,40 @@
"xp_server_is_excluded": "This server is excluded.", "xp_server_is_excluded": "This server is excluded.",
"xp_server_is_not_excluded": "This server is not excluded.", "xp_server_is_not_excluded": "This server is not excluded.",
"xp_excluded_roles": "Excluded Roles", "xp_excluded_roles": "Excluded Roles",
"xp_excluded_channels": "Excluded Channels" "xp_excluded_channels": "Excluded Channels",
"xp_level_up_channel": "Congratulations {0}, You've reached level {1}!",
"xp_level_up_dm": "Congratulations {0}, You've reached level {1} on {2} server!",
"xp_level_up_global": "Congratulations {0}, You've reached global level {1}!",
"xp_role_reward_cleared": "Level {0} will no longer reward a role.",
"xp_role_reward_added": "Users who reach level {0} will receive {1} role.",
"xp_role_rewards": "Role Rewards",
"xp_level_x": "Level {0}",
"xp_no_role_rewards": "No role reward on this page.",
"xp_server_leaderboard": "Server XP Leaderboard",
"xp_global_leaderboard": "Global XP Leaderboard",
"xp_modified": "Modified server XP of the user {0} by {1}",
"xp_club_create_error": "Failed creating the club. Make sure you're above level 5 and not a member of a club already.",
"xp_club_created": "Club {0} successfully created!",
"xp_club_not_exists": "That club doesn't exist.",
"xp_club_applied": "You've applied for membership in {0} club.",
"xp_club_apply_error": "Error applying. You are either already a member of the club, or you don't meet the minimum level requirement, or you've been banned from this one.",
"xp_club_accepted": "Accepted user {0} to the club.",
"xp_club_accept_error": "User not found",
"xp_club_left": "You've left the club.",
"xp_club_not_in_club": "You are not in a club, or you're trying to leave the club you're the owner of.",
"xp_club_user_kick": "User {0} kicked from {1} club.",
"xp_club_user_kick_fail": "Error kicking. You're either not the club owner, or that user is not in your club.",
"xp_club_user_banned": "Banned user {0} from {1} club.",
"xp_club_user_ban_fail": "Failed to ban. You're either not the club owner, or that user is not in your club or applied to it.",
"xp_club_user_unbanned": "Unbanned user {0} in {1} club.",
"xp_club_user_unban_fail": "Failed to unban. You're either not the club owner, or that user is not in your club or applied to it.",
"xp_club_level_req_changed": "Changed club's level requirement to {0}",
"xp_club_level_req_change_error": "Failed changing level requirement.",
"xp_club_disbanded": "Club {0} has been disbanded",
"xp_club_disband_error": "Error. You are either not in a club, or you are not the owner of your club.",
"xp_club_icon_error": "Not a valid image url or you're not the club owner.",
"xp_club_icon_set": "New club icon set.",
"xp_club_bans_for": "Bans for {0} club",
"xp_club_apps_for": "Applicants for {0} club",
"xp_club_leaderboard": "Club leaderboard - page {0}"
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB