diff --git a/.gitignore b/.gitignore index 0fa1ef5e..5169035d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ #Manually added files +patreon_rewards.json command_errors*.txt src/NadekoBot/Command Errors*.txt diff --git a/NadekoBot.sln b/NadekoBot.sln index a80335e2..8c235940 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.6 +VisualStudioVersion = 15.0.26430.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" EndProject diff --git a/docs/JSON Explanations.md b/docs/JSON Explanations.md index 2ac629e8..341cf992 100644 --- a/docs/JSON Explanations.md +++ b/docs/JSON Explanations.md @@ -18,7 +18,8 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl "OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6", "PatreonAccessToken": "", "Db": null, - "TotalShards": 1 + "TotalShards": 1, + "ShardRunCommand": "" } ``` ----- @@ -155,14 +156,28 @@ It should look like: - **TotalShards** - Required if the bot will be connected to more than 1500 servers. - Most likely unnecessary to change until your bot is added to more than 1500 servers. - +- **ShardRunCommand** + - Command with which to run shards 1+ + - Required if you're sharding your bot on windows using .exe, or in a custom way. + - This internally defaults to `dotnet` + - For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to something like this: `C:\Program Files\NadekoBot\system\NadekoBot.exe` +- **ShardRunArguments** + - Arguments to the shard run command + - Required if you're sharding your bot on windows using .exe, or in a custom way. + - This internally defaults to `run -c Release -- {0} {1} {2}` which will be enough to run linux and other 'from source' setups + - {0} will be replaced by the `shard ID` of the shard being ran, {1} by the shard 0's process id, and {2} by the port shard communication is happening on + - If shard0 (main window) is closed, all other shards will close too + - For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to `{0} {1} {2}` +- **ShardRunPort** + - Bot uses a random UDP port in [5000, 6000) range for communication between shards ----- ## DB files Nadeko saves all the settings and infomations in `NadekoBot.db` file here: `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data/NadekoBot.db` (macOS and Linux) -`NadekoBot\system\data` (Windows) +`NadekoBot\system\data` (Windows) + in order to open the database file you will need [DB Browser for SQLite](http://sqlitebrowser.org/). *NOTE: You don't have to worry if you don't have `NadekoBot.db` file, it gets auto created once you run the bot successfully.* diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlockingExecutor.cs b/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlockingExecutor.cs index f28eaf4f..a3e004b1 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlockingExecutor.cs +++ b/src/NadekoBot/DataStructures/ModuleBehaviors/IEarlyBlockingExecutor.cs @@ -13,6 +13,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors /// Try to execute some logic within some module's service. /// /// Whether it should block other command executions after it. - Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg); + Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg); } } diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlocker.cs b/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlocker.cs index 3b3fc020..68f33206 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlocker.cs +++ b/src/NadekoBot/DataStructures/ModuleBehaviors/ILateBlocker.cs @@ -6,7 +6,7 @@ namespace NadekoBot.DataStructures.ModuleBehaviors { public interface ILateBlocker { - Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, + Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName); } } diff --git a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateExecutor.cs b/src/NadekoBot/DataStructures/ModuleBehaviors/ILateExecutor.cs index 3cf11603..a7b3e52e 100644 --- a/src/NadekoBot/DataStructures/ModuleBehaviors/ILateExecutor.cs +++ b/src/NadekoBot/DataStructures/ModuleBehaviors/ILateExecutor.cs @@ -9,6 +9,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors /// public interface ILateExecutor { - Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg); + Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg); } } diff --git a/src/NadekoBot/DataStructures/Shard0Precondition.cs b/src/NadekoBot/DataStructures/Shard0Precondition.cs new file mode 100644 index 00000000..eaa5c591 --- /dev/null +++ b/src/NadekoBot/DataStructures/Shard0Precondition.cs @@ -0,0 +1,22 @@ +using Discord.Commands; +using Discord.WebSocket; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.DataStructures +{ + public class Shard0Precondition : PreconditionAttribute + { + public override Task CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) + { + var c = (DiscordSocketClient)context.Client; + if (c.ShardId == 0) + return Task.FromResult(PreconditionResult.FromSuccess()); + else + return Task.FromResult(PreconditionResult.FromError("Must be ran from shard #0")); + } + } +} diff --git a/src/NadekoBot/DataStructures/ShardCom/IShardComMessage.cs b/src/NadekoBot/DataStructures/ShardCom/IShardComMessage.cs new file mode 100644 index 00000000..9fb1f5d0 --- /dev/null +++ b/src/NadekoBot/DataStructures/ShardCom/IShardComMessage.cs @@ -0,0 +1,16 @@ +using Discord; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.DataStructures.ShardCom +{ + public class ShardComMessage + { + public int ShardId { get; set; } + public ConnectionState ConnectionState { get; set; } + public int Guilds { get; set; } + } +} diff --git a/src/NadekoBot/DataStructures/ShardCom/ShardComClient.cs b/src/NadekoBot/DataStructures/ShardCom/ShardComClient.cs new file mode 100644 index 00000000..67e1c9f6 --- /dev/null +++ b/src/NadekoBot/DataStructures/ShardCom/ShardComClient.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.DataStructures.ShardCom +{ + public class ShardComClient + { + private int port; + + public ShardComClient(int port) + { + this.port = port; + } + + public async Task Send(ShardComMessage data) + { + var msg = JsonConvert.SerializeObject(data); + using (var client = new UdpClient()) + { + var bytes = Encoding.UTF8.GetBytes(msg); + await client.SendAsync(bytes, bytes.Length, IPAddress.Loopback.ToString(), port).ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/DataStructures/ShardCom/ShardComServer.cs b/src/NadekoBot/DataStructures/ShardCom/ShardComServer.cs new file mode 100644 index 00000000..d0e1cbf6 --- /dev/null +++ b/src/NadekoBot/DataStructures/ShardCom/ShardComServer.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.DataStructures.ShardCom +{ + public class ShardComServer : IDisposable + { + private readonly UdpClient _client; + + public ShardComServer(int port) + { + _client = new UdpClient(port); + } + + public void Start() + { + Task.Run(async () => + { + var ip = new IPEndPoint(IPAddress.Any, 0); + while (true) + { + var recv = await _client.ReceiveAsync(); + var data = Encoding.UTF8.GetString(recv.Buffer); + var _ = OnDataReceived(JsonConvert.DeserializeObject(data)); + } + }); + } + + public void Dispose() + { + _client.Dispose(); + } + + public event Func OnDataReceived = delegate { return Task.CompletedTask; }; + } +} diff --git a/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs b/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs index 63971f5a..3bb72d4c 100644 --- a/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs +++ b/src/NadekoBot/DataStructures/TypeReaders/GuildTypeReader.cs @@ -7,9 +7,9 @@ namespace NadekoBot.TypeReaders { public class GuildTypeReader : TypeReader { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public GuildTypeReader(DiscordShardedClient client) + public GuildTypeReader(DiscordSocketClient client) { _client = client; } diff --git a/src/NadekoBot/Modules/Administration/Administration.cs b/src/NadekoBot/Modules/Administration/Administration.cs index 15185ca3..56a0affc 100644 --- a/src/NadekoBot/Modules/Administration/Administration.cs +++ b/src/NadekoBot/Modules/Administration/Administration.cs @@ -126,17 +126,17 @@ namespace NadekoBot.Modules.Administration { var guser = (IGuildUser)Context.User; - var userRoles = user.GetRoles(); - if (guser.Id != Context.Guild.OwnerId && - (user.Id == Context.Guild.OwnerId || guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position))) + var userRoles = user.GetRoles().Except(new[] { guser.Guild.EveryoneRole }); + if (user.Id == Context.Guild.OwnerId || (Context.User.Id != Context.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position))) return; try { await user.RemoveRolesAsync(userRoles).ConfigureAwait(false); await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false); } - catch + catch (Exception ex) { + Console.WriteLine(ex); await ReplyErrorLocalized("rar_err").ConfigureAwait(false); } } diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs index 9815fa1b..3ad63210 100644 --- a/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ b/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs @@ -140,7 +140,7 @@ namespace NadekoBot.Modules.Administration await uow.CompleteAsync(); } - await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page, (curPage) => + await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) => { return new EmbedBuilder() .WithTitle(GetText("self_assign_list", roleCnt)) diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs index aa99eeab..449398b4 100644 --- a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs @@ -13,6 +13,8 @@ using NadekoBot.Services; using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; using NadekoBot.Services.Administration; +using System.Diagnostics; +using NadekoBot.DataStructures; namespace NadekoBot.Modules.Administration { @@ -25,10 +27,10 @@ namespace NadekoBot.Modules.Administration private static readonly object _locker = new object(); private readonly SelfService _service; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IImagesService _images; - public SelfCommands(DbService db, SelfService service, DiscordShardedClient client, + public SelfCommands(DbService db, SelfService service, DiscordSocketClient client, IImagesService images) { _db = db; @@ -204,28 +206,28 @@ namespace NadekoBot.Modules.Administration } - [NadekoCommand, Usage, Description, Aliases] - [OwnerOnly] - public async Task ConnectShard(int shardid) - { - var shard = _client.GetShard(shardid); - - if (shard == null) - { - await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false); - return; - } - try - { - await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false); - await shard.StartAsync().ConfigureAwait(false); - await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false); - } - catch (Exception ex) - { - _log.Warn(ex); - } - } + //todo 2 shard commands + //[NadekoCommand, Usage, Description, Aliases] + //[Shard0Precondition] + //[OwnerOnly] + //public async Task RestartShard(int shardid) + //{ + // if (shardid == 0 || shardid > b) + // { + // await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false); + // return; + // } + // try + // { + // await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false); + // await shard.StartAsync().ConfigureAwait(false); + // await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false); + // } + // catch (Exception ex) + // { + // _log.Warn(ex); + // } + //} [NadekoCommand, Usage, Description, Aliases] [OwnerOnly] @@ -417,8 +419,10 @@ namespace NadekoBot.Modules.Administration [OwnerOnly] public async Task ReloadImages() { - var time = _images.Reload(); - await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false); + var sw = Stopwatch.StartNew(); + _images.Reload(); + sw.Stop(); + await ReplyConfirmLocalized("images_loaded", sw.Elapsed.TotalSeconds.ToString("F3")).ConfigureAwait(false); } private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus) diff --git a/src/NadekoBot/Modules/Administration/Commands/TimeZoneCommands.cs b/src/NadekoBot/Modules/Administration/Commands/TimeZoneCommands.cs index e054288c..c97fdbca 100644 --- a/src/NadekoBot/Modules/Administration/Commands/TimeZoneCommands.cs +++ b/src/NadekoBot/Modules/Administration/Commands/TimeZoneCommands.cs @@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Administration .ToArray(); var timezonesPerPage = 20; - await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page, + await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) => new EmbedBuilder() .WithOkColor() .WithTitle(GetText("timezones_available")) diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs index 5dac3b9c..ae033cad 100644 --- a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -17,10 +17,10 @@ namespace NadekoBot.Modules.CustomReactions private readonly IBotCredentials _creds; private readonly DbService _db; private readonly CustomReactionsService _crs; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public CustomReactions(IBotCredentials creds, DbService db, CustomReactionsService crs, - DiscordShardedClient client) + DiscordSocketClient client) { _creds = creds; _db = db; diff --git a/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs b/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs index 4db985e5..46cd25b3 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs @@ -22,12 +22,12 @@ namespace NadekoBot.Modules.Gambling { private readonly BotConfig _bc; private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public static ConcurrentDictionary AnimalRaces { get; } = new ConcurrentDictionary(); - public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordShardedClient client) + public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordSocketClient client) { _bc = bc; _cs = cs; @@ -82,14 +82,14 @@ namespace NadekoBot.Modules.Gambling private readonly ITextChannel _raceChannel; private readonly BotConfig _bc; private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly ILocalization _localization; private readonly NadekoStrings _strings; public bool Started { get; private set; } public AnimalRace(ulong serverId, ITextChannel channel, string prefix, BotConfig bc, - CurrencyService cs, DiscordShardedClient client, ILocalization localization, + CurrencyService cs, DiscordSocketClient client, ILocalization localization, NadekoStrings strings) { _prefix = prefix; diff --git a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs index 76369c15..644823bc 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/CurrencyEvents.cs @@ -34,11 +34,11 @@ namespace NadekoBot.Modules.Gambling .ToArray(); private string _secretCode = string.Empty; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly BotConfig _bc; private readonly CurrencyService _cs; - public CurrencyEvents(DiscordShardedClient client, BotConfig bc, CurrencyService cs) + public CurrencyEvents(DiscordSocketClient client, BotConfig bc, CurrencyService cs) { _client = client; _bc = bc; @@ -151,7 +151,7 @@ namespace NadekoBot.Modules.Gambling { private readonly ConcurrentHashSet _flowerReactionAwardedUsers = new ConcurrentHashSet(); private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly CurrencyService _cs; private IUserMessage StartingMessage { get; set; } @@ -159,7 +159,7 @@ namespace NadekoBot.Modules.Gambling private CancellationTokenSource Source { get; } private CancellationToken CancelToken { get; } - public FlowerReactionEvent(DiscordShardedClient client, CurrencyService cs) + public FlowerReactionEvent(DiscordSocketClient client, CurrencyService cs) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Modules/Gambling/Commands/FlowerShop.cs b/src/NadekoBot/Modules/Gambling/Commands/FlowerShop.cs index dfc8d648..dd4fd309 100644 --- a/src/NadekoBot/Modules/Gambling/Commands/FlowerShop.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/FlowerShop.cs @@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Gambling private readonly BotConfig _bc; private readonly DbService _db; private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public enum Role { @@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Gambling List } - public FlowerShop(BotConfig bc, DbService db, CurrencyService cs, DiscordShardedClient client) + public FlowerShop(BotConfig bc, DbService db, CurrencyService cs, DiscordSocketClient client) { _db = db; _bc = bc; diff --git a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs b/src/NadekoBot/Modules/Games/Commands/Acropobia.cs index 0d8e337f..44b64808 100644 --- a/src/NadekoBot/Modules/Games/Commands/Acropobia.cs +++ b/src/NadekoBot/Modules/Games/Commands/Acropobia.cs @@ -20,12 +20,12 @@ namespace NadekoBot.Modules.Games [Group] public class Acropobia : NadekoSubmodule { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; //channelId, game public static ConcurrentDictionary AcrophobiaGames { get; } = new ConcurrentDictionary(); - public Acropobia(DiscordShardedClient client) + public Acropobia(DiscordSocketClient client) { _client = client; } @@ -86,10 +86,10 @@ namespace NadekoBot.Modules.Games //text, votes private readonly ConcurrentDictionary _votes = new ConcurrentDictionary(); private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; - public AcrophobiaGame(DiscordShardedClient client, NadekoStrings strings, ITextChannel channel, int time) + public AcrophobiaGame(DiscordSocketClient client, NadekoStrings strings, ITextChannel channel, int time) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs index 24ca87c9..eddd00fc 100644 --- a/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Hangman/HangmanGame.cs @@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Games.Hangman public class HangmanGame: IDisposable { private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public IMessageChannel GameChannel { get; } public HashSet Guesses { get; } = new HashSet(); @@ -82,7 +82,7 @@ namespace NadekoBot.Modules.Games.Hangman public event Action OnEnded; - public HangmanGame(DiscordShardedClient client, IMessageChannel channel, string type) + public HangmanGame(DiscordSocketClient client, IMessageChannel channel, string type) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs b/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs index 633be81f..c515c0b8 100644 --- a/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/HangmanCommands.cs @@ -15,9 +15,9 @@ namespace NadekoBot.Modules.Games [Group] public class HangmanCommands : NadekoSubmodule { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public HangmanCommands(DiscordShardedClient client) + public HangmanCommands(DiscordSocketClient client) { _client = client; } diff --git a/src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs b/src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs index db670fd2..92b01eb3 100644 --- a/src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Models/TypingGame.cs @@ -20,13 +20,13 @@ namespace NadekoBot.Modules.Games.Models public bool IsActive { get; private set; } private readonly Stopwatch sw; private readonly List finishedUserIds; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly GamesService _games; private readonly string _prefix; private Logger _log { get; } - public TypingGame(GamesService games, DiscordShardedClient client, ITextChannel channel, string prefix) //kek@prefix + public TypingGame(GamesService games, DiscordSocketClient client, ITextChannel channel, string prefix) //kek@prefix { _log = LogManager.GetCurrentClassLogger(); _games = games; diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs index 191fd30d..f055e0bb 100644 --- a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -116,7 +116,7 @@ namespace NadekoBot.Modules.Games bool enabled; using (var uow = _db.UnitOfWork) { - var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds)); + var guildConfig = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds)); var toAdd = new GCChannelId() { ChannelId = channel.Id }; if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd)) diff --git a/src/NadekoBot/Modules/Games/Commands/PollCommands.cs b/src/NadekoBot/Modules/Games/Commands/PollCommands.cs index 487473f0..0d6b6cb2 100644 --- a/src/NadekoBot/Modules/Games/Commands/PollCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/PollCommands.cs @@ -13,10 +13,10 @@ namespace NadekoBot.Modules.Games [Group] public class PollCommands : NadekoSubmodule { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly PollService _polls; - public PollCommands(DiscordShardedClient client, PollService polls) + public PollCommands(DiscordSocketClient client, PollService polls) { _client = client; _polls = polls; @@ -26,13 +26,7 @@ namespace NadekoBot.Modules.Games [RequireUserPermission(GuildPermission.ManageMessages)] [RequireContext(ContextType.Guild)] public Task Poll([Remainder] string arg = null) - => InternalStartPoll(arg, false); - - [NadekoCommand, Usage, Description, Aliases] - [RequireUserPermission(GuildPermission.ManageMessages)] - [RequireContext(ContextType.Guild)] - public Task PublicPoll([Remainder] string arg = null) - => InternalStartPoll(arg, true); + => InternalStartPoll(arg); [NadekoCommand, Usage, Description, Aliases] [RequireUserPermission(GuildPermission.ManageMessages)] @@ -45,9 +39,9 @@ namespace NadekoBot.Modules.Games await Context.Channel.EmbedAsync(poll.GetStats(GetText("current_poll_results"))); } - private async Task InternalStartPoll(string arg, bool isPublic = false) + private async Task InternalStartPoll(string arg) { - if(await _polls.StartPoll((ITextChannel)Context.Channel, Context.Message, arg, isPublic) == false) + if(await _polls.StartPoll((ITextChannel)Context.Channel, Context.Message, arg) == false) await ReplyErrorLocalized("poll_already_running").ConfigureAwait(false); } diff --git a/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs b/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs index 72663094..6cce5191 100644 --- a/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs @@ -20,9 +20,9 @@ namespace NadekoBot.Modules.Games { public static ConcurrentDictionary RunningContests = new ConcurrentDictionary(); private readonly GamesService _games; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public SpeedTypingCommands(DiscordShardedClient client, GamesService games) + public SpeedTypingCommands(DiscordSocketClient client, GamesService games) { _games = games; _client = client; diff --git a/src/NadekoBot/Modules/Games/Commands/TicTacToe.cs b/src/NadekoBot/Modules/Games/Commands/TicTacToe.cs index 7de40565..6f626325 100644 --- a/src/NadekoBot/Modules/Games/Commands/TicTacToe.cs +++ b/src/NadekoBot/Modules/Games/Commands/TicTacToe.cs @@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Games private static readonly Dictionary _games = new Dictionary(); private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public TicTacToeCommands(DiscordShardedClient client) + public TicTacToeCommands(DiscordSocketClient client) { _client = client; } @@ -87,9 +87,9 @@ namespace NadekoBot.Modules.Games private IUserMessage _previousMessage; private Timer _timeoutTimer; private readonly NadekoStrings _strings; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public TicTacToe(NadekoStrings strings, DiscordShardedClient client, ITextChannel channel, IGuildUser firstUser) + public TicTacToe(NadekoStrings strings, DiscordSocketClient client, ITextChannel channel, IGuildUser firstUser) { _channel = channel; _strings = strings; diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs index ea6e1e2c..b1b7d477 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Games.Trivia private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1); private readonly Logger _log; private readonly NadekoStrings _strings; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly BotConfig _bc; private readonly CurrencyService _cs; @@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Games.Trivia public int WinRequirement { get; } - public TriviaGame(NadekoStrings strings, DiscordShardedClient client, BotConfig bc, + public TriviaGame(NadekoStrings strings, DiscordSocketClient client, BotConfig bc, CurrencyService cs, IGuild guild, ITextChannel channel, bool showHints, int winReq, bool isPokemon) { diff --git a/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs b/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs index d38e04dd..ac9cdcc3 100644 --- a/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs +++ b/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs @@ -18,12 +18,12 @@ namespace NadekoBot.Modules.Games public class TriviaCommands : NadekoSubmodule { private readonly CurrencyService _cs; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly BotConfig _bc; public static ConcurrentDictionary RunningTrivias { get; } = new ConcurrentDictionary(); - public TriviaCommands(DiscordShardedClient client, BotConfig bc, CurrencyService cs) + public TriviaCommands(DiscordSocketClient client, BotConfig bc, CurrencyService cs) { _cs = cs; _client = client; diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs index 18ddf039..37377713 100644 --- a/src/NadekoBot/Modules/Help/Help.cs +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -155,8 +155,8 @@ namespace NadekoBot.Modules.Help public async Task Guide() { await ConfirmLocalized("guide", - "http://nadekobot.readthedocs.io/en/latest/Commands%20List/", - "http://nadekobot.readthedocs.io/en/latest/").ConfigureAwait(false); + "http://nadekobot.readthedocs.io/en/1.3x/Commands%20List/", + "http://nadekobot.readthedocs.io/en/1.3x/").ConfigureAwait(false); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs index afd60bb2..ebec33c5 100644 --- a/src/NadekoBot/Modules/Music/Music.cs +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -22,12 +22,12 @@ namespace NadekoBot.Modules.Music public class Music : NadekoTopLevelModule { private static MusicService _music; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private readonly IGoogleApiService _google; private readonly DbService _db; - public Music(DiscordShardedClient client, IBotCredentials creds, IGoogleApiService google, + public Music(DiscordSocketClient client, IBotCredentials creds, IGoogleApiService google, DbService db, MusicService music) { _client = client; diff --git a/src/NadekoBot/Modules/NadekoModule.cs b/src/NadekoBot/Modules/NadekoModule.cs index dae471d4..45abaea9 100644 --- a/src/NadekoBot/Modules/NadekoModule.cs +++ b/src/NadekoBot/Modules/NadekoModule.cs @@ -86,13 +86,12 @@ namespace NadekoBot.Modules var text = GetText(textKey, replacements); return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + text); } - - // todo maybe make this generic and use - // TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); + + // TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ? public async Task GetUserInputAsync(ulong userId, ulong channelId) { var userInputTask = new TaskCompletionSource(); - var dsc = (DiscordShardedClient)Context.Client; + var dsc = (DiscordSocketClient)Context.Client; try { dsc.MessageReceived += MessageReceived; diff --git a/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs index 371678ff..1329fc0b 100644 --- a/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs +++ b/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs @@ -195,7 +195,7 @@ namespace NadekoBot.Modules.Permissions var fws = fwHash.ToArray(); - await channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, + await channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) => new EmbedBuilder() diff --git a/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs b/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs index d38cfb90..03316135 100644 --- a/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/CommandMapCommands.cs @@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Utility { private readonly CommandMapService _service; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public CommandMapCommands(CommandMapService service, DbService db, DiscordShardedClient client) + public CommandMapCommands(CommandMapService service, DbService db, DiscordSocketClient client) { _service = service; _db = db; diff --git a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs b/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs deleted file mode 100644 index de917054..00000000 --- a/src/NadekoBot/Modules/Utility/Commands/CrossServerTextChannel.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Attributes; -using NadekoBot.Extensions; -using NadekoBot.Services; -using NadekoBot.Services.Utility; -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Utility -{ - public partial class Utility - { - [Group] - public class CrossServerTextChannel : NadekoSubmodule - { - private readonly CrossServerTextService _service; - - public CrossServerTextChannel(CrossServerTextService service) - { - _service = service; - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [OwnerOnly] - public async Task Scsc() - { - var token = new NadekoRandom().Next(); - var set = new ConcurrentHashSet(); - if (_service.Subscribers.TryAdd(token, set)) - { - set.Add((ITextChannel) Context.Channel); - await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString()) - .ConfigureAwait(false); - } - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.ManageGuild)] - public async Task Jcsc(int token) - { - ConcurrentHashSet set; - if (!_service.Subscribers.TryGetValue(token, out set)) - return; - set.Add((ITextChannel) Context.Channel); - await ReplyConfirmLocalized("csc_join").ConfigureAwait(false); - } - - [NadekoCommand, Usage, Description, Aliases] - [RequireContext(ContextType.Guild)] - [RequireUserPermission(GuildPermission.ManageGuild)] - public async Task Lcsc() - { - foreach (var subscriber in _service.Subscribers) - { - subscriber.Value.TryRemove((ITextChannel) Context.Channel); - } - await ReplyConfirmLocalized("csc_leave").ConfigureAwait(false); - } - } - } -} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs index 3a5b8fe8..e3c625c4 100644 --- a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -16,10 +16,10 @@ namespace NadekoBot.Modules.Utility [Group] public class InfoCommands : NadekoSubmodule { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IStatsService _stats; - public InfoCommands(DiscordShardedClient client, IStatsService stats, CommandHandler ch) + public InfoCommands(DiscordSocketClient client, IStatsService stats, CommandHandler ch) { _client = client; _stats = stats; diff --git a/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs b/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs index 509419fd..293e2c42 100644 --- a/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/PatreonCommands.cs @@ -34,7 +34,9 @@ namespace NadekoBot.Modules.Utility [OwnerOnly] public async Task PatreonRewardsReload() { - await _patreon.LoadPledges().ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)) + return; + await _patreon.RefreshPledges(true).ConfigureAwait(false); await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false); } @@ -44,6 +46,7 @@ namespace NadekoBot.Modules.Utility { if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)) return; + if (DateTime.UtcNow.Day < 5) { await ReplyErrorLocalized("clpa_too_early").ConfigureAwait(false); diff --git a/src/NadekoBot/Modules/Utility/Commands/Remind.cs b/src/NadekoBot/Modules/Utility/Commands/Remind.cs index 448ec424..ce046467 100644 --- a/src/NadekoBot/Modules/Utility/Commands/Remind.cs +++ b/src/NadekoBot/Modules/Utility/Commands/Remind.cs @@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Utility [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] - [Priority(1)] + [Priority(0)] public async Task Remind(MeOrHere meorhere, string timeStr, [Remainder] string message) { ulong target; @@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Utility [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] [RequireUserPermission(GuildPermission.ManageMessages)] - [Priority(0)] + [Priority(1)] public async Task Remind(ITextChannel channel, string timeStr, [Remainder] string message) { var perms = ((IGuildUser)Context.User).GetPermissions((ITextChannel)channel); diff --git a/src/NadekoBot/Modules/Utility/Commands/RepeatCommands.cs b/src/NadekoBot/Modules/Utility/Commands/RepeatCommands.cs index 6561712d..89619c1c 100644 --- a/src/NadekoBot/Modules/Utility/Commands/RepeatCommands.cs +++ b/src/NadekoBot/Modules/Utility/Commands/RepeatCommands.cs @@ -22,10 +22,10 @@ namespace NadekoBot.Modules.Utility public class RepeatCommands : NadekoSubmodule { private readonly MessageRepeaterService _service; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly DbService _db; - public RepeatCommands(MessageRepeaterService service, DiscordShardedClient client, DbService db) + public RepeatCommands(MessageRepeaterService service, DiscordSocketClient client, DbService db) { _service = service; _client = client; diff --git a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs index 9b9c121c..1e7f5d22 100644 --- a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs +++ b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs @@ -6,7 +6,6 @@ using NadekoBot.Services.Utility; using System; using System.Linq; using System.Threading.Tasks; - namespace NadekoBot.Modules.Utility { public partial class Utility diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs index 4774a266..7a6f9ae1 100644 --- a/src/NadekoBot/Modules/Utility/Utility.cs +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -18,99 +18,25 @@ using Discord.WebSocket; using System.Diagnostics; using Color = Discord.Color; using NadekoBot.Services; +using NadekoBot.DataStructures; namespace NadekoBot.Modules.Utility { public partial class Utility : NadekoTopLevelModule { private static ConcurrentDictionary _rotatingRoleColors = new ConcurrentDictionary(); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IStatsService _stats; private readonly IBotCredentials _creds; + private readonly NadekoBot _bot; - public Utility(DiscordShardedClient client, IStatsService stats, IBotCredentials creds) + public Utility(NadekoBot bot, DiscordSocketClient client, IStatsService stats, IBotCredentials creds) { _client = client; _stats = stats; _creds = creds; - } - - //[NadekoCommand, Usage, Description, Aliases] - //[RequireContext(ContextType.Guild)] - //public async Task Midorina([Remainder] string arg) - //{ - // var channel = (ITextChannel)Context.Channel; - - // var roleNames = arg?.Split(';'); - - // if (roleNames == null || roleNames.Length == 0) - // return; - - // var j = 0; - // var roles = roleNames.Select(x => Context.Guild.Roles.FirstOrDefault(r => String.Compare(r.Name, x, StringComparison.OrdinalIgnoreCase) == 0)) - // .Where(x => x != null) - // .Take(10) - // .ToArray(); - - // var rnd = new NadekoRandom(); - // var reactions = new[] { "🎬", "🐧", "🌍", "🌺", "🚀", "☀", "🌲", "🍒", "🐾", "🏀" } - // .OrderBy(x => rnd.Next()) - // .ToArray(); - - // var roleStrings = roles - // .Select(x => $"{reactions[j++]} -> {x.Name}"); - - // var msg = await Context.Channel.SendConfirmAsync("Pick a Role", - // string.Join("\n", roleStrings)).ConfigureAwait(false); - - // for (int i = 0; i < roles.Length; i++) - // { - // try { await msg.AddReactionAsync(reactions[i]).ConfigureAwait(false); } - // catch (Exception ex) { _log.Warn(ex); } - // await Task.Delay(1000).ConfigureAwait(false); - // } - - // msg.OnReaction((r) => Task.Run(async () => - // { - // try - // { - // var usr = r.User.GetValueOrDefault() as IGuildUser; - - // if (usr == null) - // return; - - // var index = Array.IndexOf(reactions, r.Emoji.Name); - // if (index == -1) - // return; - - // await usr.RemoveRolesAsync(roles[index]); - // } - // catch (Exception ex) - // { - // _log.Warn(ex); - // } - // }), (r) => Task.Run(async () => - // { - // try - // { - // var usr = r.User.GetValueOrDefault() as IGuildUser; - - // if (usr == null) - // return; - - // var index = Array.IndexOf(reactions, r.Emoji.Name); - // if (index == -1) - // return; - - // await usr.RemoveRolesAsync(roles[index]); - // } - // catch (Exception ex) - // { - // _log.Warn(ex); - // } - // })); - //} - + _bot = bot; + } [NadekoCommand, Usage, Description, Aliases] [RequireContext(ContextType.Guild)] @@ -354,23 +280,25 @@ namespace NadekoBot.Modules.Utility } [NadekoCommand, Usage, Description, Aliases] + [Shard0Precondition] public async Task ShardStats(int page = 1) { if (--page < 0) return; + var statuses = _bot.ShardCoord.Statuses.ToArray() + .Where(x => x != null); - var status = string.Join(", ", _client.Shards.GroupBy(x => x.ConnectionState) + var status = string.Join(", ", statuses + .GroupBy(x => x.ConnectionState) .Select(x => $"{x.Count()} {x.Key}") .ToArray()); - var allShardStrings = _client.Shards + var allShardStrings = statuses .Select(x => GetText("shard_stats_txt", x.ShardId.ToString(), - Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.ToString()))) + Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.ToString()))) .ToArray(); - - await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) => { @@ -387,21 +315,9 @@ namespace NadekoBot.Modules.Utility }, allShardStrings.Length / 25); } - [NadekoCommand, Usage, Description, Aliases] - public async Task ShardId(IGuild guild) - { - var shardId = _client.GetShardIdFor(guild); - - await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false); - } - [NadekoCommand, Usage, Description, Aliases] public async Task Stats() - { - var shardId = Context.Guild != null - ? _client.GetShardIdFor(Context.Guild) - : 0; - + { await Context.Channel.EmbedAsync( new EmbedBuilder().WithOkColor() .WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}") @@ -409,7 +325,7 @@ namespace NadekoBot.Modules.Utility .WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg")) .AddField(efb => efb.WithName(GetText("author")).WithValue(_stats.Author).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).WithIsInline(true)) - .AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {_client.Shards.Count}").WithIsInline(true)) + .AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{_bot.ShardId} / {_creds.TotalShards}").WithIsInline(true)) .AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(_stats.CommandsRan.ToString()).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true)) .AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").WithIsInline(true)) @@ -417,13 +333,7 @@ namespace NadekoBot.Modules.Utility .AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true)) .AddField(efb => efb.WithName(GetText("presence")).WithValue( GetText("presence_txt", - _client.Guilds.Count, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)) -#if !GLOBAL_NADEKO - //.WithFooter(efb => efb.WithText(GetText("stats_songs", - // _music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null), - // _music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)))) -#endif - ); + _stats.GuildCount, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true))); } [NadekoCommand, Usage, Description, Aliases] diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs index 136a49ce..04dd0e4b 100644 --- a/src/NadekoBot/NadekoBot.cs +++ b/src/NadekoBot/NadekoBot.cs @@ -4,8 +4,6 @@ using Discord.WebSocket; using NadekoBot.Services; using NadekoBot.Services.Impl; using NLog; -using NLog.Config; -using NLog.Targets; using System; using System.Linq; using System.Reflection; @@ -27,8 +25,7 @@ using NadekoBot.Services.Utility; using NadekoBot.Services.Help; using System.IO; using NadekoBot.Services.Pokemon; -using NadekoBot.DataStructures; -using NadekoBot.Extensions; +using NadekoBot.DataStructures.ShardCom; namespace NadekoBot { @@ -45,47 +42,63 @@ namespace NadekoBot public static Color OkColor { get; private set; } public static Color ErrorColor { get; private set; } - public ImmutableArray AllGuildConfigs { get; } + public ImmutableArray AllGuildConfigs { get; private set; } public BotConfig BotConfig { get; } public DbService Db { get; } public CommandService CommandService { get; } public CommandHandler CommandHandler { get; private set; } - public Localization Localization { get; } - public NadekoStrings Strings { get; } - public StatsService Stats { get; } + public Localization Localization { get; private set; } + public NadekoStrings Strings { get; private set; } + public StatsService Stats { get; private set; } public ImagesService Images { get; } public CurrencyService Currency { get; } public GoogleApiService GoogleApi { get; } - public DiscordShardedClient Client { get; } + public DiscordSocketClient Client { get; } public bool Ready { get; private set; } public INServiceProvider Services { get; private set; } public BotCredentials Credentials { get; } - public NadekoBot() + private const string _mutexName = @"Global\nadeko_shards_lock"; + private readonly Semaphore sem = new Semaphore(1, 1, _mutexName); + public int ShardId { get; } + public ShardsCoordinator ShardCoord { get; private set; } + + private readonly ShardComClient _comClient; + + public NadekoBot(int shardId, int parentProcessId, int? port = null) { - SetupLogger(); + if (shardId < 0) + throw new ArgumentOutOfRangeException(nameof(shardId)); + + ShardId = shardId; + + LogSetup.SetupLogger(); _log = LogManager.GetCurrentClassLogger(); TerribleElevatedPermissionCheck(); - + Credentials = new BotCredentials(); + + port = port ?? Credentials.ShardRunPort; + _comClient = new ShardComClient(port.Value); + Db = new DbService(Credentials); using (var uow = Db.UnitOfWork) { - AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs().ToImmutableArray(); BotConfig = uow.BotConfig.GetOrCreate(); OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16)); ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16)); } - Client = new DiscordShardedClient(new DiscordSocketConfig + Client = new DiscordSocketClient(new DiscordSocketConfig { MessageCacheSize = 10, LogLevel = LogSeverity.Warning, - TotalShards = Credentials.TotalShards, ConnectionTimeout = int.MaxValue, + TotalShards = Credentials.TotalShards, + ShardId = shardId, AlwaysDownloadUsers = false, }); @@ -94,182 +107,245 @@ namespace NadekoBot CaseSensitiveCommands = false, DefaultRunMode = RunMode.Sync, }); - - //foundation services - Localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db); - Strings = new NadekoStrings(Localization); - CommandHandler = new CommandHandler(Client, Db, BotConfig, AllGuildConfigs, CommandService, Credentials, this); - Stats = new StatsService(Client, CommandHandler, Credentials); + Images = new ImagesService(); Currency = new CurrencyService(BotConfig, Db); GoogleApi = new GoogleApiService(Credentials); + SetupShard(shardId, parentProcessId, port.Value); + #if GLOBAL_NADEKO Client.Log += Client_Log; #endif } + private void StartSendingData() + { + Task.Run(async () => + { + while (true) + { + await _comClient.Send(new ShardComMessage() + { + ConnectionState = Client.ConnectionState, + Guilds = Client.ConnectionState == ConnectionState.Connected ? Client.Guilds.Count : 0, + ShardId = Client.ShardId, + }); + await Task.Delay(1000); + } + }); + } + private void AddServices() { - var soundcloudApiService = new SoundCloudApiService(Credentials); + var startingGuildIdList = Client.Guilds.Select(x => (long)x.Id).ToList(); - #region help - var helpService = new HelpService(BotConfig, CommandHandler, Strings); - #endregion + //this unit of work will be used for initialization of all modules too, to prevent multiple queries from running + using (var uow = Db.UnitOfWork) + { + AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray(); + + Localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db); + Strings = new NadekoStrings(Localization); + CommandHandler = new CommandHandler(Client, Db, BotConfig, AllGuildConfigs, CommandService, Credentials, this); + Stats = new StatsService(Client, CommandHandler, Credentials, ShardCoord); - //module services - //todo 90 - autodiscover, DI, and add instead of manual like this - #region utility - var crossServerTextService = new CrossServerTextService(AllGuildConfigs, Client); - var remindService = new RemindService(Client, BotConfig, Db); - var repeaterService = new MessageRepeaterService(this, Client, AllGuildConfigs); - var converterService = new ConverterService(Db); - var commandMapService = new CommandMapService(AllGuildConfigs); - var patreonRewardsService = new PatreonRewardsService(Credentials, Db, Currency); - var verboseErrorsService = new VerboseErrorsService(AllGuildConfigs, Db, CommandHandler, helpService); - var pruneService = new PruneService(); - #endregion + var soundcloudApiService = new SoundCloudApiService(Credentials); - #region permissions - var permissionsService = new PermissionService(Db, BotConfig, CommandHandler); - var blacklistService = new BlacklistService(BotConfig); - var cmdcdsService = new CmdCdService(AllGuildConfigs); - var filterService = new FilterService(Client, AllGuildConfigs); - var globalPermsService = new GlobalPermissionService(BotConfig); - #endregion + #region help + var helpService = new HelpService(BotConfig, CommandHandler, Strings); + #endregion - #region Searches - var searchesService = new SearchesService(Client, GoogleApi, Db); - var streamNotificationService = new StreamNotificationService(Db, Client, Strings); - var animeSearchService = new AnimeSearchService(); - #endregion + //module services + //todo 90 - autodiscover, DI, and add instead of manual like this + #region utility + var remindService = new RemindService(Client, BotConfig, Db, startingGuildIdList, uow); + var repeaterService = new MessageRepeaterService(this, Client, AllGuildConfigs); + //var converterService = new ConverterService(Db); + var commandMapService = new CommandMapService(AllGuildConfigs); + var patreonRewardsService = new PatreonRewardsService(Credentials, Db, Currency, Client); + var verboseErrorsService = new VerboseErrorsService(AllGuildConfigs, Db, CommandHandler, helpService); + var pruneService = new PruneService(); + #endregion - var clashService = new ClashOfClansService(Client, Db, Localization, Strings); - var musicService = new MusicService(GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs); - var crService = new CustomReactionsService(permissionsService, Db, Client, CommandHandler, BotConfig); + #region permissions + var permissionsService = new PermissionService(Client, Db, BotConfig, CommandHandler, Strings); + var blacklistService = new BlacklistService(BotConfig); + var cmdcdsService = new CmdCdService(AllGuildConfigs); + var filterService = new FilterService(Client, AllGuildConfigs); + var globalPermsService = new GlobalPermissionService(BotConfig); + #endregion - #region Games - var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, Strings, Images, CommandHandler); - var chatterBotService = new ChatterBotService(Client, permissionsService, AllGuildConfigs, CommandHandler); - var pollService = new PollService(Client, Strings); - #endregion + #region Searches + var searchesService = new SearchesService(Client, GoogleApi, Db); + var streamNotificationService = new StreamNotificationService(Db, Client, Strings); + var animeSearchService = new AnimeSearchService(); + #endregion - #region administration - var administrationService = new AdministrationService(AllGuildConfigs, CommandHandler); - var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, Db); - var selfService = new SelfService(Client, this, CommandHandler, Db, BotConfig, Localization, Strings, Credentials); - var vcRoleService = new VcRoleService(Client, AllGuildConfigs, Db); - var vPlusTService = new VplusTService(Client, AllGuildConfigs, Strings, Db); - var muteService = new MuteService(Client, AllGuildConfigs, Db); - var ratelimitService = new SlowmodeService(Client, AllGuildConfigs); - var protectionService = new ProtectionService(Client, AllGuildConfigs, muteService); - var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService); - var gameVcService = new GameVoiceChannelService(Client, Db, AllGuildConfigs); - var autoAssignRoleService = new AutoAssignRoleService(Client, AllGuildConfigs); - var logCommandService = new LogCommandService(Client, Strings, AllGuildConfigs, Db, muteService, protectionService); - var guildTimezoneService = new GuildTimezoneService(AllGuildConfigs, Db); - #endregion + var clashService = new ClashOfClansService(Client, Db, Localization, Strings, uow, startingGuildIdList); + var musicService = new MusicService(GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs); + var crService = new CustomReactionsService(permissionsService, Db, Strings, Client, CommandHandler, BotConfig, uow); - #region pokemon - var pokemonService = new PokemonService(); - #endregion + #region Games + var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, Strings, Images, CommandHandler); + var chatterBotService = new ChatterBotService(Client, permissionsService, AllGuildConfigs, CommandHandler, Strings); + var pollService = new PollService(Client, Strings); + #endregion + + #region administration + var administrationService = new AdministrationService(AllGuildConfigs, CommandHandler); + var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, Db); + var selfService = new SelfService(Client, this, CommandHandler, Db, BotConfig, Localization, Strings, Credentials); + var vcRoleService = new VcRoleService(Client, AllGuildConfigs, Db); + var vPlusTService = new VplusTService(Client, AllGuildConfigs, Strings, Db); + var muteService = new MuteService(Client, AllGuildConfigs, Db); + var ratelimitService = new SlowmodeService(Client, AllGuildConfigs); + var protectionService = new ProtectionService(Client, AllGuildConfigs, muteService); + var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService); + var gameVcService = new GameVoiceChannelService(Client, Db, AllGuildConfigs); + var autoAssignRoleService = new AutoAssignRoleService(Client, AllGuildConfigs); + var logCommandService = new LogCommandService(Client, Strings, AllGuildConfigs, Db, muteService, protectionService); + var guildTimezoneService = new GuildTimezoneService(AllGuildConfigs, Db); + #endregion + + #region pokemon + var pokemonService = new PokemonService(); + #endregion - //initialize Services - Services = new NServiceProvider.ServiceProviderBuilder() - .Add(Localization) - .Add(Stats) - .Add(Images) - .Add(GoogleApi) - .Add(Stats) - .Add(Credentials) - .Add(CommandService) - .Add(Strings) - .Add(Client) - .Add(BotConfig) - .Add(Currency) - .Add(CommandHandler) - .Add(Db) - //modules - .Add(crossServerTextService) - .Add(commandMapService) - .Add(remindService) - .Add(repeaterService) - .Add(converterService) - .Add(verboseErrorsService) - .Add(patreonRewardsService) - .Add(pruneService) - .Add(searchesService) - .Add(streamNotificationService) - .Add(animeSearchService) - .Add(clashService) - .Add(musicService) - .Add(greetSettingsService) - .Add(crService) - .Add(helpService) - .Add(gamesService) - .Add(chatterBotService) - .Add(pollService) - .Add(administrationService) - .Add(selfService) - .Add(vcRoleService) - .Add(vPlusTService) - .Add(muteService) - .Add(ratelimitService) - .Add(playingRotateService) - .Add(gameVcService) - .Add(autoAssignRoleService) - .Add(protectionService) - .Add(logCommandService) - .Add(guildTimezoneService) - .Add(permissionsService) - .Add(blacklistService) - .Add(cmdcdsService) - .Add(filterService) - .Add(globalPermsService) - .Add(pokemonService) - .Build(); - CommandHandler.AddServices(Services); + //initialize Services + Services = new NServiceProvider.ServiceProviderBuilder() + .Add(Localization) + .Add(Stats) + .Add(Images) + .Add(GoogleApi) + .Add(Stats) + .Add(Credentials) + .Add(CommandService) + .Add(Strings) + .Add(Client) + .Add(BotConfig) + .Add(Currency) + .Add(CommandHandler) + .Add(Db) + //modules + .Add(commandMapService) + .Add(remindService) + .Add(repeaterService) + //.Add(converterService) + .Add(verboseErrorsService) + .Add(patreonRewardsService) + .Add(pruneService) + .Add(searchesService) + .Add(streamNotificationService) + .Add(animeSearchService) + .Add(clashService) + .Add(musicService) + .Add(greetSettingsService) + .Add(crService) + .Add(helpService) + .Add(gamesService) + .Add(chatterBotService) + .Add(pollService) + .Add(administrationService) + .Add(selfService) + .Add(vcRoleService) + .Add(vPlusTService) + .Add(muteService) + .Add(ratelimitService) + .Add(playingRotateService) + .Add(gameVcService) + .Add(autoAssignRoleService) + .Add(protectionService) + .Add(logCommandService) + .Add(guildTimezoneService) + .Add(permissionsService) + .Add(blacklistService) + .Add(cmdcdsService) + .Add(filterService) + .Add(globalPermsService) + .Add(pokemonService) + .Add(this) + .Build(); - //setup typereaders - CommandService.AddTypeReader(new PermissionActionTypeReader()); - CommandService.AddTypeReader(new CommandTypeReader(CommandService, CommandHandler)); - CommandService.AddTypeReader(new CommandOrCrTypeReader(crService, CommandService, CommandHandler)); - CommandService.AddTypeReader(new ModuleTypeReader(CommandService)); - CommandService.AddTypeReader(new ModuleOrCrTypeReader(CommandService)); - CommandService.AddTypeReader(new GuildTypeReader(Client)); - CommandService.AddTypeReader(new GuildDateTimeTypeReader(guildTimezoneService)); + + CommandHandler.AddServices(Services); + + //setup typereaders + CommandService.AddTypeReader(new PermissionActionTypeReader()); + CommandService.AddTypeReader(new CommandTypeReader(CommandService, CommandHandler)); + CommandService.AddTypeReader(new CommandOrCrTypeReader(crService, CommandService, CommandHandler)); + CommandService.AddTypeReader(new ModuleTypeReader(CommandService)); + CommandService.AddTypeReader(new ModuleOrCrTypeReader(CommandService)); + CommandService.AddTypeReader(new GuildTypeReader(Client)); + CommandService.AddTypeReader(new GuildDateTimeTypeReader(guildTimezoneService)); + + } } private async Task LoginAsync(string token) { - _log.Info("Logging in..."); - //connect - await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false); - await Client.StartAsync().ConfigureAwait(false); + var clientReady = new TaskCompletionSource(); - _log.Info("Waiting for all shards to connect..."); - while (!Client.Shards.All(x => x.ConnectionState == ConnectionState.Connected)) + Task SetClientReady() { - _log.Info("Connecting... {0}/{1}", Client.Shards.Count(x => x.ConnectionState == ConnectionState.Connected), Client.Shards.Count); - await Task.Delay(1000).ConfigureAwait(false); + var _ = Task.Run(async () => + { + clientReady.TrySetResult(true); + try + { + foreach (var chan in (await Client.GetDMChannelsAsync())) + { + await chan.CloseAsync().ConfigureAwait(false); + } + } + catch + { + // ignored + } + finally + { + + } + }); + return Task.CompletedTask; + } + + //connect + try { sem.WaitOne(); } catch (AbandonedMutexException) { } + + _log.Info("Shard {0} logging in ...", ShardId); + + try + { + await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false); + await Client.StartAsync().ConfigureAwait(false); + Client.Ready += SetClientReady; + await clientReady.Task.ConfigureAwait(false); + Client.Ready -= SetClientReady; + } + finally + { + _log.Info("Shard {0} logged in.", ShardId); + sem.Release(); } } public async Task RunAsync(params string[] args) { + if(ShardId == 0) _log.Info("Starting NadekoBot v" + StatsService.BotVersion); var sw = Stopwatch.StartNew(); await LoginAsync(Credentials.Token).ConfigureAwait(false); - _log.Info("Loading services..."); + _log.Info($"Shard {ShardId} loading services..."); AddServices(); sw.Stop(); - _log.Info($"Connected in {sw.Elapsed.TotalSeconds:F2} s"); + _log.Info($"Shard {ShardId} connected in {sw.Elapsed.TotalSeconds:F2}s"); var stats = Services.GetService(); stats.Initialize(); @@ -290,15 +366,17 @@ namespace NadekoBot // .Select(x => x.Key + $"({x.Count()})"))); //unload modules which are not available on the public bot -#if GLOBAL_NADEKO +#if GLOBAL_NADEKO CommandService .Modules .ToArray() .Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot))) .ForEach(x => CommandService.RemoveModuleAsync(x)); #endif + Ready = true; - _log.Info(await stats.Print().ConfigureAwait(false)); + _log.Info($"Shard {ShardId} ready."); + //_log.Info(await stats.Print().ConfigureAwait(false)); } private Task Client_Log(LogMessage arg) @@ -313,7 +391,13 @@ namespace NadekoBot public async Task RunAndBlockAsync(params string[] args) { await RunAsync(args).ConfigureAwait(false); - await Task.Delay(-1).ConfigureAwait(false); + StartSendingData(); + if (ShardCoord != null) + await ShardCoord.RunAndBlockAsync(); + else + { + await Task.Delay(-1).ConfigureAwait(false); + } } private void TerribleElevatedPermissionCheck() @@ -331,18 +415,29 @@ namespace NadekoBot } } - private static void SetupLogger() + private void SetupShard(int shardId, int parentProcessId, int port) { - var logConfig = new LoggingConfiguration(); - var consoleTarget = new ColoredConsoleTarget() + if (shardId != 0) { - Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}" - }; - logConfig.AddTarget("Console", consoleTarget); - - logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); - - LogManager.Configuration = logConfig; + new Thread(new ThreadStart(() => + { + try + { + var p = Process.GetProcessById(parentProcessId); + if (p == null) + return; + p.WaitForExit(); + } + finally + { + Environment.Exit(10); + } + })).Start(); + } + else + { + ShardCoord = new ShardsCoordinator(port); + } } } } diff --git a/src/NadekoBot/NadekoBot.csproj b/src/NadekoBot/NadekoBot.csproj index 0cc722bc..18ad4911 100644 --- a/src/NadekoBot/NadekoBot.csproj +++ b/src/NadekoBot/NadekoBot.csproj @@ -90,5 +90,6 @@ + diff --git a/src/NadekoBot/Program.cs b/src/NadekoBot/Program.cs index 0c8832e2..09e53142 100644 --- a/src/NadekoBot/Program.cs +++ b/src/NadekoBot/Program.cs @@ -2,7 +2,17 @@ { public class Program { - public static void Main(string[] args) => - new NadekoBot().RunAndBlockAsync(args).GetAwaiter().GetResult(); + public static void Main(string[] args) + { + if (args.Length == 3 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId)) + { + int? port = null; + if (int.TryParse(args[2], out var outPort)) + port = outPort; + new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args).GetAwaiter().GetResult(); + } + else + new NadekoBot(0, 0).RunAndBlockAsync(args).GetAwaiter().GetResult(); + } } } diff --git a/src/NadekoBot/Properties/launchSettings.json b/src/NadekoBot/Properties/launchSettings.json new file mode 100644 index 00000000..b532770a --- /dev/null +++ b/src/NadekoBot/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "NadekoBot": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx index 708abfe8..e896535d 100644 --- a/src/NadekoBot/Resources/CommandStrings.resx +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -3096,14 +3096,14 @@ `{0}shardstats` or `{0}shardstats 2` - - connectshard + + restartshard - + Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors. - - `{0}connectshard 2` + + `{0}restartshard 2` shardid diff --git a/src/NadekoBot/Services/Administration/AutoAssignRoleService.cs b/src/NadekoBot/Services/Administration/AutoAssignRoleService.cs index 14679b6d..7c8b8711 100644 --- a/src/NadekoBot/Services/Administration/AutoAssignRoleService.cs +++ b/src/NadekoBot/Services/Administration/AutoAssignRoleService.cs @@ -12,12 +12,12 @@ namespace NadekoBot.Services.Administration public class AutoAssignRoleService { private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; //guildid/roleid public ConcurrentDictionary AutoAssignedRoles { get; } - public AutoAssignRoleService(DiscordShardedClient client, IEnumerable gcs) + public AutoAssignRoleService(DiscordSocketClient client, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Services/Administration/GameVoiceChannelService.cs b/src/NadekoBot/Services/Administration/GameVoiceChannelService.cs index 14f9c6c6..52ea08f7 100644 --- a/src/NadekoBot/Services/Administration/GameVoiceChannelService.cs +++ b/src/NadekoBot/Services/Administration/GameVoiceChannelService.cs @@ -16,9 +16,9 @@ namespace NadekoBot.Services.Administration private readonly Logger _log; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public GameVoiceChannelService(DiscordShardedClient client, DbService db, IEnumerable gcs) + public GameVoiceChannelService(DiscordSocketClient client, DbService db, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); _db = db; diff --git a/src/NadekoBot/Services/Administration/LogCommandService.cs b/src/NadekoBot/Services/Administration/LogCommandService.cs index 84555345..420ada80 100644 --- a/src/NadekoBot/Services/Administration/LogCommandService.cs +++ b/src/NadekoBot/Services/Administration/LogCommandService.cs @@ -16,7 +16,7 @@ namespace NadekoBot.Services.Administration public class LogCommandService { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly Logger _log; private string PrettyCurrentTime => $"【{DateTime.UtcNow:HH:mm:ss}】"; @@ -31,7 +31,7 @@ namespace NadekoBot.Services.Administration private readonly MuteService _mute; private readonly ProtectionService _prot; - public LogCommandService(DiscordShardedClient client, NadekoStrings strings, + public LogCommandService(DiscordSocketClient client, NadekoStrings strings, IEnumerable gcs, DbService db, MuteService mute, ProtectionService prot) { _client = client; @@ -74,7 +74,7 @@ namespace NadekoBot.Services.Administration _client.UserUnbanned += _client_UserUnbanned; _client.UserJoined += _client_UserJoined; _client.UserLeft += _client_UserLeft; - _client.UserPresenceUpdated += _client_UserPresenceUpdated; + //_client.UserPresenceUpdated += _client_UserPresenceUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS; _client.GuildMemberUpdated += _client_GuildUserUpdated; @@ -576,48 +576,48 @@ namespace NadekoBot.Services.Administration return Task.CompletedTask; } - private Task _client_UserPresenceUpdated(Optional optGuild, SocketUser usr, SocketPresence before, SocketPresence after) - { - var _ = Task.Run(async () => - { - try - { - var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild; + //private Task _client_UserPresenceUpdated(Optional optGuild, SocketUser usr, SocketPresence before, SocketPresence after) + //{ + // var _ = Task.Run(async () => + // { + // try + // { + // var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild; - if (guild == null) - return; + // if (guild == null) + // return; - if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting) - || (logSetting.LogUserPresenceId == null) - || before.Status == after.Status) - return; + // if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting) + // || (logSetting.LogUserPresenceId == null) + // || before.Status == after.Status) + // return; - ITextChannel logChannel; - if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null) - return; - string str = ""; - if (before.Status != after.Status) - str = "🎭" + Format.Code(PrettyCurrentTime) + - GetText(logChannel.Guild, "user_status_change", - "👤" + Format.Bold(usr.Username), - Format.Bold(after.Status.ToString())); + // ITextChannel logChannel; + // if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null) + // return; + // string str = ""; + // if (before.Status != after.Status) + // str = "🎭" + Format.Code(PrettyCurrentTime) + + // GetText(logChannel.Guild, "user_status_change", + // "👤" + Format.Bold(usr.Username), + // Format.Bold(after.Status.ToString())); - //if (before.Game?.Name != after.Game?.Name) - //{ - // if (str != "") - // str += "\n"; - // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**."; - //} + // //if (before.Game?.Name != after.Game?.Name) + // //{ + // // if (str != "") + // // str += "\n"; + // // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**."; + // //} - PresenceUpdates.AddOrUpdate(logChannel, new List() { str }, (id, list) => { list.Add(str); return list; }); - } - catch - { - // ignored - } - }); - return Task.CompletedTask; - } + // PresenceUpdates.AddOrUpdate(logChannel, new List() { str }, (id, list) => { list.Add(str); return list; }); + // } + // catch + // { + // // ignored + // } + // }); + // return Task.CompletedTask; + //} private Task _client_UserLeft(IGuildUser usr) { diff --git a/src/NadekoBot/Services/Administration/MuteService.cs b/src/NadekoBot/Services/Administration/MuteService.cs index 7ed9062a..87bde743 100644 --- a/src/NadekoBot/Services/Administration/MuteService.cs +++ b/src/NadekoBot/Services/Administration/MuteService.cs @@ -33,10 +33,10 @@ namespace NadekoBot.Services.Administration private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny); private readonly Logger _log = LogManager.GetCurrentClassLogger(); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly DbService _db; - public MuteService(DiscordShardedClient client, IEnumerable gcs, DbService db) + public MuteService(DiscordSocketClient client, IEnumerable gcs, DbService db) { _client = client; _db = db; diff --git a/src/NadekoBot/Services/Administration/PlayingRotateService.cs b/src/NadekoBot/Services/Administration/PlayingRotateService.cs index 7662b7ca..c7a1bf41 100644 --- a/src/NadekoBot/Services/Administration/PlayingRotateService.cs +++ b/src/NadekoBot/Services/Administration/PlayingRotateService.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Services.Administration public List RotatingStatusMessages { get; } public volatile bool RotatingStatuses; private readonly Timer _t; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly BotConfig _bc; private readonly MusicService _music; private readonly Logger _log; @@ -27,7 +27,7 @@ namespace NadekoBot.Services.Administration public int Index { get; set; } } - public PlayingRotateService(DiscordShardedClient client, BotConfig bc, MusicService music) + public PlayingRotateService(DiscordSocketClient client, BotConfig bc, MusicService music) { _client = client; _bc = bc; @@ -36,7 +36,7 @@ namespace NadekoBot.Services.Administration RotatingStatusMessages = _bc.RotatingStatusMessages; RotatingStatuses = _bc.RotatingStatuses; - + _t = new Timer(async (objState) => { try @@ -52,17 +52,12 @@ namespace NadekoBot.Services.Administration var status = RotatingStatusMessages[state.Index++].Status; if (string.IsNullOrWhiteSpace(status)) return; - PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client,_music))); - var shards = _client.Shards; - for (int i = 0; i < shards.Count; i++) + PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client, _music))); + ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(client))); + try { await client.SetGameAsync(status).ConfigureAwait(false); } + catch (Exception ex) { - var curShard = shards.ElementAt(i); - ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(curShard))); - try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); } - catch (Exception ex) - { - _log.Warn(ex); - } + _log.Warn(ex); } } catch (Exception ex) @@ -72,8 +67,8 @@ namespace NadekoBot.Services.Administration }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); } - public Dictionary> PlayingPlaceholders { get; } = - new Dictionary> { + public Dictionary> PlayingPlaceholders { get; } = + new Dictionary> { { "%servers%", (c, ms) => c.Guilds.Count.ToString()}, { "%users%", (c, ms) => c.Guilds.Sum(s => s.Users.Count).ToString()}, { "%playing%", (c, ms) => { @@ -90,7 +85,6 @@ namespace NadekoBot.Services.Administration }, { "%queued%", (c, ms) => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, { "%time%", (c, ms) => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) }, - { "%shardcount%", (c, ms) => c.Shards.Count.ToString() }, }; public Dictionary> ShardSpecificPlaceholders { get; } = diff --git a/src/NadekoBot/Services/Administration/ProtectionService.cs b/src/NadekoBot/Services/Administration/ProtectionService.cs index 23bca7ad..6a2ae99b 100644 --- a/src/NadekoBot/Services/Administration/ProtectionService.cs +++ b/src/NadekoBot/Services/Administration/ProtectionService.cs @@ -21,10 +21,10 @@ namespace NadekoBot.Services.Administration public event Func OnAntiProtectionTriggered = delegate { return Task.CompletedTask; }; private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly MuteService _mute; - public ProtectionService(DiscordShardedClient client, IEnumerable gcs, MuteService mute) + public ProtectionService(DiscordSocketClient client, IEnumerable gcs, MuteService mute) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Services/Administration/RatelimitService.cs b/src/NadekoBot/Services/Administration/RatelimitService.cs index b344930f..a0f4171f 100644 --- a/src/NadekoBot/Services/Administration/RatelimitService.cs +++ b/src/NadekoBot/Services/Administration/RatelimitService.cs @@ -19,9 +19,9 @@ namespace NadekoBot.Services.Administration public ConcurrentDictionary> IgnoredUsers = new ConcurrentDictionary>(); private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; - public SlowmodeService(DiscordShardedClient client, IEnumerable gcs) + public SlowmodeService(DiscordSocketClient client, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); _client = client; diff --git a/src/NadekoBot/Services/Administration/SelfService.cs b/src/NadekoBot/Services/Administration/SelfService.cs index f3148d5a..2a12ffca 100644 --- a/src/NadekoBot/Services/Administration/SelfService.cs +++ b/src/NadekoBot/Services/Administration/SelfService.cs @@ -23,11 +23,11 @@ namespace NadekoBot.Services.Administration private readonly Logger _log; private readonly ILocalization _localization; private readonly NadekoStrings _strings; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private ImmutableArray> ownerChannels = new ImmutableArray>(); - public SelfService(DiscordShardedClient client, NadekoBot bot, CommandHandler cmdHandler, DbService db, + public SelfService(DiscordSocketClient client, NadekoBot bot, CommandHandler cmdHandler, DbService db, BotConfig bc, ILocalization localization, NadekoStrings strings, IBotCredentials creds) { _bot = bot; @@ -39,12 +39,8 @@ namespace NadekoBot.Services.Administration _client = client; _creds = creds; - using (var uow = _db.UnitOfWork) - { - var config = uow.BotConfig.GetOrCreate(); - ForwardDMs = config.ForwardMessages; - ForwardDMsToAllOwners = config.ForwardToAllOwners; - } + ForwardDMs = bc.ForwardMessages; + ForwardDMsToAllOwners = bc.ForwardToAllOwners; var _ = Task.Run(async () => { @@ -67,12 +63,8 @@ namespace NadekoBot.Services.Administration _client.Guilds.SelectMany(g => g.Users); - LoadOwnerChannels(); - - if (!ownerChannels.Any()) - _log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file."); - else - _log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels."); + if(client.ShardId == 0) + LoadOwnerChannels(); }); } @@ -81,11 +73,9 @@ namespace NadekoBot.Services.Administration var hs = new HashSet(_creds.OwnerIds); var channels = new Dictionary>(); - foreach (var s in _client.Shards) + if (hs.Count > 0) { - if (hs.Count == 0) - break; - foreach (var g in s.Guilds) + foreach (var g in _client.Guilds) { if (hs.Count == 0) break; @@ -105,10 +95,15 @@ namespace NadekoBot.Services.Administration ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key)) .Select(x => x.Value) .ToImmutableArray(); + + if (!ownerChannels.Any()) + _log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file."); + else + _log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels."); } // forwards dms - public async Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg) { if (msg.Channel is IDMChannel && ForwardDMs && ownerChannels.Length > 0) { diff --git a/src/NadekoBot/Services/Administration/VcRoleService.cs b/src/NadekoBot/Services/Administration/VcRoleService.cs index 49dbe9ff..10252833 100644 --- a/src/NadekoBot/Services/Administration/VcRoleService.cs +++ b/src/NadekoBot/Services/Administration/VcRoleService.cs @@ -14,11 +14,11 @@ namespace NadekoBot.Services.Administration { private readonly Logger _log; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public ConcurrentDictionary> VcRoles { get; } - public VcRoleService(DiscordShardedClient client, IEnumerable gcs, DbService db) + public VcRoleService(DiscordSocketClient client, IEnumerable gcs, DbService db) { _log = LogManager.GetCurrentClassLogger(); _db = db; diff --git a/src/NadekoBot/Services/Administration/VplusTService.cs b/src/NadekoBot/Services/Administration/VplusTService.cs index 3d9e86cd..b4e3122d 100644 --- a/src/NadekoBot/Services/Administration/VplusTService.cs +++ b/src/NadekoBot/Services/Administration/VplusTService.cs @@ -20,12 +20,12 @@ namespace NadekoBot.Services.Administration public readonly ConcurrentHashSet VoicePlusTextCache; private readonly ConcurrentDictionary _guildLockObjects = new ConcurrentDictionary(); - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; private readonly DbService _db; private readonly Logger _log; - public VplusTService(DiscordShardedClient client, IEnumerable gcs, NadekoStrings strings, + public VplusTService(DiscordSocketClient client, IEnumerable gcs, NadekoStrings strings, DbService db) { _client = client; diff --git a/src/NadekoBot/Services/ClashOfClans/ClashOfClansService.cs b/src/NadekoBot/Services/ClashOfClans/ClashOfClansService.cs index edb78512..dcdd6376 100644 --- a/src/NadekoBot/Services/ClashOfClans/ClashOfClansService.cs +++ b/src/NadekoBot/Services/ClashOfClans/ClashOfClansService.cs @@ -1,6 +1,7 @@ using Discord; using Discord.WebSocket; using NadekoBot.Extensions; +using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; using System; using System.Collections.Concurrent; @@ -17,7 +18,7 @@ namespace NadekoBot.Services.ClashOfClans // shouldn't be here public class ClashOfClansService { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly DbService _db; private readonly ILocalization _localization; private readonly NadekoStrings _strings; @@ -25,28 +26,27 @@ namespace NadekoBot.Services.ClashOfClans public ConcurrentDictionary> ClashWars { get; set; } - public ClashOfClansService(DiscordShardedClient client, DbService db, ILocalization localization, NadekoStrings strings) + public ClashOfClansService(DiscordSocketClient client, DbService db, + ILocalization localization, NadekoStrings strings, IUnitOfWork uow, + List guilds) { _client = client; _db = db; _localization = localization; _strings = strings; - using (var uow = _db.UnitOfWork) - { - ClashWars = new ConcurrentDictionary>( - uow.ClashOfClans - .GetAllWars() - .Select(cw => - { - cw.Channel = _client.GetGuild(cw.GuildId)? - .GetTextChannel(cw.ChannelId); - return cw; - }) - .Where(cw => cw.Channel != null) - .GroupBy(cw => cw.GuildId) - .ToDictionary(g => g.Key, g => g.ToList())); - } + ClashWars = new ConcurrentDictionary>( + uow.ClashOfClans + .GetAllWars(guilds) + .Select(cw => + { + cw.Channel = _client.GetGuild(cw.GuildId)? + .GetTextChannel(cw.ChannelId); + return cw; + }) + .Where(cw => cw.Channel != null) + .GroupBy(cw => cw.GuildId) + .ToDictionary(g => g.Key, g => g.ToList())); checkWarTimer = new Timer(async _ => { diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs index 2177ce01..a1de8871 100644 --- a/src/NadekoBot/Services/CommandHandler.cs +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -28,7 +28,7 @@ namespace NadekoBot.Services { public const int GlobalCommandsCooldown = 750; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly CommandService _commandService; private readonly Logger _log; private readonly IBotCredentials _creds; @@ -48,7 +48,7 @@ namespace NadekoBot.Services public ConcurrentHashSet UsersOnShortCooldown { get; } = new ConcurrentHashSet(); private readonly Timer _clearUsersOnShortCooldown; - public CommandHandler(DiscordShardedClient client, DbService db, BotConfig bc, IEnumerable gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot) + public CommandHandler(DiscordSocketClient client, DbService db, BotConfig bc, IEnumerable gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot) { _client = client; _commandService = commandService; diff --git a/src/NadekoBot/Services/CustomReactions/CustomReactionsService.cs b/src/NadekoBot/Services/CustomReactions/CustomReactionsService.cs index 206b0bd9..c64f5c81 100644 --- a/src/NadekoBot/Services/CustomReactions/CustomReactionsService.cs +++ b/src/NadekoBot/Services/CustomReactions/CustomReactionsService.cs @@ -10,6 +10,7 @@ using System; using System.Threading.Tasks; using NadekoBot.Services.Permissions; using NadekoBot.Extensions; +using NadekoBot.Services.Database; namespace NadekoBot.Services.CustomReactions { @@ -22,13 +23,14 @@ namespace NadekoBot.Services.CustomReactions private readonly Logger _log; private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly PermissionService _perms; private readonly CommandHandler _cmd; private readonly BotConfig _bc; + private readonly NadekoStrings _strings; - public CustomReactionsService(PermissionService perms, DbService db, - DiscordShardedClient client, CommandHandler cmd, BotConfig bc) + public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings, + DiscordSocketClient client, CommandHandler cmd, BotConfig bc, IUnitOfWork uow) { _log = LogManager.GetCurrentClassLogger(); _db = db; @@ -36,16 +38,11 @@ namespace NadekoBot.Services.CustomReactions _perms = perms; _cmd = cmd; _bc = bc; - - var sw = Stopwatch.StartNew(); - using (var uow = _db.UnitOfWork) - { - var items = uow.CustomReactions.GetAll(); - GuildReactions = new ConcurrentDictionary(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(); - } - sw.Stop(); - _log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s"); + _strings = strings; + + var items = uow.CustomReactions.GetAll(); + GuildReactions = new ConcurrentDictionary(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 void ClearStats() => ReactionStats.Clear(); @@ -98,7 +95,7 @@ namespace NadekoBot.Services.CustomReactions return greaction; } - public async Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg) { // maybe this message is a custom reaction var cr = await Task.Run(() => TryGetCustomReaction(msg)).ConfigureAwait(false); @@ -114,7 +111,7 @@ namespace NadekoBot.Services.CustomReactions { if (pc.Verbose) { - var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action."; + var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild))); try { await msg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } _log.Info(returnMsg); } diff --git a/src/NadekoBot/Services/CustomReactions/Extensions.cs b/src/NadekoBot/Services/CustomReactions/Extensions.cs index e2c5481f..4d6d4d06 100644 --- a/src/NadekoBot/Services/CustomReactions/Extensions.cs +++ b/src/NadekoBot/Services/CustomReactions/Extensions.cs @@ -40,7 +40,7 @@ namespace NadekoBot.Services.CustomReactions } }, }; - public static Dictionary> placeholders = new Dictionary>() + public static Dictionary> placeholders = new Dictionary>() { {"%mention%", (ctx, client) => { return $"<@{client.CurrentUser.Id}>"; } }, {"%user%", (ctx, client) => { return ctx.Author.Mention; } }, @@ -94,7 +94,7 @@ namespace NadekoBot.Services.CustomReactions } } }; - private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordShardedClient client) + private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordSocketClient client) { foreach (var ph in placeholders) { @@ -104,7 +104,7 @@ namespace NadekoBot.Services.CustomReactions return str; } - private static async Task ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordShardedClient client, string resolvedTrigger) + private static async Task ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordSocketClient client, string resolvedTrigger) { foreach (var ph in placeholders) { @@ -127,13 +127,13 @@ namespace NadekoBot.Services.CustomReactions return str; } - public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client) + public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client) => cr.Trigger.ResolveTriggerString(ctx, client); - public static Task ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client) + public static Task ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client) => cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client)); - public static async Task Send(this CustomReaction cr, IUserMessage context, DiscordShardedClient client, CustomReactionsService crs) + public static async Task Send(this CustomReaction cr, IUserMessage context, DiscordSocketClient client, CustomReactionsService crs) { var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel; diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs index 9c084867..238130ed 100644 --- a/src/NadekoBot/Services/Database/NadekoContext.cs +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -19,7 +19,9 @@ namespace NadekoBot.Services.Database { var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); - return new NadekoContext(optionsBuilder.Options); + var ctx = new NadekoContext(optionsBuilder.Options); + ctx.Database.SetCommandTimeout(60); + return ctx; } } diff --git a/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs b/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs index 756e9789..14edcea8 100644 --- a/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs @@ -5,6 +5,6 @@ namespace NadekoBot.Services.Database.Repositories { public interface IClashOfClansRepository : IRepository { - IEnumerable GetAllWars(); + IEnumerable GetAllWars(List guilds); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs index cca54609..e56a9026 100644 --- a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs @@ -11,10 +11,10 @@ namespace NadekoBot.Services.Database.Repositories GuildConfig For(ulong guildId, Func, IQueryable> includes = null); GuildConfig LogSettingsFor(ulong guildId); IEnumerable OldPermissionsForAll(); - IEnumerable GetAllGuildConfigs(); - IEnumerable GetAllFollowedStreams(); + IEnumerable GetAllGuildConfigs(List availableGuilds); + IEnumerable GetAllFollowedStreams(List included); void SetCleverbotEnabled(ulong id, bool cleverbotEnabled); - IEnumerable Permissionsv2ForAll(); + IEnumerable Permissionsv2ForAll(List include); GuildConfig GcWithPermissionsv2For(ulong guildId); } } diff --git a/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs b/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs index 7c643ec7..07eec33f 100644 --- a/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs @@ -1,9 +1,11 @@ using NadekoBot.Services.Database.Models; +using System.Collections; +using System.Collections.Generic; namespace NadekoBot.Services.Database.Repositories { public interface IReminderRepository : IRepository { - + IEnumerable GetIncludedReminders(List guildIds); } } diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs index 54a391fa..828c4bce 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs @@ -11,9 +11,11 @@ namespace NadekoBot.Services.Database.Repositories.Impl { } - public IEnumerable GetAllWars() + public IEnumerable GetAllWars(List guilds) { - var toReturn = _set.Include(cw => cw.Bases) + var toReturn = _set + .Where(cw => guilds.Contains((long)cw.GuildId)) + .Include(cw => cw.Bases) .ToList(); toReturn.ForEach(cw => cw.Bases = cw.Bases.Where(w => w.SequenceNumber != null).OrderBy(w => w.SequenceNumber).ToList()); return toReturn; diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs index f3ed3566..3628a4e9 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs @@ -24,8 +24,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl } }; - public IEnumerable GetAllGuildConfigs() => - _set.Include(gc => gc.LogSetting) + public IEnumerable GetAllGuildConfigs(List availableGuilds) => + _set + .Where(gc => availableGuilds.Contains((long)gc.GuildId)) + .Include(gc => gc.LogSetting) .ThenInclude(ls => ls.IgnoredChannels) .Include(gc => gc.MutedUsers) .Include(gc => gc.CommandAliases) @@ -42,6 +44,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl .Include(gc => gc.SlowmodeIgnoredUsers) .Include(gc => gc.AntiSpamSetting) .ThenInclude(x => x.IgnoredChannels) + .Include(gc => gc.FollowedStreams) .ToList(); /// @@ -134,9 +137,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl return query.ToList(); } - public IEnumerable Permissionsv2ForAll() + public IEnumerable Permissionsv2ForAll(List include) { var query = _set + .Where(x => include.Contains((long)x.GuildId)) .Include(gc => gc.Permissions); return query.ToList(); @@ -167,8 +171,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl return config; } - public IEnumerable GetAllFollowedStreams() => - _set.Include(gc => gc.FollowedStreams) + public IEnumerable GetAllFollowedStreams(List included) => + _set + .Where(gc => included.Contains((long)gc.GuildId)) + .Include(gc => gc.FollowedStreams) .SelectMany(gc => gc.FollowedStreams) .ToList(); diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs index fc7c28ff..b1a0e2a0 100644 --- a/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs @@ -1,5 +1,8 @@ using NadekoBot.Services.Database.Models; using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; namespace NadekoBot.Services.Database.Repositories.Impl { @@ -8,5 +11,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl public ReminderRepository(DbContext context) : base(context) { } + + public IEnumerable GetIncludedReminders(List guildIds) + { + return _set.Where(x => guildIds.Contains((long)x.ServerId)).ToList(); + } } } diff --git a/src/NadekoBot/Services/DbService.cs b/src/NadekoBot/Services/DbService.cs index 81e56d1d..6e9532ca 100644 --- a/src/NadekoBot/Services/DbService.cs +++ b/src/NadekoBot/Services/DbService.cs @@ -32,9 +32,21 @@ namespace NadekoBot.Services public NadekoContext GetDbContext() { var context = new NadekoContext(options); + context.Database.SetCommandTimeout(60); context.Database.Migrate(); context.EnsureSeedData(); + //set important sqlite stuffs + var conn = context.Database.GetDbConnection(); + conn.Open(); + + context.Database.ExecuteSqlCommand("PRAGMA journal_mode=WAL"); + using (var com = conn.CreateCommand()) + { + com.CommandText = "PRAGMA journal_mode=WAL; PRAGMA synchronous=OFF"; + com.ExecuteNonQuery(); + } + return context; } diff --git a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs index 58b3a1e5..4903fd66 100644 --- a/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs +++ b/src/NadekoBot/Services/Discord/SocketMessageEventWrapper.cs @@ -12,7 +12,7 @@ namespace NadekoBot.Services.Discord public event Action OnReactionRemoved = delegate { }; public event Action OnReactionsCleared = delegate { }; - public ReactionEventWrapper(DiscordShardedClient client, IUserMessage msg) + public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg) { Message = msg ?? throw new ArgumentNullException(nameof(msg)); _client = client; @@ -69,7 +69,7 @@ namespace NadekoBot.Services.Discord } private bool disposing = false; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; public void Dispose() { diff --git a/src/NadekoBot/Services/Games/ChatterbotService.cs b/src/NadekoBot/Services/Games/ChatterbotService.cs index 8039b79d..833eef0b 100644 --- a/src/NadekoBot/Services/Games/ChatterbotService.cs +++ b/src/NadekoBot/Services/Games/ChatterbotService.cs @@ -15,19 +15,22 @@ namespace NadekoBot.Services.Games { public class ChatterBotService : IEarlyBlockingExecutor { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly Logger _log; private readonly PermissionService _perms; private readonly CommandHandler _cmd; + private readonly NadekoStrings _strings; public ConcurrentDictionary> ChatterBotGuilds { get; } - public ChatterBotService(DiscordShardedClient client, PermissionService perms, IEnumerable gcs, CommandHandler cmd) + public ChatterBotService(DiscordSocketClient client, PermissionService perms, IEnumerable gcs, + CommandHandler cmd, NadekoStrings strings) { _client = client; _log = LogManager.GetCurrentClassLogger(); _perms = perms; _cmd = cmd; + _strings = strings; ChatterBotGuilds = new ConcurrentDictionary>( gcs.Where(gc => gc.CleverbotEnabled) @@ -83,7 +86,7 @@ namespace NadekoBot.Services.Games return true; } - public async Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage usrMsg) + public async Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage usrMsg) { if (!(guild is SocketGuild sg)) return false; @@ -102,7 +105,7 @@ namespace NadekoBot.Services.Games if (pc.Verbose) { //todo move this to permissions - var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action."; + var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild))); try { await usrMsg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } _log.Info(returnMsg); } diff --git a/src/NadekoBot/Services/Games/GamesService.cs b/src/NadekoBot/Services/Games/GamesService.cs index 24139906..e677990b 100644 --- a/src/NadekoBot/Services/Games/GamesService.cs +++ b/src/NadekoBot/Services/Games/GamesService.cs @@ -23,7 +23,7 @@ namespace NadekoBot.Services.Games public readonly ImmutableArray EightBallResponses; private readonly Timer _t; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; private readonly IImagesService _images; private readonly Logger _log; @@ -33,7 +33,7 @@ namespace NadekoBot.Services.Games public List TypingArticles { get; } = new List(); - public GamesService(DiscordShardedClient client, BotConfig bc, IEnumerable gcs, + public GamesService(DiscordSocketClient client, BotConfig bc, IEnumerable gcs, NadekoStrings strings, IImagesService images, CommandHandler cmdHandler) { _bc = bc; diff --git a/src/NadekoBot/Services/Games/Poll.cs b/src/NadekoBot/Services/Games/Poll.cs index 8feb4cb5..5358e139 100644 --- a/src/NadekoBot/Services/Games/Poll.cs +++ b/src/NadekoBot/Services/Games/Poll.cs @@ -18,16 +18,13 @@ namespace NadekoBot.Services.Games private string[] answers { get; } private readonly ConcurrentDictionary _participants = new ConcurrentDictionary(); private readonly string _question; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; private bool running = false; - private HashSet _guildUsers; public event Action OnEnded = delegate { }; - public bool IsPublic { get; } - - public Poll(DiscordShardedClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable enumerable, bool isPublic = false) + public Poll(DiscordSocketClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable enumerable) { _client = client; _strings = strings; @@ -36,7 +33,6 @@ namespace NadekoBot.Services.Games _guild = ((ITextChannel)umsg.Channel).Guild; _question = question; answers = enumerable as string[] ?? enumerable.ToArray(); - IsPublic = isPublic; } public EmbedBuilder GetStats(string title) @@ -82,13 +78,7 @@ namespace NadekoBot.Services.Games var msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n"; var num = 1; msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); - if (!IsPublic) - msgToSend += "\n" + Format.Bold(GetText("poll_vote_private")); - else - msgToSend += "\n" + Format.Bold(GetText("poll_vote_public")); - - if (!IsPublic) - _guildUsers = new HashSet((await _guild.GetUsersAsync().ConfigureAwait(false)).Select(x => x.Id)); + msgToSend += "\n" + Format.Bold(GetText("poll_vote_public")); await _originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false); running = true; @@ -114,36 +104,16 @@ namespace NadekoBot.Services.Games return false; IMessageChannel ch; - if (IsPublic) - { - //if public, channel must be the same the poll started in - if (_originalMessage.Channel.Id != msg.Channel.Id) - return false; - ch = msg.Channel; - } - else - { - //if private, channel must be dm channel - if ((ch = msg.Channel as IDMChannel) == null) - return false; - - // user must be a member of the guild this poll is in - if (!_guildUsers.Contains(msg.Author.Id)) - return false; - } + //if public, channel must be the same the poll started in + if (_originalMessage.Channel.Id != msg.Channel.Id) + return false; + ch = msg.Channel; //user can vote only once if (_participants.TryAdd(msg.Author.Id, vote)) { - if (!IsPublic) - { - await ch.SendConfirmAsync(GetText("thanks_for_voting", Format.Bold(msg.Author.Username))).ConfigureAwait(false); - } - else - { - var toDelete = await ch.SendConfirmAsync(GetText("poll_voted", Format.Bold(msg.Author.ToString()))).ConfigureAwait(false); - toDelete.DeleteAfter(5); - } + var toDelete = await ch.SendConfirmAsync(GetText("poll_voted", Format.Bold(msg.Author.ToString()))).ConfigureAwait(false); + toDelete.DeleteAfter(5); return true; } return false; diff --git a/src/NadekoBot/Services/Games/PollService.cs b/src/NadekoBot/Services/Games/PollService.cs index a421bd77..ba6fee19 100644 --- a/src/NadekoBot/Services/Games/PollService.cs +++ b/src/NadekoBot/Services/Games/PollService.cs @@ -13,17 +13,17 @@ namespace NadekoBot.Services.Games { public ConcurrentDictionary ActivePolls = new ConcurrentDictionary(); private readonly Logger _log; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; - public PollService(DiscordShardedClient client, NadekoStrings strings) + public PollService(DiscordSocketClient client, NadekoStrings strings) { _log = LogManager.GetCurrentClassLogger(); _client = client; _strings = strings; } - public async Task StartPoll(ITextChannel channel, IUserMessage msg, string arg, bool isPublic = false) + public async Task StartPoll(ITextChannel channel, IUserMessage msg, string arg) { if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";")) return null; @@ -31,7 +31,7 @@ namespace NadekoBot.Services.Games if (data.Length < 3) return null; - var poll = new Poll(_client, _strings, msg, data[0], data.Skip(1), isPublic: isPublic); + var poll = new Poll(_client, _strings, msg, data[0], data.Skip(1)); if (ActivePolls.TryAdd(channel.Guild.Id, poll)) { poll.OnEnded += (gid) => @@ -45,20 +45,10 @@ namespace NadekoBot.Services.Games return false; } - public async Task TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg) { if (guild == null) - { - foreach (var kvp in ActivePolls) - { - if (!kvp.Value.IsPublic) - { - if (await kvp.Value.TryVote(msg).ConfigureAwait(false)) - return true; - } - } return false; - } if (!ActivePolls.TryGetValue(guild.Id, out var poll)) return false; diff --git a/src/NadekoBot/Services/GreetSettingsService.cs b/src/NadekoBot/Services/GreetSettingsService.cs index 6e2e630d..3296c602 100644 --- a/src/NadekoBot/Services/GreetSettingsService.cs +++ b/src/NadekoBot/Services/GreetSettingsService.cs @@ -17,10 +17,10 @@ namespace NadekoBot.Services private readonly DbService _db; public readonly ConcurrentDictionary GuildConfigsCache; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly Logger _log; - public GreetSettingsService(DiscordShardedClient client, IEnumerable guildConfigs, DbService db) + public GreetSettingsService(DiscordSocketClient client, IEnumerable guildConfigs, DbService db) { _db = db; _client = client; diff --git a/src/NadekoBot/Services/Help/HelpService.cs b/src/NadekoBot/Services/Help/HelpService.cs index 25a91f16..83b64927 100644 --- a/src/NadekoBot/Services/Help/HelpService.cs +++ b/src/NadekoBot/Services/Help/HelpService.cs @@ -24,7 +24,7 @@ namespace NadekoBot.Services.Help _strings = strings; } - public async Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg) + public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg) { try { diff --git a/src/NadekoBot/Services/IBotCredentials.cs b/src/NadekoBot/Services/IBotCredentials.cs index f7711b83..e0c271b5 100644 --- a/src/NadekoBot/Services/IBotCredentials.cs +++ b/src/NadekoBot/Services/IBotCredentials.cs @@ -19,6 +19,9 @@ namespace NadekoBot.Services string OsuApiKey { get; } bool IsOwner(IUser u); + int TotalShards { get; } + string ShardRunCommand { get; } + string ShardRunArguments { get; } } public class DBConfig diff --git a/src/NadekoBot/Services/IImagesService.cs b/src/NadekoBot/Services/IImagesService.cs index 0a76175d..31e6c32a 100644 --- a/src/NadekoBot/Services/IImagesService.cs +++ b/src/NadekoBot/Services/IImagesService.cs @@ -18,6 +18,6 @@ namespace NadekoBot.Services ImmutableArray WifeMatrix { get; } ImmutableArray RategirlDot { get; } - TimeSpan Reload(); + void Reload(); } } diff --git a/src/NadekoBot/Services/IStatsService.cs b/src/NadekoBot/Services/IStatsService.cs index a7735fc8..d187c413 100644 --- a/src/NadekoBot/Services/IStatsService.cs +++ b/src/NadekoBot/Services/IStatsService.cs @@ -13,6 +13,7 @@ namespace NadekoBot.Services double MessagesPerSecond { get; } long TextChannels { get; } long VoiceChannels { get; } + int GuildCount { get; } TimeSpan GetUptime(); string GetUptimeString(string separator = ", "); diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs index 92ae20c1..3d47a207 100644 --- a/src/NadekoBot/Services/Impl/BotCredentials.cs +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -30,20 +30,23 @@ namespace NadekoBot.Services.Impl public int TotalShards { get; } public string CarbonKey { get; } - public string credsFileName { get; } = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); + private readonly string _credsFileName = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); public string PatreonAccessToken { get; } + public string ShardRunCommand { get; } + public string ShardRunArguments { get; } + public int ShardRunPort { get; } public BotCredentials() { _log = LogManager.GetCurrentClassLogger(); try { File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); } catch { } - if(!File.Exists(credsFileName)) + if(!File.Exists(_credsFileName)) _log.Warn($"credentials.json is missing. Attempting to load creds from environment variables prefixed with 'NadekoBot_'. Example is in {Path.GetFullPath("./credentials_example.json")}"); try { var configBuilder = new ConfigurationBuilder(); - configBuilder.AddJsonFile(credsFileName, true) + configBuilder.AddJsonFile(_credsFileName, true) .AddEnvironmentVariables("NadekoBot_"); var data = configBuilder.Build(); @@ -61,13 +64,24 @@ namespace NadekoBot.Services.Impl MashapeKey = data[nameof(MashapeKey)]; OsuApiKey = data[nameof(OsuApiKey)]; PatreonAccessToken = data[nameof(PatreonAccessToken)]; + ShardRunCommand = data[nameof(ShardRunCommand)]; + ShardRunArguments = data[nameof(ShardRunArguments)]; + if (string.IsNullOrWhiteSpace(ShardRunCommand)) + ShardRunCommand = "dotnet"; + if (string.IsNullOrWhiteSpace(ShardRunArguments)) + ShardRunArguments = "run -c Release -- {0} {1} {2}"; + + var portStr = data[nameof(ShardRunPort)]; + if (string.IsNullOrWhiteSpace(portStr)) + ShardRunPort = new NadekoRandom().Next(5000, 6000); + else + ShardRunPort = int.Parse(portStr); int ts = 1; int.TryParse(data[nameof(TotalShards)], out ts); TotalShards = ts < 1 ? 1 : ts; - ulong clId = 0; - ulong.TryParse(data[nameof(ClientId)], out clId); + ulong.TryParse(data[nameof(ClientId)], out ulong clId); ClientId = clId; //var scId = data[nameof(SoundCloudClientId)]; @@ -81,7 +95,7 @@ namespace NadekoBot.Services.Impl ? "sqlite" : dbSection["Type"], string.IsNullOrWhiteSpace(dbSection["ConnectionString"]) - ? "Filename=./data/NadekoBot.db" + ? "Filename=./data/NadekoBot.db" : dbSection["ConnectionString"]); } catch (Exception ex) @@ -107,6 +121,10 @@ namespace NadekoBot.Services.Impl public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db"); public int TotalShards { get; set; } = 1; public string PatreonAccessToken { get; set; } = ""; + + public string ShardRunCommand { get; set; } = ""; + public string ShardRunArguments { get; set; } = ""; + public int? ShardRunPort { get; set; } = null; } private class DbModel diff --git a/src/NadekoBot/Services/Impl/ImagesService.cs b/src/NadekoBot/Services/Impl/ImagesService.cs index 4bce6148..95930464 100644 --- a/src/NadekoBot/Services/Impl/ImagesService.cs +++ b/src/NadekoBot/Services/Impl/ImagesService.cs @@ -47,12 +47,10 @@ namespace NadekoBot.Services.Impl this.Reload(); } - public TimeSpan Reload() + public void Reload() { try { - _log.Info("Loading images..."); - var sw = Stopwatch.StartNew(); Heads = File.ReadAllBytes(_headsPath).ToImmutableArray(); Tails = File.ReadAllBytes(_tailsPath).ToImmutableArray(); @@ -79,10 +77,6 @@ namespace NadekoBot.Services.Impl WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray(); RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray(); - - sw.Stop(); - _log.Info($"Images loaded after {sw.Elapsed.TotalSeconds:F2}s!"); - return sw.Elapsed; } catch (Exception ex) { diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs index bcd4a092..07d706e1 100644 --- a/src/NadekoBot/Services/Impl/StatsService.cs +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -13,11 +13,11 @@ namespace NadekoBot.Services.Impl { public class StatsService : IStatsService { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IBotCredentials _creds; private readonly DateTime _started; - public const string BotVersion = "1.43"; + public const string BotVersion = "1.5"; public string Author => "Kwoth#2560"; public string Library => "Discord.Net"; @@ -35,11 +35,16 @@ namespace NadekoBot.Services.Impl public long CommandsRan => Interlocked.Read(ref _commandsRan); private readonly Timer _carbonitexTimer; + private readonly ShardsCoordinator _sc; - public StatsService(DiscordShardedClient client, CommandHandler cmdHandler, IBotCredentials creds) + public int GuildCount => + _sc?.GuildCount ?? _client.Guilds.Count(); + + public StatsService(DiscordSocketClient client, CommandHandler cmdHandler, IBotCredentials creds, ShardsCoordinator sc) { _client = client; _creds = creds; + _sc = sc; _started = DateTime.UtcNow; _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter)); @@ -121,31 +126,34 @@ namespace NadekoBot.Services.Impl return Task.CompletedTask; }; - _carbonitexTimer = new Timer(async (state) => + if (sc != null) { - if (string.IsNullOrWhiteSpace(_creds.CarbonKey)) - return; - try + _carbonitexTimer = new Timer(async (state) => { - using (var http = new HttpClient()) + if (string.IsNullOrWhiteSpace(_creds.CarbonKey)) + return; + try { - using (var content = new FormUrlEncodedContent( - new Dictionary { - { "servercount", _client.Guilds.Count.ToString() }, - { "key", _creds.CarbonKey }})) + using (var http = new HttpClient()) { - content.Headers.Clear(); - content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + using (var content = new FormUrlEncodedContent( + new Dictionary { + { "servercount", sc.GuildCount.ToString() }, + { "key", _creds.CarbonKey }})) + { + content.Headers.Clear(); + content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); - await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false); + await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false); + } } } - } - catch - { - // ignored - } - }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); + catch + { + // ignored + } + }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); + } } public void Initialize() diff --git a/src/NadekoBot/Services/LogSetup.cs b/src/NadekoBot/Services/LogSetup.cs new file mode 100644 index 00000000..159b447e --- /dev/null +++ b/src/NadekoBot/Services/LogSetup.cs @@ -0,0 +1,28 @@ +using NLog; +using NLog.Config; +using NLog.Targets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Services +{ + public class LogSetup + { + public static void SetupLogger() + { + var logConfig = new LoggingConfiguration(); + var consoleTarget = new ColoredConsoleTarget() + { + Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}" + }; + logConfig.AddTarget("Console", consoleTarget); + + logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); + + LogManager.Configuration = logConfig; + } + } +} diff --git a/src/NadekoBot/Services/Permissions/CmdCdService.cs b/src/NadekoBot/Services/Permissions/CmdCdService.cs index 0a1cbf47..7a44a292 100644 --- a/src/NadekoBot/Services/Permissions/CmdCdService.cs +++ b/src/NadekoBot/Services/Permissions/CmdCdService.cs @@ -21,7 +21,7 @@ namespace NadekoBot.Services.Permissions v => new ConcurrentHashSet(v.CommandCooldowns))); } - public Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, + public Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) { if (guild == null) diff --git a/src/NadekoBot/Services/Permissions/FilterService.cs b/src/NadekoBot/Services/Permissions/FilterService.cs index 0d3513ad..a6d97d0d 100644 --- a/src/NadekoBot/Services/Permissions/FilterService.cs +++ b/src/NadekoBot/Services/Permissions/FilterService.cs @@ -41,7 +41,7 @@ namespace NadekoBot.Services.Permissions return words; } - public FilterService(DiscordShardedClient _client, IEnumerable gcs) + public FilterService(DiscordSocketClient _client, IEnumerable gcs) { _log = LogManager.GetCurrentClassLogger(); diff --git a/src/NadekoBot/Services/Permissions/GlobalPermissionService.cs b/src/NadekoBot/Services/Permissions/GlobalPermissionService.cs index 633a81a3..419f77f3 100644 --- a/src/NadekoBot/Services/Permissions/GlobalPermissionService.cs +++ b/src/NadekoBot/Services/Permissions/GlobalPermissionService.cs @@ -19,7 +19,7 @@ namespace NadekoBot.Services.Permissions BlockedCommands = new ConcurrentHashSet(bc.BlockedCommands.Select(x => x.Name)); } - public async Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) + public async Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) { await Task.Yield(); commandName = commandName.ToLowerInvariant(); diff --git a/src/NadekoBot/Services/Permissions/PermissionsService.cs b/src/NadekoBot/Services/Permissions/PermissionsService.cs index 765cb131..c4c586a9 100644 --- a/src/NadekoBot/Services/Permissions/PermissionsService.cs +++ b/src/NadekoBot/Services/Permissions/PermissionsService.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; + +using Microsoft.EntityFrameworkCore; using NadekoBot.DataStructures.ModuleBehaviors; using NadekoBot.Services.Database.Models; using NLog; @@ -10,6 +11,7 @@ using System.Threading.Tasks; using Discord; using Discord.WebSocket; using NadekoBot.Extensions; +using NadekoBot.Services; namespace NadekoBot.Services.Permissions { @@ -18,22 +20,26 @@ namespace NadekoBot.Services.Permissions private readonly DbService _db; private readonly Logger _log; private readonly CommandHandler _cmd; + private readonly NadekoStrings _strings; //guildid, root permission public ConcurrentDictionary Cache { get; } = new ConcurrentDictionary(); - public PermissionService(DbService db, BotConfig bc, CommandHandler cmd) + public PermissionService(DiscordSocketClient client, DbService db, BotConfig bc, CommandHandler cmd, NadekoStrings strings) { _log = LogManager.GetCurrentClassLogger(); _db = db; _cmd = cmd; + _strings = strings; var sw = Stopwatch.StartNew(); - TryMigratePermissions(bc); + if (client.ShardId == 0) + TryMigratePermissions(bc); + using (var uow = _db.UnitOfWork) { - foreach (var x in uow.GuildConfigs.Permissionsv2ForAll()) + foreach (var x in uow.GuildConfigs.Permissionsv2ForAll(client.Guilds.ToArray().Select(x => (long)x.Id).ToList())) { Cache.TryAdd(x.GuildId, new PermissionCache() { @@ -68,10 +74,9 @@ namespace NadekoBot.Services.Permissions private void TryMigratePermissions(BotConfig bc) { var log = LogManager.GetCurrentClassLogger(); - using (var uow = _db.UnitOfWork) + if (bc.PermissionVersion <= 1) { - var _bc = uow.BotConfig.GetOrCreate(); - if (_bc.PermissionVersion <= 1) + using (var uow = _db.UnitOfWork) { log.Info("Permission version is 1, upgrading to 2."); var oldCache = new ConcurrentDictionary(uow.GuildConfigs @@ -126,9 +131,13 @@ namespace NadekoBot.Services.Permissions log.Info("Permission migration to v2 is done."); } - _bc.PermissionVersion = 2; + bc.PermissionVersion = 2; + uow.Complete(); } - if (_bc.PermissionVersion <= 2) + } + if (bc.PermissionVersion <= 2) + { + using (var uow = _db.UnitOfWork) { var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" }; uow._context.Database.ExecuteSqlCommand( @@ -144,9 +153,9 @@ WHERE secondaryTargetName LIKE '.%' OR secondaryTargetName LIKE '>%' OR secondaryTargetName LIKE '-%' OR secondaryTargetName LIKE '!%';"); - _bc.PermissionVersion = 3; + bc.PermissionVersion = 3; + uow.Complete(); } - uow.Complete(); } } @@ -183,7 +192,7 @@ WHERE secondaryTargetName LIKE '.%' OR }); } - public async Task TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) + public async Task TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) { await Task.Yield(); if (guild == null) @@ -198,11 +207,9 @@ WHERE secondaryTargetName LIKE '.%' OR PermissionCache pc = GetCache(guild.Id); if (!resetCommand && !pc.Permissions.CheckPermissions(msg, commandName, moduleName, out int index)) { - var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)}** is preventing this action."; if (pc.Verbose) - try { await channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } + try { await channel.SendErrorAsync(_strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)))).ConfigureAwait(false); } catch { } return true; - //return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, returnMsg)); } @@ -215,7 +222,6 @@ WHERE secondaryTargetName LIKE '.%' OR if (pc.Verbose) try { await channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } return true; - //return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"You need the **{pc.PermRole}** role in order to use permission commands.")); } } } diff --git a/src/NadekoBot/Services/Searches/AnimeSearchService.cs b/src/NadekoBot/Services/Searches/AnimeSearchService.cs index 71820506..a4187da3 100644 --- a/src/NadekoBot/Services/Searches/AnimeSearchService.cs +++ b/src/NadekoBot/Services/Searches/AnimeSearchService.cs @@ -1,52 +1,18 @@ using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NLog; using System; -using System.Collections.Generic; -using System.Linq; using System.Net.Http; -using System.Text; -using System.Threading; using System.Threading.Tasks; namespace NadekoBot.Services.Searches { public class AnimeSearchService { - private readonly Timer _anilistTokenRefresher; private readonly Logger _log; - private static string anilistToken { get; set; } - public AnimeSearchService() { _log = LogManager.GetCurrentClassLogger(); - _anilistTokenRefresher = new Timer(async (state) => - { - try - { - var headers = new Dictionary - { - {"grant_type", "client_credentials"}, - {"client_id", "kwoth-w0ki9"}, - {"client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z"}, - }; - - using (var http = new HttpClient()) - { - //http.AddFakeHeaders(); - http.DefaultRequestHeaders.Clear(); - var formContent = new FormUrlEncodedContent(headers); - var response = await http.PostAsync("https://anilist.co/api/auth/access_token", formContent).ConfigureAwait(false); - var stringContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - anilistToken = JObject.Parse(stringContent)["access_token"].ToString(); - } - } - catch - { - // ignored - } - }, null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(29)); } public async Task GetAnimeData(string query) @@ -56,19 +22,15 @@ namespace NadekoBot.Services.Searches try { - var link = "http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query); + var link = "https://aniapi.nadekobot.me/anime/" + Uri.EscapeDataString(query.Replace("/", " ")); using (var http = new HttpClient()) { - var res = await http.GetStringAsync(link + $"?access_token={anilistToken}").ConfigureAwait(false); - var smallObj = JArray.Parse(res)[0]; - var aniData = await http.GetStringAsync("http://anilist.co/api/anime/" + smallObj["id"] + $"?access_token={anilistToken}").ConfigureAwait(false); - - return await Task.Run(() => { try { return JsonConvert.DeserializeObject(aniData); } catch { return null; } }).ConfigureAwait(false); + var res = await http.GetStringAsync(link).ConfigureAwait(false); + return JsonConvert.DeserializeObject(res); } } - catch (Exception ex) + catch { - _log.Warn(ex, "Failed anime search for {0}", query); return null; } } @@ -79,18 +41,16 @@ namespace NadekoBot.Services.Searches throw new ArgumentNullException(nameof(query)); try { + + var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " ")); using (var http = new HttpClient()) { - var res = await http.GetStringAsync("http://anilist.co/api/manga/search/" + Uri.EscapeUriString(query) + $"?access_token={anilistToken}").ConfigureAwait(false); - var smallObj = JArray.Parse(res)[0]; - var aniData = await http.GetStringAsync("http://anilist.co/api/manga/" + smallObj["id"] + $"?access_token={anilistToken}").ConfigureAwait(false); - - return await Task.Run(() => { try { return JsonConvert.DeserializeObject(aniData); } catch { return null; } }).ConfigureAwait(false); + var res = await http.GetStringAsync(link).ConfigureAwait(false); + return JsonConvert.DeserializeObject(res); } } - catch (Exception ex) + catch { - _log.Warn(ex, "Failed anime search for {0}", query); return null; } } diff --git a/src/NadekoBot/Services/Searches/SearchesService.cs b/src/NadekoBot/Services/Searches/SearchesService.cs index 864c9522..8c98a22e 100644 --- a/src/NadekoBot/Services/Searches/SearchesService.cs +++ b/src/NadekoBot/Services/Searches/SearchesService.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Searches { public class SearchesService { - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly IGoogleApiService _google; private readonly DbService _db; private readonly Logger _log; @@ -31,7 +31,7 @@ namespace NadekoBot.Services.Searches public List WowJokes { get; } = new List(); public List MagicItems { get; } = new List(); - public SearchesService(DiscordShardedClient client, IGoogleApiService google, DbService db) + public SearchesService(DiscordSocketClient client, IGoogleApiService google, DbService db) { _client = client; _google = google; diff --git a/src/NadekoBot/Services/Searches/StreamNotificationService.cs b/src/NadekoBot/Services/Searches/StreamNotificationService.cs index 3fdbb9ac..c077e4e1 100644 --- a/src/NadekoBot/Services/Searches/StreamNotificationService.cs +++ b/src/NadekoBot/Services/Searches/StreamNotificationService.cs @@ -1,6 +1,8 @@ using Discord; using Discord.WebSocket; using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; using Newtonsoft.Json; using System; @@ -20,10 +22,10 @@ namespace NadekoBot.Services.Searches private readonly ConcurrentDictionary _cachedStatuses = new ConcurrentDictionary(); private readonly DbService _db; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly NadekoStrings _strings; - public StreamNotificationService(DbService db, DiscordShardedClient client, NadekoStrings strings) + public StreamNotificationService(DbService db, DiscordSocketClient client, NadekoStrings strings) { _db = db; _client = client; @@ -35,7 +37,7 @@ namespace NadekoBot.Services.Searches IEnumerable streams; using (var uow = _db.UnitOfWork) { - streams = uow.GuildConfigs.GetAllFollowedStreams(); + streams = uow.GuildConfigs.GetAllFollowedStreams(client.Guilds.Select(x => (long)x.Id).ToList()); } await Task.WhenAll(streams.Select(async fs => @@ -73,7 +75,7 @@ namespace NadekoBot.Services.Searches })); firstStreamNotifPass = false; - }, null, TimeSpan.Zero, TimeSpan.FromSeconds(60)); + }, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60)); } public async Task GetStreamStatus(FollowedStream stream, bool checkCache = true) diff --git a/src/NadekoBot/Services/Utility/ConverterService.cs b/src/NadekoBot/Services/Utility/ConverterService.cs index 38fc7f9a..40f7fafa 100644 --- a/src/NadekoBot/Services/Utility/ConverterService.cs +++ b/src/NadekoBot/Services/Utility/ConverterService.cs @@ -1,4 +1,6 @@ -using NadekoBot.Services.Database.Models; +using Discord.WebSocket; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; using Newtonsoft.Json; using NLog; using System; @@ -13,24 +15,26 @@ namespace NadekoBot.Services.Utility { public class ConverterService { - public List Units { get; set; } = new List(); + public List Units { get; } = new List(); private readonly Logger _log; - private Timer _timer; + private readonly Timer _currencyUpdater; private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0); private readonly DbService _db; - public ConverterService(DbService db) + public ConverterService(DiscordSocketClient client, DbService db) { _log = LogManager.GetCurrentClassLogger(); _db = db; try { - var data = JsonConvert.DeserializeObject>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit() - { - Modifier = u.Modifier, - UnitType = u.UnitType, - InternalTrigger = string.Join("|", u.Triggers) - }).ToArray(); + var data = JsonConvert.DeserializeObject>( + File.ReadAllText("data/units.json")) + .Select(u => new ConvertUnit() + { + Modifier = u.Modifier, + UnitType = u.UnitType, + InternalTrigger = string.Join("|", u.Triggers) + }).ToArray(); using (var uow = _db.UnitOfWork) { @@ -47,10 +51,10 @@ namespace NadekoBot.Services.Utility _log.Warn("Could not load units: " + ex.Message); } - _timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval); + _currencyUpdater = new Timer(async (shouldLoad) => await UpdateCurrency((bool)shouldLoad), client.ShardId == 0, _updateInterval, _updateInterval); } - public static async Task UpdateCurrencyRates() + private async Task GetCurrencyRates() { using (var http = new HttpClient()) { @@ -59,38 +63,48 @@ namespace NadekoBot.Services.Utility } } - public async Task UpdateCurrency() + private async Task UpdateCurrency(bool shouldLoad) { try { - var currencyRates = await UpdateCurrencyRates(); var unitTypeString = "currency"; - var range = currencyRates.ConversionRates.Select(u => new ConvertUnit() + if (shouldLoad) { - InternalTrigger = u.Key, - Modifier = u.Value, - UnitType = unitTypeString - }).ToArray(); - var baseType = new ConvertUnit() - { - Triggers = new[] { currencyRates.Base }, - Modifier = decimal.One, - UnitType = unitTypeString - }; - var toRemove = Units.Where(u => u.UnitType == unitTypeString); + var currencyRates = await GetCurrencyRates(); + var baseType = new ConvertUnit() + { + Triggers = new[] { currencyRates.Base }, + Modifier = decimal.One, + UnitType = unitTypeString + }; + var range = currencyRates.ConversionRates.Select(u => new ConvertUnit() + { + InternalTrigger = u.Key, + Modifier = u.Value, + UnitType = unitTypeString + }).ToArray(); + var toRemove = Units.Where(u => u.UnitType == unitTypeString); - using (var uow = _db.UnitOfWork) - { - uow.ConverterUnits.RemoveRange(toRemove.ToArray()); - uow.ConverterUnits.Add(baseType); - uow.ConverterUnits.AddRange(range); + using (var uow = _db.UnitOfWork) + { + uow.ConverterUnits.RemoveRange(toRemove.ToArray()); + uow.ConverterUnits.Add(baseType); + uow.ConverterUnits.AddRange(range); - await uow.CompleteAsync().ConfigureAwait(false); + await uow.CompleteAsync().ConfigureAwait(false); + } + Units.RemoveAll(u => u.UnitType == unitTypeString); + Units.Add(baseType); + Units.AddRange(range); + } + else + { + using (var uow = _db.UnitOfWork) + { + Units.RemoveAll(u => u.UnitType == unitTypeString); + Units.AddRange(uow.ConverterUnits.GetAll().ToArray()); + } } - Units.RemoveAll(u => u.UnitType == unitTypeString); - Units.Add(baseType); - Units.AddRange(range); - _log.Info("Updated Currency"); } catch { diff --git a/src/NadekoBot/Services/Utility/MessageRepeaterService.cs b/src/NadekoBot/Services/Utility/MessageRepeaterService.cs index 57873ef1..cc402aa8 100644 --- a/src/NadekoBot/Services/Utility/MessageRepeaterService.cs +++ b/src/NadekoBot/Services/Utility/MessageRepeaterService.cs @@ -15,7 +15,7 @@ namespace NadekoBot.Services.Utility public ConcurrentDictionary> Repeaters { get; set; } public bool RepeaterReady { get; private set; } - public MessageRepeaterService(NadekoBot bot, DiscordShardedClient client, IEnumerable gcs) + public MessageRepeaterService(NadekoBot bot, DiscordSocketClient client, IEnumerable gcs) { var _ = Task.Run(async () => { diff --git a/src/NadekoBot/Services/Utility/PatreonRewardsService.cs b/src/NadekoBot/Services/Utility/PatreonRewardsService.cs index e8202d84..3eef3d72 100644 --- a/src/NadekoBot/Services/Utility/PatreonRewardsService.cs +++ b/src/NadekoBot/Services/Utility/PatreonRewardsService.cs @@ -1,12 +1,15 @@ -using NadekoBot.Services.Database.Models; +using Discord.WebSocket; +using NadekoBot.Services.Database.Models; using NadekoBot.Services.Utility.Patreon; using Newtonsoft.Json; using NLog; using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Net.Http; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -23,12 +26,15 @@ namespace NadekoBot.Services.Utility private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1); private readonly Logger _log; - public readonly TimeSpan Interval = TimeSpan.FromMinutes(15); - private IBotCredentials _creds; + public readonly TimeSpan Interval = TimeSpan.FromMinutes(3); + private readonly IBotCredentials _creds; private readonly DbService _db; private readonly CurrencyService _currency; - public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency) + private readonly string cacheFileName = "./patreon-rewards.json"; + + public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency, + DiscordSocketClient client) { _creds = creds; _db = db; @@ -36,58 +42,65 @@ namespace NadekoBot.Services.Utility if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken)) return; _log = LogManager.GetCurrentClassLogger(); - Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval); + Updater = new Timer(async (load) => await RefreshPledges((bool)load), + client.ShardId == 0, client.ShardId == 0 ? TimeSpan.Zero : TimeSpan.FromMinutes(2), Interval); } - public async Task LoadPledges() + public async Task RefreshPledges(bool shouldLoad) { - LastUpdate = DateTime.UtcNow; - await getPledgesLocker.WaitAsync(1000).ConfigureAwait(false); - try + if (shouldLoad) { - var rewards = new List(); - var users = new List(); - using (var http = new HttpClient()) + LastUpdate = DateTime.UtcNow; + await getPledgesLocker.WaitAsync().ConfigureAwait(false); + try { - http.DefaultRequestHeaders.Clear(); - http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken); - var data = new PatreonData() + var rewards = new List(); + var users = new List(); + using (var http = new HttpClient()) { - Links = new PatreonDataLinks() + http.DefaultRequestHeaders.Clear(); + http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken); + var data = new PatreonData() { - next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges" - } - }; - do + Links = new PatreonDataLinks() + { + next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges" + } + }; + do + { + var res = await http.GetStringAsync(data.Links.next) + .ConfigureAwait(false); + data = JsonConvert.DeserializeObject(res); + var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge"); + rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject(x.ToString())) + .Where(x => x.attributes.declined_since == null)); + users.AddRange(data.Included + .Where(x => x["type"].ToString() == "user") + .Select(x => JsonConvert.DeserializeObject(x.ToString()))); + } while (!string.IsNullOrWhiteSpace(data.Links.next)); + } + Pledges = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward() { - var res = await http.GetStringAsync(data.Links.next) - .ConfigureAwait(false); - data = JsonConvert.DeserializeObject(res); - var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge"); - rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject(x.ToString())) - .Where(x => x.attributes.declined_since == null)); - users.AddRange(data.Included - .Where(x => x["type"].ToString() == "user") - .Select(x => JsonConvert.DeserializeObject(x.ToString()))); - } while (!string.IsNullOrWhiteSpace(data.Links.next)); + User = y, + Reward = x, + }).ToImmutableArray(); + File.WriteAllText("./patreon_rewards.json", JsonConvert.SerializeObject(Pledges)); } - Pledges = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward() + catch (Exception ex) { - User = y, - Reward = x, - }).ToImmutableArray(); - } - catch (Exception ex) - { - _log.Warn(ex); - } - finally - { - var _ = Task.Run(async () => + _log.Warn(ex); + } + finally { - await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false); getPledgesLocker.Release(); - }); + } + } + else + { + if(File.Exists(cacheFileName)) + Pledges = JsonConvert.DeserializeObject(File.ReadAllText("./patreon_rewards.json")) + .ToImmutableArray(); } } diff --git a/src/NadekoBot/Services/Utility/RemindService.cs b/src/NadekoBot/Services/Utility/RemindService.cs index a6dd276b..8629712d 100644 --- a/src/NadekoBot/Services/Utility/RemindService.cs +++ b/src/NadekoBot/Services/Utility/RemindService.cs @@ -1,6 +1,7 @@ using Discord; using Discord.WebSocket; using NadekoBot.Extensions; +using NadekoBot.Services.Database; using NadekoBot.Services.Database.Models; using NLog; using System; @@ -30,10 +31,11 @@ namespace NadekoBot.Services.Utility private readonly CancellationTokenSource cancelSource; private readonly CancellationToken cancelAllToken; private readonly BotConfig _config; - private readonly DiscordShardedClient _client; + private readonly DiscordSocketClient _client; private readonly DbService _db; - public RemindService(DiscordShardedClient client, BotConfig config, DbService db) + public RemindService(DiscordSocketClient client, BotConfig config, DbService db, + List guilds, IUnitOfWork uow) { _config = config; _client = client; @@ -42,11 +44,8 @@ namespace NadekoBot.Services.Utility cancelSource = new CancellationTokenSource(); cancelAllToken = cancelSource.Token; - List reminders; - using (var uow = _db.UnitOfWork) - { - reminders = uow.Reminders.GetAll().ToList(); - } + + var reminders = uow.Reminders.GetIncludedReminders(guilds).ToList(); RemindMessageFormat = _config.RemindMessageFormat; foreach (var r in reminders) diff --git a/src/NadekoBot/Services/Utility/RepeatRunner.cs b/src/NadekoBot/Services/Utility/RepeatRunner.cs index 8c96dc8b..abfa21b1 100644 --- a/src/NadekoBot/Services/Utility/RepeatRunner.cs +++ b/src/NadekoBot/Services/Utility/RepeatRunner.cs @@ -22,7 +22,7 @@ namespace NadekoBot.Services.Utility private IUserMessage oldMsg = null; private Timer _t; - public RepeatRunner(DiscordShardedClient client, Repeater repeater) + public RepeatRunner(DiscordSocketClient client, Repeater repeater) { _log = LogManager.GetCurrentClassLogger(); Repeater = repeater; diff --git a/src/NadekoBot/Services/Utility/UtilityService.cs b/src/NadekoBot/Services/Utility/UtilityService.cs deleted file mode 100644 index 660984b2..00000000 --- a/src/NadekoBot/Services/Utility/UtilityService.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Discord; -using Discord.WebSocket; -using NadekoBot.Extensions; -using NadekoBot.Services.Database.Models; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Services.Utility -{ - public class CrossServerTextService - { - public readonly ConcurrentDictionary> Subscribers = - new ConcurrentDictionary>(); - private DiscordShardedClient _client; - - public CrossServerTextService(IEnumerable guildConfigs, DiscordShardedClient client) - { - _client = client; - _client.MessageReceived += Client_MessageReceived; - } - - private Task Client_MessageReceived(SocketMessage imsg) - { - var _ = Task.Run(async () => { - try - { - if (imsg.Author.IsBot) - return; - var msg = imsg as IUserMessage; - if (msg == null) - return; - var channel = imsg.Channel as ITextChannel; - if (channel == null) - return; - if (msg.Author.Id == _client.CurrentUser.Id) return; - foreach (var subscriber in Subscribers) - { - var set = subscriber.Value; - if (!set.Contains(channel)) - continue; - foreach (var chan in set.Except(new[] { channel })) - { - try - { - await chan.SendMessageAsync(GetMessage(channel, (IGuildUser)msg.Author, - msg)).ConfigureAwait(false); - } - catch - { - // ignored - } - } - } - } - catch - { - // ignored - } - }); - - return Task.CompletedTask; - } - - private string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) => - $"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions(); - } -} diff --git a/src/NadekoBot/Services/Utility/VerboseErrorsService.cs b/src/NadekoBot/Services/Utility/VerboseErrorsService.cs index 009de8ee..7658ea1c 100644 --- a/src/NadekoBot/Services/Utility/VerboseErrorsService.cs +++ b/src/NadekoBot/Services/Utility/VerboseErrorsService.cs @@ -50,12 +50,12 @@ namespace NadekoBot.Services.Utility public bool ToggleVerboseErrors(ulong guildId) { - + bool enabled; using (var uow = _db.UnitOfWork) { var gc = uow.GuildConfigs.For(guildId, set => set); - gc.VerboseErrors = !gc.VerboseErrors; + enabled = gc.VerboseErrors = !gc.VerboseErrors; uow.Complete(); @@ -65,15 +65,12 @@ namespace NadekoBot.Services.Utility guildsEnabled.TryRemove(guildId); } - if (guildsEnabled.Add(guildId)) - { - return true; - } + if (enabled) + guildsEnabled.Add(guildId); else - { guildsEnabled.TryRemove(guildId); - return false; - } + + return enabled; } } diff --git a/src/NadekoBot/ShardsCoordinator.cs b/src/NadekoBot/ShardsCoordinator.cs new file mode 100644 index 00000000..18725566 --- /dev/null +++ b/src/NadekoBot/ShardsCoordinator.cs @@ -0,0 +1,109 @@ +using NadekoBot.DataStructures.ShardCom; +using NadekoBot.Services; +using NadekoBot.Services.Impl; +using NLog; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot +{ + public class ShardsCoordinator + { + private readonly BotCredentials Credentials; + private Process[] ShardProcesses; + public ShardComMessage[] Statuses { get; } + public int GuildCount => Statuses.ToArray() + .Where(x => x != null) + .Sum(x => x.Guilds); + + private readonly Logger _log; + private readonly ShardComServer _comServer; + private readonly int _port; + + public ShardsCoordinator(int port) + { + LogSetup.SetupLogger(); + Credentials = new BotCredentials(); + ShardProcesses = new Process[Credentials.TotalShards]; + Statuses = new ShardComMessage[Credentials.TotalShards]; + _log = LogManager.GetCurrentClassLogger(); + _port = port; + + _comServer = new ShardComServer(port); + _comServer.Start(); + + _comServer.OnDataReceived += _comServer_OnDataReceived; + } + + private Task _comServer_OnDataReceived(ShardComMessage msg) + { + Statuses[msg.ShardId] = msg; + if (msg.ConnectionState == Discord.ConnectionState.Disconnected || msg.ConnectionState == Discord.ConnectionState.Disconnecting) + _log.Error("!!! SHARD {0} IS IN {1} STATE", msg.ShardId, msg.ConnectionState.ToString()); + return Task.CompletedTask; + } + + public async Task RunAsync() + { + var curProcessId = Process.GetCurrentProcess().Id; + for (int i = 1; i < Credentials.TotalShards; i++) + { + var p = Process.Start(new ProcessStartInfo() + { + FileName = Credentials.ShardRunCommand, + Arguments = string.Format(Credentials.ShardRunArguments, i, curProcessId, _port) + }); + await Task.Delay(5000); + } + } + + public async Task RunAndBlockAsync() + { + try + { + await RunAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Error(ex); + } + await Task.Run(() => + { + string input; + while ((input = Console.ReadLine()?.ToLowerInvariant()) != "quit") + { + try + { + switch (input) + { + case "ls": + var groupStr = string.Join(",", Statuses + .ToArray() + .Where(x => x != null) + .GroupBy(x => x.ConnectionState) + .Select(x => x.Count() + " " + x.Key)); + _log.Info(string.Join("\n", Statuses + .ToArray() + .Where(x => x != null) + .Select(x => $"Shard {x.ShardId} is in {x.ConnectionState.ToString()} state with {x.Guilds} servers")) + "\n" + groupStr); + break; + default: + break; + } + } + catch (Exception ex) + { + _log.Warn(ex); + } + } + }); + foreach (var p in ShardProcesses) + { + try { p.Kill(); } catch { } + try { p.Dispose(); } catch { } + } + } + } +} diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index cafb5d32..10e97e14 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -70,7 +70,7 @@ namespace NadekoBot.Extensions /// /// danny kamisama /// - public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordShardedClient client, int currentPage, Func pageFunc, int? lastPage = null, bool addPaginatedFooter = true) + public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordSocketClient client, int currentPage, Func pageFunc, int? lastPage = null, bool addPaginatedFooter = true) { var embed = pageFunc(currentPage); @@ -134,7 +134,7 @@ namespace NadekoBot.Extensions return embed.WithFooter(efb => efb.WithText(curPage.ToString())); } - public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordShardedClient client, Action reactionAdded, Action reactionRemoved = null) + public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Action reactionAdded, Action reactionRemoved = null) { if (reactionRemoved == null) reactionRemoved = delegate { }; diff --git a/src/NadekoBot/_strings/ResponseStrings.en-US.json b/src/NadekoBot/_strings/ResponseStrings.en-US.json index 98df2300..f405c63e 100644 --- a/src/NadekoBot/_strings/ResponseStrings.en-US.json +++ b/src/NadekoBot/_strings/ResponseStrings.en-US.json @@ -154,6 +154,7 @@ "administration_old_topic": "Old topic", "administration_perms": "Error. Most likely I don't have sufficient permissions.", "permissions_perms_reset": "Permissions for this server are reset.", + "permissions_trigger": "Permission number #{0} {1} is preventing this action.", "administration_prot_active": "Active protections", "administration_prot_disable": "{0} has been **disabled** on this server.", "administration_prot_enable": "{0} Enabled", @@ -446,7 +447,7 @@ "music_skipped_to": "Skipped to `{0}:{1}`", "music_songs_shuffled": "Songs shuffled", "music_song_moved": "Song moved", - "music_song_not_found": "No song found.", + "music_song_not_found": "No song found.", "music_time_format": "{0}h {1}m {2}s", "music_to_position": "To position", "music_unlimited": "unlimited", @@ -523,7 +524,7 @@ "searches_cost": "Cost", "searches_date": "Date", "searches_define": "Define:", - "searches_define_unknown": "Can't find the definition for that term.", + "searches_define_unknown": "Can't find the definition for that term.", "searches_dropped": "Dropped", "searches_episodes": "Episodes", "searches_error_occured": "Error occurred.", @@ -534,7 +535,7 @@ "searches_hashtag_error": "Failed finding a definition for that tag.", "searches_height_weight": "Height/Weight", "searches_height_weight_val": "{0}m/{1}kg", - "searches_hex_invalid": "Invalid color specified.", + "searches_hex_invalid": "Invalid color specified.", "searches_humidity": "Humidity", "searches_image_search_for": "Image search for:", "searches_imdb_fail": "Failed to find that movie.", @@ -613,9 +614,6 @@ "utility_convert_not_found": "Cannot convert {0} to {1}: units not found", "utility_convert_type_error": "Cannot convert {0} to {1}: types of unit are not equal", "utility_created_at": "Created at", - "utility_csc_join": "Joined cross server channel.", - "utility_csc_leave": "Left cross server channel.", - "utility_csc_token": "This is your CSC token", "utility_custom_emojis": "Custom emojis", "utility_error": "Error", "utility_features": "Features", diff --git a/src/NadekoBot/credentials_example.json b/src/NadekoBot/credentials_example.json index e9ec0b12..fd39b766 100644 --- a/src/NadekoBot/credentials_example.json +++ b/src/NadekoBot/credentials_example.json @@ -15,5 +15,9 @@ "Type": "sqlite", "ConnectionString": "Filename=./data/NadekoBot.db" }, - "TotalShards": 1 + "TotalShards": 1, + "PatreonAccessToken": "", + "ShardRunCommand": "", + "ShardRunArguments": "", + "ShardRunPort": null } \ No newline at end of file