diff --git a/src/NadekoBot/Common/BotConfigEditType.cs b/src/NadekoBot/Common/BotConfigEditType.cs index 5eadbe9d..92be166a 100644 --- a/src/NadekoBot/Common/BotConfigEditType.cs +++ b/src/NadekoBot/Common/BotConfigEditType.cs @@ -17,6 +17,8 @@ CurrencyDropAmountMax, MinimumBetAmount, TriviaCurrencyReward, + XpPerMessage, + XpMinutesTimeout, //ErrorColor, //after i fix the nadekobot.cs static variables //OkColor diff --git a/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.Designer.cs b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.Designer.cs new file mode 100644 index 00000000..6b324c08 --- /dev/null +++ b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.Designer.cs @@ -0,0 +1,1945 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20170908230730_xp-and-clubs")] + partial class xpandclubs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.1"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.Property("UserThreshold"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiRaidSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AntiSpamSettingId"); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.HasKey("Id"); + + b.HasIndex("AntiSpamSettingId"); + + b.ToTable("AntiSpamIgnore"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Action"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("MessageThreshold"); + + b.Property("MuteTime"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("AntiSpamSetting"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("BotConfigId1"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("BotConfigId1"); + + b.ToTable("BlockedCmdOrMdl"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BetflipMultiplier"); + + b.Property("Betroll100Multiplier"); + + b.Property("Betroll67Multiplier"); + + b.Property("Betroll91Multiplier"); + + b.Property("BufferSize"); + + b.Property("CurrencyDropAmount"); + + b.Property("CurrencyDropAmountMax"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("CustomReactionsStartWith"); + + b.Property("DMHelpString"); + + b.Property("DateAdded"); + + b.Property("DefaultPrefix"); + + b.Property("ErrorColor"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("Locale"); + + b.Property("MigrationVersion"); + + b.Property("MinimumBetAmount"); + + b.Property("OkColor"); + + b.Property("PermissionVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.Property("TriviaCurrencyReward"); + + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("DateAdded"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Mapping"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandAlias"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("CommandName"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.HasIndex("Price") + .IsUnique(); + + b.ToTable("CommandPrice"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoDeleteTrigger"); + + b.Property("ContainsAnywhere"); + + b.Property("DateAdded"); + + b.Property("DmResponse"); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AvatarId"); + + b.Property("ClubId"); + + b.Property("DateAdded"); + + b.Property("Discriminator"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 857, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasAlternateKey("UserId"); + + b.HasIndex("ClubId"); + + b.ToTable("DiscordUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DateAdded"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GameVoiceChannel"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("Locale"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("Prefix"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("TimeZoneId"); + + b.Property("VerboseErrors"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.Property("WarningsInitialized"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.Property("StartTimeOfDay"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GuildRepeater"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelCreatedId"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelDestroyedId"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("ChannelUpdatedId"); + + b.Property("DateAdded"); + + b.Property("IsLogging"); + + b.Property("LogOtherId"); + + b.Property("LogUserPresence"); + + b.Property("LogUserPresenceId"); + + b.Property("LogVoicePresence"); + + b.Property("LogVoicePresenceId"); + + b.Property("LogVoicePresenceTTSId"); + + b.Property("MessageDeleted"); + + b.Property("MessageDeletedId"); + + b.Property("MessageUpdated"); + + b.Property("MessageUpdatedId"); + + b.Property("UserBanned"); + + b.Property("UserBannedId"); + + b.Property("UserJoined"); + + b.Property("UserJoinedId"); + + b.Property("UserLeft"); + + b.Property("UserLeftId"); + + b.Property("UserMutedId"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUnbannedId"); + + b.Property("UserUpdated"); + + b.Property("UserUpdatedId"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("MutedUserId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Tag"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("NsfwBlacklitedTag"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("Permissionv2"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("DateAdded"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("DateAdded"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AmountRewardedThisMonth"); + + b.Property("DateAdded"); + + b.Property("LastReward"); + + b.Property("PatreonUserId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("RewardedUsers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Index"); + + b.Property("Name"); + + b.Property("Price"); + + b.Property("RoleId"); + + b.Property("RoleName"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("ShopEntry"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ShopEntryId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("ShopEntryId"); + + b.ToTable("ShopEntryItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredRole"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("SlowmodeIgnoredUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ChannelId"); + + b.Property("ChannelName"); + + b.Property("CommandText"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("GuildName"); + + b.Property("Index"); + + b.Property("VoiceChannelId"); + + b.Property("VoiceChannelName"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("StartupCommand"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleBlacklistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddRoleId"); + + b.Property("DateAdded"); + + b.Property("Enabled"); + + b.Property("FromRoleId"); + + b.Property("GuildConfigId"); + + b.Property("Keyword"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("StreamRoleSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("StreamRoleSettingsId"); + + b.Property("UserId"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("StreamRoleSettingsId"); + + b.ToTable("StreamRoleWhitelistedUser"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("UnmuteAt"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("UnmuteTimer"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserPokeTypes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("UserId"); + + b.Property("type"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PokeGame"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("RoleId"); + + b.Property("VoiceChannelId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("VcRoleInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AffinityId"); + + b.Property("ClaimerId"); + + b.Property("DateAdded"); + + b.Property("Price"); + + b.Property("WaifuId"); + + b.HasKey("Id"); + + b.HasIndex("AffinityId"); + + b.HasIndex("ClaimerId"); + + b.HasIndex("WaifuId") + .IsUnique(); + + b.ToTable("WaifuInfo"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Item"); + + b.Property("ItemEmoji"); + + b.Property("Price"); + + b.Property("WaifuInfoId"); + + b.HasKey("Id"); + + b.HasIndex("WaifuInfoId"); + + b.ToTable("WaifuItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("NewId"); + + b.Property("OldId"); + + b.Property("UpdateType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("NewId"); + + b.HasIndex("OldId"); + + b.HasIndex("UserId"); + + b.ToTable("WaifuUpdates"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Forgiven"); + + b.Property("ForgivenBy"); + + b.Property("GuildId"); + + b.Property("Moderator"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("Punishment"); + + b.Property("Time"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("WarningPunishment"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiRaidSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiRaidSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b => + { + b.HasOne("NadekoBot.Services.Database.Models.AntiSpamSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("AntiSpamSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("AntiSpamSetting") + .HasForeignKey("NadekoBot.Services.Database.Models.AntiSpamSetting", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlockedCmdOrMdl", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedCommands") + .HasForeignKey("BotConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("BlockedModules") + .HasForeignKey("BotConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .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 => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandAliases") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandPrice", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("CommandPrices") + .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 => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildRepeater", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GuildRepeaters") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("MutedUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklitedTag", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("NsfwBlacklistedTags") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("Permissions") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("ShopEntries") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ShopEntry") + .WithMany("Items") + .HasForeignKey("ShopEntryId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredRoles") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("SlowmodeIgnoredUsers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StartupCommand", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("StartupCommands") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Blacklist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("StreamRole") + .HasForeignKey("NadekoBot.Services.Database.Models.StreamRoleSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b => + { + b.HasOne("NadekoBot.Services.Database.Models.StreamRoleSettings") + .WithMany("Whitelist") + .HasForeignKey("StreamRoleSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("UnmuteTimers") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("VcRoleInfos") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Affinity") + .WithMany() + .HasForeignKey("AffinityId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Claimer") + .WithMany() + .HasForeignKey("ClaimerId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Waifu") + .WithOne() + .HasForeignKey("NadekoBot.Services.Database.Models.WaifuInfo", "WaifuId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.WaifuInfo") + .WithMany("Items") + .HasForeignKey("WaifuInfoId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b => + { + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "New") + .WithMany() + .HasForeignKey("NewId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "Old") + .WithMany() + .HasForeignKey("OldId"); + + b.HasOne("NadekoBot.Services.Database.Models.DiscordUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("WarnPunishments") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.cs b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.cs new file mode 100644 index 00000000..aad21131 --- /dev/null +++ b/src/NadekoBot/Migrations/20170908230730_xp-and-clubs.cs @@ -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( + name: "XpMinutesTimeout", + table: "BotConfig", + nullable: false, + defaultValue: 5) + .Annotation("Sqlite:Autoincrement", true); + + migrationBuilder.AddColumn( + name: "XpPerMessage", + table: "BotConfig", + nullable: false, + defaultValue: 3) + .Annotation("Sqlite:Autoincrement", true); + + migrationBuilder.CreateTable( + name: "Clubs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + Discrim = table.Column(nullable: false), + ImageUrl = table.Column(nullable: true), + MinimumLevelReq = table.Column(nullable: false), + Name = table.Column(maxLength: 20, nullable: false), + OwnerId = table.Column(nullable: false), + Xp = table.Column(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( + name: "LastLevelUp", + table: "DiscordUser", + nullable: false, + defaultValue: DateTime.UtcNow); + + migrationBuilder.AddColumn( + name: "NotifyOnLevelUp", + table: "DiscordUser", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + name: "UserXpStats", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AwardedXp = table.Column(nullable: false), + DateAdded = table.Column(nullable: true), + GuildId = table.Column(nullable: false), + LastLevelUp = table.Column(nullable: false, defaultValue: new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local)), + NotifyOnLevelUp = table.Column(nullable: false), + UserId = table.Column(nullable: false), + Xp = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserXpStats", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "XpSettings", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + GuildConfigId = table.Column(nullable: false), + NotifyMessage = table.Column(nullable: true), + ServerExcluded = table.Column(nullable: false), + XpRoleRewardExclusive = table.Column(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(nullable: false), + UserId = table.Column(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(nullable: false), + UserId = table.Column(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(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + ItemId = table.Column(nullable: false), + ItemType = table.Column(nullable: false), + XpSettingsId = table.Column(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(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateAdded = table.Column(nullable: true), + Level = table.Column(nullable: false), + RoleId = table.Column(nullable: false), + XpSettingsId = table.Column(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"); + } + } +} diff --git a/src/NadekoBot/Migrations/MigrationQueries.cs b/src/NadekoBot/Migrations/MigrationQueries.cs new file mode 100644 index 00000000..201f0c34 --- /dev/null +++ b/src/NadekoBot/Migrations/MigrationQueries.cs @@ -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;"; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs index 97c06375..da931d6b 100644 --- a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -183,6 +183,14 @@ namespace NadekoBot.Migrations b.Property("TriviaCurrencyReward"); + b.Property("XpMinutesTimeout") + .ValueGeneratedOnAdd() + .HasDefaultValue(5); + + b.Property("XpPerMessage") + .ValueGeneratedOnAdd() + .HasDefaultValue(3); + b.HasKey("Id"); b.ToTable("BotConfig"); @@ -238,6 +246,63 @@ namespace NadekoBot.Migrations b.ToTable("ClashOfClans"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubApplicants", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubApplicants"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubBans", b => + { + b.Property("ClubId"); + + b.Property("UserId"); + + b.HasKey("ClubId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("ClubBans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClubInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Discrim"); + + b.Property("ImageUrl"); + + b.Property("MinimumLevelReq"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20); + + b.Property("OwnerId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasAlternateKey("Name", "Discrim"); + + b.HasIndex("OwnerId") + .IsUnique(); + + b.ToTable("Clubs"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b => { b.Property("Id") @@ -391,10 +456,18 @@ namespace NadekoBot.Migrations b.Property("AvatarId"); + b.Property("ClubId"); + b.Property("DateAdded"); b.Property("Discriminator"); + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 857, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + b.Property("UserId"); b.Property("Username"); @@ -403,6 +476,8 @@ namespace NadekoBot.Migrations b.HasAlternateKey("UserId"); + b.HasIndex("ClubId"); + b.ToTable("DiscordUser"); }); @@ -445,6 +520,26 @@ namespace NadekoBot.Migrations b.ToTable("EightBallResponses"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("ItemId"); + + b.Property("ItemType"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("ExcludedItem"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => { b.Property("Id") @@ -1252,6 +1347,35 @@ namespace NadekoBot.Migrations b.ToTable("PokeGame"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AwardedXp"); + + b.Property("DateAdded"); + + b.Property("GuildId"); + + b.Property("LastLevelUp") + .ValueGeneratedOnAdd() + .HasDefaultValue(new DateTime(2017, 9, 9, 1, 7, 29, 858, DateTimeKind.Local)); + + b.Property("NotifyOnLevelUp"); + + b.Property("UserId"); + + b.Property("Xp"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "GuildId") + .IsUnique(); + + b.ToTable("UserXpStats"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b => { b.Property("Id") @@ -1393,6 +1517,51 @@ namespace NadekoBot.Migrations b.ToTable("WarningPunishment"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("Level"); + + b.Property("RoleId"); + + b.Property("XpSettingsId"); + + b.HasKey("Id"); + + b.HasAlternateKey("Level"); + + b.HasIndex("XpSettingsId"); + + b.ToTable("XpRoleReward"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("GuildConfigId"); + + b.Property("NotifyMessage"); + + b.Property("ServerExcluded"); + + b.Property("XpRoleRewardExclusive"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId") + .IsUnique(); + + b.ToTable("XpSettings"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") @@ -1442,6 +1611,40 @@ namespace NadekoBot.Migrations .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 => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") @@ -1463,6 +1666,13 @@ namespace NadekoBot.Migrations .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 => { b.HasOne("NadekoBot.Services.Database.Models.BotConfig") @@ -1470,6 +1680,13 @@ namespace NadekoBot.Migrations .HasForeignKey("BotConfigId"); }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("ExclusionList") + .HasForeignKey("XpSettingsId"); + }); + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => { b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") @@ -1707,6 +1924,21 @@ namespace NadekoBot.Migrations .WithMany("WarnPunishments") .HasForeignKey("GuildConfigId"); }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b => + { + b.HasOne("NadekoBot.Services.Database.Models.XpSettings") + .WithMany("RoleRewards") + .HasForeignKey("XpSettingsId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig") + .WithOne("XpSettings") + .HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId") + .OnDelete(DeleteBehavior.Cascade); + }); } } } diff --git a/src/NadekoBot/Modules/Administration/MigrationCommands.cs b/src/NadekoBot/Modules/Administration/MigrationCommands.cs index 1228690a..5c9b4a70 100644 --- a/src/NadekoBot/Modules/Administration/MigrationCommands.cs +++ b/src/NadekoBot/Modules/Administration/MigrationCommands.cs @@ -352,54 +352,6 @@ namespace NadekoBot.Modules.Administration oldConfig.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i })); botConfig.RotatingStatusMessages = messages; - //Prefix - botConfig.ModulePrefixes.Clear(); - botConfig.ModulePrefixes.AddRange(new HashSet - { - 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 var blacklist = new HashSet(oldConfig.ServerBlacklist.Select(server => new BlacklistItem() { ItemId = server, Type = BlacklistType.Server })); blacklist.AddRange(oldConfig.ChannelBlacklist.Select(channel => new BlacklistItem() { ItemId = channel, Type = BlacklistType.Channel })); diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs index b466b158..9867dbcb 100644 --- a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -153,7 +153,7 @@ namespace NadekoBot.Modules.CustomReactions if (Context.Guild == null) // its a private one, just send back await Context.Channel.SendFileAsync(txtStream, "customreactions.txt", GetText("list_all")).ConfigureAwait(false); 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] diff --git a/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs b/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs index 3019155e..5d46f976 100644 --- a/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs +++ b/src/NadekoBot/Modules/Gambling/WaifuClaimCommands.cs @@ -48,8 +48,8 @@ namespace NadekoBot.Modules.Gambling [Group] public class WaifuClaimCommands : NadekoSubmodule { - private static ConcurrentDictionary divorceCooldowns { get; } = new ConcurrentDictionary(); - private static ConcurrentDictionary affinityCooldowns { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary _divorceCooldowns { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary _affinityCooldowns { get; } = new ConcurrentDictionary(); enum WaifuClaimResult { @@ -219,7 +219,7 @@ namespace NadekoBot.Modules.Gambling var now = DateTime.UtcNow; if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id) result = DivorceResult.NotYourWife; - else if (divorceCooldowns.AddOrUpdate(Context.User.Id, + else if (_divorceCooldowns.AddOrUpdate(Context.User.Id, 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) { } - else if (affinityCooldowns.AddOrUpdate(Context.User.Id, + else if (_affinityCooldowns.AddOrUpdate(Context.User.Id, 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("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("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)); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs index 245b75cf..4d7a0ea3 100644 --- a/src/NadekoBot/Modules/Games/Games.cs +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -11,7 +11,7 @@ using NadekoBot.Modules.Games.Services; namespace NadekoBot.Modules.Games { - /*todo more games + /* more games - Blackjack - Shiritori - Simple RPG adventure diff --git a/src/NadekoBot/Modules/Games/Services/GamesService.cs b/src/NadekoBot/Modules/Games/Services/GamesService.cs index f09c606b..39164486 100644 --- a/src/NadekoBot/Modules/Games/Services/GamesService.cs +++ b/src/NadekoBot/Modules/Games/Services/GamesService.cs @@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Games.Services public readonly ImmutableArray EightBallResponses; private readonly Timer _t; - private readonly DiscordSocketClient _client; + private readonly CommandHandler _cmd; private readonly NadekoStrings _strings; private readonly IImagesService _images; private readonly Logger _log; @@ -38,11 +38,11 @@ namespace NadekoBot.Modules.Games.Services public List TypingArticles { get; } = new List(); - public GamesService(DiscordSocketClient client, IBotConfigProvider bc, IEnumerable gcs, + public GamesService(CommandHandler cmd, IBotConfigProvider bc, IEnumerable gcs, NadekoStrings strings, IImagesService images, CommandHandler cmdHandler) { _bc = bc; - _client = client; + _cmd = cmd; _strings = strings; _images = images; _cmdHandler = cmdHandler; @@ -59,7 +59,7 @@ namespace NadekoBot.Modules.Games.Services }, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1)); //plantpick - client.MessageReceived += PotentialFlowerGeneration; + _cmd.OnMessageNoTrigger += PotentialFlowerGeneration; GenerationChannels = new ConcurrentHashSet(gcs .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); @@ -102,7 +102,7 @@ namespace NadekoBot.Modules.Games.Services private string GetText(ITextChannel ch, string key, params object[] rep) => _strings.GetText(key, ch.GuildId, "Games".ToLowerInvariant(), rep); - private Task PotentialFlowerGeneration(SocketMessage imsg) + private Task PotentialFlowerGeneration(IUserMessage imsg) { var msg = imsg as SocketUserMessage; if (msg == null || msg.Author.IsBot) diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs index 81daf9c6..522713dd 100644 --- a/src/NadekoBot/Modules/NSFW/NSFW.cs +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -15,6 +15,7 @@ using NadekoBot.Modules.Searches.Common; using NadekoBot.Modules.Searches.Services; using NadekoBot.Modules.NSFW.Exceptions; +//todo static httpclient namespace NadekoBot.Modules.NSFW { public class NSFW : NadekoTopLevelModule diff --git a/src/NadekoBot/Modules/Searches/LoLCommands.cs b/src/NadekoBot/Modules/Searches/LoLCommands.cs index 78b6928c..39c86636 100644 --- a/src/NadekoBot/Modules/Searches/LoLCommands.cs +++ b/src/NadekoBot/Modules/Searches/LoLCommands.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using NadekoBot.Common; using NadekoBot.Common.Attributes; -//todo 50 drawing namespace NadekoBot.Modules.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 CachedChampionImages = new Dictionary(); - -// 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 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>(); - -// 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 - -// 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; -// } -// } -// } -//} +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/TranslatorCommands.cs b/src/NadekoBot/Modules/Searches/TranslatorCommands.cs index 5084e2f3..9f4960fe 100644 --- a/src/NadekoBot/Modules/Searches/TranslatorCommands.cs +++ b/src/NadekoBot/Modules/Searches/TranslatorCommands.cs @@ -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 { Del, diff --git a/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs b/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs index 81018a77..98f34eaf 100644 --- a/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs +++ b/src/NadekoBot/Modules/Utility/Services/StreamRoleService.cs @@ -207,7 +207,7 @@ namespace NadekoBot.Modules.Utility.Services if (guildSettings.TryRemove(guild.Id, out var setting) && cleanup) await RescanUsers(guild).ConfigureAwait(false); } - //todo multiple rescans at the same time? + private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null) { if (user.Game.HasValue && diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 5ee6de70..d3f0e112 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -417,7 +417,7 @@ namespace NadekoBot.Modules.Utility }) }); 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] public async Task Ping() diff --git a/src/NadekoBot/Modules/Xp/Club.cs b/src/NadekoBot/Modules/Xp/Club.cs new file mode 100644 index 00000000..a911ab4f --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Club.cs @@ -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 + { + 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); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs b/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs new file mode 100644 index 00000000..11f38648 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Common/FullUserStats.cs @@ -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; + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Common/LevelStats.cs b/src/NadekoBot/Modules/Xp/Common/LevelStats.cs new file mode 100644 index 00000000..11dfc8c7 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Common/LevelStats.cs @@ -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); + } +} diff --git a/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs b/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs new file mode 100644 index 00000000..c5d6605b --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Extensions/Extensions.cs @@ -0,0 +1,34 @@ +using NadekoBot.Modules.Xp.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Xp.Extensions +{ + public static class Extensions + { + public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(this 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); + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Services/ClubService.cs b/src/NadekoBot/Modules/Xp/Services/ClubService.cs new file mode 100644 index 00000000..f8d08f66 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Services/ClubService.cs @@ -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() + .RemoveRange(uow._context.Set().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().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() + .RemoveRange(uow._context.Set().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); + } + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs b/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs new file mode 100644 index 00000000..d1a07c11 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Services/UserCacheItem.cs @@ -0,0 +1,21 @@ +using Discord; + +namespace NadekoBot.Modules.Xp.Services +{ + public class UserCacheItem + { + public IGuildUser User { get; set; } + public IGuild Guild { get; set; } + public IMessageChannel Channel { get; set; } + + public override int GetHashCode() + { + return User.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is UserCacheItem uci && uci.User == User; + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Services/XpService.cs b/src/NadekoBot/Modules/Xp/Services/XpService.cs new file mode 100644 index 00000000..52944064 --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Services/XpService.cs @@ -0,0 +1,731 @@ +using Discord; +using Discord.WebSocket; +using NadekoBot.Common.Collections; +using NadekoBot.Extensions; +using NadekoBot.Modules.Xp.Common; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NadekoBot.Services.Impl; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +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 +{ + 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 CommandHandler _cmd; + private readonly IBotConfigProvider _bc; + private readonly IImagesService _images; + private readonly Logger _log; + private readonly NadekoStrings _strings; + private readonly FontCollection _fonts = new FontCollection(); + public const int XP_REQUIRED_LVL_1 = 36; + + private readonly ConcurrentDictionary> _excludedRoles + = new ConcurrentDictionary>(); + + private readonly ConcurrentDictionary> _excludedChannels + = new ConcurrentDictionary>(); + + private readonly ConcurrentHashSet _excludedServers + = new ConcurrentHashSet(); + + private readonly ConcurrentHashSet _rewardedUsers + = new ConcurrentHashSet(); + + private readonly ConcurrentQueue _addMessageXp + = new ConcurrentQueue(); + + private readonly ConcurrentDictionary _imageStreams + = new ConcurrentDictionary(); + + private readonly Timer updateXpTimer; + 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, + IEnumerable allGuildConfigs, IImagesService images, + DbService db, NadekoStrings strings) + { + _db = db; + _cmd = cmd; + _bc = bc; + _images = images; + _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; + + updateXpTimer = new Timer(async _ => + { + try + { + var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>(); + var roleRewards = new Dictionary>(); + + var toAddTo = new List(); + 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) + { + foreach (var item in group) + { + 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(); + } + + 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)); + + var clearRewardTimer = Task.Run(async () => + { + while (true) + { + _rewardedUsers.Clear(); + + await Task.Delay(TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout)); + } + }); + } + + public IEnumerable 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) + { + if (!(arg.Author is SocketGuildUser user) || user.IsBot) + return Task.CompletedTask; + + var _ = Task.Run(() => + { + if (!SetUserRewarded(user.Id)) + return; + + if (_excludedChannels.TryGetValue(user.Guild.Id, out var chans) && + chans.Contains(arg.Channel.Id)) + return; + + if (_excludedServers.Contains(user.Guild.Id)) + return; + + if (_excludedRoles.TryGetValue(user.Guild.Id, out var roles) && + user.Roles.Any(x => roles.Contains(x.Id))) + return; + + if (!arg.Content.Contains(' ') && arg.Content.Length < 5) + return; + _addMessageXp.Enqueue(new UserCacheItem { Guild = user.Guild, Channel = arg.Channel, User = user }); + }); + 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) + { + return _excludedServers.Contains(id); + } + + public IEnumerable GetExcludedRoles(ulong id) + { + if (_excludedRoles.TryGetValue(id, out var val)) + return val.ToArray(); + + return Enumerable.Empty(); + } + + public IEnumerable GetExcludedChannels(ulong id) + { + if (_excludedChannels.TryGetValue(id, out var val)) + return val.ToArray(); + + return Enumerable.Empty(); + } + + private bool SetUserRewarded(ulong userId) + { + return _rewardedUsers.Add(userId); + } + + public LevelStats GetGlobalUserStats(ulong userId) + { + int totalXp; + using (var uow = _db.UnitOfWork) + { + totalXp = uow.Xp.GetTotalUserXp(userId); + } + + return new LevelStats(totalXp); + } + + public FullUserStats GetUserStats(IGuildUser user) + { + DiscordUser du; + 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); + } + + public bool ToggleExcludeServer(ulong id) + { + using (var uow = _db.UnitOfWork) + { + var xpSetting = uow.GuildConfigs.XpSettingsFor(id); + if (_excludedServers.Add(id)) + { + xpSetting.ServerExcluded = true; + uow.Complete(); + return true; + } + + _excludedServers.TryRemove(id); + xpSetting.ServerExcluded = false; + uow.Complete(); + return false; + } + } + + public bool ToggleExcludeRole(ulong guildId, ulong rId) + { + var roles = _excludedRoles.GetOrAdd(guildId, _ => new ConcurrentHashSet()); + using (var uow = _db.UnitOfWork) + { + var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId); + var excludeObj = new ExcludedItem + { + ItemId = rId, + ItemType = ExcludedItemType.Role, + }; + + if (roles.Add(rId)) + { + + if (xpSetting.ExclusionList.Add(excludeObj)) + { + uow.Complete(); + } + + return true; + } + else + { + roles.TryRemove(rId); + + if (xpSetting.ExclusionList.Remove(excludeObj)) + { + uow.Complete(); + } + + return false; + } + } + } + + public bool ToggleExcludeChannel(ulong guildId, ulong chId) + { + var channels = _excludedChannels.GetOrAdd(guildId, _ => new ConcurrentHashSet()); + using (var uow = _db.UnitOfWork) + { + var xpSetting = uow.GuildConfigs.XpSettingsFor(guildId); + var excludeObj = new ExcludedItem + { + ItemId = chId, + ItemType = ExcludedItemType.Channel, + }; + + if (channels.Add(chId)) + { + + if (xpSetting.ExclusionList.Add(excludeObj)) + { + uow.Complete(); + } + + return true; + } + else + { + channels.TryRemove(chId); + + if (xpSetting.ExclusionList.Remove(excludeObj)) + { + uow.Complete(); + } + + return false; + } + } + } + + public Task> 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> 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.Black, 1); + var brush = Brushes.Solid(Rgba32.White); + var xpBgBrush = Brushes.Solid(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 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); + } + } +} diff --git a/src/NadekoBot/Modules/Xp/Xp.cs b/src/NadekoBot/Modules/Xp/Xp.cs new file mode 100644 index 00000000..4362e06c --- /dev/null +++ b/src/NadekoBot/Modules/Xp/Xp.cs @@ -0,0 +1,251 @@ +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.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Xp +{ + public partial class Xp : NadekoTopLevelModule + { + private readonly DiscordSocketClient _client; + + public Xp(DiscordSocketClient client) + { + _client = client; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Experience([Remainder]IUser user = null) + { + user = user ?? Context.User; + + await Context.Channel.TriggerTypingAsync(); + var img = await _service.GenerateImageAsync((IGuildUser)user); + + await Context.Channel.SendFileAsync(img.ToStream(), $"{user.Id}_xp.png") + .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 }; + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task XpExclude(Server _) + { + var ex = _service.ToggleExcludeServer(Context.Guild.Id); + + await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(Context.Guild.ToString())).ConfigureAwait(false); + } + + public enum Role { Role }; + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(GuildPermission.ManageRoles)] + [RequireContext(ContextType.Guild)] + public async Task XpExclude(Role _, [Remainder] IRole role) + { + var ex = _service.ToggleExcludeRole(Context.Guild.Id, role.Id); + + await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(role.ToString())).ConfigureAwait(false); + } + + public enum Channel { Channel }; + + [NadekoCommand, Usage, Description, Aliases] + [RequireUserPermission(GuildPermission.ManageChannels)] + [RequireContext(ContextType.Guild)] + public async Task XpExclude(Channel _, [Remainder] ITextChannel channel) + { + var ex = _service.ToggleExcludeChannel(Context.Guild.Id, channel.Id); + + await ReplyConfirmLocalized((ex ? "excluded" : "not_excluded"), Format.Bold(channel.ToString())).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task XpExclusionList() + { + var serverExcluded = _service.IsServerExcluded(Context.Guild.Id); + var roles = _service.GetExcludedRoles(Context.Guild.Id) + .Select(x => Context.Guild.GetRole(x)?.Name) + .Where(x => x != null); + + var chans = (await Task.WhenAll(_service.GetExcludedChannels(Context.Guild.Id) + .Select(x => Context.Guild.GetChannelAsync(x))) + .ConfigureAwait(false)) + .Where(x => x != null) + .Select(x => x.Name); + + var embed = new EmbedBuilder() + .WithTitle(GetText("exclusion_list")) + .WithDescription((serverExcluded ? GetText("server_is_excluded") : GetText("server_is_not_excluded"))) + .AddField(GetText("excluded_roles"), roles.Any() ? string.Join("\n", roles) : "-", false) + .AddField(GetText("excluded_channels"), chans.Any() ? string.Join("\n", chans) : "-", false) + .WithOkColor(); + + 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); + } + } +} diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 5e1db116..22abd80f 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -58,14 +58,14 @@ - + - - + + @@ -87,8 +87,22 @@ $(NoWarn);CS1573;CS1591 + + latest + + + + latest + + + + + + Designer + + diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index e547c0cb..81133fdc 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -3573,4 +3573,211 @@ Toggles whether the tag is blacklisted or not in nsfw searches. Provide no parameters to see the list of blacklisted tags. + + experience xp + + + `{0}xp` + + + Shows your xp stats. Specify the user to show that user's stats instead. + + + xpexclusionlist xpexl + + + `{0}xpexl` + + + Shows the roles and channels excluded from the XP system on this server, as well as whether the whole server is excluded. + + + xpexclude xpex + + + `{0}xpex User @b1nzy` `{0}xpex Server` + + + Exclude a user or a role from the xp system, or whole current server. + + + xpnotify xpn + + + `{0}xpn global dm` `{0}xpn server channel` + + + 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. + + + xprolerewards xprrs + + + `{0}xprrs` + + + Shows currently set role rewards. + + + xprolereward xprr + + + `{0}xprr 3 Social` + + + Sets a role reward on a specified level. + + + xpleaderboard xplb + + + `{0}xplb` + + + Shows current server's xp leaderboard. + + + xpgleaderboard xpglb + + + `{0}xpglb` + + + Shows current server's xp leaderboard. + + + xpadd + + + `{0}xpadd 100 @b1nzy` + + + Adds xp to a user on the server. This does not affect their global ranking. You can use negative values. + + + clubcreate + + + `{0}clubcreate b1nzy's friends` + + + Creates a club. You must be atleast level 5 and not be in the club already. + + + clubinfo + + + `{0}clubinfo b1nzy's friends#123` + + + Shows information about the club. + + + clubapply + + + `{0}clubapply b1nzy's friends#123` + + + Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list. + + + clubaccept + + + `{0}clubaccept b1nzy#1337` + + + Apply to join a club. You must meet that club's minimum level requirement, and not be on its ban list. + + + clubleave + + + `{0}clubleave` + + + Leaves the club you're currently in. + + + clubdisband + + + `{0}clubdisband` + + + Disbands the club you're the owner of. This action is irreversible. + + + clubkick + + + `{0}clubkick b1nzy#1337` + + + Kicks the user from the club. You must be the club owner. They will be able to apply again. + + + clubban + + + `{0}clubban b1nzy#1337` + + + Bans the user from the club. You must be the club owner. They will not be able to apply again. + + + clubunban + + + `{0}clubunban b1nzy#1337` + + + Unbans the previously banned user from the club. You must be the club owner. + + + clublevelreq + + + `{0}clublevelreq 7` + + + Sets the club required level to apply to join the club. You must be club owner. You can't set this number below 5. + + + clubicon + + + `{0}clubicon https://i.imgur.com/htfDMfU.png` + + + Sets the club icon. + + + clubapps + + + `{0}clubapps 2` + + + Shows the list of users who have applied to your club. Paginated. You must be club owner to use this command. + + + clubbans + + + `{0}clubbans 2` + + + Shows the list of users who have banned from your club. Paginated. You must be club owner to use this command. + + + clublb + + + `{0}clublb 2` + + + Shows club rankings on the specified page. + diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index ad51c579..c06d14ab 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -39,7 +39,7 @@ namespace NadekoBot.Services public string DefaultPrefix { get; private set; } private ConcurrentDictionary _prefixes { get; } = new ConcurrentDictionary(); - private ImmutableArray> ownerChannels { get; set; } = new ImmutableArray>(); + private ImmutableArray> OwnerChannels { get; set; } = new ImmutableArray>(); public event Func CommandExecuted = delegate { return Task.CompletedTask; }; public event Func CommandErrored = delegate { return Task.CompletedTask; }; diff --git a/src/NadekoBot/Services/Database/IUnitOfWork.cs b/src/NadekoBot/Services/Database/IUnitOfWork.cs index 52cb01cf..6b97a085 100644 --- a/src/NadekoBot/Services/Database/IUnitOfWork.cs +++ b/src/NadekoBot/Services/Database/IUnitOfWork.cs @@ -24,6 +24,8 @@ namespace NadekoBot.Services.Database IWaifuRepository Waifus { get; } IDiscordUserRepository DiscordUsers { get; } IWarningsRepository Warnings { get; } + IXpRepository Xp { get; } + IClubRepository Clubs { get; } int Complete(); Task CompleteAsync(); diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs index d765b813..98b84594 100644 --- a/src/NadekoBot/Services/Database/Models/BotConfig.cs +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -68,6 +68,8 @@ Nadeko Support Server: https://discord.gg/nadekobot"; public int PermissionVersion { get; set; } public string DefaultPrefix { get; set; } = "."; public bool CustomReactionsStartWith { get; set; } = false; + public int XpPerMessage { get; set; } = 3; + public int XpMinutesTimeout { get; set; } = 5; } public class BlockedCmdOrMdl : DbEntity diff --git a/src/NadekoBot/Services/Database/Models/ClubInfo.cs b/src/NadekoBot/Services/Database/Models/ClubInfo.cs new file mode 100644 index 00000000..68f68bc3 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/ClubInfo.cs @@ -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 Users { get; set; } = new List(); + + public List Applicants { get; set; } = new List(); + public List Bans { get; set; } = new List(); + + 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; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/DiscordUser.cs b/src/NadekoBot/Services/Database/Models/DiscordUser.cs index 86b84d5b..2ca8fc68 100644 --- a/src/NadekoBot/Services/Database/Models/DiscordUser.cs +++ b/src/NadekoBot/Services/Database/Models/DiscordUser.cs @@ -1,4 +1,6 @@ -namespace NadekoBot.Services.Database.Models +using System; + +namespace NadekoBot.Services.Database.Models { public class DiscordUser : DbEntity { @@ -6,6 +8,22 @@ public string Username { get; set; } public string Discriminator { 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() => Username + "#" + Discriminator; diff --git a/src/NadekoBot/Services/Database/Models/GuildConfig.cs b/src/NadekoBot/Services/Database/Models/GuildConfig.cs index 7ae60508..82cc8bdf 100644 --- a/src/NadekoBot/Services/Database/Models/GuildConfig.cs +++ b/src/NadekoBot/Services/Database/Models/GuildConfig.cs @@ -86,6 +86,8 @@ namespace NadekoBot.Services.Database.Models public StreamRoleSettings StreamRole { get; set; } + public XpSettings XpSettings { get; set; } + //public List ProtectionIgnoredChannels { get; set; } = new List(); } diff --git a/src/NadekoBot/Services/Database/Models/UserXpStats.cs b/src/NadekoBot/Services/Database/Models/UserXpStats.cs new file mode 100644 index 00000000..8695298e --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/UserXpStats.cs @@ -0,0 +1,16 @@ +using System; + +namespace NadekoBot.Services.Database.Models +{ + public class UserXpStats : DbEntity + { + public ulong UserId { get; set; } + public ulong GuildId { get; set; } + public int Xp { 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 } +} diff --git a/src/NadekoBot/Services/Database/Models/XpSettings.cs b/src/NadekoBot/Services/Database/Models/XpSettings.cs new file mode 100644 index 00000000..cf89d611 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/XpSettings.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Models +{ + public class XpSettings : DbEntity + { + public int GuildConfigId { get; set; } + public GuildConfig GuildConfig { get; set; } + + public HashSet RoleRewards { get; set; } = new HashSet(); + public bool XpRoleRewardExclusive { get; set; } + public string NotifyMessage { get; set; } = "Congratulations {0}! You have reached level {1}!"; + public HashSet ExclusionList { get; set; } = new HashSet(); + public bool ServerExcluded { get; set; } + } + + public enum ExcludedItemType { Channel, Role } + + public class XpRoleReward : DbEntity + { + public int Level { get; set; } + public ulong RoleId { get; set; } + + public override int GetHashCode() + { + return Level.GetHashCode() ^ RoleId.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is XpRoleReward xrr && xrr.Level == Level && xrr.RoleId == RoleId; + } + } + + public class ExcludedItem : DbEntity + { + public ulong ItemId { get; set; } + public ExcludedItemType ItemType { get; set; } + + public override int GetHashCode() + { + return ItemId.GetHashCode() ^ ItemType.GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is ExcludedItem ei && ei.ItemId == ItemId && ei.ItemType == ItemType; + } + } +} diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index 7c00e1f3..5c04c816 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -4,6 +4,7 @@ using System.Linq; using NadekoBot.Services.Database.Models; using NadekoBot.Extensions; using Microsoft.EntityFrameworkCore.Infrastructure; +using System; namespace NadekoBot.Services.Database { @@ -43,6 +44,8 @@ namespace NadekoBot.Services.Database public DbSet PokeGame { get; set; } public DbSet WaifuUpdates { get; set; } public DbSet Warnings { get; set; } + public DbSet UserXpStats { get; set; } + public DbSet Clubs { get; set; } //logging public DbSet LogSettings { get; set; } @@ -70,24 +73,6 @@ namespace NadekoBot.Services.Database { var bc = new BotConfig(); - bc.ModulePrefixes.AddRange(new HashSet() - { - 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 { new RaceAnimal { Icon = "🐼", Name = "Panda" }, @@ -176,7 +161,14 @@ namespace NadekoBot.Services.Database #endregion #region BotConfig - //var botConfigEntity = modelBuilder.Entity(); + var botConfigEntity = modelBuilder.Entity(); + + botConfigEntity.Property(x => x.XpMinutesTimeout) + .HasDefaultValue(5); + + botConfigEntity.Property(x => x.XpPerMessage) + .HasDefaultValue(3); + //botConfigEntity // .HasMany(c => c.ModulePrefixes) // .WithOne(mp => mp.BotConfig) @@ -277,9 +269,19 @@ namespace NadekoBot.Services.Database // .WithOne(); // //.HasForeignKey(w => w.ClaimerId) // //.IsRequired(false); + #endregion + #region DiscordUser + var du = modelBuilder.Entity(); du.HasAlternateKey(w => w.UserId); + du.HasOne(x => x.Club) + .WithMany(x => x.Users) + .IsRequired(false); + + modelBuilder.Entity() + .Property(x => x.LastLevelUp) + .HasDefaultValue(DateTime.Now); #endregion @@ -292,6 +294,63 @@ namespace NadekoBot.Services.Database pr.HasIndex(x => x.UserId) .IsUnique(); #endregion + + #region XpStats + modelBuilder.Entity() + .HasIndex(x => new { x.UserId, x.GuildId }) + .IsUnique(); + + modelBuilder.Entity() + .Property(x => x.LastLevelUp) + .HasDefaultValue(DateTime.Now); + #endregion + + #region XpSettings + modelBuilder.Entity() + .HasOne(x => x.GuildConfig) + .WithOne(x => x.XpSettings); + #endregion + + #region XpRoleReward + modelBuilder.Entity() + .HasAlternateKey(x => x.Level); + #endregion + + #region Club + var ci = modelBuilder.Entity(); + ci.HasOne(x => x.Owner) + .WithOne() + .HasForeignKey(x => x.OwnerId); + + + ci.HasAlternateKey(x => new { x.Name, x.Discrim }); + #endregion + + #region ClubManytoMany + + modelBuilder.Entity() + .HasKey(t => new { t.ClubId, t.UserId }); + + modelBuilder.Entity() + .HasOne(pt => pt.User) + .WithMany(); + + modelBuilder.Entity() + .HasOne(pt => pt.Club) + .WithMany(x => x.Applicants); + + modelBuilder.Entity() + .HasKey(t => new { t.ClubId, t.UserId }); + + modelBuilder.Entity() + .HasOne(pt => pt.User) + .WithMany(); + + modelBuilder.Entity() + .HasOne(pt => pt.Club) + .WithMany(x => x.Bans); + + #endregion } } } diff --git a/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs b/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs new file mode 100644 index 00000000..086d638b --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IClubRepository.cs @@ -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 + { + int GetNextDiscrim(string clubName); + ClubInfo GetByName(string v, int discrim, Func, IQueryable> func = null); + ClubInfo GetByOwner(ulong userId, Func, IQueryable> func = null); + ClubInfo GetByMember(ulong userId, Func, IQueryable> func = null); + ClubInfo[] GetClubLeaderboardPage(int page); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs index e56a9026..498b72ed 100644 --- a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs @@ -16,5 +16,6 @@ namespace NadekoBot.Services.Database.Repositories void SetCleverbotEnabled(ulong id, bool cleverbotEnabled); IEnumerable Permissionsv2ForAll(List include); GuildConfig GcWithPermissionsv2For(ulong guildId); + XpSettings XpSettingsFor(ulong guildId); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs new file mode 100644 index 00000000..667dd315 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IXpRepository.cs @@ -0,0 +1,14 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IXpRepository : IRepository + { + 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); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs index dd329796..c21b1ff2 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs @@ -20,7 +20,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl .Include(bc => bc.RaceAnimals) .Include(bc => bc.Blacklist) .Include(bc => bc.EightBallResponses) - .Include(bc => bc.ModulePrefixes) .Include(bc => bc.StartupCommands) .Include(bc => bc.BlockedCommands) .Include(bc => bc.BlockedModules) diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs new file mode 100644 index 00000000..21421bfa --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ClubRepository.cs @@ -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, IClubRepository + { + public ClubRepository(DbContext context) : base(context) + { + } + + public ClubInfo GetByOwner(ulong userId, Func, IQueryable> 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, IQueryable> 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, IQueryable> 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(); + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs index e61c2d0e..6fa22ebc 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DiscordUserRepository.cs @@ -15,7 +15,15 @@ namespace NadekoBot.Services.Database.Repositories.Impl { 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) _set.Add(toReturn = new DiscordUser() @@ -24,6 +32,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl Discriminator = original.Discriminator, UserId = original.Id, Username = original.Username, + Club = null, }); return toReturn; diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs index e6087bec..1c296db7 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs @@ -189,5 +189,19 @@ namespace NadekoBot.Services.Database.Repositories.Impl conf.CleverbotEnabled = cleverbotEnabled; } + + public XpSettings XpSettingsFor(ulong guildId) + { + var gc = For(guildId, + set => set.Include(x => x.XpSettings) + .ThenInclude(x => x.RoleRewards) + .Include(x => x.XpSettings) + .ThenInclude(x => x.ExclusionList)); + + if (gc.XpSettings == null) + gc.XpSettings = new XpSettings(); + + return gc.XpSettings; + } } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs new file mode 100644 index 00000000..ddd5a8e2 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/XpRepository.cs @@ -0,0 +1,77 @@ +using NadekoBot.Services.Database.Models; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +//todo add pagination to .lb +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class XpRepository : Repository, IXpRepository + { + public XpRepository(DbContext context) : base(context) + { + } + + public UserXpStats GetOrCreateUser(ulong guildId, ulong userId) + { + var usr = _set.FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId); + + if (usr == null) + { + _context.Add(usr = new UserXpStats() + { + Xp = 0, + UserId = userId, + NotifyOnLevelUp = XpNotificationType.None, + GuildId = guildId, + }); + } + + 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(); + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/UnitOfWork.cs b/src/NadekoBot/Services/Database/UnitOfWork.cs index 02e78a97..27a43b13 100644 --- a/src/NadekoBot/Services/Database/UnitOfWork.cs +++ b/src/NadekoBot/Services/Database/UnitOfWork.cs @@ -57,6 +57,12 @@ namespace NadekoBot.Services.Database private IWarningsRepository _warnings; public IWarningsRepository Warnings => _warnings ?? (_warnings = new WarningsRepository(_context)); + private IXpRepository _xp; + public IXpRepository Xp => _xp ?? (_xp = new XpRepository(_context)); + + private IClubRepository _clubs; + public IClubRepository Clubs => _clubs ?? (_clubs = new ClubRepository(_context)); + public UnitOfWork(NadekoContext context) { _context = context; diff --git a/src/NadekoBot/Services/DbService.cs b/src/NadekoBot/Services/DbService.cs index 6e9532ca..5bc9f6bf 100644 --- a/src/NadekoBot/Services/DbService.cs +++ b/src/NadekoBot/Services/DbService.cs @@ -1,11 +1,13 @@ using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Database; +using System.Linq; namespace NadekoBot.Services { public class DbService { private readonly DbContextOptions options; + private readonly DbContextOptions migrateOptions; private readonly string _connectionString; @@ -15,25 +17,23 @@ namespace NadekoBot.Services var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite(creds.Db.ConnectionString); 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() { 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.Migrate(); context.EnsureSeedData(); //set important sqlite stuffs diff --git a/src/NadekoBot/Services/IImagesService.cs b/src/NadekoBot/Services/IImagesService.cs index 08d8f504..0b2b94f2 100644 --- a/src/NadekoBot/Services/IImagesService.cs +++ b/src/NadekoBot/Services/IImagesService.cs @@ -17,6 +17,8 @@ namespace NadekoBot.Services ImmutableArray WifeMatrix { get; } ImmutableArray RategirlDot { get; } + ImmutableArray XpCard { get; } + void Reload(); } } diff --git a/src/NadekoBot/Services/Impl/BotConfigProvider.cs b/src/NadekoBot/Services/Impl/BotConfigProvider.cs index 0ed97550..2d293401 100644 --- a/src/NadekoBot/Services/Impl/BotConfigProvider.cs +++ b/src/NadekoBot/Services/Impl/BotConfigProvider.cs @@ -122,6 +122,18 @@ namespace NadekoBot.Services.Impl else return false; break; + case BotConfigEditType.XpPerMessage: + if (int.TryParse(newValue, out var xp) && xp > 0) + bc.XpPerMessage = xp; + else + return false; + break; + case BotConfigEditType.XpMinutesTimeout: + if (int.TryParse(newValue, out var min) && min > 0) + bc.XpMinutesTimeout = min; + else + return false; + break; default: return false; } diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs index ec09f169..45727fba 100644 --- a/src/NadekoBot/Services/Impl/GoogleApiService.cs +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -377,8 +377,7 @@ namespace NadekoBot.Services.Impl private string ConvertToLanguageCode(string language) { - string mode; - _languageDictionary.TryGetValue(language, out mode); + _languageDictionary.TryGetValue(language, out var mode); return mode; } } diff --git a/src/NadekoBot/Services/Impl/ImagesService.cs b/src/NadekoBot/Services/Impl/ImagesService.cs index 7153328f..3bb0bd2e 100644 --- a/src/NadekoBot/Services/Impl/ImagesService.cs +++ b/src/NadekoBot/Services/Impl/ImagesService.cs @@ -25,6 +25,8 @@ namespace NadekoBot.Services.Impl private const string _wifeMatrixPath = _basePath + "rategirl/wifematrix.png"; private const string _rategirlDot = _basePath + "rategirl/dot.png"; + private const string _xpCardPath = _basePath + "xp/xp.png"; + public ImmutableArray Heads { get; private set; } public ImmutableArray Tails { get; private set; } @@ -40,6 +42,8 @@ namespace NadekoBot.Services.Impl public ImmutableArray WifeMatrix { get; private set; } public ImmutableArray RategirlDot { get; private set; } + public ImmutableArray XpCard { get; private set; } + public ImagesService() { _log = LogManager.GetCurrentClassLogger(); @@ -76,6 +80,8 @@ namespace NadekoBot.Services.Impl WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray(); RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray(); + + XpCard = File.ReadAllBytes(_xpCardPath).ToImmutableArray(); } catch (Exception ex) { diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index f9f5add5..7e5f560d 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -20,7 +20,7 @@ namespace NadekoBot.Services.Impl private readonly IBotCredentials _creds; private readonly DateTime _started; - public const string BotVersion = "1.7.1"; + public const string BotVersion = "1.8"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index c901afaf..84901d21 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -132,7 +132,7 @@ namespace NadekoBot.Extensions public static string ToJson(this T any, Formatting formatting = Formatting.Indented) => JsonConvert.SerializeObject(any, formatting); - public static Stream ToStream(this ImageSharp.Image img) + public static MemoryStream ToStream(this ImageSharp.Image img) { var imageStream = new MemoryStream(); img.SaveAsPng(imageStream); diff --git a/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs b/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs index 806ec904..992d7453 100644 --- a/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs +++ b/src/NadekoBot/_Extensions/IMessageChannelExtensions.cs @@ -10,7 +10,7 @@ namespace NadekoBot.Extensions public static class IMessageChannelExtensions { public static Task EmbedAsync(this IMessageChannel ch, EmbedBuilder embed, string msg = "") - => ch.SendMessageAsync(msg, embed: embed); + => ch.SendMessageAsync(msg, embed: embed.Build()); public static Task SendErrorAsync(this IMessageChannel ch, string title, string error, string url = null, string footer = null) { @@ -20,11 +20,11 @@ namespace NadekoBot.Extensions eb.WithUrl(url); if (!string.IsNullOrWhiteSpace(footer)) eb.WithFooter(efb => efb.WithText(footer)); - return ch.SendMessageAsync("", embed: eb); + return ch.SendMessageAsync("", embed: eb.Build()); } public static Task 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 SendConfirmAsync(this IMessageChannel ch, string title, string text, string url = null, string footer = null) { @@ -34,11 +34,11 @@ namespace NadekoBot.Extensions eb.WithUrl(url); if (!string.IsNullOrWhiteSpace(footer)) eb.WithFooter(efb => efb.WithText(footer)); - return ch.SendMessageAsync("", embed: eb); + return ch.SendMessageAsync("", embed: eb.Build()); } public static Task 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 SendTableAsync(this IMessageChannel ch, string seed, IEnumerable items, Func howToPrint, int columns = 3) { diff --git a/src/NadekoBot/_Extensions/IUserExtensions.cs b/src/NadekoBot/_Extensions/IUserExtensions.cs index d1183e19..89650f21 100644 --- a/src/NadekoBot/_Extensions/IUserExtensions.cs +++ b/src/NadekoBot/_Extensions/IUserExtensions.cs @@ -1,4 +1,5 @@ using Discord; +using NadekoBot.Services.Database.Models; using System; using System.IO; using System.Threading.Tasks; @@ -8,14 +9,14 @@ namespace NadekoBot.Extensions public static class IUserExtensions { public static async Task 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 SendConfirmAsync(this IUser user, string title, string text, string url = null) { var eb = new EmbedBuilder().WithOkColor().WithDescription(text); if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) eb.WithUrl(url); - return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb); + return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Build()); } public static async Task 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); if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute)) eb.WithUrl(url); - return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb); + return await (await user.GetOrCreateDMChannelAsync()).SendMessageAsync("", embed: eb.Build()); } public static async Task 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 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); @@ -39,5 +40,10 @@ namespace NadekoBot.Extensions usr.AvatarId.StartsWith("a_") ? $"{DiscordConfig.CDNUrl}avatars/{usr.Id}/{usr.AvatarId}.gif" : 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"; } } diff --git a/src/NadekoBot/_strings/ResponseStrings.en-US.json b/src/NadekoBot/_strings/ResponseStrings.en-US.json index 2d123486..1ee47eed 100644 --- a/src/NadekoBot/_strings/ResponseStrings.en-US.json +++ b/src/NadekoBot/_strings/ResponseStrings.en-US.json @@ -822,5 +822,51 @@ "administration_prefix_current": "Prefix on this server is {0}", "administration_prefix_new": "Changed prefix on this server from {0} to {1}", "administration_defprefix_current": "Default bot prefix is {0}", - "administration_defprefix_new": "Changed Default bot prefix from {0} to {1}" + "administration_defprefix_new": "Changed Default bot prefix from {0} to {1}", + "xp_server_level": "Server Level", + "xp_level": "Level", + "xp_club": "Club", + "xp_xp": "Experience", + "xp_excluded": "{0} has been excluded from the XP system on this server.", + "xp_not_excluded": "{0} is no longer excluded from the XP system on this server.", + "xp_exclusion_list": "Exclusion List", + "xp_server_is_excluded": "This server is excluded.", + "xp_server_is_not_excluded": "This server is not excluded.", + "xp_excluded_roles": "Excluded Roles", + "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}" } \ No newline at end of file diff --git a/src/NadekoBot/data/fonts/Uni Sans.ttf b/src/NadekoBot/data/fonts/Uni Sans.ttf new file mode 100644 index 00000000..a7b39d02 Binary files /dev/null and b/src/NadekoBot/data/fonts/Uni Sans.ttf differ diff --git a/src/NadekoBot/data/fonts/WhitneyBold.ttf b/src/NadekoBot/data/fonts/WhitneyBold.ttf new file mode 100644 index 00000000..301bc88d Binary files /dev/null and b/src/NadekoBot/data/fonts/WhitneyBold.ttf differ diff --git a/src/NadekoBot/data/fonts/WhitneyBoldItalic.ttf b/src/NadekoBot/data/fonts/WhitneyBoldItalic.ttf new file mode 100644 index 00000000..7e65c9f4 Binary files /dev/null and b/src/NadekoBot/data/fonts/WhitneyBoldItalic.ttf differ diff --git a/src/NadekoBot/data/images/xp/xp.png b/src/NadekoBot/data/images/xp/xp.png new file mode 100644 index 00000000..28025180 Binary files /dev/null and b/src/NadekoBot/data/images/xp/xp.png differ