| @@ -1,7 +1,7 @@ | |||||||
|  |  | ||||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||||
| # Visual Studio 15 | # Visual Studio 15 | ||||||
| VisualStudioVersion = 15.0.26430.12 | VisualStudioVersion = 15.0.26730.3 | ||||||
| MinimumVisualStudioVersion = 10.0.40219.1 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" | ||||||
| EndProject | EndProject | ||||||
| @@ -33,4 +33,7 @@ Global | |||||||
| 	GlobalSection(NestedProjects) = preSolution | 	GlobalSection(NestedProjects) = preSolution | ||||||
| 		{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} | 		{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
|  | 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||||
|  | 		SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4} | ||||||
|  | 	EndGlobalSection | ||||||
| EndGlobal | EndGlobal | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| { | { | ||||||
|   "sdk": { "version": "1.0.1" } |   "sdk": { "version": "2.0.0" } | ||||||
| } | } | ||||||
| @@ -2,12 +2,11 @@ | |||||||
| using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||||
| using Discord.Commands; | using Discord.Commands; | ||||||
| using NadekoBot.Services.Impl; | using NadekoBot.Services.Impl; | ||||||
|  |  | ||||||
| namespace NadekoBot.Common.Attributes | namespace NadekoBot.Common.Attributes | ||||||
| { | { | ||||||
|     public class Aliases : AliasAttribute |     public class Aliases : AliasAttribute | ||||||
|     { |     { | ||||||
|         public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ').Skip(1).ToArray()) |         public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd.Split(' ').Skip(1).ToArray()) | ||||||
|         { |         { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ namespace NadekoBot.Common.Attributes | |||||||
| { | { | ||||||
|     public class Description : SummaryAttribute |     public class Description : SummaryAttribute | ||||||
|     { |     { | ||||||
|         public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_desc")) |         public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Desc) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ namespace NadekoBot.Common.Attributes | |||||||
| { | { | ||||||
|     public class NadekoCommand : CommandAttribute |     public class NadekoCommand : CommandAttribute | ||||||
|     { |     { | ||||||
|         public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ')[0]) |         public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Cmd) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ namespace NadekoBot.Common.Attributes | |||||||
| { | { | ||||||
|     public class Usage : RemarksAttribute |     public class Usage : RemarksAttribute | ||||||
|     { |     { | ||||||
|         public Usage([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant()+"_usage")) |         public Usage([CallerMemberName] string memberName="") : base(Localization.LoadCommand(memberName.ToLowerInvariant()).Usage) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|         } |         } | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								src/NadekoBot/Common/CommandData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/NadekoBot/Common/CommandData.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | namespace NadekoBot.Common | ||||||
|  | { | ||||||
|  |     public class CommandData | ||||||
|  |     { | ||||||
|  |         public string Cmd { get; set; } | ||||||
|  |         public string Usage { get; set; } | ||||||
|  |         public string Desc { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1949
									
								
								src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1949
									
								
								src/NadekoBot/Migrations/20170913022654_total-xp.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										27
									
								
								src/NadekoBot/Migrations/20170913022654_total-xp.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/NadekoBot/Migrations/20170913022654_total-xp.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  |  | ||||||
|  | namespace NadekoBot.Migrations | ||||||
|  | { | ||||||
|  |     public partial class totalxp : Migration | ||||||
|  |     { | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.AddColumn<int>( | ||||||
|  |                 name: "TotalXp", | ||||||
|  |                 table: "DiscordUser", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValue: 0); | ||||||
|  |  | ||||||
|  |             migrationBuilder.Sql(MigrationQueries.TotalXp); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "TotalXp", | ||||||
|  |                 table: "DiscordUser"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1951
									
								
								src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1951
									
								
								src/NadekoBot/Migrations/20170915034808_club-admins.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										25
									
								
								src/NadekoBot/Migrations/20170915034808_club-admins.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/NadekoBot/Migrations/20170915034808_club-admins.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  |  | ||||||
|  | namespace NadekoBot.Migrations | ||||||
|  | { | ||||||
|  |     public partial class clubadmins : Migration | ||||||
|  |     { | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.AddColumn<bool>( | ||||||
|  |                 name: "IsClubAdmin", | ||||||
|  |                 table: "DiscordUser", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValue: false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "IsClubAdmin", | ||||||
|  |                 table: "DiscordUser"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -34,5 +34,9 @@ INSERT INTO DiscordUser | |||||||
|     FROM DiscordUser_tmp; |     FROM DiscordUser_tmp; | ||||||
|  |  | ||||||
| DROP TABLE DiscordUser_tmp;"; | DROP TABLE DiscordUser_tmp;"; | ||||||
|  |         public static string TotalXp { get; } = | ||||||
|  | @"UPDATE DiscordUser | ||||||
|  | SET TotalXp = ifnull((SELECT SUM(Xp) FROM UserXpStats WHERE UserId = DiscordUser.UserId), 0)"; | ||||||
|  |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -462,14 +462,18 @@ namespace NadekoBot.Migrations | |||||||
|  |  | ||||||
|                     b.Property<string>("Discriminator"); |                     b.Property<string>("Discriminator"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsClubAdmin"); | ||||||
|  |  | ||||||
|                     b.Property<DateTime>("LastLevelUp") |                     b.Property<DateTime>("LastLevelUp") | ||||||
|                         .ValueGeneratedOnAdd() |                         .ValueGeneratedOnAdd() | ||||||
|                         .HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 236, DateTimeKind.Local)); |                         .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local)); | ||||||
|  |  | ||||||
|                     b.Property<DateTime>("LastXpGain"); |                     b.Property<DateTime>("LastXpGain"); | ||||||
|  |  | ||||||
|                     b.Property<int>("NotifyOnLevelUp"); |                     b.Property<int>("NotifyOnLevelUp"); | ||||||
|  |  | ||||||
|  |                     b.Property<int>("TotalXp"); | ||||||
|  |  | ||||||
|                     b.Property<ulong>("UserId"); |                     b.Property<ulong>("UserId"); | ||||||
|  |  | ||||||
|                     b.Property<string>("Username"); |                     b.Property<string>("Username"); | ||||||
| @@ -1362,7 +1366,7 @@ namespace NadekoBot.Migrations | |||||||
|  |  | ||||||
|                     b.Property<DateTime>("LastLevelUp") |                     b.Property<DateTime>("LastLevelUp") | ||||||
|                         .ValueGeneratedOnAdd() |                         .ValueGeneratedOnAdd() | ||||||
|                         .HasDefaultValue(new DateTime(2017, 9, 11, 22, 0, 31, 238, DateTimeKind.Local)); |                         .HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local)); | ||||||
|  |  | ||||||
|                     b.Property<int>("NotifyOnLevelUp"); |                     b.Property<int>("NotifyOnLevelUp"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,8 +30,9 @@ namespace NadekoBot.Modules.Administration | |||||||
|             private readonly IImagesService _images; |             private readonly IImagesService _images; | ||||||
|             private readonly MusicService _music; |             private readonly MusicService _music; | ||||||
|             private readonly IBotConfigProvider _bc; |             private readonly IBotConfigProvider _bc; | ||||||
|  |             private readonly NadekoBot _bot; | ||||||
|  |  | ||||||
|             public SelfCommands(DbService db, DiscordSocketClient client, |             public SelfCommands(DbService db, NadekoBot bot, DiscordSocketClient client, | ||||||
|                 MusicService music, IImagesService images, IBotConfigProvider bc) |                 MusicService music, IImagesService images, IBotConfigProvider bc) | ||||||
|             { |             { | ||||||
|                 _db = db; |                 _db = db; | ||||||
| @@ -39,6 +40,7 @@ namespace NadekoBot.Modules.Administration | |||||||
|                 _images = images; |                 _images = images; | ||||||
|                 _music = music; |                 _music = music; | ||||||
|                 _bc = bc; |                 _bc = bc; | ||||||
|  |                 _bot = bot; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             [NadekoCommand, Usage, Description, Aliases] |             [NadekoCommand, Usage, Description, Aliases] | ||||||
| @@ -349,7 +351,7 @@ namespace NadekoBot.Modules.Administration | |||||||
|             [OwnerOnly] |             [OwnerOnly] | ||||||
|             public async Task SetGame([Remainder] string game = null) |             public async Task SetGame([Remainder] string game = null) | ||||||
|             { |             { | ||||||
|                 await _client.SetGameAsync(game).ConfigureAwait(false); |                 await _bot.SetGameAsync(game).ConfigureAwait(false); | ||||||
|  |  | ||||||
|                 await ReplyConfirmLocalized("set_game").ConfigureAwait(false); |                 await ReplyConfirmLocalized("set_game").ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ using NadekoBot.Modules.Music.Services; | |||||||
| using NadekoBot.Services; | using NadekoBot.Services; | ||||||
| using NadekoBot.Services.Database.Models; | using NadekoBot.Services.Database.Models; | ||||||
| using NLog; | using NLog; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
| namespace NadekoBot.Modules.Administration.Services | namespace NadekoBot.Modules.Administration.Services | ||||||
| { | { | ||||||
| @@ -16,6 +17,7 @@ namespace NadekoBot.Modules.Administration.Services | |||||||
|         private readonly DiscordSocketClient _client; |         private readonly DiscordSocketClient _client; | ||||||
|         private readonly MusicService _music; |         private readonly MusicService _music; | ||||||
|         private readonly Logger _log; |         private readonly Logger _log; | ||||||
|  |         private readonly IDataCache _cache; | ||||||
|         private readonly Replacer _rep; |         private readonly Replacer _rep; | ||||||
|         private readonly DbService _db; |         private readonly DbService _db; | ||||||
|         private readonly IBotConfigProvider _bcp; |         private readonly IBotConfigProvider _bcp; | ||||||
| @@ -27,50 +29,60 @@ namespace NadekoBot.Modules.Administration.Services | |||||||
|             public int Index { get; set; } |             public int Index { get; set; } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp, MusicService music, DbService db) |         public PlayingRotateService(DiscordSocketClient client, IBotConfigProvider bcp,  | ||||||
|  |             MusicService music, DbService db, IDataCache cache, NadekoBot bot) | ||||||
|         { |         { | ||||||
|             _client = client; |             _client = client; | ||||||
|             _bcp = bcp; |             _bcp = bcp; | ||||||
|             _music = music; |             _music = music; | ||||||
|             _db = db; |             _db = db; | ||||||
|             _log = LogManager.GetCurrentClassLogger(); |             _log = LogManager.GetCurrentClassLogger(); | ||||||
|             _rep = new ReplacementBuilder() |             _cache = cache; | ||||||
|                 .WithClient(client) |  | ||||||
|                 .WithStats(client) |  | ||||||
|                 .WithMusic(music) |  | ||||||
|                 .Build(); |  | ||||||
|  |  | ||||||
|             _t = new Timer(async (objState) => |             if (client.ShardId == 0) | ||||||
|             { |             { | ||||||
|                 try |  | ||||||
|  |                 _rep = new ReplacementBuilder() | ||||||
|  |                     .WithClient(client) | ||||||
|  |                     .WithStats(client) | ||||||
|  |                     .WithMusic(music) | ||||||
|  |                     .Build(); | ||||||
|  |  | ||||||
|  |                 _t = new Timer(async (objState) => | ||||||
|                 { |                 { | ||||||
|                     bcp.Reload(); |                     try | ||||||
|  |                     { | ||||||
|  |                         bcp.Reload(); | ||||||
|  |  | ||||||
|                     var state = (TimerState)objState;  |                         var state = (TimerState)objState; | ||||||
|                     if (!BotConfig.RotatingStatuses) |                         if (!BotConfig.RotatingStatuses) | ||||||
|                         return; |                             return; | ||||||
|                     if (state.Index >= BotConfig.RotatingStatusMessages.Count) |                         if (state.Index >= BotConfig.RotatingStatusMessages.Count) | ||||||
|                         state.Index = 0; |                             state.Index = 0; | ||||||
|  |  | ||||||
|                     if (!BotConfig.RotatingStatusMessages.Any()) |                         if (!BotConfig.RotatingStatusMessages.Any()) | ||||||
|                         return; |                             return; | ||||||
|                     var status = BotConfig.RotatingStatusMessages[state.Index++].Status; |                         var status = BotConfig.RotatingStatusMessages[state.Index++].Status; | ||||||
|                     if (string.IsNullOrWhiteSpace(status)) |                         if (string.IsNullOrWhiteSpace(status)) | ||||||
|                         return; |                             return; | ||||||
|  |  | ||||||
|                     status = _rep.Replace(status); |                         status = _rep.Replace(status); | ||||||
|  |  | ||||||
|                     try { await client.SetGameAsync(status).ConfigureAwait(false); } |                         try | ||||||
|  |                         { | ||||||
|  |                             await bot.SetGameAsync(status).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |                         catch (Exception ex) | ||||||
|  |                         { | ||||||
|  |                             _log.Warn(ex); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                     catch (Exception ex) |                     catch (Exception ex) | ||||||
|                     { |                     { | ||||||
|                         _log.Warn(ex); |                         _log.Warn("Rotating playing status errored.\n" + ex); | ||||||
|                     } |                     } | ||||||
|                 } |                 }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); | ||||||
|                 catch (Exception ex) |             } | ||||||
|                 { |  | ||||||
|                     _log.Warn("Rotating playing status errored.\n" + ex); |  | ||||||
|                 } |  | ||||||
|             }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -58,8 +58,7 @@ namespace NadekoBot.Modules.CustomReactions | |||||||
|  |  | ||||||
|             if (channel == null) |             if (channel == null) | ||||||
|             { |             { | ||||||
|                 Array.Resize(ref _service.GlobalReactions, _service.GlobalReactions.Length + 1); |                 await _service.AddGcr(cr).ConfigureAwait(false); | ||||||
|                 _service.GlobalReactions[_service.GlobalReactions.Length - 1] = cr; |  | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
| @@ -237,8 +236,7 @@ namespace NadekoBot.Modules.CustomReactions | |||||||
|                     if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null) |                     if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null) | ||||||
|                     { |                     { | ||||||
|                         uow.CustomReactions.Remove(toDelete); |                         uow.CustomReactions.Remove(toDelete); | ||||||
|                         //todo 91 i can dramatically improve performance of this, if Ids are ordered. |                         await _service.DelGcr(toDelete.Id); | ||||||
|                         _service.GlobalReactions = _service.GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray(); |  | ||||||
|                         success = true; |                         success = true; | ||||||
|                     } |                     } | ||||||
|                     else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId) |                     else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId) | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ using NadekoBot.Modules.CustomReactions.Extensions; | |||||||
| using NadekoBot.Modules.Permissions.Common; | using NadekoBot.Modules.Permissions.Common; | ||||||
| using NadekoBot.Modules.Permissions.Services; | using NadekoBot.Modules.Permissions.Services; | ||||||
| using NadekoBot.Services.Impl; | using NadekoBot.Services.Impl; | ||||||
|  | using Newtonsoft.Json; | ||||||
|  |  | ||||||
| namespace NadekoBot.Modules.CustomReactions.Services | namespace NadekoBot.Modules.CustomReactions.Services | ||||||
| { | { | ||||||
| @@ -32,9 +33,11 @@ namespace NadekoBot.Modules.CustomReactions.Services | |||||||
|         private readonly CommandHandler _cmd; |         private readonly CommandHandler _cmd; | ||||||
|         private readonly IBotConfigProvider _bc; |         private readonly IBotConfigProvider _bc; | ||||||
|         private readonly NadekoStrings _strings; |         private readonly NadekoStrings _strings; | ||||||
|  |         private readonly IDataCache _cache; | ||||||
|  |  | ||||||
|         public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings, |         public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings, | ||||||
|             DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow) |             DiscordSocketClient client, CommandHandler cmd, IBotConfigProvider bc, IUnitOfWork uow,  | ||||||
|  |             IDataCache cache) | ||||||
|         { |         { | ||||||
|             _log = LogManager.GetCurrentClassLogger(); |             _log = LogManager.GetCurrentClassLogger(); | ||||||
|             _db = db; |             _db = db; | ||||||
| @@ -43,12 +46,38 @@ namespace NadekoBot.Modules.CustomReactions.Services | |||||||
|             _cmd = cmd; |             _cmd = cmd; | ||||||
|             _bc = bc; |             _bc = bc; | ||||||
|             _strings = strings; |             _strings = strings; | ||||||
|  |             _cache = cache; | ||||||
|  |  | ||||||
|  |             var sub = _cache.Redis.GetSubscriber(); | ||||||
|  |             sub.Subscribe("gcr.added", (ch, msg) => | ||||||
|  |             { | ||||||
|  |                 Array.Resize(ref GlobalReactions, GlobalReactions.Length + 1); | ||||||
|  |                 GlobalReactions[GlobalReactions.Length - 1] = JsonConvert.DeserializeObject<CustomReaction>(msg); | ||||||
|  |             }, StackExchange.Redis.CommandFlags.FireAndForget); | ||||||
|  |             sub.Subscribe("gcr.deleted", (ch, msg) => | ||||||
|  |             { | ||||||
|  |                 var id = int.Parse(msg); | ||||||
|  |                 GlobalReactions = GlobalReactions.Where(cr => cr?.Id != id).ToArray(); | ||||||
|  |             }, StackExchange.Redis.CommandFlags.FireAndForget); | ||||||
|  |  | ||||||
|             var items = uow.CustomReactions.GetAll(); |             var items = uow.CustomReactions.GetAll(); | ||||||
|  |  | ||||||
|             GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray())); |             GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray())); | ||||||
|             GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray(); |             GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public Task AddGcr(CustomReaction cr) | ||||||
|  |         { | ||||||
|  |             var sub = _cache.Redis.GetSubscriber(); | ||||||
|  |             return sub.PublishAsync("gcr.added", JsonConvert.SerializeObject(cr)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Task DelGcr(int id) | ||||||
|  |         { | ||||||
|  |             var sub = _cache.Redis.GetSubscriber(); | ||||||
|  |             return sub.PublishAsync("gcr.deleted", id); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public void ClearStats() => ReactionStats.Clear(); |         public void ClearStats() => ReactionStats.Clear(); | ||||||
|  |  | ||||||
|         public CustomReaction TryGetCustomReaction(IUserMessage umsg) |         public CustomReaction TryGetCustomReaction(IUserMessage umsg) | ||||||
|   | |||||||
| @@ -331,7 +331,7 @@ namespace NadekoBot.Modules.Gambling | |||||||
|                 var embed = new EmbedBuilder().WithOkColor(); |                 var embed = new EmbedBuilder().WithOkColor(); | ||||||
|  |  | ||||||
|                 if (entry.Type == ShopEntryType.Role) |                 if (entry.Type == ShopEntryType.Role) | ||||||
|                     return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(entry.RoleName))).WithIsInline(true)) |                     return embed.AddField(efb => efb.WithName(GetText("name")).WithValue(GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE"))).WithIsInline(true)) | ||||||
|                             .AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true)) |                             .AddField(efb => efb.WithName(GetText("price")).WithValue(entry.Price.ToString()).WithIsInline(true)) | ||||||
|                             .AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true)); |                             .AddField(efb => efb.WithName(GetText("type")).WithValue(entry.Type.ToString()).WithIsInline(true)); | ||||||
|                 else if (entry.Type == ShopEntryType.List) |                 else if (entry.Type == ShopEntryType.List) | ||||||
| @@ -349,7 +349,7 @@ namespace NadekoBot.Modules.Gambling | |||||||
|             { |             { | ||||||
|                 if (entry.Type == ShopEntryType.Role) |                 if (entry.Type == ShopEntryType.Role) | ||||||
|                 { |                 { | ||||||
|                     return GetText("shop_role", Format.Bold(entry.RoleName)); |                     return GetText("shop_role", Format.Bold(Context.Guild.GetRole(entry.RoleId)?.Name ?? "MISSING_ROLE")); | ||||||
|                 } |                 } | ||||||
|                 else if (entry.Type == ShopEntryType.List) |                 else if (entry.Type == ShopEntryType.List) | ||||||
|                 { |                 { | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ namespace NadekoBot.Modules.Games | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         [NadekoCommand, Usage, Description, Aliases] |         [NadekoCommand, Usage, Description, Aliases] | ||||||
|         public async Task _8Ball([Remainder] string question = null) |         public async Task EightBall([Remainder] string question = null) | ||||||
|         { |         { | ||||||
|             if (string.IsNullOrWhiteSpace(question)) |             if (string.IsNullOrWhiteSpace(question)) | ||||||
|                 return; |                 return; | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ using System; | |||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Threading; | using System.Threading; | ||||||
| using System.Threading.Tasks; |  | ||||||
|  |  | ||||||
| namespace NadekoBot.Modules.Music.Common | namespace NadekoBot.Modules.Music.Common | ||||||
| { | { | ||||||
| @@ -18,27 +17,16 @@ namespace NadekoBot.Modules.Music.Common | |||||||
|  |  | ||||||
|         public string SongUri { get; private set; } |         public string SongUri { get; private set; } | ||||||
|  |  | ||||||
|         //private volatile bool restart = false; |  | ||||||
|  |  | ||||||
|         public SongBuffer(string songUri, string skipTo, bool isLocal) |         public SongBuffer(string songUri, string skipTo, bool isLocal) | ||||||
|         { |         { | ||||||
|             _log = LogManager.GetCurrentClassLogger(); |             _log = LogManager.GetCurrentClassLogger(); | ||||||
|             //_log.Warn(songUri); |  | ||||||
|             this.SongUri = songUri; |             this.SongUri = songUri; | ||||||
|             this._isLocal = isLocal; |             this._isLocal = isLocal; | ||||||
|  |  | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 this.p = StartFFmpegProcess(SongUri, 0); |                 this.p = StartFFmpegProcess(SongUri, 0); | ||||||
|                 var t = Task.Run(() => |  | ||||||
|                 { |  | ||||||
|                     this.p.BeginErrorReadLine(); |  | ||||||
|                     this.p.ErrorDataReceived += P_ErrorDataReceived; |  | ||||||
|                     this.p.WaitForExit(); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 this._outStream = this.p.StandardOutput.BaseStream; |                 this._outStream = this.p.StandardOutput.BaseStream; | ||||||
|  |  | ||||||
|             } |             } | ||||||
|             catch (System.ComponentModel.Win32Exception) |             catch (System.ComponentModel.Win32Exception) | ||||||
|             { |             { | ||||||
| @@ -68,113 +56,14 @@ Check the guides for your platform on how to setup ffmpeg correctly: | |||||||
|                 Arguments = args, |                 Arguments = args, | ||||||
|                 UseShellExecute = false, |                 UseShellExecute = false, | ||||||
|                 RedirectStandardOutput = true, |                 RedirectStandardOutput = true, | ||||||
|                 RedirectStandardError = true, |                 RedirectStandardError = false, | ||||||
|                 CreateNoWindow = true, |                 CreateNoWindow = true, | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void P_ErrorDataReceived(object sender, DataReceivedEventArgs e) |  | ||||||
|         { |  | ||||||
|             if (string.IsNullOrWhiteSpace(e.Data)) |  | ||||||
|                 return; |  | ||||||
|             _log.Error(">>> " + e.Data); |  | ||||||
|             if (e.Data?.Contains("Error in the pull function") == true) |  | ||||||
|             { |  | ||||||
|                 _log.Error("Ignore this."); |  | ||||||
|                 //restart = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private readonly object locker = new object(); |         private readonly object locker = new object(); | ||||||
|         private readonly bool _isLocal; |         private readonly bool _isLocal; | ||||||
|  |  | ||||||
|         public Task<bool> StartBuffering(CancellationToken cancelToken) |  | ||||||
|         { |  | ||||||
|             var toReturn = new TaskCompletionSource<bool>(); |  | ||||||
|             var _ = Task.Run(() => |  | ||||||
|             { |  | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|  |  | ||||||
|                     ////int maxLoopsPerSec = 25; |  | ||||||
|                     //var sw = Stopwatch.StartNew(); |  | ||||||
|                     ////var delay = 1000 / maxLoopsPerSec; |  | ||||||
|                     //int currentLoops = 0; |  | ||||||
|                     //int _bytesSent = 0; |  | ||||||
|                     //try |  | ||||||
|                     //{ |  | ||||||
|                     //    //do |  | ||||||
|                     //    //{ |  | ||||||
|                     //    //    if (restart) |  | ||||||
|                     //    //    { |  | ||||||
|                     //    //        var cur = _bytesSent / 3840 / (1000 / 20.0f); |  | ||||||
|                     //    //        _log.Info("Restarting"); |  | ||||||
|                     //    //        try { this.p.StandardOutput.Dispose(); } catch { } |  | ||||||
|                     //    //        try { this.p.Dispose(); } catch { } |  | ||||||
|                     //    //        this.p = StartFFmpegProcess(SongUri, cur); |  | ||||||
|                     //    //    } |  | ||||||
|                     //    //    restart = false; |  | ||||||
|                     //        ++currentLoops; |  | ||||||
|                     //        byte[] buffer = new byte[readSize]; |  | ||||||
|                     //        int bytesRead = 1; |  | ||||||
|                     //        while (!cancelToken.IsCancellationRequested && !this.p.HasExited) |  | ||||||
|                     //        { |  | ||||||
|                     //            bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, readSize, cancelToken).ConfigureAwait(false); |  | ||||||
|                     //            _bytesSent += bytesRead; |  | ||||||
|                     //            if (bytesRead == 0) |  | ||||||
|                     //                break; |  | ||||||
|                     //            bool written; |  | ||||||
|                     //            do |  | ||||||
|                     //            { |  | ||||||
|                     //                lock (locker) |  | ||||||
|                     //                    written = _outStream.Write(buffer, 0, bytesRead); |  | ||||||
|                     //                if (!written) |  | ||||||
|                     //                    await Task.Delay(2000, cancelToken); |  | ||||||
|                     //            } |  | ||||||
|                     //            while (!written && !cancelToken.IsCancellationRequested); |  | ||||||
|                     //            lock (locker) |  | ||||||
|                     //                if (_outStream.Length > 200_000 || bytesRead == 0) |  | ||||||
|                     //                    if (toReturn.TrySetResult(true)) |  | ||||||
|                     //                        _log.Info("Prebuffering finished in {0}", sw.Elapsed.TotalSeconds.ToString("F2")); |  | ||||||
|  |  | ||||||
|                     //            //_log.Info(_outStream.Length); |  | ||||||
|                     //            await Task.Delay(10); |  | ||||||
|                     //        } |  | ||||||
|                     //        //if (cancelToken.IsCancellationRequested) |  | ||||||
|                     //        //    _log.Info("Song canceled"); |  | ||||||
|                     //        //else if (p.HasExited) |  | ||||||
|                     //        //    _log.Info("Song buffered completely (FFmpeg exited)"); |  | ||||||
|                     //        //else if (bytesRead == 0) |  | ||||||
|                     //        //    _log.Info("Nothing read"); |  | ||||||
|                     //    //} |  | ||||||
|                     //    //while (restart && !cancelToken.IsCancellationRequested); |  | ||||||
|                     //return Task.CompletedTask; |  | ||||||
|                     toReturn.TrySetResult(true); |  | ||||||
|                 } |  | ||||||
|                 catch (System.ComponentModel.Win32Exception) |  | ||||||
|                 { |  | ||||||
|                     _log.Error(@"You have not properly installed or configured FFMPEG.  |  | ||||||
| Please install and configure FFMPEG to play music.  |  | ||||||
| Check the guides for your platform on how to setup ffmpeg correctly: |  | ||||||
|     Windows Guide: https://goo.gl/OjKk8F |  | ||||||
|     Linux Guide:  https://goo.gl/ShjCUo"); |  | ||||||
|                 } |  | ||||||
|                 catch (OperationCanceledException) { } |  | ||||||
|                 catch (InvalidOperationException) { } // when ffmpeg is disposed |  | ||||||
|                 catch (Exception ex) |  | ||||||
|                 { |  | ||||||
|                     _log.Info(ex); |  | ||||||
|                 } |  | ||||||
|                 finally |  | ||||||
|                 { |  | ||||||
|                     if (toReturn.TrySetResult(false)) |  | ||||||
|                         _log.Info("Prebuffering failed"); |  | ||||||
|                 } |  | ||||||
|             }, cancelToken); |  | ||||||
|  |  | ||||||
|             return toReturn.Task; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public int Read(byte[] b, int offset, int toRead) |         public int Read(byte[] b, int offset, int toRead) | ||||||
|         { |         { | ||||||
|             lock (locker) |             lock (locker) | ||||||
| @@ -204,214 +93,3 @@ Check the guides for your platform on how to setup ffmpeg correctly: | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| //namespace NadekoBot.Services.Music |  | ||||||
| //{ |  | ||||||
| //    /// <summary> |  | ||||||
| //    /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. |  | ||||||
| //    /// It also help for large music by deleting files that are already seen. |  | ||||||
| //    /// </summary> |  | ||||||
| //    class SongBuffer : Stream |  | ||||||
| //    { |  | ||||||
| //        public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize) |  | ||||||
| //        { |  | ||||||
| //            MusicPlayer = musicPlayer; |  | ||||||
| //            Basename = basename; |  | ||||||
| //            SongInfo = songInfo; |  | ||||||
| //            SkipTo = skipTo; |  | ||||||
| //            MaxFileSize = maxFileSize; |  | ||||||
| //            CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); |  | ||||||
| //            _log = LogManager.GetCurrentClassLogger(); |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        MusicPlayer MusicPlayer { get; } |  | ||||||
|  |  | ||||||
| //        private string Basename { get; } |  | ||||||
|  |  | ||||||
| //        private SongInfo SongInfo { get; } |  | ||||||
|  |  | ||||||
| //        private int SkipTo { get; } |  | ||||||
|  |  | ||||||
| //        private int MaxFileSize { get; } = 2.MiB(); |  | ||||||
|  |  | ||||||
| //        private long FileNumber = -1; |  | ||||||
|  |  | ||||||
| //        private long NextFileToRead = 0; |  | ||||||
|  |  | ||||||
| //        public bool BufferingCompleted { get; private set; } = false; |  | ||||||
|  |  | ||||||
| //        private ulong CurrentBufferSize = 0; |  | ||||||
|  |  | ||||||
| //        private FileStream CurrentFileStream; |  | ||||||
| //        private Logger _log; |  | ||||||
|  |  | ||||||
| //        public Task BufferSong(CancellationToken cancelToken) => |  | ||||||
| //           Task.Run(async () => |  | ||||||
| //           { |  | ||||||
| //               Process p = null; |  | ||||||
| //               FileStream outStream = null; |  | ||||||
| //               try |  | ||||||
| //               { |  | ||||||
| //                   p = Process.Start(new ProcessStartInfo |  | ||||||
| //                   { |  | ||||||
| //                       FileName = "ffmpeg", |  | ||||||
| //                       Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet", |  | ||||||
| //                       UseShellExecute = false, |  | ||||||
| //                       RedirectStandardOutput = true, |  | ||||||
| //                       RedirectStandardError = false, |  | ||||||
| //                       CreateNoWindow = true, |  | ||||||
| //                   }); |  | ||||||
|  |  | ||||||
| //                   byte[] buffer = new byte[81920]; |  | ||||||
| //                   int currentFileSize = 0; |  | ||||||
| //                   ulong prebufferSize = 100ul.MiB(); |  | ||||||
|  |  | ||||||
| //                   outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); |  | ||||||
| //                   while (!p.HasExited) //Also fix low bandwidth |  | ||||||
| //                   { |  | ||||||
| //                       int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); |  | ||||||
| //                       if (currentFileSize >= MaxFileSize) |  | ||||||
| //                       { |  | ||||||
| //                           try |  | ||||||
| //                           { |  | ||||||
| //                               outStream.Dispose(); |  | ||||||
| //                           } |  | ||||||
| //                           catch { } |  | ||||||
| //                           outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read); |  | ||||||
| //                           currentFileSize = bytesRead; |  | ||||||
| //                       } |  | ||||||
| //                       else |  | ||||||
| //                       { |  | ||||||
| //                           currentFileSize += bytesRead; |  | ||||||
| //                       } |  | ||||||
| //                       CurrentBufferSize += Convert.ToUInt64(bytesRead); |  | ||||||
| //                       await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); |  | ||||||
| //                       while (CurrentBufferSize > prebufferSize) |  | ||||||
| //                           await Task.Delay(100, cancelToken); |  | ||||||
| //                   } |  | ||||||
| //                   BufferingCompleted = true; |  | ||||||
| //               } |  | ||||||
| //               catch (System.ComponentModel.Win32Exception) |  | ||||||
| //               { |  | ||||||
| //                   var oldclr = Console.ForegroundColor; |  | ||||||
| //                   Console.ForegroundColor = ConsoleColor.Red; |  | ||||||
| //                   Console.WriteLine(@"You have not properly installed or configured FFMPEG.  |  | ||||||
| //Please install and configure FFMPEG to play music.  |  | ||||||
| //Check the guides for your platform on how to setup ffmpeg correctly: |  | ||||||
| //    Windows Guide: https://goo.gl/OjKk8F |  | ||||||
| //    Linux Guide:  https://goo.gl/ShjCUo"); |  | ||||||
| //                   Console.ForegroundColor = oldclr; |  | ||||||
| //               } |  | ||||||
| //               catch (Exception ex) |  | ||||||
| //               { |  | ||||||
| //                   Console.WriteLine($"Buffering stopped: {ex.Message}"); |  | ||||||
| //               } |  | ||||||
| //               finally |  | ||||||
| //               { |  | ||||||
| //                   if (outStream != null) |  | ||||||
| //                       outStream.Dispose(); |  | ||||||
| //                   if (p != null) |  | ||||||
| //                   { |  | ||||||
| //                       try |  | ||||||
| //                       { |  | ||||||
| //                           p.Kill(); |  | ||||||
| //                       } |  | ||||||
| //                       catch { } |  | ||||||
| //                       p.Dispose(); |  | ||||||
| //                   } |  | ||||||
| //               } |  | ||||||
| //           }); |  | ||||||
|  |  | ||||||
| //        /// <summary> |  | ||||||
| //        /// Return the next file to read, and delete the old one |  | ||||||
| //        /// </summary> |  | ||||||
| //        /// <returns>Name of the file to read</returns> |  | ||||||
| //        private string GetNextFile() |  | ||||||
| //        { |  | ||||||
| //            string filename = Basename + "-" + NextFileToRead; |  | ||||||
|  |  | ||||||
| //            if (NextFileToRead != 0) |  | ||||||
| //            { |  | ||||||
| //                try |  | ||||||
| //                { |  | ||||||
| //                    CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length); |  | ||||||
| //                    File.Delete(Basename + "-" + (NextFileToRead - 1)); |  | ||||||
| //                } |  | ||||||
| //                catch { } |  | ||||||
| //            } |  | ||||||
| //            NextFileToRead++; |  | ||||||
| //            return filename; |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        private bool IsNextFileReady() |  | ||||||
| //        { |  | ||||||
| //            return NextFileToRead <= FileNumber; |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        private void CleanFiles() |  | ||||||
| //        { |  | ||||||
| //            for (long i = NextFileToRead - 1; i <= FileNumber; i++) |  | ||||||
| //            { |  | ||||||
| //                try |  | ||||||
| //                { |  | ||||||
| //                    File.Delete(Basename + "-" + i); |  | ||||||
| //                } |  | ||||||
| //                catch { } |  | ||||||
| //            } |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        //Stream part |  | ||||||
|  |  | ||||||
| //        public override bool CanRead => true; |  | ||||||
|  |  | ||||||
| //        public override bool CanSeek => false; |  | ||||||
|  |  | ||||||
| //        public override bool CanWrite => false; |  | ||||||
|  |  | ||||||
| //        public override long Length => (long)CurrentBufferSize; |  | ||||||
|  |  | ||||||
| //        public override long Position { get; set; } = 0; |  | ||||||
|  |  | ||||||
| //        public override void Flush() { } |  | ||||||
|  |  | ||||||
| //        public override int Read(byte[] buffer, int offset, int count) |  | ||||||
| //        { |  | ||||||
| //            int read = CurrentFileStream.Read(buffer, offset, count); |  | ||||||
| //            if (read < count) |  | ||||||
| //            { |  | ||||||
| //                if (!BufferingCompleted || IsNextFileReady()) |  | ||||||
| //                { |  | ||||||
| //                    CurrentFileStream.Dispose(); |  | ||||||
| //                    CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); |  | ||||||
| //                    read += CurrentFileStream.Read(buffer, read + offset, count - read); |  | ||||||
| //                } |  | ||||||
| //                if (read < count) |  | ||||||
| //                    Array.Clear(buffer, read, count - read); |  | ||||||
| //            } |  | ||||||
| //            return read; |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        public override long Seek(long offset, SeekOrigin origin) |  | ||||||
| //        { |  | ||||||
| //            throw new NotImplementedException(); |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        public override void SetLength(long value) |  | ||||||
| //        { |  | ||||||
| //            throw new NotImplementedException(); |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        public override void Write(byte[] buffer, int offset, int count) |  | ||||||
| //        { |  | ||||||
| //            throw new NotImplementedException(); |  | ||||||
| //        } |  | ||||||
|  |  | ||||||
| //        public new void Dispose() |  | ||||||
| //        { |  | ||||||
| //            CurrentFileStream.Dispose(); |  | ||||||
| //            MusicPlayer.SongCancelSource.Cancel(); |  | ||||||
| //            CleanFiles(); |  | ||||||
| //            base.Dispose(); |  | ||||||
| //        } |  | ||||||
| //    } |  | ||||||
| //} |  | ||||||
| @@ -16,9 +16,13 @@ using NadekoBot.Modules.NSFW.Exceptions; | |||||||
|  |  | ||||||
| namespace NadekoBot.Modules.NSFW | namespace NadekoBot.Modules.NSFW | ||||||
| { | { | ||||||
|  |     // thanks to halitalf for adding autoboob and autobutt features :D | ||||||
|     public class NSFW : NadekoTopLevelModule<SearchesService> |     public class NSFW : NadekoTopLevelModule<SearchesService> | ||||||
|     { |     { | ||||||
|         private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>(); |         private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>(); | ||||||
|  |         private static readonly ConcurrentDictionary<ulong, Timer> _autoBoobTimers = new ConcurrentDictionary<ulong, Timer>(); | ||||||
|  |         private static readonly ConcurrentDictionary<ulong, Timer> _autoButtTimers = new ConcurrentDictionary<ulong, Timer>(); | ||||||
|  |  | ||||||
|         private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>(); |         private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>(); | ||||||
|  |  | ||||||
|         private async Task InternalHentai(IMessageChannel channel, string tag, bool noError) |         private async Task InternalHentai(IMessageChannel channel, string tag, bool noError) | ||||||
| @@ -49,10 +53,34 @@ namespace NadekoBot.Modules.NSFW | |||||||
|                 .WithDescription($"[{GetText("tag")}: {tag}]({img})")) |                 .WithDescription($"[{GetText("tag")}: {tag}]({img})")) | ||||||
|                 .ConfigureAwait(false); |                 .ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|  |         private async Task InternalBoobs(IMessageChannel Channel) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 JToken obj; | ||||||
|  |                 obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.oboobs.ru/boobs/{new NadekoRandom().Next(0, 10330)}").ConfigureAwait(false))[0]; | ||||||
|  |                 await Channel.SendMessageAsync($"http://media.oboobs.ru/{obj["preview"]}").ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private async Task InternalButts(IMessageChannel Channel) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 JToken obj; | ||||||
|  |                 obj = JArray.Parse(await _service.Http.GetStringAsync($"http://api.obutts.ru/butts/{new NadekoRandom().Next(0, 4335)}").ConfigureAwait(false))[0]; | ||||||
|  |                 await Channel.SendMessageAsync($"http://media.obutts.ru/{obj["preview"]}").ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 await Channel.SendErrorAsync(ex.Message).ConfigureAwait(false); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         [NadekoCommand, Usage, Description, Aliases] |  | ||||||
|         public Task Hentai([Remainder] string tag = null) => |  | ||||||
|             InternalHentai(Context.Channel, tag, false); |  | ||||||
| #if !GLOBAL_NADEKO | #if !GLOBAL_NADEKO | ||||||
|         [NadekoCommand, Usage, Description, Aliases] |         [NadekoCommand, Usage, Description, Aliases] | ||||||
|         [RequireUserPermission(ChannelPermission.ManageMessages)] |         [RequireUserPermission(ChannelPermission.ManageMessages)] | ||||||
| @@ -65,7 +93,7 @@ namespace NadekoBot.Modules.NSFW | |||||||
|                 if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return; |                 if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return; | ||||||
|  |  | ||||||
|                 t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer |                 t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer | ||||||
|                 await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false); |                 await ReplyConfirmLocalized("stopped").ConfigureAwait(false); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -99,8 +127,90 @@ namespace NadekoBot.Modules.NSFW | |||||||
|                 interval, |                 interval, | ||||||
|                 string.Join(", ", tagsArr)).ConfigureAwait(false); |                 string.Join(", ", tagsArr)).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [NadekoCommand, Usage, Description, Aliases] | ||||||
|  |         [RequireUserPermission(ChannelPermission.ManageMessages)] | ||||||
|  |         public async Task AutoBoobs(int interval = 0) | ||||||
|  |         { | ||||||
|  |             Timer t; | ||||||
|  |  | ||||||
|  |             if (interval == 0) | ||||||
|  |             { | ||||||
|  |                 if (!_autoBoobTimers.TryRemove(Context.Channel.Id, out t)) return; | ||||||
|  |  | ||||||
|  |                 t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer | ||||||
|  |                 await ReplyConfirmLocalized("stopped").ConfigureAwait(false); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (interval < 20) | ||||||
|  |                 return; | ||||||
|  |  | ||||||
|  |             t = new Timer(async (state) => | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     await InternalBoobs(Context.Channel).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |                 catch | ||||||
|  |                 { | ||||||
|  |                     // ignored | ||||||
|  |                 } | ||||||
|  |             }, null, interval * 1000, interval * 1000); | ||||||
|  |  | ||||||
|  |             _autoBoobTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => | ||||||
|  |             { | ||||||
|  |                 old.Change(Timeout.Infinite, Timeout.Infinite); | ||||||
|  |                 return t; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             await ReplyConfirmLocalized("started", interval).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [NadekoCommand, Usage, Description, Aliases] | ||||||
|  |         [RequireUserPermission(ChannelPermission.ManageMessages)] | ||||||
|  |         public async Task AutoButts(int interval = 0) | ||||||
|  |         { | ||||||
|  |             Timer t; | ||||||
|  |  | ||||||
|  |             if (interval == 0) | ||||||
|  |             { | ||||||
|  |                 if (!_autoButtTimers.TryRemove(Context.Channel.Id, out t)) return; | ||||||
|  |  | ||||||
|  |                 t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer | ||||||
|  |                 await ReplyConfirmLocalized("stopped").ConfigureAwait(false); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (interval < 20) | ||||||
|  |                 return; | ||||||
|  |  | ||||||
|  |             t = new Timer(async (state) => | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     await InternalButts(Context.Channel).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |                 catch | ||||||
|  |                 { | ||||||
|  |                     // ignored | ||||||
|  |                 } | ||||||
|  |             }, null, interval * 1000, interval * 1000); | ||||||
|  |  | ||||||
|  |             _autoButtTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) => | ||||||
|  |             { | ||||||
|  |                 old.Change(Timeout.Infinite, Timeout.Infinite); | ||||||
|  |                 return t; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             await ReplyConfirmLocalized("started", interval).ConfigureAwait(false); | ||||||
|  |         } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |         [NadekoCommand, Usage, Description, Aliases] | ||||||
|  |         public Task Hentai([Remainder] string tag = null) => | ||||||
|  |             InternalHentai(Context.Channel, tag, false); | ||||||
|  |  | ||||||
|         [NadekoCommand, Usage, Description, Aliases] |         [NadekoCommand, Usage, Description, Aliases] | ||||||
|         public async Task HentaiBomb([Remainder] string tag = null) |         public async Task HentaiBomb([Remainder] string tag = null) | ||||||
|         { |         { | ||||||
| @@ -199,7 +309,7 @@ namespace NadekoBot.Modules.NSFW | |||||||
|                 tag = tag.Trim().ToLowerInvariant(); |                 tag = tag.Trim().ToLowerInvariant(); | ||||||
|                 var added = _service.ToggleBlacklistedTag(Context.Guild.Id, tag); |                 var added = _service.ToggleBlacklistedTag(Context.Guild.Id, tag); | ||||||
|  |  | ||||||
|                 if(added) |                 if (added) | ||||||
|                     await ReplyConfirmLocalized("blacklisted_tag_add", tag).ConfigureAwait(false); |                     await ReplyConfirmLocalized("blacklisted_tag_add", tag).ConfigureAwait(false); | ||||||
|                 else |                 else | ||||||
|                     await ReplyConfirmLocalized("blacklisted_tag_remove", tag).ConfigureAwait(false); |                     await ReplyConfirmLocalized("blacklisted_tag_remove", tag).ConfigureAwait(false); | ||||||
|   | |||||||
| @@ -135,11 +135,11 @@ namespace NadekoBot.Modules.Permissions.Services | |||||||
|                 { |                 { | ||||||
|                     var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" }; |                     var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" }; | ||||||
|                     uow._context.Database.ExecuteSqlCommand( |                     uow._context.Database.ExecuteSqlCommand( | ||||||
|     $@"UPDATE {nameof(Permissionv2)} |     @"UPDATE Permissionv2 | ||||||
| SET secondaryTargetName=trim(substr(secondaryTargetName, 3)) | SET secondaryTargetName=trim(substr(secondaryTargetName, 3)) | ||||||
| WHERE secondaryTargetName LIKE '!!%' OR secondaryTargetName LIKE '!m%'; | WHERE secondaryTargetName LIKE '!!%' OR secondaryTargetName LIKE '!m%'; | ||||||
|  |  | ||||||
| UPDATE {nameof(Permissionv2)} | UPDATE Permissionv2 | ||||||
| SET secondaryTargetName=substr(secondaryTargetName, 2) | SET secondaryTargetName=substr(secondaryTargetName, 2) | ||||||
| WHERE secondaryTargetName LIKE '.%' OR | WHERE secondaryTargetName LIKE '.%' OR | ||||||
| secondaryTargetName LIKE '~%' OR | secondaryTargetName LIKE '~%' OR | ||||||
|   | |||||||
| @@ -76,11 +76,13 @@ namespace NadekoBot.Modules.Searches.Common | |||||||
|                     if (images.Length == 0) |                     if (images.Length == 0) | ||||||
|                         return null; |                         return null; | ||||||
|                     var toReturn = images[_rng.Next(images.Length)]; |                     var toReturn = images[_rng.Next(images.Length)]; | ||||||
|  | #if !GLOBAL_NADEKO | ||||||
|                     foreach (var dledImg in images) |                     foreach (var dledImg in images) | ||||||
|                     { |                     { | ||||||
|                         if(dledImg != toReturn) |                         if(dledImg != toReturn) | ||||||
|                             _cache.Add(dledImg); |                             _cache.Add(dledImg); | ||||||
|                     } |                     } | ||||||
|  | #endif | ||||||
|                     return toReturn; |                     return toReturn; | ||||||
|                 }                     |                 }                     | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -11,10 +11,14 @@ namespace NadekoBot.Modules.Searches.Services | |||||||
|     public class AnimeSearchService : INService |     public class AnimeSearchService : INService | ||||||
|     { |     { | ||||||
|         private readonly Logger _log; |         private readonly Logger _log; | ||||||
|  |         private readonly IDataCache _cache; | ||||||
|  |         private readonly HttpClient _http; | ||||||
|  |  | ||||||
|         public AnimeSearchService() |         public AnimeSearchService(IDataCache cache) | ||||||
|         { |         { | ||||||
|             _log = LogManager.GetCurrentClassLogger(); |             _log = LogManager.GetCurrentClassLogger(); | ||||||
|  |             _cache = cache; | ||||||
|  |             _http = new HttpClient(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public async Task<AnimeResult> GetAnimeData(string query) |         public async Task<AnimeResult> GetAnimeData(string query) | ||||||
| @@ -25,11 +29,16 @@ namespace NadekoBot.Modules.Searches.Services | |||||||
|             { |             { | ||||||
|  |  | ||||||
|                 var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " ")); |                 var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " ")); | ||||||
|                 using (var http = new HttpClient()) |                 link = link.ToLowerInvariant(); | ||||||
|  |                 var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false); | ||||||
|  |                 if (!ok) | ||||||
|                 { |                 { | ||||||
|                     var res = await http.GetStringAsync(link).ConfigureAwait(false); |                     data = await _http.GetStringAsync(link).ConfigureAwait(false); | ||||||
|                     return JsonConvert.DeserializeObject<AnimeResult>(res); |                     await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 return JsonConvert.DeserializeObject<AnimeResult>(data); | ||||||
|             } |             } | ||||||
|             catch |             catch | ||||||
|             { |             { | ||||||
| @@ -44,12 +53,17 @@ namespace NadekoBot.Modules.Searches.Services | |||||||
|             try |             try | ||||||
|             { |             { | ||||||
|  |  | ||||||
|                 var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); |                  var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); | ||||||
|                 using (var http = new HttpClient()) |                 link = link.ToLowerInvariant(); | ||||||
|  |                 var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false); | ||||||
|  |                 if (!ok) | ||||||
|                 { |                 { | ||||||
|                     var res = await http.GetStringAsync(link).ConfigureAwait(false); |                     data = await _http.GetStringAsync(link).ConfigureAwait(false); | ||||||
|                     return JsonConvert.DeserializeObject<MangaResult>(res); |                     await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 return JsonConvert.DeserializeObject<MangaResult>(data); | ||||||
|             } |             } | ||||||
|             catch |             catch | ||||||
|             { |             { | ||||||
|   | |||||||
| @@ -26,6 +26,27 @@ namespace NadekoBot.Modules.Xp | |||||||
|                 _client = client; |                 _client = client; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             [NadekoCommand, Usage, Description, Aliases] | ||||||
|  |             public async Task ClubAdmin([Remainder]IUser toAdmin) | ||||||
|  |             { | ||||||
|  |                 bool admin; | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     admin = _service.ToggleAdmin(Context.User, toAdmin); | ||||||
|  |                 } | ||||||
|  |                 catch (InvalidOperationException) | ||||||
|  |                 { | ||||||
|  |                     await ReplyErrorLocalized("club_admin_error").ConfigureAwait(false); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if(admin) | ||||||
|  |                     await ReplyConfirmLocalized("club_admin_add", Format.Bold(toAdmin.ToString())).ConfigureAwait(false); | ||||||
|  |                 else | ||||||
|  |                     await ReplyConfirmLocalized("club_admin_remove", Format.Bold(toAdmin.ToString())).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |  | ||||||
|             [NadekoCommand, Usage, Description, Aliases] |             [NadekoCommand, Usage, Description, Aliases] | ||||||
|             public async Task ClubCreate([Remainder]string clubName) |             public async Task ClubCreate([Remainder]string clubName) | ||||||
|             { |             { | ||||||
| @@ -97,7 +118,14 @@ namespace NadekoBot.Modules.Xp | |||||||
|                         .AddField("Level Req.", club.MinimumLevelReq.ToString(), true) |                         .AddField("Level Req.", club.MinimumLevelReq.ToString(), true) | ||||||
|                         .AddField("Members", string.Join("\n", club.Users |                         .AddField("Members", string.Join("\n", club.Users | ||||||
|                             .Skip(page * 10) |                             .Skip(page * 10) | ||||||
|                             .Take(10)), false); |                             .Take(10) | ||||||
|  |                             .OrderByDescending(x => x.IsClubAdmin) | ||||||
|  |                             .Select(x =>  | ||||||
|  |                             { | ||||||
|  |                                 if (x.IsClubAdmin) | ||||||
|  |                                     return x.ToString() + "⭐"; | ||||||
|  |                                 return x.ToString(); | ||||||
|  |                             })), false); | ||||||
|  |  | ||||||
|                     if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) |                     if (Uri.IsWellFormedUriString(club.ImageUrl, UriKind.Absolute)) | ||||||
|                         return embed.WithThumbnailUrl(club.ImageUrl); |                         return embed.WithThumbnailUrl(club.ImageUrl); | ||||||
| @@ -112,7 +140,7 @@ namespace NadekoBot.Modules.Xp | |||||||
|                 if (--page < 0) |                 if (--page < 0) | ||||||
|                     return Task.CompletedTask; |                     return Task.CompletedTask; | ||||||
|  |  | ||||||
|                 var club = _service.GetBansAndApplications(Context.User.Id); |                 var club = _service.GetClubWithBansAndApplications(Context.User.Id); | ||||||
|                 if (club == null) |                 if (club == null) | ||||||
|                     return ReplyErrorLocalized("club_not_exists"); |                     return ReplyErrorLocalized("club_not_exists"); | ||||||
|  |  | ||||||
| @@ -131,7 +159,8 @@ namespace NadekoBot.Modules.Xp | |||||||
|  |  | ||||||
|                         return new EmbedBuilder() |                         return new EmbedBuilder() | ||||||
|                             .WithTitle(GetText("club_bans_for", club.ToString())) |                             .WithTitle(GetText("club_bans_for", club.ToString())) | ||||||
|                             .WithDescription(toShow); |                             .WithDescription(toShow) | ||||||
|  |                             .WithOkColor(); | ||||||
|  |  | ||||||
|                     }, bans.Length / 10); |                     }, bans.Length / 10); | ||||||
|             } |             } | ||||||
| @@ -143,11 +172,11 @@ namespace NadekoBot.Modules.Xp | |||||||
|                 if (--page < 0) |                 if (--page < 0) | ||||||
|                     return Task.CompletedTask; |                     return Task.CompletedTask; | ||||||
|  |  | ||||||
|                 var club = _service.GetBansAndApplications(Context.User.Id); |                 var club = _service.GetClubWithBansAndApplications(Context.User.Id); | ||||||
|                 if (club == null) |                 if (club == null) | ||||||
|                     return ReplyErrorLocalized("club_not_exists"); |                     return ReplyErrorLocalized("club_not_exists"); | ||||||
|  |  | ||||||
|                 var bans = club |                 var apps = club | ||||||
|                     .Applicants |                     .Applicants | ||||||
|                     .Select(x => x.User) |                     .Select(x => x.User) | ||||||
|                     .ToArray(); |                     .ToArray(); | ||||||
| @@ -155,16 +184,17 @@ namespace NadekoBot.Modules.Xp | |||||||
|                 return Context.Channel.SendPaginatedConfirmAsync(_client, page, |                 return Context.Channel.SendPaginatedConfirmAsync(_client, page, | ||||||
|                     curPage => |                     curPage => | ||||||
|                     { |                     { | ||||||
|                         var toShow = string.Join("\n", bans |                         var toShow = string.Join("\n", apps | ||||||
|                             .Skip(page * 10) |                             .Skip(page * 10) | ||||||
|                             .Take(10) |                             .Take(10) | ||||||
|                             .Select(x => x.ToString())); |                             .Select(x => x.ToString())); | ||||||
|  |  | ||||||
|                         return new EmbedBuilder() |                         return new EmbedBuilder() | ||||||
|                             .WithTitle(GetText("club_apps_for", club.ToString())) |                             .WithTitle(GetText("club_apps_for", club.ToString())) | ||||||
|                             .WithDescription(toShow); |                             .WithDescription(toShow) | ||||||
|  |                             .WithOkColor(); | ||||||
|  |  | ||||||
|                     }, bans.Length / 10); |                     }, apps.Length / 10); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             [NadekoCommand, Usage, Description, Aliases] |             [NadekoCommand, Usage, Description, Aliases] | ||||||
| @@ -282,7 +312,7 @@ namespace NadekoBot.Modules.Xp | |||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     await ReplyErrorLocalized("club_disaband_error").ConfigureAwait(false); |                     await ReplyErrorLocalized("club_disband_error").ConfigureAwait(false); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,10 +27,11 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             { |             { | ||||||
|                 var du = uow.DiscordUsers.GetOrCreate(user); |                 var du = uow.DiscordUsers.GetOrCreate(user); | ||||||
|                 uow._context.SaveChanges(); |                 uow._context.SaveChanges(); | ||||||
|                 var xp = new LevelStats(uow.Xp.GetTotalUserXp(user.Id)); |                 var xp = new LevelStats(du.TotalXp); | ||||||
|  |  | ||||||
|                 if (xp.Level >= 5 && du.Club == null) |                 if (xp.Level >= 5 && du.Club == null) | ||||||
|                 { |                 { | ||||||
|  |                     du.IsClubAdmin = true; | ||||||
|                     du.Club = new ClubInfo() |                     du.Club = new ClubInfo() | ||||||
|                     { |                     { | ||||||
|                         Name = clubName, |                         Name = clubName, | ||||||
| @@ -52,6 +53,27 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public bool ToggleAdmin(IUser owner, IUser toAdmin) | ||||||
|  |         { | ||||||
|  |             bool newState; | ||||||
|  |             using (var uow = _db.UnitOfWork) | ||||||
|  |             { | ||||||
|  |                 var club = uow.Clubs.GetByOwner(owner.Id); | ||||||
|  |                 var adminUser = uow.DiscordUsers.GetOrCreate(toAdmin); | ||||||
|  |  | ||||||
|  |                 if (club.OwnerId == adminUser.Id) | ||||||
|  |                     return true; | ||||||
|  |  | ||||||
|  |                 if (club == null || club.Owner.UserId != owner.Id ||  | ||||||
|  |                     !club.Users.Contains(adminUser)) | ||||||
|  |                     throw new InvalidOperationException(); | ||||||
|  |  | ||||||
|  |                 newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin; | ||||||
|  |                 uow.Complete(); | ||||||
|  |             } | ||||||
|  |             return newState; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public ClubInfo GetClubByMember(IUser user) |         public ClubInfo GetClubByMember(IUser user) | ||||||
|         { |         { | ||||||
|             using (var uow = _db.UnitOfWork) |             using (var uow = _db.UnitOfWork) | ||||||
| @@ -107,7 +129,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|                 uow._context.SaveChanges(); |                 uow._context.SaveChanges(); | ||||||
|  |  | ||||||
|                 if (du.Club != null |                 if (du.Club != null | ||||||
|                     || new LevelStats(uow.Xp.GetTotalUserXp(user.Id)).Level < club.MinimumLevelReq |                     || new LevelStats(du.TotalXp).Level < club.MinimumLevelReq | ||||||
|                     || club.Bans.Any(x => x.UserId == du.Id) |                     || club.Bans.Any(x => x.UserId == du.Id) | ||||||
|                     || club.Applicants.Any(x => x.UserId == du.Id)) |                     || club.Applicants.Any(x => x.UserId == du.Id)) | ||||||
|                 { |                 { | ||||||
| @@ -134,11 +156,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             discordUser = null; |             discordUser = null; | ||||||
|             using (var uow = _db.UnitOfWork) |             using (var uow = _db.UnitOfWork) | ||||||
|             { |             { | ||||||
|                 var club = uow.Clubs.GetByOwner(clubOwnerUserId, |                 var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId); | ||||||
|                     set => set.Include(x => x.Applicants) |  | ||||||
|                         .ThenInclude(x => x.Club) |  | ||||||
|                         .Include(x => x.Applicants) |  | ||||||
|                         .ThenInclude(x => x.User)); |  | ||||||
|                 if (club == null) |                 if (club == null) | ||||||
|                     return false; |                     return false; | ||||||
|  |  | ||||||
| @@ -147,6 +165,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|                     return false; |                     return false; | ||||||
|  |  | ||||||
|                 applicant.User.Club = club; |                 applicant.User.Club = club; | ||||||
|  |                 applicant.User.IsClubAdmin = false; | ||||||
|                 club.Applicants.Remove(applicant); |                 club.Applicants.Remove(applicant); | ||||||
|  |  | ||||||
|                 //remove that user's all other applications |                 //remove that user's all other applications | ||||||
| @@ -159,15 +178,11 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public ClubInfo GetBansAndApplications(ulong ownerUserId) |         public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId) | ||||||
|         { |         { | ||||||
|             using (var uow = _db.UnitOfWork) |             using (var uow = _db.UnitOfWork) | ||||||
|             { |             { | ||||||
|                 return uow.Clubs.GetByOwner(ownerUserId, |                 return uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||||
|                     x => x.Include(y => y.Bans) |  | ||||||
|                           .ThenInclude(y => y.User) |  | ||||||
|                           .Include(y => y.Applicants) |  | ||||||
|                           .ThenInclude(y => y.User)); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -180,6 +195,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|                     return false; |                     return false; | ||||||
|  |  | ||||||
|                 du.Club = null; |                 du.Club = null; | ||||||
|  |                 du.IsClubAdmin = false; | ||||||
|                 uow.Complete(); |                 uow.Complete(); | ||||||
|             } |             } | ||||||
|             return true; |             return true; | ||||||
| @@ -221,9 +237,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|         { |         { | ||||||
|             using (var uow = _db.UnitOfWork) |             using (var uow = _db.UnitOfWork) | ||||||
|             { |             { | ||||||
|                 club = uow.Clubs.GetByOwner(ownerUserId, |                 club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||||
|                     set => set.Include(x => x.Applicants) |  | ||||||
|                         .ThenInclude(x => x.User)); |  | ||||||
|                 if (club == null) |                 if (club == null) | ||||||
|                     return false; |                     return false; | ||||||
|  |  | ||||||
| @@ -256,9 +270,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|         { |         { | ||||||
|             using (var uow = _db.UnitOfWork) |             using (var uow = _db.UnitOfWork) | ||||||
|             { |             { | ||||||
|                 club = uow.Clubs.GetByOwner(ownerUserId, |                 club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||||
|                     set => set.Include(x => x.Bans) |  | ||||||
|                         .ThenInclude(x => x.User)); |  | ||||||
|                 if (club == null) |                 if (club == null) | ||||||
|                     return false; |                     return false; | ||||||
|  |  | ||||||
| @@ -277,7 +289,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|         { |         { | ||||||
|             using (var uow = _db.UnitOfWork) |             using (var uow = _db.UnitOfWork) | ||||||
|             { |             { | ||||||
|                 club = uow.Clubs.GetByOwner(ownerUserId); |                 club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||||
|                 if (club == null) |                 if (club == null) | ||||||
|                     return false; |                     return false; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,6 +36,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|         private readonly IImagesService _images; |         private readonly IImagesService _images; | ||||||
|         private readonly Logger _log; |         private readonly Logger _log; | ||||||
|         private readonly NadekoStrings _strings; |         private readonly NadekoStrings _strings; | ||||||
|  |         private readonly IDataCache _cache; | ||||||
|         private readonly FontCollection _fonts = new FontCollection(); |         private readonly FontCollection _fonts = new FontCollection(); | ||||||
|         public const int XP_REQUIRED_LVL_1 = 36; |         public const int XP_REQUIRED_LVL_1 = 36; | ||||||
|  |  | ||||||
| @@ -54,9 +55,6 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|         private readonly ConcurrentQueue<UserCacheItem> _addMessageXp  |         private readonly ConcurrentQueue<UserCacheItem> _addMessageXp  | ||||||
|             = new ConcurrentQueue<UserCacheItem>(); |             = new ConcurrentQueue<UserCacheItem>(); | ||||||
|  |  | ||||||
|         private readonly ConcurrentDictionary<string, byte[]> _imageStreams  |  | ||||||
|             = new ConcurrentDictionary<string, byte[]>(); |  | ||||||
|  |  | ||||||
|         private readonly Timer updateXpTimer; |         private readonly Timer updateXpTimer; | ||||||
|         private readonly HttpClient http = new HttpClient(); |         private readonly HttpClient http = new HttpClient(); | ||||||
|         private FontFamily _usernameFontFamily; |         private FontFamily _usernameFontFamily; | ||||||
| @@ -69,7 +67,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|  |  | ||||||
|         public XpService(CommandHandler cmd, IBotConfigProvider bc, |         public XpService(CommandHandler cmd, IBotConfigProvider bc, | ||||||
|             IEnumerable<GuildConfig> allGuildConfigs, IImagesService images, |             IEnumerable<GuildConfig> allGuildConfigs, IImagesService images, | ||||||
|             DbService db, NadekoStrings strings) |             DbService db, NadekoStrings strings, IDataCache cache) | ||||||
|         { |         { | ||||||
|             _db = db; |             _db = db; | ||||||
|             _cmd = cmd; |             _cmd = cmd; | ||||||
| @@ -77,6 +75,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             _images = images; |             _images = images; | ||||||
|             _log = LogManager.GetCurrentClassLogger(); |             _log = LogManager.GetCurrentClassLogger(); | ||||||
|             _strings = strings; |             _strings = strings; | ||||||
|  |             _cache = cache; | ||||||
|  |  | ||||||
|             //load settings |             //load settings | ||||||
|             allGuildConfigs = allGuildConfigs.Where(x => x.XpSettings != null); |             allGuildConfigs = allGuildConfigs.Where(x => x.XpSettings != null); | ||||||
| @@ -145,12 +144,13 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|  |  | ||||||
|                             du.LastXpGain = DateTime.UtcNow; |                             du.LastXpGain = DateTime.UtcNow; | ||||||
|  |  | ||||||
|                             var globalXp = uow.Xp.GetTotalUserXp(item.Key.User.Id); |                             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); | ||||||
|  |  | ||||||
|                             var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); |                             var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); | ||||||
|                             usr.Xp += xp; |                             usr.Xp += xp; | ||||||
|  |                             du.TotalXp += xp; | ||||||
|                             if (du.Club != null) |                             if (du.Club != null) | ||||||
|                                 du.Club.Xp += xp; |                                 du.Club.Xp += xp; | ||||||
|                             var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); |                             var newGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); | ||||||
| @@ -308,11 +308,11 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public (ulong UserId, int TotalXp)[] GetUserXps(int page) |         public DiscordUser[] GetUserXps(int page) | ||||||
|         { |         { | ||||||
|             using (var uow = _db.UnitOfWork) |             using (var uow = _db.UnitOfWork) | ||||||
|             { |             { | ||||||
|                 return uow.Xp.GetUsersFor(page); |                 return uow.DiscordUsers.GetUsersXpLeaderboardFor(page); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -403,17 +403,6 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             return _rewardedUsers.Add(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) |         public FullUserStats GetUserStats(IGuildUser user) | ||||||
|         { |         { | ||||||
|             DiscordUser du; |             DiscordUser du; | ||||||
| @@ -425,8 +414,8 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             { |             { | ||||||
|                 du = uow.DiscordUsers.GetOrCreate(user); |                 du = uow.DiscordUsers.GetOrCreate(user); | ||||||
|                 stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id); |                 stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id); | ||||||
|                 totalXp = uow.Xp.GetTotalUserXp(user.Id); |                 totalXp = du.TotalXp; | ||||||
|                 globalRank = uow.Xp.GetUserGlobalRanking(user.Id); |                 globalRank = uow.DiscordUsers.GetUserGlobalRanking(user.Id); | ||||||
|                 guildRank = uow.Xp.GetUserGuildRanking(user.Id, user.GuildId); |                 guildRank = uow.Xp.GetUserGuildRanking(user.Id, user.GuildId); | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -550,7 +539,7 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Task<Image<Rgba32>> GenerateImageAsync(IGuildUser user) |         public Task<MemoryStream> GenerateImageAsync(IGuildUser user) | ||||||
|         { |         { | ||||||
|             return GenerateImageAsync(GetUserStats(user)); |             return GenerateImageAsync(GetUserStats(user)); | ||||||
|         } |         } | ||||||
| @@ -566,170 +555,166 @@ namespace NadekoBot.Modules.Xp.Services | |||||||
|             _timeFont = _fonts.Find("Whitney-Bold").CreateFont(20); |             _timeFont = _fonts.Find("Whitney-Bold").CreateFont(20); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Task<Image<Rgba32>> GenerateImageAsync(FullUserStats stats) => Task.Run(async () => |         public Task<MemoryStream> GenerateImageAsync(FullUserStats stats) => Task.Run(async () => | ||||||
|         { |         { | ||||||
|             var img = Image.Load(_images.XpCard.ToArray()); |             using (var img = Image.Load(_images.XpCard.ToArray())) | ||||||
|  |  | ||||||
|             var username = stats.User.ToString(); |  | ||||||
|             var usernameFont = _usernameFontFamily |  | ||||||
|                 .CreateFont(username.Length <= 6 |  | ||||||
|                     ? 50 |  | ||||||
|                     : 50 - username.Length); |  | ||||||
|  |  | ||||||
|             img.DrawText("@" + username, usernameFont, Rgba32.White, |  | ||||||
|                 new PointF(130, 5)); |  | ||||||
|  |  | ||||||
|             // level |  | ||||||
|  |  | ||||||
|             img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White, |  | ||||||
|                 new PointF(47, 137)); |  | ||||||
|  |  | ||||||
|             img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White, |  | ||||||
|                 new PointF(47, 285)); |  | ||||||
|  |  | ||||||
|             //club name |  | ||||||
|  |  | ||||||
|             var clubName = stats.User.Club?.ToString() ?? "-"; |  | ||||||
|  |  | ||||||
|             var clubFont = _clubFontFamily |  | ||||||
|                 .CreateFont(clubName.Length <= 8 |  | ||||||
|                     ? 35 |  | ||||||
|                     : 35 - (clubName.Length / 2)); |  | ||||||
|  |  | ||||||
|             img.DrawText(clubName, clubFont, Rgba32.White, |  | ||||||
|                 new PointF(650 - clubName.Length * 10, 40)); |  | ||||||
|  |  | ||||||
|             var pen = new Pen<Rgba32>(Rgba32.Black, 1); |  | ||||||
|             var brush = Brushes.Solid<Rgba32>(Rgba32.White); |  | ||||||
|             var xpBgBrush = Brushes.Solid<Rgba32>(new Rgba32(0, 0, 0, 0.4f)); |  | ||||||
|  |  | ||||||
|             var global = stats.Global; |  | ||||||
|             var guild = stats.Guild; |  | ||||||
|  |  | ||||||
|             //xp bar |  | ||||||
|  |  | ||||||
|             img.FillPolygon(xpBgBrush, new[] { |  | ||||||
|                 new PointF(321, 104), |  | ||||||
|                 new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104), |  | ||||||
|                 new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235), |  | ||||||
|                 new PointF(286, 235), |  | ||||||
|             }); |  | ||||||
|             img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen, |  | ||||||
|                 new PointF(430, 130)); |  | ||||||
|  |  | ||||||
|             img.FillPolygon(xpBgBrush, new[] { |  | ||||||
|                 new PointF(282, 248), |  | ||||||
|                 new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248), |  | ||||||
|                 new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379), |  | ||||||
|                 new PointF(247, 379), |  | ||||||
|             }); |  | ||||||
|             img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen, |  | ||||||
|                 new PointF(400, 270)); |  | ||||||
|  |  | ||||||
|             if (stats.FullGuildStats.AwardedXp != 0) |  | ||||||
|             { |             { | ||||||
|                 var sign = stats.FullGuildStats.AwardedXp > 0 |  | ||||||
|                     ? "+ " |  | ||||||
|                     : ""; |  | ||||||
|                 img.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", _awardedFont, brush, pen, |  | ||||||
|                     new PointF(445 - (Math.Max(0, (stats.FullGuildStats.AwardedXp.ToString().Length - 2)) * 5), 335)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             //ranking |                 var username = stats.User.ToString(); | ||||||
|  |                 var usernameFont = _usernameFontFamily | ||||||
|  |                     .CreateFont(username.Length <= 6 | ||||||
|  |                         ? 50 | ||||||
|  |                         : 50 - username.Length); | ||||||
|  |  | ||||||
|             img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White, |                 img.DrawText("@" + username, usernameFont, Rgba32.White, | ||||||
|                 new PointF(148, 170)); |                     new PointF(130, 5)); | ||||||
|  |  | ||||||
|             img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White, |                 // level | ||||||
|                 new PointF(148, 317)); |  | ||||||
|  |  | ||||||
|             //time on this level |                 img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White, | ||||||
|  |                     new PointF(47, 137)); | ||||||
|  |  | ||||||
|             string GetTimeSpent(DateTime time) |                 img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White, | ||||||
|             { |                     new PointF(47, 285)); | ||||||
|                 var offset = DateTime.UtcNow - time; |  | ||||||
|                 return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m"; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White, |                 //club name | ||||||
|                 new PointF(50, 197)); |  | ||||||
|  |  | ||||||
|             img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White, |                 var clubName = stats.User.Club?.ToString() ?? "-"; | ||||||
|                 new PointF(50, 344)); |  | ||||||
|  |  | ||||||
|             //avatar |                 var clubFont = _clubFontFamily | ||||||
|  |                     .CreateFont(clubName.Length <= 8 | ||||||
|  |                         ? 35 | ||||||
|  |                         : 35 - (clubName.Length / 2)); | ||||||
|  |  | ||||||
|             if (stats.User.AvatarId != null) |                 img.DrawText(clubName, clubFont, Rgba32.White, | ||||||
|             { |                     new PointF(650 - clubName.Length * 10, 40)); | ||||||
|                 try |  | ||||||
|  |                 var pen = new Pen<Rgba32>(Rgba32.Black, 1); | ||||||
|  |                 var brush = Brushes.Solid<Rgba32>(Rgba32.White); | ||||||
|  |                 var xpBgBrush = Brushes.Solid<Rgba32>(new Rgba32(0, 0, 0, 0.4f)); | ||||||
|  |  | ||||||
|  |                 var global = stats.Global; | ||||||
|  |                 var guild = stats.Guild; | ||||||
|  |  | ||||||
|  |                 //xp bar | ||||||
|  |  | ||||||
|  |                 img.FillPolygon(xpBgBrush, new[] { | ||||||
|  |                     new PointF(321, 104), | ||||||
|  |                     new PointF(321 + (450 * (global.LevelXp / (float)global.RequiredXp)), 104), | ||||||
|  |                     new PointF(286 + (450 * (global.LevelXp / (float)global.RequiredXp)), 235), | ||||||
|  |                     new PointF(286, 235), | ||||||
|  |                 }); | ||||||
|  |                 img.DrawText($"{global.LevelXp}/{global.RequiredXp}", _xpFont, brush, pen, | ||||||
|  |                     new PointF(430, 130)); | ||||||
|  |  | ||||||
|  |                 img.FillPolygon(xpBgBrush, new[] { | ||||||
|  |                     new PointF(282, 248), | ||||||
|  |                     new PointF(282 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 248), | ||||||
|  |                     new PointF(247 + (450 * (guild.LevelXp / (float)guild.RequiredXp)), 379), | ||||||
|  |                     new PointF(247, 379), | ||||||
|  |                 }); | ||||||
|  |                 img.DrawText($"{guild.LevelXp}/{guild.RequiredXp}", _xpFont, brush, pen, | ||||||
|  |                     new PointF(400, 270)); | ||||||
|  |  | ||||||
|  |                 if (stats.FullGuildStats.AwardedXp != 0) | ||||||
|                 { |                 { | ||||||
|                     var avatarUrl = stats.User.RealAvatarUrl(); |                     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)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                     byte[] s; |                 //ranking | ||||||
|                     if (!_imageStreams.TryGetValue(avatarUrl, out s)) |  | ||||||
|  |                 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 | ||||||
|                     { |                     { | ||||||
|                         using (var temp = await http.GetStreamAsync(avatarUrl)) |                         var avatarUrl = stats.User.RealAvatarUrl(); | ||||||
|  |  | ||||||
|  |                         var (succ, data) = await _cache.TryGetImageDataAsync(avatarUrl); | ||||||
|  |                         if (!succ) | ||||||
|                         { |                         { | ||||||
|                             var tempDraw = Image.Load(temp); |                             using (var temp = await http.GetStreamAsync(avatarUrl)) | ||||||
|                             tempDraw = tempDraw.Resize(69, 70); |                             using (var tempDraw = Image.Load(temp).Resize(69, 70)) | ||||||
|                             ApplyRoundedCorners(tempDraw, 35); |                             { | ||||||
|                             s = tempDraw.ToStream().ToArray(); |                                 ApplyRoundedCorners(tempDraw, 35); | ||||||
|  |                                 data = tempDraw.ToStream().ToArray(); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             await _cache.SetImageDataAsync(avatarUrl, data); | ||||||
|                         } |                         } | ||||||
|  |                         var toDraw = Image.Load(data); | ||||||
|  |  | ||||||
|                         _imageStreams.AddOrUpdate(avatarUrl, s, (k, v) => s); |  | ||||||
|  |                         img.DrawImage(toDraw, | ||||||
|  |                             1, | ||||||
|  |                             new Size(69, 70), | ||||||
|  |                             new Point(32, 10)); | ||||||
|                     } |                     } | ||||||
|                     var toDraw = Image.Load(s); |                     catch (Exception ex) | ||||||
|  |  | ||||||
|  |  | ||||||
|                     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)) |                         _log.Warn(ex); | ||||||
|                         { |  | ||||||
|                             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) |  | ||||||
|  |                 //club image | ||||||
|  |  | ||||||
|  |                 if (!string.IsNullOrWhiteSpace(stats.User.Club?.ImageUrl)) | ||||||
|                 { |                 { | ||||||
|                     _log.Warn(ex); |                     var imgUrl = stats.User.Club.ImageUrl; | ||||||
|  |                     try | ||||||
|  |                     { | ||||||
|  |                         var (succ, data) = await _cache.TryGetImageDataAsync(imgUrl); | ||||||
|  |                         if (!succ) | ||||||
|  |                         { | ||||||
|  |                             using (var temp = await http.GetStreamAsync(imgUrl)) | ||||||
|  |                             using (var tempDraw = Image.Load(temp).Resize(45, 45)) | ||||||
|  |                             { | ||||||
|  |                                 ApplyRoundedCorners(tempDraw, 22.5f); | ||||||
|  |                                 data = tempDraw.ToStream().ToArray(); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             await _cache.SetImageDataAsync(imgUrl, data); | ||||||
|  |                         } | ||||||
|  |                         var toDraw = Image.Load(data); | ||||||
|  |  | ||||||
|  |                         img.DrawImage(toDraw, | ||||||
|  |                             1, | ||||||
|  |                             new Size(45, 45), | ||||||
|  |                             new Point(722, 25)); | ||||||
|  |                     } | ||||||
|  |                     catch (Exception ex) | ||||||
|  |                     { | ||||||
|  |                         _log.Warn(ex); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 return img.Resize(432, 211).ToStream(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             var arr = img.ToStream().ToArray(); |  | ||||||
|  |  | ||||||
|             //_log.Info("{0:F2} KB", arr.Length * 1.0f / 1.KB()); |  | ||||||
|  |  | ||||||
|             return img; |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| using Discord; | using Discord; | ||||||
| using Discord.Commands; | using Discord.Commands; | ||||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||||
|  | using NadekoBot.Common; | ||||||
| using NadekoBot.Common.Attributes; | using NadekoBot.Common.Attributes; | ||||||
| using NadekoBot.Extensions; | using NadekoBot.Extensions; | ||||||
| using NadekoBot.Modules.Xp.Common; | using NadekoBot.Modules.Xp.Common; | ||||||
| using NadekoBot.Modules.Xp.Services; | using NadekoBot.Modules.Xp.Services; | ||||||
|  | using NadekoBot.Services; | ||||||
| using NadekoBot.Services.Database.Models; | using NadekoBot.Services.Database.Models; | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| @@ -15,23 +17,55 @@ namespace NadekoBot.Modules.Xp | |||||||
|     public partial class Xp : NadekoTopLevelModule<XpService> |     public partial class Xp : NadekoTopLevelModule<XpService> | ||||||
|     { |     { | ||||||
|         private readonly DiscordSocketClient _client; |         private readonly DiscordSocketClient _client; | ||||||
|  |         private readonly DbService _db; | ||||||
|  |  | ||||||
|         public Xp(DiscordSocketClient client) |         public Xp(DiscordSocketClient client,DbService db) | ||||||
|         { |         { | ||||||
|             _client = client; |             _client = client; | ||||||
|  |             _db = db; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         //[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)] | ||||||
|  |         //[Ratelimit(30)] | ||||||
|         public async Task Experience([Remainder]IUser user = null) |         public async Task Experience([Remainder]IUser user = null) | ||||||
|         { |         { | ||||||
|             user = user ?? Context.User; |             user = user ?? Context.User; | ||||||
|  |             var sw = Stopwatch.StartNew(); | ||||||
|             await Context.Channel.TriggerTypingAsync(); |             await Context.Channel.TriggerTypingAsync(); | ||||||
|             var img = await _service.GenerateImageAsync((IGuildUser)user); |             var img = await _service.GenerateImageAsync((IGuildUser)user); | ||||||
|              |             sw.Stop(); | ||||||
|             await Context.Channel.SendFileAsync(img.ToStream(), $"{user.Id}_xp.png") |             _log.Info("Generating finished in {0:F2}s", sw.Elapsed.TotalSeconds); | ||||||
|  |             sw.Restart(); | ||||||
|  |             await Context.Channel.SendFileAsync(img, $"{user.Id}_xp.png") | ||||||
|                 .ConfigureAwait(false); |                 .ConfigureAwait(false); | ||||||
|  |             sw.Stop(); | ||||||
|  |             _log.Info("Sending finished in {0:F2}s", sw.Elapsed.TotalSeconds); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         [NadekoCommand, Usage, Description, Aliases] |         [NadekoCommand, Usage, Description, Aliases] | ||||||
| @@ -40,7 +74,7 @@ namespace NadekoBot.Modules.Xp | |||||||
|         { |         { | ||||||
|             page--; |             page--; | ||||||
|  |  | ||||||
|             if (page < 0) |             if (page < 0 || page > 100) | ||||||
|                 return Task.CompletedTask; |                 return Task.CompletedTask; | ||||||
|  |  | ||||||
|             var roles = _service.GetRoleRewards(Context.Guild.Id) |             var roles = _service.GetRoleRewards(Context.Guild.Id) | ||||||
| @@ -169,7 +203,7 @@ namespace NadekoBot.Modules.Xp | |||||||
|         [RequireContext(ContextType.Guild)] |         [RequireContext(ContextType.Guild)] | ||||||
|         public Task XpLeaderboard(int page = 1) |         public Task XpLeaderboard(int page = 1) | ||||||
|         { |         { | ||||||
|             if (--page < 0) |             if (--page < 0 || page > 100) | ||||||
|                 return Task.CompletedTask; |                 return Task.CompletedTask; | ||||||
|  |  | ||||||
|             return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) => |             return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) => | ||||||
| @@ -210,32 +244,28 @@ namespace NadekoBot.Modules.Xp | |||||||
|         [RequireContext(ContextType.Guild)] |         [RequireContext(ContextType.Guild)] | ||||||
|         public async Task XpGlobalLeaderboard(int page = 1) |         public async Task XpGlobalLeaderboard(int page = 1) | ||||||
|         { |         { | ||||||
|             if (--page < 0) |             if (--page < 0 || page > 100) | ||||||
|                 return; |                 return; | ||||||
|  |             var users = _service.GetUserXps(page); | ||||||
|  |  | ||||||
|             await Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) => |             var embed = new EmbedBuilder() | ||||||
|  |                 .WithTitle(GetText("global_leaderboard")) | ||||||
|  |                 .WithOkColor(); | ||||||
|  |  | ||||||
|  |             if (!users.Any()) | ||||||
|  |                 embed.WithDescription("-"); | ||||||
|  |             else | ||||||
|             { |             { | ||||||
|                 var users = _service.GetUserXps(curPage); |                 for (int i = 0; i < users.Length; i++) | ||||||
|  |  | ||||||
|                 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 = users[i]; | ||||||
|                     { |                     embed.AddField( | ||||||
|                         var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false); |                         $"#{(i + 1 + page * 9)} {(user.ToString())}",  | ||||||
|                         embed.AddField( |                         $"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp"); | ||||||
|                             $"#{(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); |             } | ||||||
|  |  | ||||||
|  |             await Context.Channel.EmbedAsync(embed); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         [NadekoCommand, Usage, Description, Aliases] |         [NadekoCommand, Usage, Description, Aliases] | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ using NadekoBot.Common.ShardCom; | |||||||
| using NadekoBot.Common.TypeReaders; | using NadekoBot.Common.TypeReaders; | ||||||
| using NadekoBot.Common.TypeReaders.Models; | using NadekoBot.Common.TypeReaders.Models; | ||||||
| using NadekoBot.Services.Database; | using NadekoBot.Services.Database; | ||||||
|  | using StackExchange.Redis; | ||||||
|  | using Newtonsoft.Json; | ||||||
|  |  | ||||||
| namespace NadekoBot | namespace NadekoBot | ||||||
| { | { | ||||||
| @@ -139,6 +141,7 @@ namespace NadekoBot | |||||||
|                     .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this |                     .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this | ||||||
|                     .AddManual<NadekoBot>(this) |                     .AddManual<NadekoBot>(this) | ||||||
|                     .AddManual<IUnitOfWork>(uow) |                     .AddManual<IUnitOfWork>(uow) | ||||||
|  |                     .AddManual<IDataCache>(new RedisCache()) | ||||||
|                     .LoadFrom(Assembly.GetEntryAssembly()) |                     .LoadFrom(Assembly.GetEntryAssembly()) | ||||||
|                     .Build(); |                     .Build(); | ||||||
|  |  | ||||||
| @@ -239,13 +242,6 @@ namespace NadekoBot | |||||||
| #if GLOBAL_NADEKO | #if GLOBAL_NADEKO | ||||||
|             isPublicNadeko = true; |             isPublicNadeko = true; | ||||||
| #endif | #endif | ||||||
|             //_log.Info(string.Join(", ", CommandService.Commands |  | ||||||
|             //    .Distinct(x => x.Name + x.Module.Name) |  | ||||||
|             //    .SelectMany(x => x.Aliases) |  | ||||||
|             //    .GroupBy(x => x) |  | ||||||
|             //    .Where(x => x.Count() > 1) |  | ||||||
|             //    .Select(x => x.Key + $"({x.Count()})"))); |  | ||||||
|  |  | ||||||
|             //unload modules which are not available on the public bot |             //unload modules which are not available on the public bot | ||||||
|  |  | ||||||
|             if(isPublicNadeko) |             if(isPublicNadeko) | ||||||
| @@ -256,6 +252,7 @@ namespace NadekoBot | |||||||
|                     .ForEach(x => CommandService.RemoveModuleAsync(x)); |                     .ForEach(x => CommandService.RemoveModuleAsync(x)); | ||||||
|  |  | ||||||
|             Ready.TrySetResult(true); |             Ready.TrySetResult(true); | ||||||
|  |             HandleStatusChanges(); | ||||||
|             _log.Info($"Shard {Client.ShardId} ready."); |             _log.Info($"Shard {Client.ShardId} ready."); | ||||||
|             //_log.Info(await stats.Print().ConfigureAwait(false)); |             //_log.Info(await stats.Print().ConfigureAwait(false)); | ||||||
|         } |         } | ||||||
| @@ -318,5 +315,51 @@ namespace NadekoBot | |||||||
|                 } |                 } | ||||||
|             })).Start(); |             })).Start(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private void HandleStatusChanges() | ||||||
|  |         { | ||||||
|  |             var sub = Services.GetService<IDataCache>().Redis.GetSubscriber(); | ||||||
|  |             sub.Subscribe("status.game_set", async (ch, game) => | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     var obj = new { Name = default(string) }; | ||||||
|  |                     obj = JsonConvert.DeserializeAnonymousType(game, obj); | ||||||
|  |                     await Client.SetGameAsync(obj.Name).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     _log.Warn(ex); | ||||||
|  |                 } | ||||||
|  |             }, CommandFlags.FireAndForget); | ||||||
|  |  | ||||||
|  |             sub.Subscribe("status.stream_set", async (ch, streamData) => | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     var obj = new { Name = "", Url = "" }; | ||||||
|  |                     obj = JsonConvert.DeserializeAnonymousType(streamData, obj); | ||||||
|  |                     await Client.SetGameAsync(obj.Name, obj.Url, StreamType.Twitch).ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |                 catch (Exception ex) | ||||||
|  |                 { | ||||||
|  |                     _log.Warn(ex); | ||||||
|  |                 } | ||||||
|  |             }, CommandFlags.FireAndForget); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Task SetGameAsync(string game) | ||||||
|  |         { | ||||||
|  |             var obj = new { Name = game }; | ||||||
|  |             var sub = Services.GetService<IDataCache>().Redis.GetSubscriber(); | ||||||
|  |             return sub.PublishAsync("status.game_set", JsonConvert.SerializeObject(obj)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Task SetStreamAsync(string name, string url) | ||||||
|  |         { | ||||||
|  |             var obj = new { Name = name, Url = url }; | ||||||
|  |             var sub = Services.GetService<IDataCache>().Redis.GetSubscriber(); | ||||||
|  |             return sub.PublishAsync("status.game_set", JsonConvert.SerializeObject(obj)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,30 +1,18 @@ | |||||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|   <PropertyGroup> |   <PropertyGroup> | ||||||
|     <Description>General purpose Discord bot written in C#.</Description> |     <TargetFramework>netcoreapp2.0</TargetFramework> | ||||||
|     <Copyright>Kwoth</Copyright> |     <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion> | ||||||
|     <Authors>Kwoth</Authors> |     <OutputType>exe</OutputType> | ||||||
|     <PublisherName>Kwoth</PublisherName> |     <AssetTargetFallback>$(AssetTargetFallback);dnxcore50;portable-net45+win8+wpa81</AssetTargetFallback> | ||||||
|     <TargetFramework>netcoreapp1.1</TargetFramework> |  | ||||||
|     <AllowUnsafeBlocks>true</AllowUnsafeBlocks> |  | ||||||
|     <AssemblyName>NadekoBot</AssemblyName> |  | ||||||
|     <OutputType>Exe</OutputType> |  | ||||||
|     <PackageId>NadekoBot</PackageId> |  | ||||||
|     <RuntimeFrameworkVersion>1.1.1</RuntimeFrameworkVersion> |  | ||||||
|     <PackageTargetFallback>$(PackageTargetFallback);dnxcore50;portable-net45+win8+wpa81</PackageTargetFallback> |  | ||||||
|     <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |  | ||||||
|     <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |  | ||||||
|     <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |  | ||||||
|     <GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute> |  | ||||||
|     <GeneratePackageOnBuild>False</GeneratePackageOnBuild> |  | ||||||
|     <AssemblyVersion>1.0.0.0</AssemblyVersion> |  | ||||||
|     <FileVersion>1.0.0.0</FileVersion> |  | ||||||
|     <ApplicationIcon>nadeko_icon.ico</ApplicationIcon> |     <ApplicationIcon>nadeko_icon.ico</ApplicationIcon> | ||||||
|     <RuntimeIdentifiers>win7-x64<!--;ubuntu.14.04-x64;osx.10.10-x64 --></RuntimeIdentifiers> |     <RuntimeIdentifiers>win7-x64<!--;ubuntu.14.04-x64;osx.10.10-x64 --></RuntimeIdentifiers> | ||||||
|  |     <Configurations>Debug;Release;global_nadeko</Configurations> | ||||||
|  |     <LangVersion>latest</LangVersion> | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
|   <PropertyGroup Condition=" '$(Version)' == '' "> |   <PropertyGroup Condition=" '$(Version)' == '' "> | ||||||
|     <VersionPrefix Condition=" '$(VersionPrefix)' == '' ">1.4.1</VersionPrefix> |     <VersionPrefix Condition=" '$(VersionPrefix)' == '' ">1.9.1</VersionPrefix> | ||||||
|     <Version Condition=" '$(VersionSuffix)' != '' ">$(VersionPrefix).$(VersionSuffix)</Version> |     <Version Condition=" '$(VersionSuffix)' != '' ">$(VersionPrefix).$(VersionSuffix)</Version> | ||||||
|     <Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version> |     <Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
| @@ -59,27 +47,24 @@ | |||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="AngleSharp" Version="0.9.9" /> |     <PackageReference Include="AngleSharp" Version="0.9.9" /> | ||||||
|     <PackageReference Include="Discord.Net" Version="2.0.0-alpha-build-00828" /> |     <PackageReference Include="Discord.Net" Version="2.0.0-alpha-build-00828" /> | ||||||
|     <PackageReference Include="libvideo" Version="1.0.1" /> |     <PackageReference Include="CoreCLR-NCalc" Version="2.1.3" /> | ||||||
|     <PackageReference Include="CoreCLR-NCalc" Version="2.1.2" /> |     <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.29.1.138" /> | ||||||
|     <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" /> |     <PackageReference Include="Google.Apis.YouTube.v3" Version="1.29.1.976" /> | ||||||
|     <PackageReference Include="Google.Apis.YouTube.v3" Version="1.20.0.701" /> |     <PackageReference Include="Google.Apis.Customsearch.v1" Version="1.29.1.896" /> | ||||||
|     <PackageReference Include="Google.Apis.Customsearch.v1" Version="1.20.0.466" /> |  | ||||||
|     <PackageReference Include="ImageSharp" Version="1.0.0-alpha9-00194" /> |     <PackageReference Include="ImageSharp" Version="1.0.0-alpha9-00194" /> | ||||||
|     <PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha9-00189" /> |     <PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha9-00189" /> | ||||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" /> |     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" /> |     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" /> |     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" /> |     <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" /> |     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" /> |     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> |     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" /> |     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" /> |     <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> | ||||||
|     <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> |     <PackageReference Include="NLog" Version="5.0.0-beta10" /> | ||||||
|     <PackageReference Include="NLog" Version="5.0.0-beta03" /> |     <PackageReference Include="StackExchange.Redis" Version="1.2.6" /> | ||||||
|     <PackageReference Include="NYoutubeDL" Version="0.4.4" /> |     <PackageReference Include="System.ValueTuple" Version="4.4.0" /> | ||||||
|     <PackageReference Include="System.ValueTuple" Version="4.4.0-preview1-25305-02" /> |  | ||||||
|     <PackageReference Include="System.Xml.XPath" Version="4.3.0" /> |  | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <PropertyGroup Condition=" '$(Configuration)' == 'GlobalNadeko' "> |   <PropertyGroup Condition=" '$(Configuration)' == 'GlobalNadeko' "> | ||||||
| @@ -87,22 +72,12 @@ | |||||||
|     <NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> |     <NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|  |  | ||||||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> |  | ||||||
|     <LangVersion>latest</LangVersion> |  | ||||||
|   </PropertyGroup> |  | ||||||
|  |  | ||||||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | ||||||
|     <LangVersion>latest</LangVersion> |     <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> |     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> | ||||||
|     <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> |     <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" /> | ||||||
|   </ItemGroup> |  | ||||||
|  |  | ||||||
|   <ItemGroup> |  | ||||||
|     <Compile Update="Resources\CommandStrings.Designer.cs"> |  | ||||||
|       <SubType>Designer</SubType> |  | ||||||
|     </Compile> |  | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -1,15 +0,0 @@ | |||||||
| <?xml version="1.0"?> |  | ||||||
| <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> |  | ||||||
|   <metadata> |  | ||||||
|     <id>NadekoBot</id> |  | ||||||
|     <version>1.4.0-2$suffix$</version> |  | ||||||
|     <title>NadekoBot</title> |  | ||||||
|     <authors>Kwoth</authors> |  | ||||||
|     <owners>Kwoth</owners> |  | ||||||
|     <description>General purpose discord chat bot written in C#.</description> |  | ||||||
|     <tags>nadeko;bot;nadekobot;discord bot</tags> |  | ||||||
|     <projectUrl>https://github.com/Kwoth/NadekoBot</projectUrl> |  | ||||||
|     <licenseUrl>https://choosealicense.com/licenses/unlicense/</licenseUrl> |  | ||||||
|     <requireLicenseAcceptance>false</requireLicenseAcceptance> |  | ||||||
|   </metadata> |  | ||||||
| </package> |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |  | ||||||
| 	<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cadministration_005Ccommands/@EntryIndexedValue">True</s:Boolean> |  | ||||||
| 	<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cgambling_005Ccommands/@EntryIndexedValue">True</s:Boolean> |  | ||||||
| 	<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cgames_005Ccommands/@EntryIndexedValue">True</s:Boolean> |  | ||||||
| 	<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cpermissions_005Ccommands/@EntryIndexedValue">True</s:Boolean> |  | ||||||
| 	<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Csearches_005Ccommands/@EntryIndexedValue">True</s:Boolean> |  | ||||||
| 	<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cutility_005Ccommands/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| using System.Reflection; |  | ||||||
| using System.Runtime.InteropServices; |  | ||||||
|  |  | ||||||
| // General Information about an assembly is controlled through the following |  | ||||||
| // set of attributes. Change these attribute values to modify the information |  | ||||||
| // associated with an assembly. |  | ||||||
| [assembly: AssemblyConfiguration("")] |  | ||||||
| [assembly: AssemblyCompany("")] |  | ||||||
| [assembly: AssemblyProduct("NadekoBot")] |  | ||||||
| [assembly: AssemblyTrademark("")] |  | ||||||
| [assembly: AssemblyInformationalVersion("1.0")] |  | ||||||
|  |  | ||||||
| // Setting ComVisible to false makes the types in this assembly not visible |  | ||||||
| // to COM components.  If you need to access a type in this assembly from |  | ||||||
| // COM, set the ComVisible attribute to true on that type. |  | ||||||
| [assembly: ComVisible(false)] |  | ||||||
|  |  | ||||||
| // The following GUID is for the ID of the typelib if this project is exposed to COM |  | ||||||
| [assembly: Guid("f8225ac4-3cbc-40b4-bcf3-1cacf276bf29")] |  | ||||||
| @@ -3589,7 +3589,7 @@ | |||||||
|     <value>`{0}xpex Role Excluded-Role` `{0}xpex Server`</value> |     <value>`{0}xpex Role Excluded-Role` `{0}xpex Server`</value> | ||||||
|   </data> |   </data> | ||||||
|   <data name="xpexclude_desc" xml:space="preserve"> |   <data name="xpexclude_desc" xml:space="preserve"> | ||||||
|     <value>Exclude a user or a role from the xp system, or whole current server.</value> |     <value>Exclude a channel, role or current server from the xp system.</value> | ||||||
|   </data> |   </data> | ||||||
|   <data name="xpnotify_cmd" xml:space="preserve"> |   <data name="xpnotify_cmd" xml:space="preserve"> | ||||||
|     <value>xpnotify xpn</value> |     <value>xpnotify xpn</value> | ||||||
| @@ -3780,4 +3780,31 @@ | |||||||
|   <data name="nsfwclearcache_desc" xml:space="preserve"> |   <data name="nsfwclearcache_desc" xml:space="preserve"> | ||||||
|     <value>Clears nsfw cache.</value> |     <value>Clears nsfw cache.</value> | ||||||
|   </data> |   </data> | ||||||
|  |   <data name="clubadmin_cmd" xml:space="preserve"> | ||||||
|  |     <value>clubadmin</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="clubadmin_usage" xml:space="preserve"> | ||||||
|  |     <value>`{0}clubadmin`</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="clubadmin_desc" xml:space="preserve"> | ||||||
|  |     <value>Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications.</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="autoboobs_cmd" xml:space="preserve"> | ||||||
|  |     <value>autoboobs</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="autoboobs_desc" xml:space="preserve"> | ||||||
|  |     <value>Posts a boobs every X seconds. 20 seconds minimum. Provide no arguments to disable.</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="autoboobs_usage" xml:space="preserve"> | ||||||
|  |     <value>`{0}autoboobs 30` or `{0}autoboobs`</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="autobutts_cmd" xml:space="preserve"> | ||||||
|  |     <value>autobutts</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="autobutts_desc" xml:space="preserve"> | ||||||
|  |     <value>Posts a butts every X seconds. 20 seconds minimum. Provide no arguments to disable.</value> | ||||||
|  |   </data> | ||||||
|  |   <data name="autobutts_usage" xml:space="preserve"> | ||||||
|  |     <value>`{0}autobutts 30` or `{0}autobutts`</value> | ||||||
|  |   </data> | ||||||
| </root> | </root> | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System.ComponentModel.DataAnnotations.Schema; | using Newtonsoft.Json; | ||||||
|  | using System.ComponentModel.DataAnnotations.Schema; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
|  |  | ||||||
| namespace NadekoBot.Services.Database.Models | namespace NadekoBot.Services.Database.Models | ||||||
| @@ -6,7 +7,9 @@ namespace NadekoBot.Services.Database.Models | |||||||
|     public class CustomReaction : DbEntity |     public class CustomReaction : DbEntity | ||||||
|     { |     { | ||||||
|         public ulong? GuildId { get; set; } |         public ulong? GuildId { get; set; } | ||||||
|  |  | ||||||
|         [NotMapped] |         [NotMapped] | ||||||
|  |         [JsonIgnore] | ||||||
|         public Regex Regex { get; set; } |         public Regex Regex { get; set; } | ||||||
|         public string Response { get; set; } |         public string Response { get; set; } | ||||||
|         public string Trigger { get; set; } |         public string Trigger { get; set; } | ||||||
| @@ -16,6 +19,7 @@ namespace NadekoBot.Services.Database.Models | |||||||
|         public bool AutoDeleteTrigger { get; set; } |         public bool AutoDeleteTrigger { get; set; } | ||||||
|         public bool DmResponse { get; set; } |         public bool DmResponse { get; set; } | ||||||
|  |  | ||||||
|  |         [JsonIgnore] | ||||||
|         public bool IsGlobal => !GuildId.HasValue; |         public bool IsGlobal => !GuildId.HasValue; | ||||||
|  |  | ||||||
|         public bool ContainsAnywhere { get; set; } |         public bool ContainsAnywhere { get; set; } | ||||||
|   | |||||||
| @@ -10,6 +10,9 @@ namespace NadekoBot.Services.Database.Models | |||||||
|         public string AvatarId { get; set; } |         public string AvatarId { get; set; } | ||||||
|          |          | ||||||
|         public ClubInfo Club { get; set; } |         public ClubInfo Club { get; set; } | ||||||
|  |         public bool IsClubAdmin { get; set; } | ||||||
|  |  | ||||||
|  |         public int TotalXp { get; set; } | ||||||
|         public DateTime LastLevelUp { get; set; } = DateTime.UtcNow; |         public DateTime LastLevelUp { get; set; } = DateTime.UtcNow; | ||||||
|         public DateTime LastXpGain { get; set; } = DateTime.MinValue; |         public DateTime LastXpGain { get; set; } = DateTime.MinValue; | ||||||
|         public XpNotificationType NotifyOnLevelUp { get; set; } |         public XpNotificationType NotifyOnLevelUp { get; set; } | ||||||
|   | |||||||
| @@ -3,23 +3,21 @@ using System.Collections.Generic; | |||||||
| using System.Linq; | using System.Linq; | ||||||
| using NadekoBot.Services.Database.Models; | using NadekoBot.Services.Database.Models; | ||||||
| using NadekoBot.Extensions; | using NadekoBot.Extensions; | ||||||
| using Microsoft.EntityFrameworkCore.Infrastructure; |  | ||||||
| using System; | using System; | ||||||
|  | using Microsoft.EntityFrameworkCore.Design; | ||||||
|  | using Microsoft.Data.Sqlite; | ||||||
|  | using System.IO; | ||||||
|  |  | ||||||
| namespace NadekoBot.Services.Database | namespace NadekoBot.Services.Database | ||||||
| { | { | ||||||
|  |     public class NadekoContextFactory : IDesignTimeDbContextFactory<NadekoContext> | ||||||
|     public class NadekoContextFactory : IDbContextFactory<NadekoContext> |  | ||||||
|     {         |     {         | ||||||
|         /// <summary> |         public NadekoContext CreateDbContext(string[] args) | ||||||
|         /// :\ Used for migrations |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="options"></param> |  | ||||||
|         /// <returns></returns> |  | ||||||
|         public NadekoContext Create(DbContextFactoryOptions options) |  | ||||||
|         { |         { | ||||||
|             var optionsBuilder = new DbContextOptionsBuilder(); |             var optionsBuilder = new DbContextOptionsBuilder<NadekoContext>(); | ||||||
|             optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); |             var builder = new SqliteConnectionStringBuilder("Data Source=data/NadekoBot.db"); | ||||||
|  |             builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); | ||||||
|  |             optionsBuilder.UseSqlite(builder.ToString()); | ||||||
|             var ctx = new NadekoContext(optionsBuilder.Options); |             var ctx = new NadekoContext(optionsBuilder.Options); | ||||||
|             ctx.Database.SetCommandTimeout(60); |             ctx.Database.SetCommandTimeout(60); | ||||||
|             return ctx; |             return ctx; | ||||||
| @@ -58,12 +56,7 @@ namespace NadekoBot.Services.Database | |||||||
|         public DbSet<ModulePrefix> ModulePrefixes { get; set; } |         public DbSet<ModulePrefix> ModulePrefixes { get; set; } | ||||||
|         public DbSet<RewardedUser> RewardedUsers { get; set; } |         public DbSet<RewardedUser> RewardedUsers { get; set; } | ||||||
|  |  | ||||||
|         public NadekoContext() : base() |         public NadekoContext(DbContextOptions<NadekoContext> options) : base(options) | ||||||
|         { |  | ||||||
|  |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public NadekoContext(DbContextOptions options) : base(options) |  | ||||||
|         { |         { | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -231,7 +224,7 @@ namespace NadekoBot.Services.Database | |||||||
|             musicPlaylistEntity |             musicPlaylistEntity | ||||||
|                 .HasMany(p => p.Songs) |                 .HasMany(p => p.Songs) | ||||||
|                 .WithOne() |                 .WithOne() | ||||||
|                 .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade); |                 .OnDelete(DeleteBehavior.Cascade); | ||||||
|  |  | ||||||
|  |  | ||||||
|             #endregion |             #endregion | ||||||
| @@ -329,7 +322,7 @@ namespace NadekoBot.Services.Database | |||||||
|             #region ClubManytoMany |             #region ClubManytoMany | ||||||
|  |  | ||||||
|             modelBuilder.Entity<ClubApplicants>() |             modelBuilder.Entity<ClubApplicants>() | ||||||
|             .HasKey(t => new { t.ClubId, t.UserId }); |                 .HasKey(t => new { t.ClubId, t.UserId }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity<ClubApplicants>() |             modelBuilder.Entity<ClubApplicants>() | ||||||
|                 .HasOne(pt => pt.User) |                 .HasOne(pt => pt.User) | ||||||
| @@ -340,7 +333,7 @@ namespace NadekoBot.Services.Database | |||||||
|                 .WithMany(x => x.Applicants); |                 .WithMany(x => x.Applicants); | ||||||
|  |  | ||||||
|             modelBuilder.Entity<ClubBans>() |             modelBuilder.Entity<ClubBans>() | ||||||
|             .HasKey(t => new { t.ClubId, t.UserId }); |                 .HasKey(t => new { t.ClubId, t.UserId }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity<ClubBans>() |             modelBuilder.Entity<ClubBans>() | ||||||
|                 .HasOne(pt => pt.User) |                 .HasOne(pt => pt.User) | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ namespace NadekoBot.Services.Database.Repositories | |||||||
|         int GetNextDiscrim(string clubName); |         int GetNextDiscrim(string clubName); | ||||||
|         ClubInfo GetByName(string v, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); |         ClubInfo GetByName(string v, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); | ||||||
|         ClubInfo GetByOwner(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); |         ClubInfo GetByOwner(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); | ||||||
|  |         ClubInfo GetByOwnerOrAdmin(ulong userId); | ||||||
|         ClubInfo GetByMember(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); |         ClubInfo GetByMember(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); | ||||||
|         ClubInfo[] GetClubLeaderboardPage(int page); |         ClubInfo[] GetClubLeaderboardPage(int page); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,5 +6,7 @@ namespace NadekoBot.Services.Database.Repositories | |||||||
|     public interface IDiscordUserRepository : IRepository<DiscordUser> |     public interface IDiscordUserRepository : IRepository<DiscordUser> | ||||||
|     { |     { | ||||||
|         DiscordUser GetOrCreate(IUser original); |         DiscordUser GetOrCreate(IUser original); | ||||||
|  |         int GetUserGlobalRanking(ulong id); | ||||||
|  |         DiscordUser[] GetUsersXpLeaderboardFor(int page); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,10 +5,7 @@ namespace NadekoBot.Services.Database.Repositories | |||||||
|     public interface IXpRepository : IRepository<UserXpStats> |     public interface IXpRepository : IRepository<UserXpStats> | ||||||
|     { |     { | ||||||
|         UserXpStats GetOrCreateUser(ulong guildId, ulong userId); |         UserXpStats GetOrCreateUser(ulong guildId, ulong userId); | ||||||
|         int GetTotalUserXp(ulong userId); |  | ||||||
|         UserXpStats[] GetUsersFor(ulong guildId, int page); |  | ||||||
|         (ulong UserId, int TotalXp)[] GetUsersFor(int page); |  | ||||||
|         int GetUserGlobalRanking(ulong userId); |  | ||||||
|         int GetUserGuildRanking(ulong userId, ulong guildId); |         int GetUserGuildRanking(ulong userId, ulong guildId); | ||||||
|  |         UserXpStats[] GetUsersFor(ulong guildId, int page); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,6 +24,30 @@ namespace NadekoBot.Services.Database.Repositories.Impl | |||||||
|             return func(_set).FirstOrDefault(x => x.Owner.UserId == userId); |             return func(_set).FirstOrDefault(x => x.Owner.UserId == userId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public ClubInfo GetByOwnerOrAdmin(ulong userId) | ||||||
|  |         { | ||||||
|  |             return _set | ||||||
|  |                 .Include(x => x.Bans) | ||||||
|  |                     .ThenInclude(x => x.User) | ||||||
|  |                 .Include(x => x.Applicants) | ||||||
|  |                     .ThenInclude(x => x.User) | ||||||
|  |                 .Include(x => x.Owner) | ||||||
|  |                 .FirstOrDefault(x => x.Owner.UserId == userId) ?? | ||||||
|  |             _context.Set<DiscordUser>() | ||||||
|  |                 .Include(x => x.Club) | ||||||
|  |                     .ThenInclude(x => x.Users) | ||||||
|  |                 .Include(x => x.Club) | ||||||
|  |                     .ThenInclude(x => x.Bans) | ||||||
|  |                         .ThenInclude(x => x.User) | ||||||
|  |                 .Include(x => x.Club) | ||||||
|  |                     .ThenInclude(x => x.Applicants) | ||||||
|  |                         .ThenInclude(x => x.User) | ||||||
|  |                 .Include(x => x.Club) | ||||||
|  |                 .ThenInclude(x => x.Owner) | ||||||
|  |                 .FirstOrDefault(x => x.UserId == userId && x.IsClubAdmin) | ||||||
|  |                 ?.Club; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public ClubInfo GetByName(string name, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null) |         public ClubInfo GetByName(string name, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null) | ||||||
|         { |         { | ||||||
|             if (func == null) |             if (func == null) | ||||||
|   | |||||||
| @@ -37,5 +37,23 @@ namespace NadekoBot.Services.Database.Repositories.Impl | |||||||
|  |  | ||||||
|             return toReturn; |             return toReturn; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public int GetUserGlobalRanking(ulong id) | ||||||
|  |         { | ||||||
|  |             return _set.Count(x => x.TotalXp >  | ||||||
|  |                 _set.Where(y => y.UserId == id) | ||||||
|  |                     .DefaultIfEmpty() | ||||||
|  |                     .Sum(y => y.TotalXp)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public DiscordUser[] GetUsersXpLeaderboardFor(int page) | ||||||
|  |         { | ||||||
|  |             return _set | ||||||
|  |                 .OrderByDescending(x => x.TotalXp) | ||||||
|  |                 .Skip(page * 9) | ||||||
|  |                 .Take(9) | ||||||
|  |                 .AsEnumerable() | ||||||
|  |                 .ToArray(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,11 +29,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl | |||||||
|             return usr; |             return usr; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public int GetTotalUserXp(ulong userId) |  | ||||||
|         { |  | ||||||
|             return _set.Where(x => x.UserId == userId).Sum(x => x.Xp); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public UserXpStats[] GetUsersFor(ulong guildId, int page) |         public UserXpStats[] GetUsersFor(ulong guildId, int page) | ||||||
|         { |         { | ||||||
|             return _set.Where(x => x.GuildId == guildId) |             return _set.Where(x => x.GuildId == guildId) | ||||||
| @@ -43,15 +38,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl | |||||||
|                 .ToArray(); |                 .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) |         public int GetUserGuildRanking(ulong userId, ulong guildId) | ||||||
|         { |         { | ||||||
|             return _set |             return _set | ||||||
| @@ -62,16 +48,5 @@ namespace NadekoBot.Services.Database.Repositories.Impl | |||||||
|                     .DefaultIfEmpty() |                     .DefaultIfEmpty() | ||||||
|                     .Sum())) + 1; |                     .Sum())) + 1; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public (ulong UserId, int TotalXp)[] GetUsersFor(int page) |  | ||||||
|         { |  | ||||||
|             return _set.GroupBy(x => x.UserId) |  | ||||||
|                 .OrderByDescending(x => x.Sum(y => y.Xp)) |  | ||||||
|                 .Skip(page * 9) |  | ||||||
|                 .Take(9) |  | ||||||
|                 .AsEnumerable() |  | ||||||
|                 .Select(x => (x.Key, x.Sum(y => y.Xp))) |  | ||||||
|                 .ToArray(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1,25 +1,28 @@ | |||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.Data.Sqlite; | ||||||
|  | using Microsoft.EntityFrameworkCore; | ||||||
| using NadekoBot.Services.Database; | using NadekoBot.Services.Database; | ||||||
|  | using System; | ||||||
|  | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
|  |  | ||||||
| namespace NadekoBot.Services | namespace NadekoBot.Services | ||||||
| { | { | ||||||
|     public class DbService |     public class DbService | ||||||
|     { |     { | ||||||
|         private readonly DbContextOptions options; |         private readonly DbContextOptions<NadekoContext> options; | ||||||
|         private readonly DbContextOptions migrateOptions; |         private readonly DbContextOptions<NadekoContext> migrateOptions; | ||||||
|  |  | ||||||
|         private readonly string _connectionString; |  | ||||||
|  |  | ||||||
|         public DbService(IBotCredentials creds) |         public DbService(IBotCredentials creds) | ||||||
|         { |         { | ||||||
|             _connectionString = creds.Db.ConnectionString; |             var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString); | ||||||
|             var optionsBuilder = new DbContextOptionsBuilder(); |             builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); | ||||||
|             optionsBuilder.UseSqlite(creds.Db.ConnectionString); |              | ||||||
|  |             var optionsBuilder = new DbContextOptionsBuilder<NadekoContext>(); | ||||||
|  |             optionsBuilder.UseSqlite(builder.ToString()); | ||||||
|             options = optionsBuilder.Options; |             options = optionsBuilder.Options; | ||||||
|  |  | ||||||
|             optionsBuilder = new DbContextOptionsBuilder(); |             optionsBuilder = new DbContextOptionsBuilder<NadekoContext>(); | ||||||
|             optionsBuilder.UseSqlite(creds.Db.ConnectionString, x => x.SuppressForeignKeyEnforcement()); |             optionsBuilder.UseSqlite(builder.ToString(), x => x.SuppressForeignKeyEnforcement()); | ||||||
|             migrateOptions = optionsBuilder.Options; |             migrateOptions = optionsBuilder.Options; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								src/NadekoBot/Services/IDataCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/NadekoBot/Services/IDataCache.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | using StackExchange.Redis; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | namespace NadekoBot.Services | ||||||
|  | { | ||||||
|  |     public interface IDataCache | ||||||
|  |     { | ||||||
|  |         ConnectionMultiplexer Redis { get; } | ||||||
|  |         Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key); | ||||||
|  |         Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key); | ||||||
|  |         Task SetImageDataAsync(string key, byte[] data); | ||||||
|  |         Task SetAnimeDataAsync(string link, string data); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -90,18 +90,13 @@ namespace NadekoBot.Services.Impl | |||||||
|                 ulong.TryParse(data[nameof(ClientId)], out ulong clId); |                 ulong.TryParse(data[nameof(ClientId)], out ulong clId); | ||||||
|                 ClientId = clId; |                 ClientId = clId; | ||||||
|  |  | ||||||
|                 //var scId = data[nameof(SoundCloudClientId)]; |  | ||||||
|                 //SoundCloudClientId = scId; |  | ||||||
|                 //SoundCloudClientId = string.IsNullOrWhiteSpace(scId) |  | ||||||
|                 //    ?  |  | ||||||
|                 //    : scId; |  | ||||||
|                 CarbonKey = data[nameof(CarbonKey)]; |                 CarbonKey = data[nameof(CarbonKey)]; | ||||||
|                 var dbSection = data.GetSection("db"); |                 var dbSection = data.GetSection("db"); | ||||||
|                 Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"])  |                 Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"])  | ||||||
|                                 ? "sqlite"  |                                 ? "sqlite"  | ||||||
|                                 : dbSection["Type"],  |                                 : dbSection["Type"],  | ||||||
|                             string.IsNullOrWhiteSpace(dbSection["ConnectionString"])  |                             string.IsNullOrWhiteSpace(dbSection["ConnectionString"])  | ||||||
|                                 ? "Filename=./data/NadekoBot.db" |                                 ? "Data Source=data/NadekoBot.db" | ||||||
|                                 : dbSection["ConnectionString"]); |                                 : dbSection["ConnectionString"]); | ||||||
|             } |             } | ||||||
|             catch (Exception ex) |             catch (Exception ex) | ||||||
| @@ -125,7 +120,7 @@ namespace NadekoBot.Services.Impl | |||||||
|             public string SoundCloudClientId { get; set; } = ""; |             public string SoundCloudClientId { get; set; } = ""; | ||||||
|             public string CleverbotApiKey { get; } = ""; |             public string CleverbotApiKey { get; } = ""; | ||||||
|             public string CarbonKey { get; set; } = ""; |             public string CarbonKey { get; set; } = ""; | ||||||
|             public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db"); |             public DBConfig Db { get; set; } = new DBConfig("sqlite", "Data Source=data/NadekoBot.db"); | ||||||
|             public int TotalShards { get; set; } = 1; |             public int TotalShards { get; set; } = 1; | ||||||
|             public string PatreonAccessToken { get; set; } = ""; |             public string PatreonAccessToken { get; set; } = ""; | ||||||
|             public string PatreonCampaignId { get; set; } = "334038"; |             public string PatreonCampaignId { get; set; } = "334038"; | ||||||
|   | |||||||
| @@ -5,6 +5,9 @@ using System.Linq; | |||||||
| using Discord; | using Discord; | ||||||
| using NLog; | using NLog; | ||||||
| using NadekoBot.Services.Database.Models; | using NadekoBot.Services.Database.Models; | ||||||
|  | using NadekoBot.Common; | ||||||
|  | using Newtonsoft.Json; | ||||||
|  | using System.IO; | ||||||
|  |  | ||||||
| namespace NadekoBot.Services.Impl | namespace NadekoBot.Services.Impl | ||||||
| { | { | ||||||
| @@ -16,6 +19,14 @@ namespace NadekoBot.Services.Impl | |||||||
|         public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; } |         public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; } | ||||||
|         public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture; |         public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture; | ||||||
|  |  | ||||||
|  |         private static readonly Dictionary<string, CommandData> _commandData; | ||||||
|  |  | ||||||
|  |         static Localization() | ||||||
|  |         { | ||||||
|  |             _commandData = JsonConvert.DeserializeObject<Dictionary<string, CommandData>>( | ||||||
|  |                 File.ReadAllText("./data/command_strings.json")); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private Localization() { } |         private Localization() { } | ||||||
|         public Localization(IBotConfigProvider bcp, IEnumerable<GuildConfig> gcs, DbService db) |         public Localization(IBotConfigProvider bcp, IEnumerable<GuildConfig> gcs, DbService db) | ||||||
|         { |         { | ||||||
| @@ -117,10 +128,19 @@ namespace NadekoBot.Services.Impl | |||||||
|             return info ?? DefaultCultureInfo; |             return info ?? DefaultCultureInfo; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static string LoadCommandString(string key) |         public static CommandData LoadCommand(string key) | ||||||
|         { |         { | ||||||
|             string toReturn = Resources.CommandStrings.ResourceManager.GetString(key); |             _commandData.TryGetValue(key, out var toReturn); | ||||||
|             return string.IsNullOrWhiteSpace(toReturn) ? key : toReturn; |  | ||||||
|  |             if (toReturn == null) | ||||||
|  |                 return new CommandData | ||||||
|  |                 { | ||||||
|  |                     Cmd = key, | ||||||
|  |                     Desc = key, | ||||||
|  |                     Usage = key, | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |             return toReturn; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								src/NadekoBot/Services/Impl/RedisCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/NadekoBot/Services/Impl/RedisCache.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | using StackExchange.Redis; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | namespace NadekoBot.Services.Impl | ||||||
|  | { | ||||||
|  |     public class RedisCache : IDataCache | ||||||
|  |     { | ||||||
|  |         public ConnectionMultiplexer Redis { get; } | ||||||
|  |         private readonly IDatabase _db; | ||||||
|  |  | ||||||
|  |         public RedisCache() | ||||||
|  |         { | ||||||
|  |             Redis = ConnectionMultiplexer.Connect("127.0.0.1"); | ||||||
|  |             Redis.PreserveAsyncOrder = false; | ||||||
|  |             _db = Redis.GetDatabase(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public async Task<(bool Success, byte[] Data)> TryGetImageDataAsync(string key) | ||||||
|  |         { | ||||||
|  |             byte[] x = await _db.StringGetAsync("image_" + key); | ||||||
|  |             return (x != null, x); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Task SetImageDataAsync(string key, byte[] data) | ||||||
|  |         { | ||||||
|  |             return _db.StringSetAsync("image_" + key, data); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public async Task<(bool Success, string Data)> TryGetAnimeDataAsync(string key) | ||||||
|  |         { | ||||||
|  |             string x = await _db.StringGetAsync("anime_" + key); | ||||||
|  |             return (x != null, x); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Task SetAnimeDataAsync(string key, string data) | ||||||
|  |         { | ||||||
|  |             return _db.StringSetAsync("anime_" + key, data); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -20,7 +20,7 @@ namespace NadekoBot.Services.Impl | |||||||
|         private readonly IBotCredentials _creds; |         private readonly IBotCredentials _creds; | ||||||
|         private readonly DateTime _started; |         private readonly DateTime _started; | ||||||
|  |  | ||||||
|         public const string BotVersion = "1.8.4"; |         public const string BotVersion = "1.9.0"; | ||||||
|  |  | ||||||
|         public string Author => "Kwoth#2560"; |         public string Author => "Kwoth#2560"; | ||||||
|         public string Library => "Discord.Net"; |         public string Library => "Discord.Net"; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ namespace NadekoBot.Services | |||||||
|             var logConfig = new LoggingConfiguration(); |             var logConfig = new LoggingConfiguration(); | ||||||
|             var consoleTarget = new ColoredConsoleTarget() |             var consoleTarget = new ColoredConsoleTarget() | ||||||
|             { |             { | ||||||
|                 Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}" |                 Layout = @"${date:format=HH\:mm\:ss} ${logger:shortName=True} | ${message}" | ||||||
|             }; |             }; | ||||||
|             logConfig.AddTarget("Console", consoleTarget); |             logConfig.AddTarget("Console", consoleTarget); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ | |||||||
|   "customreactions_stats_not_found": "No stats for that trigger found, no action taken.", |   "customreactions_stats_not_found": "No stats for that trigger found, no action taken.", | ||||||
|   "customreactions_trigger": "Trigger", |   "customreactions_trigger": "Trigger", | ||||||
|   "customreactions_redacted_too_long": "Redecated because it's too long.", |   "customreactions_redacted_too_long": "Redecated because it's too long.", | ||||||
|   "nsfw_autohentai_stopped": "Autohentai stopped.", |  | ||||||
|   "nsfw_not_found": "No results found.", |   "nsfw_not_found": "No results found.", | ||||||
|   "nsfw_blacklisted_tag_list": "List of blacklisted tags:", |   "nsfw_blacklisted_tag_list": "List of blacklisted tags:", | ||||||
|   "nsfw_blacklisted_tag": "One or more tags you've used are blacklisted", |   "nsfw_blacklisted_tag": "One or more tags you've used are blacklisted", | ||||||
| @@ -868,5 +867,10 @@ | |||||||
|   "xp_club_icon_set": "New club icon set.", |   "xp_club_icon_set": "New club icon set.", | ||||||
|   "xp_club_bans_for": "Bans for {0} club", |   "xp_club_bans_for": "Bans for {0} club", | ||||||
|   "xp_club_apps_for": "Applicants for {0} club", |   "xp_club_apps_for": "Applicants for {0} club", | ||||||
|   "xp_club_leaderboard": "Club leaderboard - page {0}" |   "xp_club_leaderboard": "Club leaderboard - page {0}", | ||||||
|  |   "xp_club_admin_add": "{0} is now a club admin.", | ||||||
|  |   "xp_club_admin_remove": "{0} is no longer club admin.", | ||||||
|  |   "xp_club_admin_error": "Error. You are either not the owner of the club, or that user is not in your club.", | ||||||
|  |   "nsfw_started": "Started. Reposting every {0}s.", | ||||||
|  |   "nsfw_stopped": "Stopped reposting." | ||||||
| } | } | ||||||
| @@ -13,7 +13,7 @@ | |||||||
|   "CarbonKey": "", |   "CarbonKey": "", | ||||||
|   "Db": { |   "Db": { | ||||||
|     "Type": "sqlite", |     "Type": "sqlite", | ||||||
|     "ConnectionString": "Filename=./data/NadekoBot.db" |     "ConnectionString": "Data Source=data/NadekoBot.db" | ||||||
|   }, |   }, | ||||||
|   "TotalShards": 1, |   "TotalShards": 1, | ||||||
|   "PatreonAccessToken": "", |   "PatreonAccessToken": "", | ||||||
|   | |||||||
							
								
								
									
										2057
									
								
								src/NadekoBot/data/command_strings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2057
									
								
								src/NadekoBot/data/command_strings.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user