Merge remote-tracking branch 'Kwoth/1.9' into 1.9

This commit is contained in:
Shikhir Arora 2017-11-16 09:41:06 -05:00
commit b844579d3b
24 changed files with 2440 additions and 128 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace NadekoBot.Migrations
{
public partial class currencylevelupreward : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "XpCurrencyReward",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Amount = table.Column<int>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Level = table.Column<int>(type: "INTEGER", nullable: false),
XpSettingsId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_XpCurrencyReward", x => x.Id);
table.ForeignKey(
name: "FK_XpCurrencyReward_XpSettings_XpSettingsId",
column: x => x.XpSettingsId,
principalTable: "XpSettings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_XpCurrencyReward_XpSettingsId",
table: "XpCurrencyReward",
column: "XpSettingsId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "XpCurrencyReward");
}
}
}

View File

@ -1551,6 +1551,26 @@ namespace NadekoBot.Migrations
b.ToTable("WarningPunishment"); b.ToTable("WarningPunishment");
}); });
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpCurrencyReward", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<int>("Level");
b.Property<int>("XpSettingsId");
b.HasKey("Id");
b.HasIndex("XpSettingsId");
b.ToTable("XpCurrencyReward");
});
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b => modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -1972,6 +1992,14 @@ namespace NadekoBot.Migrations
.HasForeignKey("GuildConfigId"); .HasForeignKey("GuildConfigId");
}); });
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpCurrencyReward", b =>
{
b.HasOne("NadekoBot.Core.Services.Database.Models.XpSettings", "XpSettings")
.WithMany("CurrencyRewards")
.HasForeignKey("XpSettingsId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b => modelBuilder.Entity("NadekoBot.Core.Services.Database.Models.XpRoleReward", b =>
{ {
b.HasOne("NadekoBot.Core.Services.Database.Models.XpSettings", "XpSettings") b.HasOne("NadekoBot.Core.Services.Database.Models.XpSettings", "XpSettings")

View File

@ -47,6 +47,7 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task LanguageSet() public async Task LanguageSet()
{ {
var cul = _localization.GetCultureInfo(Context.Guild); var cul = _localization.GetCultureInfo(Context.Guild);
@ -57,6 +58,7 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)] [RequireUserPermission(GuildPermission.Administrator)]
[Priority(1)]
public async Task LanguageSet(string name) public async Task LanguageSet(string name)
{ {
try try

View File

@ -106,11 +106,60 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task LogEvents() public async Task LogEvents()
{ {
_service.GuildLogSettings.TryGetValue(Context.Guild.Id, out LogSetting l);
var str = string.Join("\n", Enum.GetNames(typeof(LogType))
.Select(x =>
{
var val = l == null ? null : GetLogProperty(l, Enum.Parse<LogType>(x));
if (val != null)
return $"{Format.Bold(x)} <#{val}>";
return Format.Bold(x);
}));
await Context.Channel.SendConfirmAsync(Format.Bold(GetText("log_events")) + "\n" + await Context.Channel.SendConfirmAsync(Format.Bold(GetText("log_events")) + "\n" +
$"```fix\n{string.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>())}```") str)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
private ulong? GetLogProperty(LogSetting l, LogType type)
{
switch (type)
{
case LogType.Other:
return l.LogOtherId;
case LogType.MessageUpdated:
return l.MessageUpdatedId;
case LogType.MessageDeleted:
return l.MessageDeletedId;
case LogType.UserJoined:
return l.UserJoinedId;
case LogType.UserLeft:
return l.UserLeftId;
case LogType.UserBanned:
return l.UserBannedId;
case LogType.UserUnbanned:
return l.UserUnbannedId;
case LogType.UserUpdated:
return l.UserUpdatedId;
case LogType.ChannelCreated:
return l.ChannelCreatedId;
case LogType.ChannelDestroyed:
return l.ChannelDestroyedId;
case LogType.ChannelUpdated:
return l.ChannelUpdatedId;
case LogType.UserPresence:
return l.LogUserPresenceId;
case LogType.VoicePresence:
return l.LogVoicePresenceId;
case LogType.VoicePresenceTTS:
return l.LogVoicePresenceTTSId;
case LogType.UserMuted:
return l.UserMutedId;
default:
return null;
}
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)] [RequireUserPermission(GuildPermission.Administrator)]

View File

@ -2,16 +2,16 @@
using AngleSharp.Dom.Html; using AngleSharp.Dom.Html;
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Replacements;
using NadekoBot.Core.Services.Database.Models;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.CustomReactions.Services; using NadekoBot.Modules.CustomReactions.Services;
using NadekoBot.Core.Services.Database.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Common;
using NadekoBot.Common.Replacements;
namespace NadekoBot.Modules.CustomReactions.Extensions namespace NadekoBot.Modules.CustomReactions.Extensions
{ {
@ -89,7 +89,7 @@ namespace NadekoBot.Modules.CustomReactions.Extensions
public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client) public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client)
=> cr.Trigger.ResolveTriggerString(ctx, client); => cr.Trigger.ResolveTriggerString(ctx, client);
public static Task<string > ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, bool containsAnywhere) public static Task<string> ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, bool containsAnywhere)
=> cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client), containsAnywhere); => cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client), containsAnywhere);
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, CustomReactionsService crs) public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, CustomReactionsService crs)
@ -127,14 +127,35 @@ namespace NadekoBot.Modules.CustomReactions.Extensions
public static WordPosition GetWordPosition(this string str, string word) public static WordPosition GetWordPosition(this string str, string word)
{ {
if (str.StartsWith(word + " ")) var wordIndex = str.IndexOf(word);
return WordPosition.Start; if (wordIndex == -1)
else if (str.EndsWith(" " + word))
return WordPosition.End;
else if (str.Contains(" " + word + " "))
return WordPosition.Middle;
else
return WordPosition.None; return WordPosition.None;
if (wordIndex == 0)
{
if (word.Length < str.Length && str.isValidWordDivider(word.Length))
return WordPosition.Start;
}
else if ((wordIndex + word.Length) == str.Length)
{
if (str.isValidWordDivider(wordIndex - 1))
return WordPosition.End;
}
else if (str.isValidWordDivider(wordIndex - 1) && str.isValidWordDivider(wordIndex + word.Length))
return WordPosition.Middle;
return WordPosition.None;
}
private static bool isValidWordDivider(this string str, int index)
{
var ch = str[index];
if (ch >= 'a' && ch <= 'z')
return false;
if (ch >= 'A' && ch <= 'Z')
return false;
return true;
} }
} }
@ -145,4 +166,4 @@ namespace NadekoBot.Modules.CustomReactions.Extensions
Middle, Middle,
End, End,
} }
} }

View File

@ -20,14 +20,16 @@ namespace NadekoBot.Modules.Gambling
private readonly IImageCache _images; private readonly IImageCache _images;
private readonly IBotConfigProvider _bc; private readonly IBotConfigProvider _bc;
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private readonly DbService _db;
private static readonly NadekoRandom rng = new NadekoRandom(); private static readonly NadekoRandom rng = new NadekoRandom();
public FlipCoinCommands(IDataCache data, CurrencyService cs, IBotConfigProvider bc) public FlipCoinCommands(IDataCache data, CurrencyService cs,
IBotConfigProvider bc, DbService db)
{ {
_images = data.LocalImages; _images = data.LocalImages;
_bc = bc; _bc = bc;
_cs = cs; _cs = cs;
_db = db;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -86,7 +88,18 @@ namespace NadekoBot.Modules.Gambling
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Betflip(int amount, BetFlipGuess guess) public Task Betflip(Allin _, BetFlipGuess guess)
{
long cur;
using (var uow = _db.UnitOfWork)
{
cur = uow.Currency.GetUserCurrency(Context.User.Id);
}
return Betflip(cur, guess);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Betflip(long amount, BetFlipGuess guess)
{ {
if (amount < _bc.BotConfig.MinimumBetAmount) if (amount < _bc.BotConfig.MinimumBetAmount)
{ {

View File

@ -278,9 +278,9 @@ namespace NadekoBot.Modules.Gambling
// }); // });
// return Task.CompletedTask; // return Task.CompletedTask;
//} //}
public enum Allin { Allin, All }
[NadekoCommand, Usage, Description, Aliases] private async Task InternallBetroll(long amount)
public async Task BetRoll(long amount)
{ {
if (amount < 1) if (amount < 1)
return; return;
@ -303,24 +303,39 @@ namespace NadekoBot.Modules.Gambling
{ {
str += GetText("br_win", (amount * _bc.BotConfig.Betroll67Multiplier) + CurrencySign, 66); str += GetText("br_win", (amount * _bc.BotConfig.Betroll67Multiplier) + CurrencySign, 66);
await _cs.AddAsync(Context.User, "Betroll Gamble", await _cs.AddAsync(Context.User, "Betroll Gamble",
(int) (amount * _bc.BotConfig.Betroll67Multiplier), false).ConfigureAwait(false); (int)(amount * _bc.BotConfig.Betroll67Multiplier), false).ConfigureAwait(false);
} }
else if (rnd < 100) else if (rnd < 100)
{ {
str += GetText("br_win", (amount * _bc.BotConfig.Betroll91Multiplier) + CurrencySign, 90); str += GetText("br_win", (amount * _bc.BotConfig.Betroll91Multiplier) + CurrencySign, 90);
await _cs.AddAsync(Context.User, "Betroll Gamble", await _cs.AddAsync(Context.User, "Betroll Gamble",
(int) (amount * _bc.BotConfig.Betroll91Multiplier), false).ConfigureAwait(false); (int)(amount * _bc.BotConfig.Betroll91Multiplier), false).ConfigureAwait(false);
} }
else else
{ {
str += GetText("br_win", (amount * _bc.BotConfig.Betroll100Multiplier) + CurrencySign, 99) + " 👑"; str += GetText("br_win", (amount * _bc.BotConfig.Betroll100Multiplier) + CurrencySign, 99) + " 👑";
await _cs.AddAsync(Context.User, "Betroll Gamble", await _cs.AddAsync(Context.User, "Betroll Gamble",
(int) (amount * _bc.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false); (int)(amount * _bc.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false);
} }
} }
await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
public Task BetRoll(long amount)
=> InternallBetroll(amount);
[NadekoCommand, Usage, Description, Aliases]
public Task BetRoll(Allin _)
{
long cur;
using (var uow = _db.UnitOfWork)
{
cur = uow.Currency.GetUserCurrency(Context.User.Id);
}
return InternallBetroll(cur);
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Leaderboard(int page = 1) public async Task Leaderboard(int page = 1)
{ {

View File

@ -15,16 +15,31 @@ namespace NadekoBot.Modules.Gambling
{ {
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private readonly IBotConfigProvider _bc; private readonly IBotConfigProvider _bc;
private readonly DbService _db;
public WheelOfFortuneCommands(CurrencyService cs, IBotConfigProvider bc) public WheelOfFortuneCommands(CurrencyService cs, IBotConfigProvider bc,
DbService db)
{ {
_cs = cs; _cs = cs;
_bc = bc; _bc = bc;
_db = db;
}
public enum Allin { Allin, All }
[NadekoCommand, Usage, Description, Aliases]
public Task WheelOfFortune(Allin _)
{
long cur;
using (var uow = _db.UnitOfWork)
{
cur = uow.Currency.GetUserCurrency(Context.User.Id);
}
return WheelOfFortune(cur);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] public async Task WheelOfFortune(long bet)
public async Task WheelOfFortune(int bet)
{ {
const int minBet = 10; const int minBet = 10;
if (bet < minBet) if (bet < minBet)

View File

@ -11,6 +11,8 @@ using System.Collections.Generic;
using NadekoBot.Common.Attributes; using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Help.Services; using NadekoBot.Modules.Help.Services;
using NadekoBot.Modules.Permissions.Services; using NadekoBot.Modules.Permissions.Services;
using NadekoBot.Common;
using NadekoBot.Common.Replacements;
namespace NadekoBot.Modules.Help namespace NadekoBot.Modules.Help
{ {
@ -23,8 +25,23 @@ namespace NadekoBot.Modules.Help
private readonly CommandService _cmds; private readonly CommandService _cmds;
private readonly GlobalPermissionService _perms; private readonly GlobalPermissionService _perms;
public string HelpString => String.Format(_config.BotConfig.HelpString, _creds.ClientId, Prefix); public EmbedBuilder GetHelpStringEmbed()
public string DMHelpString => _config.BotConfig.DMHelpString; {
var r = new ReplacementBuilder()
.WithDefault(Context)
.WithOverride("{0}", () => _creds.ClientId.ToString())
.WithOverride("{1}", () => Prefix)
.Build();
if (!CREmbed.TryParse(_config.BotConfig.HelpString, out var embed))
return new EmbedBuilder().WithOkColor()
.WithDescription(String.Format(_config.BotConfig.HelpString, _creds.ClientId, Prefix));
r.Replace(embed);
return embed.ToEmbed();
}
public Help(IBotCredentials creds, GlobalPermissionService perms, IBotConfigProvider config, CommandService cmds) public Help(IBotCredentials creds, GlobalPermissionService perms, IBotConfigProvider config, CommandService cmds)
{ {
@ -94,16 +111,10 @@ namespace NadekoBot.Modules.Help
if (com == null) if (com == null)
{ {
IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)Context.User).GetOrCreateDMChannelAsync() : channel; IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)Context.User).GetOrCreateDMChannelAsync() : channel;
await ch.SendMessageAsync(HelpString).ConfigureAwait(false); await ch.EmbedAsync(GetHelpStringEmbed()).ConfigureAwait(false);
return; return;
} }
//if (com == null)
//{
// await ReplyErrorLocalized("command_not_found").ConfigureAwait(false);
// return;
//}
var embed = _service.GetCommandHelp(com, Context.Guild); var embed = _service.GetCommandHelp(com, Context.Guild);
await channel.EmbedAsync(embed).ConfigureAwait(false); await channel.EmbedAsync(embed).ConfigureAwait(false);
} }

View File

@ -9,6 +9,8 @@ using NadekoBot.Common.Attributes;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Core.Services; using NadekoBot.Core.Services;
using NadekoBot.Core.Services.Impl; using NadekoBot.Core.Services.Impl;
using NadekoBot.Common;
using NLog;
namespace NadekoBot.Modules.Help.Services namespace NadekoBot.Modules.Help.Services
{ {
@ -17,25 +19,33 @@ namespace NadekoBot.Modules.Help.Services
private readonly IBotConfigProvider _bc; private readonly IBotConfigProvider _bc;
private readonly CommandHandler _ch; private readonly CommandHandler _ch;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly Logger _log;
public HelpService(IBotConfigProvider bc, CommandHandler ch, NadekoStrings strings) public HelpService(IBotConfigProvider bc, CommandHandler ch, NadekoStrings strings)
{ {
_bc = bc; _bc = bc;
_ch = ch; _ch = ch;
_strings = strings; _strings = strings;
_log = LogManager.GetCurrentClassLogger();
} }
public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg) public Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{ {
try try
{ {
if(guild == null) if (guild == null)
await msg.Channel.SendMessageAsync(_bc.BotConfig.DMHelpString).ConfigureAwait(false); {
if (CREmbed.TryParse(_bc.BotConfig.DMHelpString, out var embed))
return msg.Channel.EmbedAsync(embed.ToEmbed(), embed.PlainText?.SanitizeMentions() ?? "");
return msg.Channel.SendMessageAsync(_bc.BotConfig.DMHelpString);
}
} }
catch (Exception) catch (Exception ex)
{ {
//ignore _log.Warn(ex);
} }
return Task.CompletedTask;
} }
public EmbedBuilder GetCommandHelp(CommandInfo com, IGuild guild) public EmbedBuilder GetCommandHelp(CommandInfo com, IGuild guild)

View File

@ -89,6 +89,7 @@ namespace NadekoBot.Modules.Permissions
} }
else else
{ {
_service.InviteFilteringChannels.TryRemove(channel.Id);
await ReplyConfirmLocalized("invite_filter_channel_off").ConfigureAwait(false); await ReplyConfirmLocalized("invite_filter_channel_off").ConfigureAwait(false);
} }
} }

View File

@ -26,7 +26,7 @@ namespace NadekoBot.Modules.Searches
if (novelData == null) if (novelData == null)
{ {
await ReplyErrorLocalized("error_finding_novel").ConfigureAwait(false); await ReplyErrorLocalized("failed_finding_novel").ConfigureAwait(false);
return; return;
} }
@ -35,7 +35,7 @@ namespace NadekoBot.Modules.Searches
.WithTitle(novelData.Title) .WithTitle(novelData.Title)
.WithUrl(novelData.Link) .WithUrl(novelData.Link)
.WithImageUrl(novelData.ImageUrl) .WithImageUrl(novelData.ImageUrl)
.AddField(efb => efb.WithName(GetText("authors")).WithValue(String.Join("\n", novelData.Authors)).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("authors")).WithValue(string.Join("\n", novelData.Authors)).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("status")).WithValue(novelData.Status).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("status")).WithValue(novelData.Status).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("genres")).WithValue(string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" })).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("genres")).WithValue(string.Join(" ", novelData.Genres.Any() ? novelData.Genres : new[] { "none" })).WithIsInline(true))
.WithFooter(efb => efb.WithText(GetText("score") + " " + novelData.Score)); .WithFooter(efb => efb.WithText(GetText("score") + " " + novelData.Score));

View File

@ -67,6 +67,8 @@ namespace NadekoBot.Modules.Searches.Services
var document = await BrowsingContext.New(config).OpenAsync(link); var document = await BrowsingContext.New(config).OpenAsync(link);
var imageElem = document.QuerySelector("div.seriesimg > img"); var imageElem = document.QuerySelector("div.seriesimg > img");
if (imageElem == null)
return null;
var imageUrl = ((IHtmlImageElement)imageElem).Source; var imageUrl = ((IHtmlImageElement)imageElem).Source;
var descElem = document.QuerySelector("div#editdescription > p"); var descElem = document.QuerySelector("div#editdescription > p");

View File

@ -51,7 +51,6 @@ namespace NadekoBot.Modules.Searches.Services
public ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; } = new ConcurrentDictionary<ulong, Timer>(); public ConcurrentDictionary<ulong, Timer> AutoButtTimers { get; } = new ConcurrentDictionary<ulong, Timer>();
private readonly ConcurrentDictionary<ulong, HashSet<string>> _blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>(); private readonly ConcurrentDictionary<ulong, HashSet<string>> _blacklistedTags = new ConcurrentDictionary<ulong, HashSet<string>>();
private readonly Timer _t;
private readonly SemaphoreSlim _cryptoLock = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _cryptoLock = new SemaphoreSlim(1, 1);
public async Task<CryptoData[]> CryptoData() public async Task<CryptoData[]> CryptoData()
@ -132,22 +131,6 @@ namespace NadekoBot.Modules.Searches.Services
return Task.CompletedTask; return Task.CompletedTask;
}; };
if (client.ShardId == 0)
{
_t = new Timer(async _ =>
{
var r = _cache.Redis.GetDatabase();
try
{
}
catch (Exception ex)
{
_log.Warn(ex);
}
}, null, TimeSpan.Zero, TimeSpan.FromHours(1));
}
//joke commands //joke commands
if (File.Exists("data/wowjokes.json")) if (File.Exists("data/wowjokes.json"))
{ {

View File

@ -7,7 +7,7 @@
public object declined_since { get; set; } public object declined_since { get; set; }
public bool is_twitch_pledge { get; set; } public bool is_twitch_pledge { get; set; }
public bool patron_pays_fees { get; set; } public bool patron_pays_fees { get; set; }
public int pledge_cap_cents { get; set; } public int? pledge_cap_cents { get; set; }
} }
public class Address public class Address

View File

@ -118,7 +118,7 @@ namespace NadekoBot.Modules.Utility
[OwnerOnly] [OwnerOnly]
public async Task Activity(int page = 1) public async Task Activity(int page = 1)
{ {
const int activityPerPage = 15; const int activityPerPage = 10;
page -= 1; page -= 1;
if (page < 0) if (page < 0)

View File

@ -36,6 +36,8 @@ namespace NadekoBot.Modules.Xp.Services
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly IDataCache _cache; private readonly IDataCache _cache;
private readonly FontProvider _fonts; private readonly FontProvider _fonts;
private readonly IBotCredentials _creds;
private readonly CurrencyService _cs;
public const int XP_REQUIRED_LVL_1 = 36; public const int XP_REQUIRED_LVL_1 = 36;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedRoles
@ -60,7 +62,7 @@ namespace NadekoBot.Modules.Xp.Services
public XpService(CommandHandler cmd, IBotConfigProvider bc, public XpService(CommandHandler cmd, IBotConfigProvider bc,
NadekoBot bot, DbService db, NadekoStrings strings, IDataCache cache, NadekoBot bot, DbService db, NadekoStrings strings, IDataCache cache,
FontProvider fonts) FontProvider fonts, IBotCredentials creds, CurrencyService cs)
{ {
_db = db; _db = db;
_cmd = cmd; _cmd = cmd;
@ -70,6 +72,8 @@ namespace NadekoBot.Modules.Xp.Services
_strings = strings; _strings = strings;
_cache = cache; _cache = cache;
_fonts = fonts; _fonts = fonts;
_creds = creds;
_cs = cs;
//load settings //load settings
var allGuildConfigs = bot.AllGuildConfigs.Where(x => x.XpSettings != null); var allGuildConfigs = bot.AllGuildConfigs.Where(x => x.XpSettings != null);
@ -105,6 +109,7 @@ namespace NadekoBot.Modules.Xp.Services
{ {
var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>(); var toNotify = new List<(IMessageChannel MessageChannel, IUser User, int Level, XpNotificationType NotifyType, NotifOf NotifOf)>();
var roleRewards = new Dictionary<ulong, List<XpRoleReward>>(); var roleRewards = new Dictionary<ulong, List<XpRoleReward>>();
var curRewards = new Dictionary<ulong, List<XpCurrencyReward>>();
var toAddTo = new List<UserCacheItem>(); var toAddTo = new List<UserCacheItem>();
while (_addMessageXp.TryDequeue(out var usr)) while (_addMessageXp.TryDequeue(out var usr))
@ -120,14 +125,13 @@ namespace NadekoBot.Modules.Xp.Services
{ {
var xp = item.Select(x => bc.BotConfig.XpPerMessage).Sum(); var xp = item.Select(x => bc.BotConfig.XpPerMessage).Sum();
//1. Mass query discord users and userxpstats and get them from local dict
//2. (better but much harder) Move everything to the database, and get old and new xp
// amounts for every user (in order to give rewards)
var usr = uow.Xp.GetOrCreateUser(item.Key.GuildId, item.Key.User.Id); var usr = uow.Xp.GetOrCreateUser(item.Key.GuildId, item.Key.User.Id);
var du = uow.DiscordUsers.GetOrCreate(item.Key.User); var du = uow.DiscordUsers.GetOrCreate(item.Key.User);
if (du.LastXpGain + TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout) > DateTime.UtcNow)
continue;
du.LastXpGain = DateTime.UtcNow;
var globalXp = du.TotalXp; var globalXp = du.TotalXp;
var oldGlobalLevelData = new LevelStats(globalXp); var oldGlobalLevelData = new LevelStats(globalXp);
var newGlobalLevelData = new LevelStats(globalXp + xp); var newGlobalLevelData = new LevelStats(globalXp + xp);
@ -156,21 +160,34 @@ namespace NadekoBot.Modules.Xp.Services
toNotify.Add((first.Channel, first.User, newGuildLevelData.Level, usr.NotifyOnLevelUp, NotifOf.Server)); toNotify.Add((first.Channel, first.User, newGuildLevelData.Level, usr.NotifyOnLevelUp, NotifOf.Server));
//give role //give role
if (!roleRewards.TryGetValue(usr.GuildId, out var rewards)) if (!roleRewards.TryGetValue(usr.GuildId, out var rrews))
{ {
rewards = uow.GuildConfigs.XpSettingsFor(usr.GuildId).RoleRewards.ToList(); rrews = uow.GuildConfigs.XpSettingsFor(usr.GuildId).RoleRewards.ToList();
roleRewards.Add(usr.GuildId, rewards); roleRewards.Add(usr.GuildId, rrews);
} }
var rew = rewards.FirstOrDefault(x => x.Level == newGuildLevelData.Level); if (!curRewards.TryGetValue(usr.GuildId, out var crews))
if (rew != null)
{ {
var role = first.User.Guild.GetRole(rew.RoleId); crews = uow.GuildConfigs.XpSettingsFor(usr.GuildId).CurrencyRewards.ToList();
curRewards.Add(usr.GuildId, crews);
}
var rrew = rrews.FirstOrDefault(x => x.Level == newGuildLevelData.Level);
if (rrew != null)
{
var role = first.User.Guild.GetRole(rrew.RoleId);
if (role != null) if (role != null)
{ {
var __ = first.User.AddRoleAsync(role); var __ = first.User.AddRoleAsync(role);
} }
} }
//get currency reward for this level
var crew = crews.FirstOrDefault(x => x.Level == newGuildLevelData.Level);
if (crew != null)
{
//give the user the reward if it exists
await _cs.AddAsync(item.Key.User.Id, "Level-up Reward", crew.Amount, uow);
}
} }
} }
@ -241,6 +258,50 @@ namespace NadekoBot.Modules.Xp.Services
}, token); }, token);
} }
public void SetCurrencyReward(ulong guildId, int level, int amount)
{
using (var uow = _db.UnitOfWork)
{
var settings = uow.GuildConfigs.XpSettingsFor(guildId);
if (amount <= 0)
{
var toRemove = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
if (toRemove != null)
{
uow._context.Remove(toRemove);
settings.CurrencyRewards.Remove(toRemove);
}
}
else
{
var rew = settings.CurrencyRewards.FirstOrDefault(x => x.Level == level);
if (rew != null)
rew.Amount = amount;
else
settings.CurrencyRewards.Add(new XpCurrencyReward()
{
Level = level,
Amount = amount,
});
}
uow.Complete();
}
}
public IEnumerable<XpCurrencyReward> GetCurrencyRewards(ulong id)
{
using (var uow = _db.UnitOfWork)
{
return uow.GuildConfigs.XpSettingsFor(id)
.CurrencyRewards
.ToArray();
}
}
public IEnumerable<XpRoleReward> GetRoleRewards(ulong id) public IEnumerable<XpRoleReward> GetRoleRewards(ulong id)
{ {
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
@ -385,7 +446,13 @@ namespace NadekoBot.Modules.Xp.Services
private bool SetUserRewarded(ulong userId) private bool SetUserRewarded(ulong userId)
{ {
return _rewardedUsers.Add(userId); var r = _cache.Redis.GetDatabase();
var key = $"{_creds.RedisKey()}_user_xp_gain_{userId}";
return r.StringSet(key,
true,
TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout),
StackExchange.Redis.When.NotExists);
} }
public FullUserStats GetUserStats(IGuildUser user) public FullUserStats GetUserStats(IGuildUser user)

View File

@ -17,40 +17,18 @@ namespace NadekoBot.Modules.Xp
{ {
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
private readonly IBotConfigProvider _bc;
public Xp(DiscordSocketClient client,DbService db) public Xp(DiscordSocketClient client,DbService db, IBotConfigProvider bc)
{ {
_client = client; _client = client;
_db = db; _db = db;
_bc = bc;
} }
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//[OwnerOnly]
//public async Task Populate()
//{
// var rng = new NadekoRandom();
// using (var uow = _db.UnitOfWork)
// {
// for (var i = 0ul; i < 1000000; i++)
// {
// uow.DiscordUsers.Add(new DiscordUser()
// {
// AvatarId = i.ToString(),
// Discriminator = "1234",
// UserId = i,
// Username = i.ToString(),
// Club = null,
// });
// var xp = uow.Xp.GetOrCreateUser(Context.Guild.Id, i);
// xp.Xp = rng.Next(100, 100000);
// }
// uow.Complete();
// }
//}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
//todo add ratelimit attribute
//[Ratelimit(30)] //[Ratelimit(30)]
public async Task Experience([Remainder]IUser user = null) public async Task Experience([Remainder]IUser user = null)
{ {
@ -69,34 +47,39 @@ namespace NadekoBot.Modules.Xp
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public Task XpRoleRewards(int page = 1) public Task XpLevelUpRewards(int page = 1)
{ {
page--; page--;
if (page < 0 || page > 100) if (page < 0 || page > 100)
return Task.CompletedTask; return Task.CompletedTask;
var roles = _service.GetRoleRewards(Context.Guild.Id)
.OrderBy(x => x.Level)
.Skip(page * 9)
.Take(9);
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithTitle(GetText("role_rewards")) .WithTitle(GetText("level_up_rewards"))
.WithOkColor(); .WithOkColor();
if (!roles.Any()) var rewards = _service.GetRoleRewards(Context.Guild.Id)
return Context.Channel.EmbedAsync(embed.WithDescription(GetText("no_role_rewards"))); .OrderBy(x => x.Level)
.Select(x =>
{
var str = Context.Guild.GetRole(x.RoleId)?.ToString();
if (str != null)
str = GetText("role_reward", Format.Bold(str));
return (x.Level, RoleStr: str);
})
.Where(x => x.RoleStr != null)
.Concat(_service.GetCurrencyRewards(Context.Guild.Id)
.OrderBy(x => x.Level)
.Select(x => (x.Level, Format.Bold(x.Amount + _bc.BotConfig.CurrencySign))))
.GroupBy(x => x.Level)
.OrderBy(x => x.Key)
.Skip(page * 9)
.Take(9)
.ForEach(x => embed.AddField(GetText("level_x", x.Key), string.Join("\n", x.Select(y => y.Item2))));
foreach (var rolerew in roles) if (!rewards.Any())
{ return Context.Channel.EmbedAsync(embed.WithDescription(GetText("no_level_up_rewards")));
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); return Context.Channel.EmbedAsync(embed);
} }
@ -116,6 +99,22 @@ namespace NadekoBot.Modules.Xp
await ReplyConfirmLocalized("role_reward_added", level, Format.Bold(role.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("role_reward_added", level, Format.Bold(role.ToString())).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task XpCurrencyReward(int level, int amount=0)
{
if (level < 1 || amount < 0)
return;
_service.SetCurrencyReward(Context.Guild.Id, level, amount);
if (amount == 0)
await ReplyConfirmLocalized("cur_reward_cleared", level, _bc.BotConfig.CurrencySign).ConfigureAwait(false);
else
await ReplyConfirmLocalized("cur_reward_added", level, Format.Bold(amount + _bc.BotConfig.CurrencySign)).ConfigureAwait(false);
}
public enum NotifyPlace public enum NotifyPlace
{ {
Server = 0, Server = 0,

View File

@ -8,6 +8,7 @@ namespace NadekoBot.Core.Services.Database.Models
public GuildConfig GuildConfig { get; set; } public GuildConfig GuildConfig { get; set; }
public HashSet<XpRoleReward> RoleRewards { get; set; } = new HashSet<XpRoleReward>(); public HashSet<XpRoleReward> RoleRewards { get; set; } = new HashSet<XpRoleReward>();
public HashSet<XpCurrencyReward> CurrencyRewards { get; set; } = new HashSet<XpCurrencyReward>();
public bool XpRoleRewardExclusive { get; set; } public bool XpRoleRewardExclusive { get; set; }
public string NotifyMessage { get; set; } = "Congratulations {0}! You have reached level {1}!"; public string NotifyMessage { get; set; } = "Congratulations {0}! You have reached level {1}!";
public HashSet<ExcludedItem> ExclusionList { get; set; } = new HashSet<ExcludedItem>(); public HashSet<ExcludedItem> ExclusionList { get; set; } = new HashSet<ExcludedItem>();
@ -35,6 +36,25 @@ namespace NadekoBot.Core.Services.Database.Models
} }
} }
public class XpCurrencyReward : DbEntity
{
public int XpSettingsId { get; set; }
public XpSettings XpSettings { get; set; }
public int Level { get; set; }
public int Amount { get; set; }
public override int GetHashCode()
{
return Level.GetHashCode() ^ XpSettingsId.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is XpCurrencyReward xrr && xrr.Level == Level && xrr.XpSettingsId == XpSettingsId;
}
}
public class ExcludedItem : DbEntity public class ExcludedItem : DbEntity
{ {
public ulong ItemId { get; set; } public ulong ItemId { get; set; }

View File

@ -200,6 +200,8 @@ namespace NadekoBot.Core.Services.Database.Repositories.Impl
set => set.Include(x => x.XpSettings) set => set.Include(x => x.XpSettings)
.ThenInclude(x => x.RoleRewards) .ThenInclude(x => x.RoleRewards)
.Include(x => x.XpSettings) .Include(x => x.XpSettings)
.ThenInclude(x => x.CurrencyRewards)
.Include(x => x.XpSettings)
.ThenInclude(x => x.ExclusionList)); .ThenInclude(x => x.ExclusionList));
if (gc.XpSettings == null) if (gc.XpSettings == null)

View File

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

View File

@ -228,9 +228,6 @@
"gambling_flipped": "flipped {0}.", "gambling_flipped": "flipped {0}.",
"gambling_flip_guess": "You guessed it! You won {0}", "gambling_flip_guess": "You guessed it! You won {0}",
"gambling_flip_invalid": "Invalid number specified. You can flip 1 to {0} coins.", "gambling_flip_invalid": "Invalid number specified. You can flip 1 to {0} coins.",
"gambling_flowerreaction_desc": "Add {0} reaction to this message to get {1} ",
"gambling_flowerreaction_footer": "This event is active for up to {0} hours.",
"gambling_flowerreaction_title": "Flower reaction event started!",
"gambling_gifted": "has gifted {0} to {1}", "gambling_gifted": "has gifted {0} to {1}",
"gambling_has": "{0} has {1}", "gambling_has": "{0} has {1}",
"gambling_heads": "Head", "gambling_heads": "Head",
@ -841,11 +838,14 @@
"xp_level_up_channel": "Congratulations {0}, You've reached level {1}!", "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_dm": "Congratulations {0}, You've reached level {1} on {2} server!",
"xp_level_up_global": "Congratulations {0}, You've reached global level {1}!", "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_cleared": "Reaching level {0} will no longer reward a role.",
"xp_role_reward_added": "Users who reach level {0} will receive {1} role.", "xp_role_reward_added": "Users who reach level {0} will receive {1} role.",
"xp_role_rewards": "Role Rewards", "xp_cur_reward_cleared": "Reaching level {0} will no longer reward any {1}.",
"xp_cur_reward_added": "Users who reach level {0} will receive {1}.",
"xp_level_up_rewards": "Level Up Rewards",
"xp_level_x": "Level {0}", "xp_level_x": "Level {0}",
"xp_no_role_rewards": "No role reward on this page.", "xp_role_reward": "{0} role",
"xp_no_level_up_rewards": "No level up reward on this page.",
"xp_server_leaderboard": "Server XP Leaderboard", "xp_server_leaderboard": "Server XP Leaderboard",
"xp_global_leaderboard": "Global XP Leaderboard", "xp_global_leaderboard": "Global XP Leaderboard",
"xp_modified": "Modified server XP of the user {0} by {1}", "xp_modified": "Modified server XP of the user {0} by {1}",
@ -923,5 +923,5 @@
"administration_invalid": "Invalid / Can't be found ({0})", "administration_invalid": "Invalid / Can't be found ({0})",
"administration_mass_kill_in_progress": "Mass Banning and Blacklisting of {0} users is in progress...", "administration_mass_kill_in_progress": "Mass Banning and Blacklisting of {0} users is in progress...",
"administration_mass_kill_completed": "Mass Banning and Blacklisting of {0} users is complete.", "administration_mass_kill_completed": "Mass Banning and Blacklisting of {0} users is complete.",
"searches_error_finding_novel": "Can't find that novel. Make sure you've typed the exact full name, and that it exists on novelupdates.com" "searches_failed_finding_novel": "Can't find that novel. Make sure you've typed the exact full name, and that it exists on novelupdates.com"
} }

View File

@ -2815,11 +2815,11 @@
"{0}xpn server channel" "{0}xpn server channel"
] ]
}, },
"xprolerewards": { "xpleveluprewards": {
"Cmd": "xprolerewards xprrs", "Cmd": "xplvluprewards xprews xpcrs xprrs xprolerewards xpcurrewards",
"Desc": "Shows currently set role rewards.", "Desc": "Shows currently set level up rewards.",
"Usage": [ "Usage": [
"{0}xprrs" "{0}xprews"
] ]
}, },
"xprolereward": { "xprolereward": {
@ -2829,6 +2829,13 @@
"{0}xprr 3 Social" "{0}xprr 3 Social"
] ]
}, },
"xpcurrencyreward": {
"Cmd": "xpcurreward xpcr",
"Desc": "Sets a currency reward on a specified level. Provide no amount in order to remove the reward.",
"Usage": [
"{0}xpcr 3 50"
]
},
"xpleaderboard": { "xpleaderboard": {
"Cmd": "xpleaderboard xplb", "Cmd": "xpleaderboard xplb",
"Desc": "Shows current server's xp leaderboard.", "Desc": "Shows current server's xp leaderboard.",