| @@ -1,7 +1,7 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio 15 | ||||
| VisualStudioVersion = 15.0.26430.12 | ||||
| VisualStudioVersion = 15.0.26730.3 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" | ||||
| EndProject | ||||
| @@ -33,4 +33,7 @@ Global | ||||
| 	GlobalSection(NestedProjects) = preSolution | ||||
| 		{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| { | ||||
|   "sdk": { "version": "1.0.1" } | ||||
|   "sdk": { "version": "2.0.0" } | ||||
| } | ||||
| @@ -2,12 +2,11 @@ | ||||
| using System.Runtime.CompilerServices; | ||||
| using Discord.Commands; | ||||
| using NadekoBot.Services.Impl; | ||||
|  | ||||
| namespace NadekoBot.Common.Attributes | ||||
| { | ||||
|     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 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 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 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; | ||||
|  | ||||
| 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<bool>("IsClubAdmin"); | ||||
|  | ||||
|                     b.Property<DateTime>("LastLevelUp") | ||||
|                         .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<int>("NotifyOnLevelUp"); | ||||
|  | ||||
|                     b.Property<int>("TotalXp"); | ||||
|  | ||||
|                     b.Property<ulong>("UserId"); | ||||
|  | ||||
|                     b.Property<string>("Username"); | ||||
| @@ -1362,7 +1366,7 @@ namespace NadekoBot.Migrations | ||||
|  | ||||
|                     b.Property<DateTime>("LastLevelUp") | ||||
|                         .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"); | ||||
|  | ||||
|   | ||||
| @@ -30,8 +30,9 @@ namespace NadekoBot.Modules.Administration | ||||
|             private readonly IImagesService _images; | ||||
|             private readonly MusicService _music; | ||||
|             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) | ||||
|             { | ||||
|                 _db = db; | ||||
| @@ -39,6 +40,7 @@ namespace NadekoBot.Modules.Administration | ||||
|                 _images = images; | ||||
|                 _music = music; | ||||
|                 _bc = bc; | ||||
|                 _bot = bot; | ||||
|             } | ||||
|  | ||||
|             [NadekoCommand, Usage, Description, Aliases] | ||||
| @@ -349,7 +351,7 @@ namespace NadekoBot.Modules.Administration | ||||
|             [OwnerOnly] | ||||
|             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); | ||||
|             } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ using NadekoBot.Modules.Music.Services; | ||||
| using NadekoBot.Services; | ||||
| using NadekoBot.Services.Database.Models; | ||||
| using NLog; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace NadekoBot.Modules.Administration.Services | ||||
| { | ||||
| @@ -16,6 +17,7 @@ namespace NadekoBot.Modules.Administration.Services | ||||
|         private readonly DiscordSocketClient _client; | ||||
|         private readonly MusicService _music; | ||||
|         private readonly Logger _log; | ||||
|         private readonly IDataCache _cache; | ||||
|         private readonly Replacer _rep; | ||||
|         private readonly DbService _db; | ||||
|         private readonly IBotConfigProvider _bcp; | ||||
| @@ -27,50 +29,60 @@ namespace NadekoBot.Modules.Administration.Services | ||||
|             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; | ||||
|             _bcp = bcp; | ||||
|             _music = music; | ||||
|             _db = db; | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             _rep = new ReplacementBuilder() | ||||
|                 .WithClient(client) | ||||
|                 .WithStats(client) | ||||
|                 .WithMusic(music) | ||||
|                 .Build(); | ||||
|             _cache = cache; | ||||
|  | ||||
|             _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;  | ||||
|                     if (!BotConfig.RotatingStatuses) | ||||
|                         return; | ||||
|                     if (state.Index >= BotConfig.RotatingStatusMessages.Count) | ||||
|                         state.Index = 0; | ||||
|                         var state = (TimerState)objState; | ||||
|                         if (!BotConfig.RotatingStatuses) | ||||
|                             return; | ||||
|                         if (state.Index >= BotConfig.RotatingStatusMessages.Count) | ||||
|                             state.Index = 0; | ||||
|  | ||||
|                     if (!BotConfig.RotatingStatusMessages.Any()) | ||||
|                         return; | ||||
|                     var status = BotConfig.RotatingStatusMessages[state.Index++].Status; | ||||
|                     if (string.IsNullOrWhiteSpace(status)) | ||||
|                         return; | ||||
|                         if (!BotConfig.RotatingStatusMessages.Any()) | ||||
|                             return; | ||||
|                         var status = BotConfig.RotatingStatusMessages[state.Index++].Status; | ||||
|                         if (string.IsNullOrWhiteSpace(status)) | ||||
|                             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) | ||||
|                     { | ||||
|                         _log.Warn(ex); | ||||
|                         _log.Warn("Rotating playing status errored.\n" + ex); | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     _log.Warn("Rotating playing status errored.\n" + ex); | ||||
|                 } | ||||
|             }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); | ||||
|                 }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -58,8 +58,7 @@ namespace NadekoBot.Modules.CustomReactions | ||||
|  | ||||
|             if (channel == null) | ||||
|             { | ||||
|                 Array.Resize(ref _service.GlobalReactions, _service.GlobalReactions.Length + 1); | ||||
|                 _service.GlobalReactions[_service.GlobalReactions.Length - 1] = cr; | ||||
|                 await _service.AddGcr(cr).ConfigureAwait(false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
| @@ -237,8 +236,7 @@ namespace NadekoBot.Modules.CustomReactions | ||||
|                     if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null) | ||||
|                     { | ||||
|                         uow.CustomReactions.Remove(toDelete); | ||||
|                         //todo 91 i can dramatically improve performance of this, if Ids are ordered. | ||||
|                         _service.GlobalReactions = _service.GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray(); | ||||
|                         await _service.DelGcr(toDelete.Id); | ||||
|                         success = true; | ||||
|                     } | ||||
|                     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.Services; | ||||
| using NadekoBot.Services.Impl; | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| namespace NadekoBot.Modules.CustomReactions.Services | ||||
| { | ||||
| @@ -32,9 +33,11 @@ namespace NadekoBot.Modules.CustomReactions.Services | ||||
|         private readonly CommandHandler _cmd; | ||||
|         private readonly IBotConfigProvider _bc; | ||||
|         private readonly NadekoStrings _strings; | ||||
|         private readonly IDataCache _cache; | ||||
|  | ||||
|         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(); | ||||
|             _db = db; | ||||
| @@ -43,12 +46,38 @@ namespace NadekoBot.Modules.CustomReactions.Services | ||||
|             _cmd = cmd; | ||||
|             _bc = bc; | ||||
|             _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(); | ||||
|  | ||||
|             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(); | ||||
|         } | ||||
|  | ||||
|         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 CustomReaction TryGetCustomReaction(IUserMessage umsg) | ||||
|   | ||||
| @@ -331,7 +331,7 @@ namespace NadekoBot.Modules.Gambling | ||||
|                 var embed = new EmbedBuilder().WithOkColor(); | ||||
|  | ||||
|                 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("type")).WithValue(entry.Type.ToString()).WithIsInline(true)); | ||||
|                 else if (entry.Type == ShopEntryType.List) | ||||
| @@ -349,7 +349,7 @@ namespace NadekoBot.Modules.Gambling | ||||
|             { | ||||
|                 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) | ||||
|                 { | ||||
|   | ||||
| @@ -38,7 +38,7 @@ namespace NadekoBot.Modules.Games | ||||
|         } | ||||
|  | ||||
|         [NadekoCommand, Usage, Description, Aliases] | ||||
|         public async Task _8Ball([Remainder] string question = null) | ||||
|         public async Task EightBall([Remainder] string question = null) | ||||
|         { | ||||
|             if (string.IsNullOrWhiteSpace(question)) | ||||
|                 return; | ||||
|   | ||||
| @@ -3,7 +3,6 @@ using System; | ||||
| using System.Diagnostics; | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace NadekoBot.Modules.Music.Common | ||||
| { | ||||
| @@ -18,27 +17,16 @@ namespace NadekoBot.Modules.Music.Common | ||||
|  | ||||
|         public string SongUri { get; private set; } | ||||
|  | ||||
|         //private volatile bool restart = false; | ||||
|  | ||||
|         public SongBuffer(string songUri, string skipTo, bool isLocal) | ||||
|         { | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             //_log.Warn(songUri); | ||||
|             this.SongUri = songUri; | ||||
|             this._isLocal = isLocal; | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 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; | ||||
|  | ||||
|             } | ||||
|             catch (System.ComponentModel.Win32Exception) | ||||
|             { | ||||
| @@ -68,113 +56,14 @@ Check the guides for your platform on how to setup ffmpeg correctly: | ||||
|                 Arguments = args, | ||||
|                 UseShellExecute = false, | ||||
|                 RedirectStandardOutput = true, | ||||
|                 RedirectStandardError = true, | ||||
|                 RedirectStandardError = false, | ||||
|                 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 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) | ||||
|         { | ||||
|             lock (locker) | ||||
| @@ -203,215 +92,4 @@ Check the guides for your platform on how to setup ffmpeg correctly: | ||||
|             this.p.Dispose(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| //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 | ||||
| { | ||||
|     // thanks to halitalf for adding autoboob and autobutt features :D | ||||
|     public class NSFW : NadekoTopLevelModule<SearchesService> | ||||
|     { | ||||
|         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 async Task InternalHentai(IMessageChannel channel, string tag, bool noError) | ||||
| @@ -49,10 +53,34 @@ namespace NadekoBot.Modules.NSFW | ||||
|                 .WithDescription($"[{GetText("tag")}: {tag}]({img})")) | ||||
|                 .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 | ||||
|         [NadekoCommand, Usage, Description, Aliases] | ||||
|         [RequireUserPermission(ChannelPermission.ManageMessages)] | ||||
| @@ -65,7 +93,7 @@ namespace NadekoBot.Modules.NSFW | ||||
|                 if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return; | ||||
|  | ||||
|                 t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer | ||||
|                 await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false); | ||||
|                 await ReplyConfirmLocalized("stopped").ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
| @@ -99,8 +127,90 @@ namespace NadekoBot.Modules.NSFW | ||||
|                 interval, | ||||
|                 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 | ||||
|  | ||||
|         [NadekoCommand, Usage, Description, Aliases] | ||||
|         public Task Hentai([Remainder] string tag = null) => | ||||
|             InternalHentai(Context.Channel, tag, false); | ||||
|  | ||||
|         [NadekoCommand, Usage, Description, Aliases] | ||||
|         public async Task HentaiBomb([Remainder] string tag = null) | ||||
|         { | ||||
| @@ -199,7 +309,7 @@ namespace NadekoBot.Modules.NSFW | ||||
|                 tag = tag.Trim().ToLowerInvariant(); | ||||
|                 var added = _service.ToggleBlacklistedTag(Context.Guild.Id, tag); | ||||
|  | ||||
|                 if(added) | ||||
|                 if (added) | ||||
|                     await ReplyConfirmLocalized("blacklisted_tag_add", tag).ConfigureAwait(false); | ||||
|                 else | ||||
|                     await ReplyConfirmLocalized("blacklisted_tag_remove", tag).ConfigureAwait(false); | ||||
|   | ||||
| @@ -135,11 +135,11 @@ namespace NadekoBot.Modules.Permissions.Services | ||||
|                 { | ||||
|                     var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" }; | ||||
|                     uow._context.Database.ExecuteSqlCommand( | ||||
|     $@"UPDATE {nameof(Permissionv2)} | ||||
|     @"UPDATE Permissionv2 | ||||
| SET secondaryTargetName=trim(substr(secondaryTargetName, 3)) | ||||
| WHERE secondaryTargetName LIKE '!!%' OR secondaryTargetName LIKE '!m%'; | ||||
|  | ||||
| UPDATE {nameof(Permissionv2)} | ||||
| UPDATE Permissionv2 | ||||
| SET secondaryTargetName=substr(secondaryTargetName, 2) | ||||
| WHERE secondaryTargetName LIKE '.%' OR | ||||
| secondaryTargetName LIKE '~%' OR | ||||
|   | ||||
| @@ -76,11 +76,13 @@ namespace NadekoBot.Modules.Searches.Common | ||||
|                     if (images.Length == 0) | ||||
|                         return null; | ||||
|                     var toReturn = images[_rng.Next(images.Length)]; | ||||
| #if !GLOBAL_NADEKO | ||||
|                     foreach (var dledImg in images) | ||||
|                     { | ||||
|                         if(dledImg != toReturn) | ||||
|                             _cache.Add(dledImg); | ||||
|                     } | ||||
| #endif | ||||
|                     return toReturn; | ||||
|                 }                     | ||||
|             } | ||||
|   | ||||
| @@ -11,10 +11,14 @@ namespace NadekoBot.Modules.Searches.Services | ||||
|     public class AnimeSearchService : INService | ||||
|     { | ||||
|         private readonly Logger _log; | ||||
|         private readonly IDataCache _cache; | ||||
|         private readonly HttpClient _http; | ||||
|  | ||||
|         public AnimeSearchService() | ||||
|         public AnimeSearchService(IDataCache cache) | ||||
|         { | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             _cache = cache; | ||||
|             _http = new HttpClient(); | ||||
|         } | ||||
|  | ||||
|         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("/", " ")); | ||||
|                 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); | ||||
|                     return JsonConvert.DeserializeObject<AnimeResult>(res); | ||||
|                     data = await _http.GetStringAsync(link).ConfigureAwait(false); | ||||
|                     await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|                 return JsonConvert.DeserializeObject<AnimeResult>(data); | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
| @@ -44,12 +53,17 @@ namespace NadekoBot.Modules.Searches.Services | ||||
|             try | ||||
|             { | ||||
|  | ||||
|                 var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); | ||||
|                 using (var http = new HttpClient()) | ||||
|                  var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); | ||||
|                 link = link.ToLowerInvariant(); | ||||
|                 var (ok, data) = await _cache.TryGetAnimeDataAsync(link).ConfigureAwait(false); | ||||
|                 if (!ok) | ||||
|                 { | ||||
|                     var res = await http.GetStringAsync(link).ConfigureAwait(false); | ||||
|                     return JsonConvert.DeserializeObject<MangaResult>(res); | ||||
|                     data = await _http.GetStringAsync(link).ConfigureAwait(false); | ||||
|                     await _cache.SetAnimeDataAsync(link, data).ConfigureAwait(false); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|                 return JsonConvert.DeserializeObject<MangaResult>(data); | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|   | ||||
| @@ -26,6 +26,27 @@ namespace NadekoBot.Modules.Xp | ||||
|                 _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] | ||||
|             public async Task ClubCreate([Remainder]string clubName) | ||||
|             { | ||||
| @@ -97,7 +118,14 @@ namespace NadekoBot.Modules.Xp | ||||
|                         .AddField("Level Req.", club.MinimumLevelReq.ToString(), true) | ||||
|                         .AddField("Members", string.Join("\n", club.Users | ||||
|                             .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)) | ||||
|                         return embed.WithThumbnailUrl(club.ImageUrl); | ||||
| @@ -112,7 +140,7 @@ namespace NadekoBot.Modules.Xp | ||||
|                 if (--page < 0) | ||||
|                     return Task.CompletedTask; | ||||
|  | ||||
|                 var club = _service.GetBansAndApplications(Context.User.Id); | ||||
|                 var club = _service.GetClubWithBansAndApplications(Context.User.Id); | ||||
|                 if (club == null) | ||||
|                     return ReplyErrorLocalized("club_not_exists"); | ||||
|  | ||||
| @@ -131,7 +159,8 @@ namespace NadekoBot.Modules.Xp | ||||
|  | ||||
|                         return new EmbedBuilder() | ||||
|                             .WithTitle(GetText("club_bans_for", club.ToString())) | ||||
|                             .WithDescription(toShow); | ||||
|                             .WithDescription(toShow) | ||||
|                             .WithOkColor(); | ||||
|  | ||||
|                     }, bans.Length / 10); | ||||
|             } | ||||
| @@ -143,11 +172,11 @@ namespace NadekoBot.Modules.Xp | ||||
|                 if (--page < 0) | ||||
|                     return Task.CompletedTask; | ||||
|  | ||||
|                 var club = _service.GetBansAndApplications(Context.User.Id); | ||||
|                 var club = _service.GetClubWithBansAndApplications(Context.User.Id); | ||||
|                 if (club == null) | ||||
|                     return ReplyErrorLocalized("club_not_exists"); | ||||
|  | ||||
|                 var bans = club | ||||
|                 var apps = club | ||||
|                     .Applicants | ||||
|                     .Select(x => x.User) | ||||
|                     .ToArray(); | ||||
| @@ -155,16 +184,17 @@ namespace NadekoBot.Modules.Xp | ||||
|                 return Context.Channel.SendPaginatedConfirmAsync(_client, page, | ||||
|                     curPage => | ||||
|                     { | ||||
|                         var toShow = string.Join("\n", bans | ||||
|                         var toShow = string.Join("\n", apps | ||||
|                             .Skip(page * 10) | ||||
|                             .Take(10) | ||||
|                             .Select(x => x.ToString())); | ||||
|  | ||||
|                         return new EmbedBuilder() | ||||
|                             .WithTitle(GetText("club_apps_for", club.ToString())) | ||||
|                             .WithDescription(toShow); | ||||
|                             .WithDescription(toShow) | ||||
|                             .WithOkColor(); | ||||
|  | ||||
|                     }, bans.Length / 10); | ||||
|                     }, apps.Length / 10); | ||||
|             } | ||||
|  | ||||
|             [NadekoCommand, Usage, Description, Aliases] | ||||
| @@ -282,7 +312,7 @@ namespace NadekoBot.Modules.Xp | ||||
|                 } | ||||
|                 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); | ||||
|                 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) | ||||
|                 { | ||||
|                     du.IsClubAdmin = true; | ||||
|                     du.Club = new ClubInfo() | ||||
|                     { | ||||
|                         Name = clubName, | ||||
| @@ -52,6 +53,27 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|             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) | ||||
|         { | ||||
|             using (var uow = _db.UnitOfWork) | ||||
| @@ -107,7 +129,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|                 uow._context.SaveChanges(); | ||||
|  | ||||
|                 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.Applicants.Any(x => x.UserId == du.Id)) | ||||
|                 { | ||||
| @@ -134,11 +156,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|             discordUser = null; | ||||
|             using (var uow = _db.UnitOfWork) | ||||
|             { | ||||
|                 var club = uow.Clubs.GetByOwner(clubOwnerUserId, | ||||
|                     set => set.Include(x => x.Applicants) | ||||
|                         .ThenInclude(x => x.Club) | ||||
|                         .Include(x => x.Applicants) | ||||
|                         .ThenInclude(x => x.User)); | ||||
|                 var club = uow.Clubs.GetByOwnerOrAdmin(clubOwnerUserId); | ||||
|                 if (club == null) | ||||
|                     return false; | ||||
|  | ||||
| @@ -147,6 +165,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|                     return false; | ||||
|  | ||||
|                 applicant.User.Club = club; | ||||
|                 applicant.User.IsClubAdmin = false; | ||||
|                 club.Applicants.Remove(applicant); | ||||
|  | ||||
|                 //remove that user's all other applications | ||||
| @@ -159,15 +178,11 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         public ClubInfo GetBansAndApplications(ulong ownerUserId) | ||||
|         public ClubInfo GetClubWithBansAndApplications(ulong ownerUserId) | ||||
|         { | ||||
|             using (var uow = _db.UnitOfWork) | ||||
|             { | ||||
|                 return uow.Clubs.GetByOwner(ownerUserId, | ||||
|                     x => x.Include(y => y.Bans) | ||||
|                           .ThenInclude(y => y.User) | ||||
|                           .Include(y => y.Applicants) | ||||
|                           .ThenInclude(y => y.User)); | ||||
|                 return uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -180,6 +195,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|                     return false; | ||||
|  | ||||
|                 du.Club = null; | ||||
|                 du.IsClubAdmin = false; | ||||
|                 uow.Complete(); | ||||
|             } | ||||
|             return true; | ||||
| @@ -221,9 +237,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|         { | ||||
|             using (var uow = _db.UnitOfWork) | ||||
|             { | ||||
|                 club = uow.Clubs.GetByOwner(ownerUserId, | ||||
|                     set => set.Include(x => x.Applicants) | ||||
|                         .ThenInclude(x => x.User)); | ||||
|                 club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||
|                 if (club == null) | ||||
|                     return false; | ||||
|  | ||||
| @@ -256,9 +270,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|         { | ||||
|             using (var uow = _db.UnitOfWork) | ||||
|             { | ||||
|                 club = uow.Clubs.GetByOwner(ownerUserId, | ||||
|                     set => set.Include(x => x.Bans) | ||||
|                         .ThenInclude(x => x.User)); | ||||
|                 club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||
|                 if (club == null) | ||||
|                     return false; | ||||
|  | ||||
| @@ -277,7 +289,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|         { | ||||
|             using (var uow = _db.UnitOfWork) | ||||
|             { | ||||
|                 club = uow.Clubs.GetByOwner(ownerUserId); | ||||
|                 club = uow.Clubs.GetByOwnerOrAdmin(ownerUserId); | ||||
|                 if (club == null) | ||||
|                     return false; | ||||
|  | ||||
|   | ||||
| @@ -36,6 +36,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|         private readonly IImagesService _images; | ||||
|         private readonly Logger _log; | ||||
|         private readonly NadekoStrings _strings; | ||||
|         private readonly IDataCache _cache; | ||||
|         private readonly FontCollection _fonts = new FontCollection(); | ||||
|         public const int XP_REQUIRED_LVL_1 = 36; | ||||
|  | ||||
| @@ -54,9 +55,6 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|         private readonly ConcurrentQueue<UserCacheItem> _addMessageXp  | ||||
|             = new ConcurrentQueue<UserCacheItem>(); | ||||
|  | ||||
|         private readonly ConcurrentDictionary<string, byte[]> _imageStreams  | ||||
|             = new ConcurrentDictionary<string, byte[]>(); | ||||
|  | ||||
|         private readonly Timer updateXpTimer; | ||||
|         private readonly HttpClient http = new HttpClient(); | ||||
|         private FontFamily _usernameFontFamily; | ||||
| @@ -69,7 +67,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|  | ||||
|         public XpService(CommandHandler cmd, IBotConfigProvider bc, | ||||
|             IEnumerable<GuildConfig> allGuildConfigs, IImagesService images, | ||||
|             DbService db, NadekoStrings strings) | ||||
|             DbService db, NadekoStrings strings, IDataCache cache) | ||||
|         { | ||||
|             _db = db; | ||||
|             _cmd = cmd; | ||||
| @@ -77,6 +75,7 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|             _images = images; | ||||
|             _log = LogManager.GetCurrentClassLogger(); | ||||
|             _strings = strings; | ||||
|             _cache = cache; | ||||
|  | ||||
|             //load settings | ||||
|             allGuildConfigs = allGuildConfigs.Where(x => x.XpSettings != null); | ||||
| @@ -145,12 +144,13 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|  | ||||
|                             du.LastXpGain = DateTime.UtcNow; | ||||
|  | ||||
|                             var globalXp = uow.Xp.GetTotalUserXp(item.Key.User.Id); | ||||
|                             var globalXp = du.TotalXp; | ||||
|                             var oldGlobalLevelData = new LevelStats(globalXp); | ||||
|                             var newGlobalLevelData = new LevelStats(globalXp + xp); | ||||
|  | ||||
|                             var oldGuildLevelData = new LevelStats(usr.Xp + usr.AwardedXp); | ||||
|                             usr.Xp += xp; | ||||
|                             du.TotalXp += xp; | ||||
|                             if (du.Club != null) | ||||
|                                 du.Club.Xp += xp; | ||||
|                             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) | ||||
|             { | ||||
|                 return uow.Xp.GetUsersFor(page); | ||||
|                 return uow.DiscordUsers.GetUsersXpLeaderboardFor(page); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -403,17 +403,6 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|             return _rewardedUsers.Add(userId); | ||||
|         } | ||||
|  | ||||
|         public LevelStats GetGlobalUserStats(ulong userId) | ||||
|         { | ||||
|             int totalXp; | ||||
|             using (var uow = _db.UnitOfWork) | ||||
|             { | ||||
|                 totalXp = uow.Xp.GetTotalUserXp(userId); | ||||
|             } | ||||
|  | ||||
|             return new LevelStats(totalXp); | ||||
|         } | ||||
|  | ||||
|         public FullUserStats GetUserStats(IGuildUser user) | ||||
|         { | ||||
|             DiscordUser du; | ||||
| @@ -425,8 +414,8 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|             { | ||||
|                 du = uow.DiscordUsers.GetOrCreate(user); | ||||
|                 stats = uow.Xp.GetOrCreateUser(user.GuildId, user.Id); | ||||
|                 totalXp = uow.Xp.GetTotalUserXp(user.Id); | ||||
|                 globalRank = uow.Xp.GetUserGlobalRanking(user.Id); | ||||
|                 totalXp = du.TotalXp; | ||||
|                 globalRank = uow.DiscordUsers.GetUserGlobalRanking(user.Id); | ||||
|                 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)); | ||||
|         } | ||||
| @@ -566,170 +555,166 @@ namespace NadekoBot.Modules.Xp.Services | ||||
|             _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()); | ||||
|  | ||||
|             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) | ||||
|             using (var img = Image.Load(_images.XpCard.ToArray())) | ||||
|             { | ||||
|                 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, | ||||
|                 new PointF(148, 170)); | ||||
|                 img.DrawText("@" + username, usernameFont, Rgba32.White, | ||||
|                     new PointF(130, 5)); | ||||
|  | ||||
|             img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White, | ||||
|                 new PointF(148, 317)); | ||||
|                 // level | ||||
|  | ||||
|             //time on this level | ||||
|                 img.DrawText(stats.Global.Level.ToString(), _levelFont, Rgba32.White, | ||||
|                     new PointF(47, 137)); | ||||
|  | ||||
|             string GetTimeSpent(DateTime time) | ||||
|             { | ||||
|                 var offset = DateTime.UtcNow - time; | ||||
|                 return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m"; | ||||
|             } | ||||
|                 img.DrawText(stats.Guild.Level.ToString(), _levelFont, Rgba32.White, | ||||
|                     new PointF(47, 285)); | ||||
|  | ||||
|             img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White, | ||||
|                 new PointF(50, 197)); | ||||
|                 //club name | ||||
|  | ||||
|             img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White, | ||||
|                 new PointF(50, 344)); | ||||
|                 var clubName = stats.User.Club?.ToString() ?? "-"; | ||||
|  | ||||
|             //avatar | ||||
|                 var clubFont = _clubFontFamily | ||||
|                     .CreateFont(clubName.Length <= 8 | ||||
|                         ? 35 | ||||
|                         : 35 - (clubName.Length / 2)); | ||||
|  | ||||
|             if (stats.User.AvatarId != null) | ||||
|             { | ||||
|                 try | ||||
|                 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 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; | ||||
|                     if (!_imageStreams.TryGetValue(avatarUrl, out s)) | ||||
|                 //ranking | ||||
|  | ||||
|                 img.DrawText(stats.GlobalRanking.ToString(), _rankFont, Rgba32.White, | ||||
|                     new PointF(148, 170)); | ||||
|  | ||||
|                 img.DrawText(stats.GuildRanking.ToString(), _rankFont, Rgba32.White, | ||||
|                     new PointF(148, 317)); | ||||
|  | ||||
|                 //time on this level | ||||
|  | ||||
|                 string GetTimeSpent(DateTime time) | ||||
|                 { | ||||
|                     var offset = DateTime.UtcNow - time; | ||||
|                     return $"{offset.Days}d{offset.Hours}h{offset.Minutes}m"; | ||||
|                 } | ||||
|  | ||||
|                 img.DrawText(GetTimeSpent(stats.User.LastLevelUp), _timeFont, Rgba32.White, | ||||
|                     new PointF(50, 197)); | ||||
|  | ||||
|                 img.DrawText(GetTimeSpent(stats.FullGuildStats.LastLevelUp), _timeFont, Rgba32.White, | ||||
|                     new PointF(50, 344)); | ||||
|  | ||||
|                 //avatar | ||||
|  | ||||
|                 if (stats.User.AvatarId != null) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         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); | ||||
|                             tempDraw = tempDraw.Resize(69, 70); | ||||
|                             ApplyRoundedCorners(tempDraw, 35); | ||||
|                             s = tempDraw.ToStream().ToArray(); | ||||
|                             using (var temp = await http.GetStreamAsync(avatarUrl)) | ||||
|                             using (var tempDraw = Image.Load(temp).Resize(69, 70)) | ||||
|                             { | ||||
|                                 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); | ||||
|  | ||||
|  | ||||
|                     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)) | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         using (var temp = await http.GetStreamAsync(imgUrl)) | ||||
|                         { | ||||
|                             var tempDraw = Image.Load(temp); | ||||
|                             tempDraw = tempDraw.Resize(45, 45); | ||||
|                             ApplyRoundedCorners(tempDraw, 22.5f); | ||||
|                             s = tempDraw.ToStream().ToArray(); | ||||
|                         } | ||||
|  | ||||
|                         _imageStreams.AddOrUpdate(imgUrl, s, (k, v) => s); | ||||
|                         _log.Warn(ex); | ||||
|                     } | ||||
|                     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.Commands; | ||||
| using Discord.WebSocket; | ||||
| using NadekoBot.Common; | ||||
| using NadekoBot.Common.Attributes; | ||||
| using NadekoBot.Extensions; | ||||
| using NadekoBot.Modules.Xp.Common; | ||||
| using NadekoBot.Modules.Xp.Services; | ||||
| using NadekoBot.Services; | ||||
| using NadekoBot.Services.Database.Models; | ||||
| using System.Diagnostics; | ||||
| using System.Linq; | ||||
| @@ -15,23 +17,55 @@ namespace NadekoBot.Modules.Xp | ||||
|     public partial class Xp : NadekoTopLevelModule<XpService> | ||||
|     { | ||||
|         private readonly DiscordSocketClient _client; | ||||
|         private readonly DbService _db; | ||||
|  | ||||
|         public Xp(DiscordSocketClient client) | ||||
|         public Xp(DiscordSocketClient client,DbService db) | ||||
|         { | ||||
|             _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] | ||||
|         [RequireContext(ContextType.Guild)] | ||||
|         //[Ratelimit(30)] | ||||
|         public async Task Experience([Remainder]IUser user = null) | ||||
|         { | ||||
|             user = user ?? Context.User; | ||||
|  | ||||
|             var sw = Stopwatch.StartNew(); | ||||
|             await Context.Channel.TriggerTypingAsync(); | ||||
|             var img = await _service.GenerateImageAsync((IGuildUser)user); | ||||
|              | ||||
|             await Context.Channel.SendFileAsync(img.ToStream(), $"{user.Id}_xp.png") | ||||
|             sw.Stop(); | ||||
|             _log.Info("Generating finished in {0:F2}s", sw.Elapsed.TotalSeconds); | ||||
|             sw.Restart(); | ||||
|             await Context.Channel.SendFileAsync(img, $"{user.Id}_xp.png") | ||||
|                 .ConfigureAwait(false); | ||||
|             sw.Stop(); | ||||
|             _log.Info("Sending finished in {0:F2}s", sw.Elapsed.TotalSeconds); | ||||
|         } | ||||
|  | ||||
|         [NadekoCommand, Usage, Description, Aliases] | ||||
| @@ -40,7 +74,7 @@ namespace NadekoBot.Modules.Xp | ||||
|         { | ||||
|             page--; | ||||
|  | ||||
|             if (page < 0) | ||||
|             if (page < 0 || page > 100) | ||||
|                 return Task.CompletedTask; | ||||
|  | ||||
|             var roles = _service.GetRoleRewards(Context.Guild.Id) | ||||
| @@ -169,7 +203,7 @@ namespace NadekoBot.Modules.Xp | ||||
|         [RequireContext(ContextType.Guild)] | ||||
|         public Task XpLeaderboard(int page = 1) | ||||
|         { | ||||
|             if (--page < 0) | ||||
|             if (--page < 0 || page > 100) | ||||
|                 return Task.CompletedTask; | ||||
|  | ||||
|             return Context.Channel.SendPaginatedConfirmAsync(_client, page, async (curPage) => | ||||
| @@ -210,32 +244,28 @@ namespace NadekoBot.Modules.Xp | ||||
|         [RequireContext(ContextType.Guild)] | ||||
|         public async Task XpGlobalLeaderboard(int page = 1) | ||||
|         { | ||||
|             if (--page < 0) | ||||
|             if (--page < 0 || page > 100) | ||||
|                 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); | ||||
|  | ||||
|                 var embed = new EmbedBuilder() | ||||
|                     .WithTitle(GetText("global_leaderboard")) | ||||
|                     .WithOkColor(); | ||||
|  | ||||
|                 if (!users.Any()) | ||||
|                     return embed.WithDescription("-"); | ||||
|                 else | ||||
|                 for (int i = 0; i < users.Length; i++) | ||||
|                 { | ||||
|                     for (int i = 0; i < users.Length; i++) | ||||
|                     { | ||||
|                         var user = await Context.Guild.GetUserAsync(users[i].UserId).ConfigureAwait(false); | ||||
|                         embed.AddField( | ||||
|                             $"#{(i + 1 + curPage * 9)} {(user?.ToString() ?? users[i].UserId.ToString())}",  | ||||
|                             $"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp"); | ||||
|                     } | ||||
|  | ||||
|                     return embed; | ||||
|                     var user = users[i]; | ||||
|                     embed.AddField( | ||||
|                         $"#{(i + 1 + page * 9)} {(user.ToString())}",  | ||||
|                         $"{GetText("level_x", LevelStats.FromXp(users[i].TotalXp).Level)} - {users[i].TotalXp}xp"); | ||||
|                 } | ||||
|             }, addPaginatedFooter: false); | ||||
|             } | ||||
|  | ||||
|             await Context.Channel.EmbedAsync(embed); | ||||
|         } | ||||
|  | ||||
|         [NadekoCommand, Usage, Description, Aliases] | ||||
|   | ||||
| @@ -20,6 +20,8 @@ using NadekoBot.Common.ShardCom; | ||||
| using NadekoBot.Common.TypeReaders; | ||||
| using NadekoBot.Common.TypeReaders.Models; | ||||
| using NadekoBot.Services.Database; | ||||
| using StackExchange.Redis; | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| namespace NadekoBot | ||||
| { | ||||
| @@ -139,6 +141,7 @@ namespace NadekoBot | ||||
|                     .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this | ||||
|                     .AddManual<NadekoBot>(this) | ||||
|                     .AddManual<IUnitOfWork>(uow) | ||||
|                     .AddManual<IDataCache>(new RedisCache()) | ||||
|                     .LoadFrom(Assembly.GetEntryAssembly()) | ||||
|                     .Build(); | ||||
|  | ||||
| @@ -239,13 +242,6 @@ namespace NadekoBot | ||||
| #if GLOBAL_NADEKO | ||||
|             isPublicNadeko = true; | ||||
| #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 | ||||
|  | ||||
|             if(isPublicNadeko) | ||||
| @@ -256,6 +252,7 @@ namespace NadekoBot | ||||
|                     .ForEach(x => CommandService.RemoveModuleAsync(x)); | ||||
|  | ||||
|             Ready.TrySetResult(true); | ||||
|             HandleStatusChanges(); | ||||
|             _log.Info($"Shard {Client.ShardId} ready."); | ||||
|             //_log.Info(await stats.Print().ConfigureAwait(false)); | ||||
|         } | ||||
| @@ -318,5 +315,51 @@ namespace NadekoBot | ||||
|                 } | ||||
|             })).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"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <Description>General purpose Discord bot written in C#.</Description> | ||||
|     <Copyright>Kwoth</Copyright> | ||||
|     <Authors>Kwoth</Authors> | ||||
|     <PublisherName>Kwoth</PublisherName> | ||||
|     <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> | ||||
|     <TargetFramework>netcoreapp2.0</TargetFramework> | ||||
|     <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion> | ||||
|     <OutputType>exe</OutputType> | ||||
|     <AssetTargetFallback>$(AssetTargetFallback);dnxcore50;portable-net45+win8+wpa81</AssetTargetFallback> | ||||
|     <ApplicationIcon>nadeko_icon.ico</ApplicationIcon> | ||||
|     <RuntimeIdentifiers>win7-x64<!--;ubuntu.14.04-x64;osx.10.10-x64 --></RuntimeIdentifiers> | ||||
|     <Configurations>Debug;Release;global_nadeko</Configurations> | ||||
|     <LangVersion>latest</LangVersion> | ||||
|     </PropertyGroup> | ||||
|  | ||||
|   <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=" '$(Version)' == '' ">$(VersionPrefix)</Version> | ||||
|   </PropertyGroup> | ||||
| @@ -59,27 +47,24 @@ | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="AngleSharp" Version="0.9.9" /> | ||||
|     <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.2" /> | ||||
|     <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" /> | ||||
|     <PackageReference Include="Google.Apis.YouTube.v3" Version="1.20.0.701" /> | ||||
|     <PackageReference Include="Google.Apis.Customsearch.v1" Version="1.20.0.466" /> | ||||
|     <PackageReference Include="CoreCLR-NCalc" Version="2.1.3" /> | ||||
|     <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.29.1.138" /> | ||||
|     <PackageReference Include="Google.Apis.YouTube.v3" Version="1.29.1.976" /> | ||||
|     <PackageReference Include="Google.Apis.Customsearch.v1" Version="1.29.1.896" /> | ||||
|     <PackageReference Include="ImageSharp" Version="1.0.0-alpha9-00194" /> | ||||
|     <PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha9-00189" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> | ||||
|     <PackageReference Include="NLog" Version="5.0.0-beta03" /> | ||||
|     <PackageReference Include="NYoutubeDL" Version="0.4.4" /> | ||||
|     <PackageReference Include="System.ValueTuple" Version="4.4.0-preview1-25305-02" /> | ||||
|     <PackageReference Include="System.Xml.XPath" Version="4.3.0" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> | ||||
|     <PackageReference Include="NLog" Version="5.0.0-beta10" /> | ||||
|     <PackageReference Include="StackExchange.Redis" Version="1.2.6" /> | ||||
|     <PackageReference Include="System.ValueTuple" Version="4.4.0" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <PropertyGroup Condition=" '$(Configuration)' == 'GlobalNadeko' "> | ||||
| @@ -87,22 +72,12 @@ | ||||
|     <NoWarn>$(NoWarn);CS1573;CS1591</NoWarn> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | ||||
|     <LangVersion>latest</LangVersion> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | ||||
|     <LangVersion>latest</LangVersion> | ||||
|     <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> | ||||
|     <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Compile Update="Resources\CommandStrings.Designer.cs"> | ||||
|       <SubType>Designer</SubType> | ||||
|     </Compile> | ||||
|     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> | ||||
|     <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" /> | ||||
|   </ItemGroup> | ||||
| </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> | ||||
|   </data> | ||||
|   <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 name="xpnotify_cmd" xml:space="preserve"> | ||||
|     <value>xpnotify xpn</value> | ||||
| @@ -3780,4 +3780,31 @@ | ||||
|   <data name="nsfwclearcache_desc" xml:space="preserve"> | ||||
|     <value>Clears nsfw cache.</value> | ||||
|   </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> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using Newtonsoft.Json; | ||||
| using System.ComponentModel.DataAnnotations.Schema; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| namespace NadekoBot.Services.Database.Models | ||||
| @@ -6,7 +7,9 @@ namespace NadekoBot.Services.Database.Models | ||||
|     public class CustomReaction : DbEntity | ||||
|     { | ||||
|         public ulong? GuildId { get; set; } | ||||
|  | ||||
|         [NotMapped] | ||||
|         [JsonIgnore] | ||||
|         public Regex Regex { get; set; } | ||||
|         public string Response { get; set; } | ||||
|         public string Trigger { get; set; } | ||||
| @@ -16,6 +19,7 @@ namespace NadekoBot.Services.Database.Models | ||||
|         public bool AutoDeleteTrigger { get; set; } | ||||
|         public bool DmResponse { get; set; } | ||||
|  | ||||
|         [JsonIgnore] | ||||
|         public bool IsGlobal => !GuildId.HasValue; | ||||
|  | ||||
|         public bool ContainsAnywhere { get; set; } | ||||
|   | ||||
| @@ -10,6 +10,9 @@ namespace NadekoBot.Services.Database.Models | ||||
|         public string AvatarId { 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 LastXpGain { get; set; } = DateTime.MinValue; | ||||
|         public XpNotificationType NotifyOnLevelUp { get; set; } | ||||
|   | ||||
| @@ -3,23 +3,21 @@ using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using NadekoBot.Services.Database.Models; | ||||
| using NadekoBot.Extensions; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using System; | ||||
| using Microsoft.EntityFrameworkCore.Design; | ||||
| using Microsoft.Data.Sqlite; | ||||
| using System.IO; | ||||
|  | ||||
| namespace NadekoBot.Services.Database | ||||
| { | ||||
|  | ||||
|     public class NadekoContextFactory : IDbContextFactory<NadekoContext> | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// :\ Used for migrations | ||||
|         /// </summary> | ||||
|         /// <param name="options"></param> | ||||
|         /// <returns></returns> | ||||
|         public NadekoContext Create(DbContextFactoryOptions options) | ||||
|     public class NadekoContextFactory : IDesignTimeDbContextFactory<NadekoContext> | ||||
|     {         | ||||
|         public NadekoContext CreateDbContext(string[] args) | ||||
|         { | ||||
|             var optionsBuilder = new DbContextOptionsBuilder(); | ||||
|             optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); | ||||
|             var optionsBuilder = new DbContextOptionsBuilder<NadekoContext>(); | ||||
|             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); | ||||
|             ctx.Database.SetCommandTimeout(60); | ||||
|             return ctx; | ||||
| @@ -58,12 +56,7 @@ namespace NadekoBot.Services.Database | ||||
|         public DbSet<ModulePrefix> ModulePrefixes { get; set; } | ||||
|         public DbSet<RewardedUser> RewardedUsers { get; set; } | ||||
|  | ||||
|         public NadekoContext() : base() | ||||
|         { | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public NadekoContext(DbContextOptions options) : base(options) | ||||
|         public NadekoContext(DbContextOptions<NadekoContext> options) : base(options) | ||||
|         { | ||||
|         } | ||||
|  | ||||
| @@ -231,7 +224,7 @@ namespace NadekoBot.Services.Database | ||||
|             musicPlaylistEntity | ||||
|                 .HasMany(p => p.Songs) | ||||
|                 .WithOne() | ||||
|                 .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade); | ||||
|                 .OnDelete(DeleteBehavior.Cascade); | ||||
|  | ||||
|  | ||||
|             #endregion | ||||
| @@ -329,7 +322,7 @@ namespace NadekoBot.Services.Database | ||||
|             #region ClubManytoMany | ||||
|  | ||||
|             modelBuilder.Entity<ClubApplicants>() | ||||
|             .HasKey(t => new { t.ClubId, t.UserId }); | ||||
|                 .HasKey(t => new { t.ClubId, t.UserId }); | ||||
|  | ||||
|             modelBuilder.Entity<ClubApplicants>() | ||||
|                 .HasOne(pt => pt.User) | ||||
| @@ -340,7 +333,7 @@ namespace NadekoBot.Services.Database | ||||
|                 .WithMany(x => x.Applicants); | ||||
|  | ||||
|             modelBuilder.Entity<ClubBans>() | ||||
|             .HasKey(t => new { t.ClubId, t.UserId }); | ||||
|                 .HasKey(t => new { t.ClubId, t.UserId }); | ||||
|  | ||||
|             modelBuilder.Entity<ClubBans>() | ||||
|                 .HasOne(pt => pt.User) | ||||
|   | ||||
| @@ -10,6 +10,7 @@ namespace NadekoBot.Services.Database.Repositories | ||||
|         int GetNextDiscrim(string clubName); | ||||
|         ClubInfo GetByName(string v, int discrim, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); | ||||
|         ClubInfo GetByOwner(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); | ||||
|         ClubInfo GetByOwnerOrAdmin(ulong userId); | ||||
|         ClubInfo GetByMember(ulong userId, Func<DbSet<ClubInfo>, IQueryable<ClubInfo>> func = null); | ||||
|         ClubInfo[] GetClubLeaderboardPage(int page); | ||||
|     } | ||||
|   | ||||
| @@ -6,5 +6,7 @@ namespace NadekoBot.Services.Database.Repositories | ||||
|     public interface IDiscordUserRepository : IRepository<DiscordUser> | ||||
|     { | ||||
|         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> | ||||
|     { | ||||
|         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); | ||||
|         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); | ||||
|         } | ||||
|  | ||||
|         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) | ||||
|         { | ||||
|             if (func == null) | ||||
|   | ||||
| @@ -37,5 +37,23 @@ namespace NadekoBot.Services.Database.Repositories.Impl | ||||
|  | ||||
|             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; | ||||
|         } | ||||
|  | ||||
|         public int GetTotalUserXp(ulong userId) | ||||
|         { | ||||
|             return _set.Where(x => x.UserId == userId).Sum(x => x.Xp); | ||||
|         } | ||||
|  | ||||
|         public UserXpStats[] GetUsersFor(ulong guildId, int page) | ||||
|         { | ||||
|             return _set.Where(x => x.GuildId == guildId) | ||||
| @@ -43,15 +38,6 @@ namespace NadekoBot.Services.Database.Repositories.Impl | ||||
|                 .ToArray(); | ||||
|         } | ||||
|  | ||||
|         public int GetUserGlobalRanking(ulong userId) | ||||
|         { | ||||
|             return _set | ||||
|                 .GroupBy(x => x.UserId) | ||||
|                 .Count(x => x.Sum(y => y.Xp) > _set | ||||
|                     .Where(y => y.UserId == userId) | ||||
|                     .Sum(y => y.Xp)) + 1; | ||||
|         } | ||||
|  | ||||
|         public int GetUserGuildRanking(ulong userId, ulong guildId) | ||||
|         { | ||||
|             return _set | ||||
| @@ -62,16 +48,5 @@ namespace NadekoBot.Services.Database.Repositories.Impl | ||||
|                     .DefaultIfEmpty() | ||||
|                     .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 System; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
|  | ||||
| namespace NadekoBot.Services | ||||
| { | ||||
|     public class DbService | ||||
|     { | ||||
|         private readonly DbContextOptions options; | ||||
|         private readonly DbContextOptions migrateOptions; | ||||
|  | ||||
|         private readonly string _connectionString; | ||||
|         private readonly DbContextOptions<NadekoContext> options; | ||||
|         private readonly DbContextOptions<NadekoContext> migrateOptions; | ||||
|  | ||||
|         public DbService(IBotCredentials creds) | ||||
|         { | ||||
|             _connectionString = creds.Db.ConnectionString; | ||||
|             var optionsBuilder = new DbContextOptionsBuilder(); | ||||
|             optionsBuilder.UseSqlite(creds.Db.ConnectionString); | ||||
|             var builder = new SqliteConnectionStringBuilder(creds.Db.ConnectionString); | ||||
|             builder.DataSource = Path.Combine(AppContext.BaseDirectory, builder.DataSource); | ||||
|              | ||||
|             var optionsBuilder = new DbContextOptionsBuilder<NadekoContext>(); | ||||
|             optionsBuilder.UseSqlite(builder.ToString()); | ||||
|             options = optionsBuilder.Options; | ||||
|  | ||||
|             optionsBuilder = new DbContextOptionsBuilder(); | ||||
|             optionsBuilder.UseSqlite(creds.Db.ConnectionString, x => x.SuppressForeignKeyEnforcement()); | ||||
|             optionsBuilder = new DbContextOptionsBuilder<NadekoContext>(); | ||||
|             optionsBuilder.UseSqlite(builder.ToString(), x => x.SuppressForeignKeyEnforcement()); | ||||
|             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); | ||||
|                 ClientId = clId; | ||||
|  | ||||
|                 //var scId = data[nameof(SoundCloudClientId)]; | ||||
|                 //SoundCloudClientId = scId; | ||||
|                 //SoundCloudClientId = string.IsNullOrWhiteSpace(scId) | ||||
|                 //    ?  | ||||
|                 //    : scId; | ||||
|                 CarbonKey = data[nameof(CarbonKey)]; | ||||
|                 var dbSection = data.GetSection("db"); | ||||
|                 Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"])  | ||||
|                                 ? "sqlite"  | ||||
|                                 : dbSection["Type"],  | ||||
|                             string.IsNullOrWhiteSpace(dbSection["ConnectionString"])  | ||||
|                                 ? "Filename=./data/NadekoBot.db" | ||||
|                                 ? "Data Source=data/NadekoBot.db" | ||||
|                                 : dbSection["ConnectionString"]); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
| @@ -125,7 +120,7 @@ namespace NadekoBot.Services.Impl | ||||
|             public string SoundCloudClientId { get; set; } = ""; | ||||
|             public string CleverbotApiKey { get; } = ""; | ||||
|             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 string PatreonAccessToken { get; set; } = ""; | ||||
|             public string PatreonCampaignId { get; set; } = "334038"; | ||||
|   | ||||
| @@ -5,6 +5,9 @@ using System.Linq; | ||||
| using Discord; | ||||
| using NLog; | ||||
| using NadekoBot.Services.Database.Models; | ||||
| using NadekoBot.Common; | ||||
| using Newtonsoft.Json; | ||||
| using System.IO; | ||||
|  | ||||
| namespace NadekoBot.Services.Impl | ||||
| { | ||||
| @@ -16,6 +19,14 @@ namespace NadekoBot.Services.Impl | ||||
|         public ConcurrentDictionary<ulong, CultureInfo> GuildCultureInfos { get; } | ||||
|         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() { } | ||||
|         public Localization(IBotConfigProvider bcp, IEnumerable<GuildConfig> gcs, DbService db) | ||||
|         { | ||||
| @@ -117,10 +128,19 @@ namespace NadekoBot.Services.Impl | ||||
|             return info ?? DefaultCultureInfo; | ||||
|         } | ||||
|  | ||||
|         public static string LoadCommandString(string key) | ||||
|         public static CommandData LoadCommand(string key) | ||||
|         { | ||||
|             string toReturn = Resources.CommandStrings.ResourceManager.GetString(key); | ||||
|             return string.IsNullOrWhiteSpace(toReturn) ? key : toReturn; | ||||
|             _commandData.TryGetValue(key, out var 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 DateTime _started; | ||||
|  | ||||
|         public const string BotVersion = "1.8.4"; | ||||
|         public const string BotVersion = "1.9.0"; | ||||
|  | ||||
|         public string Author => "Kwoth#2560"; | ||||
|         public string Library => "Discord.Net"; | ||||
|   | ||||
| @@ -11,7 +11,7 @@ namespace NadekoBot.Services | ||||
|             var logConfig = new LoggingConfiguration(); | ||||
|             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); | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,6 @@ | ||||
|   "customreactions_stats_not_found": "No stats for that trigger found, no action taken.", | ||||
|   "customreactions_trigger": "Trigger", | ||||
|   "customreactions_redacted_too_long": "Redecated because it's too long.", | ||||
|   "nsfw_autohentai_stopped": "Autohentai stopped.", | ||||
|   "nsfw_not_found": "No results found.", | ||||
|   "nsfw_blacklisted_tag_list": "List of blacklisted tags:", | ||||
|   "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_bans_for": "Bans 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": "", | ||||
|   "Db": { | ||||
|     "Type": "sqlite", | ||||
|     "ConnectionString": "Filename=./data/NadekoBot.db" | ||||
|     "ConnectionString": "Data Source=data/NadekoBot.db" | ||||
|   }, | ||||
|   "TotalShards": 1, | ||||
|   "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