Merge branch 'shard-process' into 1.4

This commit is contained in:
Master Kwoth 2017-06-25 07:06:46 +02:00
commit 61a96d9c4f
100 changed files with 1081 additions and 934 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
#Manually added files #Manually added files
patreon_rewards.json
command_errors*.txt command_errors*.txt
src/NadekoBot/Command Errors*.txt src/NadekoBot/Command Errors*.txt

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.6 VisualStudioVersion = 15.0.26430.12
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}"
EndProject EndProject

View File

@ -18,7 +18,8 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl
"OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6", "OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6",
"PatreonAccessToken": "", "PatreonAccessToken": "",
"Db": null, "Db": null,
"TotalShards": 1 "TotalShards": 1,
"ShardRunCommand": ""
} }
``` ```
----- -----
@ -155,14 +156,28 @@ It should look like:
- **TotalShards** - **TotalShards**
- Required if the bot will be connected to more than 1500 servers. - 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. - 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 ## DB files
Nadeko saves all the settings and infomations in `NadekoBot.db` file here: 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/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/). 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.* *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.*

View File

@ -13,6 +13,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
/// Try to execute some logic within some module's service. /// Try to execute some logic within some module's service.
/// </summary> /// </summary>
/// <returns>Whether it should block other command executions after it.</returns> /// <returns>Whether it should block other command executions after it.</returns>
Task<bool> TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg); Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg);
} }
} }

View File

@ -6,7 +6,7 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
{ {
public interface ILateBlocker public interface ILateBlocker
{ {
Task<bool> TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, Task<bool> TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild,
IMessageChannel channel, IUser user, string moduleName, string commandName); IMessageChannel channel, IUser user, string moduleName, string commandName);
} }
} }

View File

@ -9,6 +9,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
/// </summary> /// </summary>
public interface ILateExecutor public interface ILateExecutor
{ {
Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg); Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg);
} }
} }

View File

@ -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<PreconditionResult> 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"));
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}
}

View File

@ -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<ShardComMessage>(data));
}
});
}
public void Dispose()
{
_client.Dispose();
}
public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; };
}
}

View File

@ -7,9 +7,9 @@ namespace NadekoBot.TypeReaders
{ {
public class GuildTypeReader : TypeReader public class GuildTypeReader : TypeReader
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public GuildTypeReader(DiscordShardedClient client) public GuildTypeReader(DiscordSocketClient client)
{ {
_client = client; _client = client;
} }

View File

@ -126,17 +126,17 @@ namespace NadekoBot.Modules.Administration
{ {
var guser = (IGuildUser)Context.User; var guser = (IGuildUser)Context.User;
var userRoles = user.GetRoles(); var userRoles = user.GetRoles().Except(new[] { guser.Guild.EveryoneRole });
if (guser.Id != Context.Guild.OwnerId && if (user.Id == Context.Guild.OwnerId || (Context.User.Id != Context.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position)))
(user.Id == Context.Guild.OwnerId || guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position)))
return; return;
try try
{ {
await user.RemoveRolesAsync(userRoles).ConfigureAwait(false); await user.RemoveRolesAsync(userRoles).ConfigureAwait(false);
await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch (Exception ex)
{ {
Console.WriteLine(ex);
await ReplyErrorLocalized("rar_err").ConfigureAwait(false); await ReplyErrorLocalized("rar_err").ConfigureAwait(false);
} }
} }

View File

@ -140,7 +140,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page, (curPage) => await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) =>
{ {
return new EmbedBuilder() return new EmbedBuilder()
.WithTitle(GetText("self_assign_list", roleCnt)) .WithTitle(GetText("self_assign_list", roleCnt))

View File

@ -13,6 +13,8 @@ using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Administration; using NadekoBot.Services.Administration;
using System.Diagnostics;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
@ -25,10 +27,10 @@ namespace NadekoBot.Modules.Administration
private static readonly object _locker = new object(); private static readonly object _locker = new object();
private readonly SelfService _service; private readonly SelfService _service;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly IImagesService _images; private readonly IImagesService _images;
public SelfCommands(DbService db, SelfService service, DiscordShardedClient client, public SelfCommands(DbService db, SelfService service, DiscordSocketClient client,
IImagesService images) IImagesService images)
{ {
_db = db; _db = db;
@ -204,28 +206,28 @@ namespace NadekoBot.Modules.Administration
} }
[NadekoCommand, Usage, Description, Aliases] //todo 2 shard commands
[OwnerOnly] //[NadekoCommand, Usage, Description, Aliases]
public async Task ConnectShard(int shardid) //[Shard0Precondition]
{ //[OwnerOnly]
var shard = _client.GetShard(shardid); //public async Task RestartShard(int shardid)
//{
if (shard == null) // if (shardid == 0 || shardid > b)
{ // {
await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false); // await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false);
return; // return;
} // }
try // try
{ // {
await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false); // await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false);
await shard.StartAsync().ConfigureAwait(false); // await shard.StartAsync().ConfigureAwait(false);
await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false); // await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false);
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
_log.Warn(ex); // _log.Warn(ex);
} // }
} //}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
@ -417,8 +419,10 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task ReloadImages() public async Task ReloadImages()
{ {
var time = _images.Reload(); var sw = Stopwatch.StartNew();
await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false); _images.Reload();
sw.Stop();
await ReplyConfirmLocalized("images_loaded", sw.Elapsed.TotalSeconds.ToString("F3")).ConfigureAwait(false);
} }
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus) private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)

View File

@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Administration
.ToArray(); .ToArray();
var timezonesPerPage = 20; var timezonesPerPage = 20;
await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page, await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page,
(curPage) => new EmbedBuilder() (curPage) => new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithTitle(GetText("timezones_available")) .WithTitle(GetText("timezones_available"))

View File

@ -17,10 +17,10 @@ namespace NadekoBot.Modules.CustomReactions
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly DbService _db; private readonly DbService _db;
private readonly CustomReactionsService _crs; private readonly CustomReactionsService _crs;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public CustomReactions(IBotCredentials creds, DbService db, CustomReactionsService crs, public CustomReactions(IBotCredentials creds, DbService db, CustomReactionsService crs,
DiscordShardedClient client) DiscordSocketClient client)
{ {
_creds = creds; _creds = creds;
_db = db; _db = db;

View File

@ -22,12 +22,12 @@ namespace NadekoBot.Modules.Gambling
{ {
private readonly BotConfig _bc; private readonly BotConfig _bc;
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>(); public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordShardedClient client) public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordSocketClient client)
{ {
_bc = bc; _bc = bc;
_cs = cs; _cs = cs;
@ -82,14 +82,14 @@ namespace NadekoBot.Modules.Gambling
private readonly ITextChannel _raceChannel; private readonly ITextChannel _raceChannel;
private readonly BotConfig _bc; private readonly BotConfig _bc;
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly ILocalization _localization; private readonly ILocalization _localization;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
public bool Started { get; private set; } public bool Started { get; private set; }
public AnimalRace(ulong serverId, ITextChannel channel, string prefix, BotConfig bc, public AnimalRace(ulong serverId, ITextChannel channel, string prefix, BotConfig bc,
CurrencyService cs, DiscordShardedClient client, ILocalization localization, CurrencyService cs, DiscordSocketClient client, ILocalization localization,
NadekoStrings strings) NadekoStrings strings)
{ {
_prefix = prefix; _prefix = prefix;

View File

@ -34,11 +34,11 @@ namespace NadekoBot.Modules.Gambling
.ToArray(); .ToArray();
private string _secretCode = string.Empty; private string _secretCode = string.Empty;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly BotConfig _bc; private readonly BotConfig _bc;
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
public CurrencyEvents(DiscordShardedClient client, BotConfig bc, CurrencyService cs) public CurrencyEvents(DiscordSocketClient client, BotConfig bc, CurrencyService cs)
{ {
_client = client; _client = client;
_bc = bc; _bc = bc;
@ -151,7 +151,7 @@ namespace NadekoBot.Modules.Gambling
{ {
private readonly ConcurrentHashSet<ulong> _flowerReactionAwardedUsers = new ConcurrentHashSet<ulong>(); private readonly ConcurrentHashSet<ulong> _flowerReactionAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private IUserMessage StartingMessage { get; set; } private IUserMessage StartingMessage { get; set; }
@ -159,7 +159,7 @@ namespace NadekoBot.Modules.Gambling
private CancellationTokenSource Source { get; } private CancellationTokenSource Source { get; }
private CancellationToken CancelToken { get; } private CancellationToken CancelToken { get; }
public FlowerReactionEvent(DiscordShardedClient client, CurrencyService cs) public FlowerReactionEvent(DiscordSocketClient client, CurrencyService cs)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client; _client = client;

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Gambling
private readonly BotConfig _bc; private readonly BotConfig _bc;
private readonly DbService _db; private readonly DbService _db;
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public enum Role public enum Role
{ {
@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Gambling
List List
} }
public FlowerShop(BotConfig bc, DbService db, CurrencyService cs, DiscordShardedClient client) public FlowerShop(BotConfig bc, DbService db, CurrencyService cs, DiscordSocketClient client)
{ {
_db = db; _db = db;
_bc = bc; _bc = bc;

View File

@ -20,12 +20,12 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class Acropobia : NadekoSubmodule public class Acropobia : NadekoSubmodule
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
//channelId, game //channelId, game
public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>(); public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
public Acropobia(DiscordShardedClient client) public Acropobia(DiscordSocketClient client)
{ {
_client = client; _client = client;
} }
@ -86,10 +86,10 @@ namespace NadekoBot.Modules.Games
//text, votes //text, votes
private readonly ConcurrentDictionary<string, int> _votes = new ConcurrentDictionary<string, int>(); private readonly ConcurrentDictionary<string, int> _votes = new ConcurrentDictionary<string, int>();
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings; 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(); _log = LogManager.GetCurrentClassLogger();
_client = client; _client = client;

View File

@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Games.Hangman
public class HangmanGame: IDisposable public class HangmanGame: IDisposable
{ {
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public IMessageChannel GameChannel { get; } public IMessageChannel GameChannel { get; }
public HashSet<char> Guesses { get; } = new HashSet<char>(); public HashSet<char> Guesses { get; } = new HashSet<char>();
@ -82,7 +82,7 @@ namespace NadekoBot.Modules.Games.Hangman
public event Action<HangmanGame> OnEnded; public event Action<HangmanGame> OnEnded;
public HangmanGame(DiscordShardedClient client, IMessageChannel channel, string type) public HangmanGame(DiscordSocketClient client, IMessageChannel channel, string type)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client; _client = client;

View File

@ -15,9 +15,9 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class HangmanCommands : NadekoSubmodule public class HangmanCommands : NadekoSubmodule
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public HangmanCommands(DiscordShardedClient client) public HangmanCommands(DiscordSocketClient client)
{ {
_client = client; _client = client;
} }

View File

@ -20,13 +20,13 @@ namespace NadekoBot.Modules.Games.Models
public bool IsActive { get; private set; } public bool IsActive { get; private set; }
private readonly Stopwatch sw; private readonly Stopwatch sw;
private readonly List<ulong> finishedUserIds; private readonly List<ulong> finishedUserIds;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly GamesService _games; private readonly GamesService _games;
private readonly string _prefix; private readonly string _prefix;
private Logger _log { get; } 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(); _log = LogManager.GetCurrentClassLogger();
_games = games; _games = games;

View File

@ -116,7 +116,7 @@ namespace NadekoBot.Modules.Games
bool enabled; bool enabled;
using (var uow = _db.UnitOfWork) 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 }; var toAdd = new GCChannelId() { ChannelId = channel.Id };
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd)) if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))

View File

@ -13,10 +13,10 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class PollCommands : NadekoSubmodule public class PollCommands : NadekoSubmodule
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly PollService _polls; private readonly PollService _polls;
public PollCommands(DiscordShardedClient client, PollService polls) public PollCommands(DiscordSocketClient client, PollService polls)
{ {
_client = client; _client = client;
_polls = polls; _polls = polls;
@ -26,13 +26,7 @@ namespace NadekoBot.Modules.Games
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public Task Poll([Remainder] string arg = null) public Task Poll([Remainder] string arg = null)
=> InternalStartPoll(arg, false); => InternalStartPoll(arg);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public Task PublicPoll([Remainder] string arg = null)
=> InternalStartPoll(arg, true);
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
@ -45,9 +39,9 @@ namespace NadekoBot.Modules.Games
await Context.Channel.EmbedAsync(poll.GetStats(GetText("current_poll_results"))); 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); await ReplyErrorLocalized("poll_already_running").ConfigureAwait(false);
} }

View File

@ -20,9 +20,9 @@ namespace NadekoBot.Modules.Games
{ {
public static ConcurrentDictionary<ulong, TypingGame> RunningContests = new ConcurrentDictionary<ulong, TypingGame>(); public static ConcurrentDictionary<ulong, TypingGame> RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
private readonly GamesService _games; 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; _games = games;
_client = client; _client = client;

View File

@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Games
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>(); private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1); 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; _client = client;
} }
@ -87,9 +87,9 @@ namespace NadekoBot.Modules.Games
private IUserMessage _previousMessage; private IUserMessage _previousMessage;
private Timer _timeoutTimer; private Timer _timeoutTimer;
private readonly NadekoStrings _strings; 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; _channel = channel;
_strings = strings; _strings = strings;

View File

@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Games.Trivia
private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1);
private readonly Logger _log; private readonly Logger _log;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly BotConfig _bc; private readonly BotConfig _bc;
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Games.Trivia
public int WinRequirement { get; } 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, CurrencyService cs, IGuild guild, ITextChannel channel,
bool showHints, int winReq, bool isPokemon) bool showHints, int winReq, bool isPokemon)
{ {

View File

@ -18,12 +18,12 @@ namespace NadekoBot.Modules.Games
public class TriviaCommands : NadekoSubmodule public class TriviaCommands : NadekoSubmodule
{ {
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly BotConfig _bc; private readonly BotConfig _bc;
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>(); public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public TriviaCommands(DiscordShardedClient client, BotConfig bc, CurrencyService cs) public TriviaCommands(DiscordSocketClient client, BotConfig bc, CurrencyService cs)
{ {
_cs = cs; _cs = cs;
_client = client; _client = client;

View File

@ -155,8 +155,8 @@ namespace NadekoBot.Modules.Help
public async Task Guide() public async Task Guide()
{ {
await ConfirmLocalized("guide", await ConfirmLocalized("guide",
"http://nadekobot.readthedocs.io/en/latest/Commands%20List/", "http://nadekobot.readthedocs.io/en/1.3x/Commands%20List/",
"http://nadekobot.readthedocs.io/en/latest/").ConfigureAwait(false); "http://nadekobot.readthedocs.io/en/1.3x/").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]

View File

@ -22,12 +22,12 @@ namespace NadekoBot.Modules.Music
public class Music : NadekoTopLevelModule public class Music : NadekoTopLevelModule
{ {
private static MusicService _music; private static MusicService _music;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly IGoogleApiService _google; private readonly IGoogleApiService _google;
private readonly DbService _db; private readonly DbService _db;
public Music(DiscordShardedClient client, IBotCredentials creds, IGoogleApiService google, public Music(DiscordSocketClient client, IBotCredentials creds, IGoogleApiService google,
DbService db, MusicService music) DbService db, MusicService music)
{ {
_client = client; _client = client;

View File

@ -86,13 +86,12 @@ namespace NadekoBot.Modules
var text = GetText(textKey, replacements); var text = GetText(textKey, replacements);
return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + text); 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<string> GetUserInputAsync(ulong userId, ulong channelId) public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
{ {
var userInputTask = new TaskCompletionSource<string>(); var userInputTask = new TaskCompletionSource<string>();
var dsc = (DiscordShardedClient)Context.Client; var dsc = (DiscordSocketClient)Context.Client;
try try
{ {
dsc.MessageReceived += MessageReceived; dsc.MessageReceived += MessageReceived;

View File

@ -195,7 +195,7 @@ namespace NadekoBot.Modules.Permissions
var fws = fwHash.ToArray(); var fws = fwHash.ToArray();
await channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, await channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client,
page, page,
(curPage) => (curPage) =>
new EmbedBuilder() new EmbedBuilder()

View File

@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Utility
{ {
private readonly CommandMapService _service; private readonly CommandMapService _service;
private readonly DbService _db; 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; _service = service;
_db = db; _db = db;

View File

@ -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<ITextChannel>();
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<ITextChannel> 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);
}
}
}
}

View File

@ -16,10 +16,10 @@ namespace NadekoBot.Modules.Utility
[Group] [Group]
public class InfoCommands : NadekoSubmodule public class InfoCommands : NadekoSubmodule
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly IStatsService _stats; private readonly IStatsService _stats;
public InfoCommands(DiscordShardedClient client, IStatsService stats, CommandHandler ch) public InfoCommands(DiscordSocketClient client, IStatsService stats, CommandHandler ch)
{ {
_client = client; _client = client;
_stats = stats; _stats = stats;

View File

@ -34,7 +34,9 @@ namespace NadekoBot.Modules.Utility
[OwnerOnly] [OwnerOnly]
public async Task PatreonRewardsReload() 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); await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false);
} }
@ -44,6 +46,7 @@ namespace NadekoBot.Modules.Utility
{ {
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken)) if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
return; return;
if (DateTime.UtcNow.Day < 5) if (DateTime.UtcNow.Day < 5)
{ {
await ReplyErrorLocalized("clpa_too_early").ConfigureAwait(false); await ReplyErrorLocalized("clpa_too_early").ConfigureAwait(false);

View File

@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[Priority(1)] [Priority(0)]
public async Task Remind(MeOrHere meorhere, string timeStr, [Remainder] string message) public async Task Remind(MeOrHere meorhere, string timeStr, [Remainder] string message)
{ {
ulong target; ulong target;
@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
[Priority(0)] [Priority(1)]
public async Task Remind(ITextChannel channel, string timeStr, [Remainder] string message) public async Task Remind(ITextChannel channel, string timeStr, [Remainder] string message)
{ {
var perms = ((IGuildUser)Context.User).GetPermissions((ITextChannel)channel); var perms = ((IGuildUser)Context.User).GetPermissions((ITextChannel)channel);

View File

@ -22,10 +22,10 @@ namespace NadekoBot.Modules.Utility
public class RepeatCommands : NadekoSubmodule public class RepeatCommands : NadekoSubmodule
{ {
private readonly MessageRepeaterService _service; private readonly MessageRepeaterService _service;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
public RepeatCommands(MessageRepeaterService service, DiscordShardedClient client, DbService db) public RepeatCommands(MessageRepeaterService service, DiscordSocketClient client, DbService db)
{ {
_service = service; _service = service;
_client = client; _client = client;

View File

@ -6,7 +6,6 @@ using NadekoBot.Services.Utility;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility
{ {
public partial class Utility public partial class Utility

View File

@ -18,99 +18,25 @@ using Discord.WebSocket;
using System.Diagnostics; using System.Diagnostics;
using Color = Discord.Color; using Color = Discord.Color;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.Utility namespace NadekoBot.Modules.Utility
{ {
public partial class Utility : NadekoTopLevelModule public partial class Utility : NadekoTopLevelModule
{ {
private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>(); private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly IStatsService _stats; private readonly IStatsService _stats;
private readonly IBotCredentials _creds; 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; _client = client;
_stats = stats; _stats = stats;
_creds = creds; _creds = creds;
} _bot = bot;
}
//[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<string>(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<string>(reactions, r.Emoji.Name);
// if (index == -1)
// return;
// await usr.RemoveRolesAsync(roles[index]);
// }
// catch (Exception ex)
// {
// _log.Warn(ex);
// }
// }));
//}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -354,23 +280,25 @@ namespace NadekoBot.Modules.Utility
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[Shard0Precondition]
public async Task ShardStats(int page = 1) public async Task ShardStats(int page = 1)
{ {
if (--page < 0) if (--page < 0)
return; 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}") .Select(x => $"{x.Count()} {x.Key}")
.ToArray()); .ToArray());
var allShardStrings = _client.Shards var allShardStrings = statuses
.Select(x => .Select(x =>
GetText("shard_stats_txt", x.ShardId.ToString(), 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(); .ToArray();
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) => await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
{ {
@ -387,21 +315,9 @@ namespace NadekoBot.Modules.Utility
}, allShardStrings.Length / 25); }, 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] [NadekoCommand, Usage, Description, Aliases]
public async Task Stats() public async Task Stats()
{ {
var shardId = Context.Guild != null
? _client.GetShardIdFor(Context.Guild)
: 0;
await Context.Channel.EmbedAsync( await Context.Channel.EmbedAsync(
new EmbedBuilder().WithOkColor() new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}") .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")) .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("author")).WithValue(_stats.Author).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).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("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("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").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("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("presence")).WithValue( .AddField(efb => efb.WithName(GetText("presence")).WithValue(
GetText("presence_txt", GetText("presence_txt",
_client.Guilds.Count, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)) _stats.GuildCount, _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
);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]

View File

@ -4,8 +4,6 @@ using Discord.WebSocket;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Impl; using NadekoBot.Services.Impl;
using NLog; using NLog;
using NLog.Config;
using NLog.Targets;
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -27,8 +25,7 @@ using NadekoBot.Services.Utility;
using NadekoBot.Services.Help; using NadekoBot.Services.Help;
using System.IO; using System.IO;
using NadekoBot.Services.Pokemon; using NadekoBot.Services.Pokemon;
using NadekoBot.DataStructures; using NadekoBot.DataStructures.ShardCom;
using NadekoBot.Extensions;
namespace NadekoBot namespace NadekoBot
{ {
@ -45,47 +42,63 @@ namespace NadekoBot
public static Color OkColor { get; private set; } public static Color OkColor { get; private set; }
public static Color ErrorColor { get; private set; } public static Color ErrorColor { get; private set; }
public ImmutableArray<GuildConfig> AllGuildConfigs { get; } public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
public BotConfig BotConfig { get; } public BotConfig BotConfig { get; }
public DbService Db { get; } public DbService Db { get; }
public CommandService CommandService { get; } public CommandService CommandService { get; }
public CommandHandler CommandHandler { get; private set; } public CommandHandler CommandHandler { get; private set; }
public Localization Localization { get; } public Localization Localization { get; private set; }
public NadekoStrings Strings { get; } public NadekoStrings Strings { get; private set; }
public StatsService Stats { get; } public StatsService Stats { get; private set; }
public ImagesService Images { get; } public ImagesService Images { get; }
public CurrencyService Currency { get; } public CurrencyService Currency { get; }
public GoogleApiService GoogleApi { get; } public GoogleApiService GoogleApi { get; }
public DiscordShardedClient Client { get; } public DiscordSocketClient Client { get; }
public bool Ready { get; private set; } public bool Ready { get; private set; }
public INServiceProvider Services { get; private set; } public INServiceProvider Services { get; private set; }
public BotCredentials Credentials { get; } 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(); _log = LogManager.GetCurrentClassLogger();
TerribleElevatedPermissionCheck(); TerribleElevatedPermissionCheck();
Credentials = new BotCredentials(); Credentials = new BotCredentials();
port = port ?? Credentials.ShardRunPort;
_comClient = new ShardComClient(port.Value);
Db = new DbService(Credentials); Db = new DbService(Credentials);
using (var uow = Db.UnitOfWork) using (var uow = Db.UnitOfWork)
{ {
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs().ToImmutableArray();
BotConfig = uow.BotConfig.GetOrCreate(); BotConfig = uow.BotConfig.GetOrCreate();
OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16)); OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16)); ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
} }
Client = new DiscordShardedClient(new DiscordSocketConfig Client = new DiscordSocketClient(new DiscordSocketConfig
{ {
MessageCacheSize = 10, MessageCacheSize = 10,
LogLevel = LogSeverity.Warning, LogLevel = LogSeverity.Warning,
TotalShards = Credentials.TotalShards,
ConnectionTimeout = int.MaxValue, ConnectionTimeout = int.MaxValue,
TotalShards = Credentials.TotalShards,
ShardId = shardId,
AlwaysDownloadUsers = false, AlwaysDownloadUsers = false,
}); });
@ -94,182 +107,245 @@ namespace NadekoBot
CaseSensitiveCommands = false, CaseSensitiveCommands = false,
DefaultRunMode = RunMode.Sync, 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(); Images = new ImagesService();
Currency = new CurrencyService(BotConfig, Db); Currency = new CurrencyService(BotConfig, Db);
GoogleApi = new GoogleApiService(Credentials); GoogleApi = new GoogleApiService(Credentials);
SetupShard(shardId, parentProcessId, port.Value);
#if GLOBAL_NADEKO #if GLOBAL_NADEKO
Client.Log += Client_Log; Client.Log += Client_Log;
#endif #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() private void AddServices()
{ {
var soundcloudApiService = new SoundCloudApiService(Credentials); var startingGuildIdList = Client.Guilds.Select(x => (long)x.Id).ToList();
#region help //this unit of work will be used for initialization of all modules too, to prevent multiple queries from running
var helpService = new HelpService(BotConfig, CommandHandler, Strings); using (var uow = Db.UnitOfWork)
#endregion {
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 var soundcloudApiService = new SoundCloudApiService(Credentials);
//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
#region permissions #region help
var permissionsService = new PermissionService(Db, BotConfig, CommandHandler); var helpService = new HelpService(BotConfig, CommandHandler, Strings);
var blacklistService = new BlacklistService(BotConfig); #endregion
var cmdcdsService = new CmdCdService(AllGuildConfigs);
var filterService = new FilterService(Client, AllGuildConfigs);
var globalPermsService = new GlobalPermissionService(BotConfig);
#endregion
#region Searches //module services
var searchesService = new SearchesService(Client, GoogleApi, Db); //todo 90 - autodiscover, DI, and add instead of manual like this
var streamNotificationService = new StreamNotificationService(Db, Client, Strings); #region utility
var animeSearchService = new AnimeSearchService(); var remindService = new RemindService(Client, BotConfig, Db, startingGuildIdList, uow);
#endregion 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); #region permissions
var musicService = new MusicService(GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs); var permissionsService = new PermissionService(Client, Db, BotConfig, CommandHandler, Strings);
var crService = new CustomReactionsService(permissionsService, Db, Client, CommandHandler, BotConfig); var blacklistService = new BlacklistService(BotConfig);
var cmdcdsService = new CmdCdService(AllGuildConfigs);
var filterService = new FilterService(Client, AllGuildConfigs);
var globalPermsService = new GlobalPermissionService(BotConfig);
#endregion
#region Games #region Searches
var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, Strings, Images, CommandHandler); var searchesService = new SearchesService(Client, GoogleApi, Db);
var chatterBotService = new ChatterBotService(Client, permissionsService, AllGuildConfigs, CommandHandler); var streamNotificationService = new StreamNotificationService(Db, Client, Strings);
var pollService = new PollService(Client, Strings); var animeSearchService = new AnimeSearchService();
#endregion #endregion
#region administration var clashService = new ClashOfClansService(Client, Db, Localization, Strings, uow, startingGuildIdList);
var administrationService = new AdministrationService(AllGuildConfigs, CommandHandler); var musicService = new MusicService(GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs);
var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, Db); var crService = new CustomReactionsService(permissionsService, Db, Strings, Client, CommandHandler, BotConfig, uow);
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 #region Games
var pokemonService = new PokemonService(); var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, Strings, Images, CommandHandler);
#endregion 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<ILocalization>(Localization)
.Add<IStatsService>(Stats)
.Add<IImagesService>(Images)
.Add<IGoogleApiService>(GoogleApi)
.Add<IStatsService>(Stats)
.Add<IBotCredentials>(Credentials)
.Add<CommandService>(CommandService)
.Add<NadekoStrings>(Strings)
.Add<DiscordShardedClient>(Client)
.Add<BotConfig>(BotConfig)
.Add<CurrencyService>(Currency)
.Add<CommandHandler>(CommandHandler)
.Add<DbService>(Db)
//modules
.Add(crossServerTextService)
.Add(commandMapService)
.Add(remindService)
.Add(repeaterService)
.Add(converterService)
.Add(verboseErrorsService)
.Add(patreonRewardsService)
.Add(pruneService)
.Add<SearchesService>(searchesService)
.Add(streamNotificationService)
.Add(animeSearchService)
.Add<ClashOfClansService>(clashService)
.Add<MusicService>(musicService)
.Add<GreetSettingsService>(greetSettingsService)
.Add<CustomReactionsService>(crService)
.Add<HelpService>(helpService)
.Add<GamesService>(gamesService)
.Add(chatterBotService)
.Add(pollService)
.Add<AdministrationService>(administrationService)
.Add(selfService)
.Add(vcRoleService)
.Add(vPlusTService)
.Add(muteService)
.Add(ratelimitService)
.Add(playingRotateService)
.Add(gameVcService)
.Add(autoAssignRoleService)
.Add(protectionService)
.Add(logCommandService)
.Add(guildTimezoneService)
.Add<PermissionService>(permissionsService)
.Add(blacklistService)
.Add(cmdcdsService)
.Add(filterService)
.Add(globalPermsService)
.Add<PokemonService>(pokemonService)
.Build();
CommandHandler.AddServices(Services); //initialize Services
Services = new NServiceProvider.ServiceProviderBuilder()
.Add<ILocalization>(Localization)
.Add<IStatsService>(Stats)
.Add<IImagesService>(Images)
.Add<IGoogleApiService>(GoogleApi)
.Add<IStatsService>(Stats)
.Add<IBotCredentials>(Credentials)
.Add<CommandService>(CommandService)
.Add<NadekoStrings>(Strings)
.Add<DiscordSocketClient>(Client)
.Add<BotConfig>(BotConfig)
.Add<CurrencyService>(Currency)
.Add<CommandHandler>(CommandHandler)
.Add<DbService>(Db)
//modules
.Add(commandMapService)
.Add(remindService)
.Add(repeaterService)
//.Add(converterService)
.Add(verboseErrorsService)
.Add(patreonRewardsService)
.Add(pruneService)
.Add<SearchesService>(searchesService)
.Add(streamNotificationService)
.Add(animeSearchService)
.Add<ClashOfClansService>(clashService)
.Add<MusicService>(musicService)
.Add<GreetSettingsService>(greetSettingsService)
.Add<CustomReactionsService>(crService)
.Add<HelpService>(helpService)
.Add<GamesService>(gamesService)
.Add(chatterBotService)
.Add(pollService)
.Add<AdministrationService>(administrationService)
.Add(selfService)
.Add(vcRoleService)
.Add(vPlusTService)
.Add(muteService)
.Add(ratelimitService)
.Add(playingRotateService)
.Add(gameVcService)
.Add(autoAssignRoleService)
.Add(protectionService)
.Add(logCommandService)
.Add(guildTimezoneService)
.Add<PermissionService>(permissionsService)
.Add(blacklistService)
.Add(cmdcdsService)
.Add(filterService)
.Add(globalPermsService)
.Add<PokemonService>(pokemonService)
.Add<NadekoBot>(this)
.Build();
//setup typereaders
CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader()); CommandHandler.AddServices(Services);
CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader(CommandService, CommandHandler));
CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader(crService, CommandService, CommandHandler)); //setup typereaders
CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService)); CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService)); CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader(CommandService, CommandHandler));
CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client)); CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader(crService, CommandService, CommandHandler));
CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader(guildTimezoneService)); CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService));
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService));
CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader(guildTimezoneService));
}
} }
private async Task LoginAsync(string token) private async Task LoginAsync(string token)
{ {
_log.Info("Logging in..."); var clientReady = new TaskCompletionSource<bool>();
//connect
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
await Client.StartAsync().ConfigureAwait(false);
_log.Info("Waiting for all shards to connect..."); Task SetClientReady()
while (!Client.Shards.All(x => x.ConnectionState == ConnectionState.Connected))
{ {
_log.Info("Connecting... {0}/{1}", Client.Shards.Count(x => x.ConnectionState == ConnectionState.Connected), Client.Shards.Count); var _ = Task.Run(async () =>
await Task.Delay(1000).ConfigureAwait(false); {
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) public async Task RunAsync(params string[] args)
{ {
if(ShardId == 0)
_log.Info("Starting NadekoBot v" + StatsService.BotVersion); _log.Info("Starting NadekoBot v" + StatsService.BotVersion);
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
await LoginAsync(Credentials.Token).ConfigureAwait(false); await LoginAsync(Credentials.Token).ConfigureAwait(false);
_log.Info("Loading services..."); _log.Info($"Shard {ShardId} loading services...");
AddServices(); AddServices();
sw.Stop(); 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<IStatsService>(); var stats = Services.GetService<IStatsService>();
stats.Initialize(); stats.Initialize();
@ -290,15 +366,17 @@ namespace NadekoBot
// .Select(x => x.Key + $"({x.Count()})"))); // .Select(x => x.Key + $"({x.Count()})")));
//unload modules which are not available on the public bot //unload modules which are not available on the public bot
#if GLOBAL_NADEKO #if GLOBAL_NADEKO
CommandService CommandService
.Modules .Modules
.ToArray() .ToArray()
.Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot))) .Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot)))
.ForEach(x => CommandService.RemoveModuleAsync(x)); .ForEach(x => CommandService.RemoveModuleAsync(x));
#endif #endif
Ready = true; 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) private Task Client_Log(LogMessage arg)
@ -313,7 +391,13 @@ namespace NadekoBot
public async Task RunAndBlockAsync(params string[] args) public async Task RunAndBlockAsync(params string[] args)
{ {
await RunAsync(args).ConfigureAwait(false); 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() 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(); if (shardId != 0)
var consoleTarget = new ColoredConsoleTarget()
{ {
Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}" new Thread(new ThreadStart(() =>
}; {
logConfig.AddTarget("Console", consoleTarget); try
{
logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); var p = Process.GetProcessById(parentProcessId);
if (p == null)
LogManager.Configuration = logConfig; return;
p.WaitForExit();
}
finally
{
Environment.Exit(10);
}
})).Start();
}
else
{
ShardCoord = new ShardsCoordinator(port);
}
} }
} }
} }

View File

@ -90,5 +90,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Modules\Music\Classes\" /> <Folder Include="Modules\Music\Classes\" />
<Folder Include="Utility\Services\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,7 +2,17 @@
{ {
public class Program public class Program
{ {
public static void Main(string[] args) => public static void Main(string[] args)
new NadekoBot().RunAndBlockAsync(args).GetAwaiter().GetResult(); {
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();
}
} }
} }

View File

@ -0,0 +1,7 @@
{
"profiles": {
"NadekoBot": {
"commandName": "Project"
}
}
}

View File

@ -3096,14 +3096,14 @@
<data name="shardstats_usage" xml:space="preserve"> <data name="shardstats_usage" xml:space="preserve">
<value>`{0}shardstats` or `{0}shardstats 2`</value> <value>`{0}shardstats` or `{0}shardstats 2`</value>
</data> </data>
<data name="connectshard_cmd" xml:space="preserve"> <data name="restartshard_cmd" xml:space="preserve">
<value>connectshard</value> <value>restartshard</value>
</data> </data>
<data name="connectshard_desc" xml:space="preserve"> <data name="restartshard_desc" xml:space="preserve">
<value>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.</value> <value>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.</value>
</data> </data>
<data name="connectshard_usage" xml:space="preserve"> <data name="restartshard_usage" xml:space="preserve">
<value>`{0}connectshard 2`</value> <value>`{0}restartshard 2`</value>
</data> </data>
<data name="shardid_cmd" xml:space="preserve"> <data name="shardid_cmd" xml:space="preserve">
<value>shardid</value> <value>shardid</value>

View File

@ -12,12 +12,12 @@ namespace NadekoBot.Services.Administration
public class AutoAssignRoleService public class AutoAssignRoleService
{ {
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
//guildid/roleid //guildid/roleid
public ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; } public ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; }
public AutoAssignRoleService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs) public AutoAssignRoleService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client; _client = client;

View File

@ -16,9 +16,9 @@ namespace NadekoBot.Services.Administration
private readonly Logger _log; private readonly Logger _log;
private readonly DbService _db; private readonly DbService _db;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public GameVoiceChannelService(DiscordShardedClient client, DbService db, IEnumerable<GuildConfig> gcs) public GameVoiceChannelService(DiscordSocketClient client, DbService db, IEnumerable<GuildConfig> gcs)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_db = db; _db = db;

View File

@ -16,7 +16,7 @@ namespace NadekoBot.Services.Administration
public class LogCommandService public class LogCommandService
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly Logger _log; private readonly Logger _log;
private string PrettyCurrentTime => $"【{DateTime.UtcNow:HH:mm:ss}】"; private string PrettyCurrentTime => $"【{DateTime.UtcNow:HH:mm:ss}】";
@ -31,7 +31,7 @@ namespace NadekoBot.Services.Administration
private readonly MuteService _mute; private readonly MuteService _mute;
private readonly ProtectionService _prot; private readonly ProtectionService _prot;
public LogCommandService(DiscordShardedClient client, NadekoStrings strings, public LogCommandService(DiscordSocketClient client, NadekoStrings strings,
IEnumerable<GuildConfig> gcs, DbService db, MuteService mute, ProtectionService prot) IEnumerable<GuildConfig> gcs, DbService db, MuteService mute, ProtectionService prot)
{ {
_client = client; _client = client;
@ -74,7 +74,7 @@ namespace NadekoBot.Services.Administration
_client.UserUnbanned += _client_UserUnbanned; _client.UserUnbanned += _client_UserUnbanned;
_client.UserJoined += _client_UserJoined; _client.UserJoined += _client_UserJoined;
_client.UserLeft += _client_UserLeft; _client.UserLeft += _client_UserLeft;
_client.UserPresenceUpdated += _client_UserPresenceUpdated; //_client.UserPresenceUpdated += _client_UserPresenceUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS; _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS;
_client.GuildMemberUpdated += _client_GuildUserUpdated; _client.GuildMemberUpdated += _client_GuildUserUpdated;
@ -576,48 +576,48 @@ namespace NadekoBot.Services.Administration
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after) //private Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
{ //{
var _ = Task.Run(async () => // var _ = Task.Run(async () =>
{ // {
try // try
{ // {
var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild; // var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild;
if (guild == null) // if (guild == null)
return; // return;
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting) // if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|| (logSetting.LogUserPresenceId == null) // || (logSetting.LogUserPresenceId == null)
|| before.Status == after.Status) // || before.Status == after.Status)
return; // return;
ITextChannel logChannel; // ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null) // if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null)
return; // return;
string str = ""; // string str = "";
if (before.Status != after.Status) // if (before.Status != after.Status)
str = "🎭" + Format.Code(PrettyCurrentTime) + // str = "🎭" + Format.Code(PrettyCurrentTime) +
GetText(logChannel.Guild, "user_status_change", // GetText(logChannel.Guild, "user_status_change",
"👤" + Format.Bold(usr.Username), // "👤" + Format.Bold(usr.Username),
Format.Bold(after.Status.ToString())); // Format.Bold(after.Status.ToString()));
//if (before.Game?.Name != after.Game?.Name) // //if (before.Game?.Name != after.Game?.Name)
//{ // //{
// if (str != "") // // if (str != "")
// str += "\n"; // // str += "\n";
// str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**."; // // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
//} // //}
PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; }); // PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
} // }
catch // catch
{ // {
// ignored // // ignored
} // }
}); // });
return Task.CompletedTask; // return Task.CompletedTask;
} //}
private Task _client_UserLeft(IGuildUser usr) private Task _client_UserLeft(IGuildUser usr)
{ {

View File

@ -33,10 +33,10 @@ namespace NadekoBot.Services.Administration
private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny); private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
private readonly Logger _log = LogManager.GetCurrentClassLogger(); private readonly Logger _log = LogManager.GetCurrentClassLogger();
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
public MuteService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, DbService db) public MuteService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
{ {
_client = client; _client = client;
_db = db; _db = db;

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Services.Administration
public List<PlayingStatus> RotatingStatusMessages { get; } public List<PlayingStatus> RotatingStatusMessages { get; }
public volatile bool RotatingStatuses; public volatile bool RotatingStatuses;
private readonly Timer _t; private readonly Timer _t;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly BotConfig _bc; private readonly BotConfig _bc;
private readonly MusicService _music; private readonly MusicService _music;
private readonly Logger _log; private readonly Logger _log;
@ -27,7 +27,7 @@ namespace NadekoBot.Services.Administration
public int Index { get; set; } public int Index { get; set; }
} }
public PlayingRotateService(DiscordShardedClient client, BotConfig bc, MusicService music) public PlayingRotateService(DiscordSocketClient client, BotConfig bc, MusicService music)
{ {
_client = client; _client = client;
_bc = bc; _bc = bc;
@ -36,7 +36,7 @@ namespace NadekoBot.Services.Administration
RotatingStatusMessages = _bc.RotatingStatusMessages; RotatingStatusMessages = _bc.RotatingStatusMessages;
RotatingStatuses = _bc.RotatingStatuses; RotatingStatuses = _bc.RotatingStatuses;
_t = new Timer(async (objState) => _t = new Timer(async (objState) =>
{ {
try try
@ -52,17 +52,12 @@ namespace NadekoBot.Services.Administration
var status = RotatingStatusMessages[state.Index++].Status; var status = RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status)) if (string.IsNullOrWhiteSpace(status))
return; return;
PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client,_music))); PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client, _music)));
var shards = _client.Shards; ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(client)));
for (int i = 0; i < shards.Count; i++) try { await client.SetGameAsync(status).ConfigureAwait(false); }
catch (Exception ex)
{ {
var curShard = shards.ElementAt(i); _log.Warn(ex);
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);
}
} }
} }
catch (Exception ex) catch (Exception ex)
@ -72,8 +67,8 @@ namespace NadekoBot.Services.Administration
}, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); }, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
} }
public Dictionary<string, Func<DiscordShardedClient, MusicService, string>> PlayingPlaceholders { get; } = public Dictionary<string, Func<DiscordSocketClient, MusicService, string>> PlayingPlaceholders { get; } =
new Dictionary<string, Func<DiscordShardedClient, MusicService, string>> { new Dictionary<string, Func<DiscordSocketClient, MusicService, string>> {
{ "%servers%", (c, ms) => c.Guilds.Count.ToString()}, { "%servers%", (c, ms) => c.Guilds.Count.ToString()},
{ "%users%", (c, ms) => c.Guilds.Sum(s => s.Users.Count).ToString()}, { "%users%", (c, ms) => c.Guilds.Sum(s => s.Users.Count).ToString()},
{ "%playing%", (c, ms) => { { "%playing%", (c, ms) => {
@ -90,7 +85,6 @@ namespace NadekoBot.Services.Administration
}, },
{ "%queued%", (c, ms) => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, { "%queued%", (c, ms) => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
{ "%time%", (c, ms) => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) }, { "%time%", (c, ms) => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
{ "%shardcount%", (c, ms) => c.Shards.Count.ToString() },
}; };
public Dictionary<string, Func<DiscordSocketClient, string>> ShardSpecificPlaceholders { get; } = public Dictionary<string, Func<DiscordSocketClient, string>> ShardSpecificPlaceholders { get; } =

View File

@ -21,10 +21,10 @@ namespace NadekoBot.Services.Administration
public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered = delegate { return Task.CompletedTask; }; public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered = delegate { return Task.CompletedTask; };
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly MuteService _mute; private readonly MuteService _mute;
public ProtectionService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, MuteService mute) public ProtectionService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, MuteService mute)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client; _client = client;

View File

@ -19,9 +19,9 @@ namespace NadekoBot.Services.Administration
public ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>(); public ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>();
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public SlowmodeService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs) public SlowmodeService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client; _client = client;

View File

@ -23,11 +23,11 @@ namespace NadekoBot.Services.Administration
private readonly Logger _log; private readonly Logger _log;
private readonly ILocalization _localization; private readonly ILocalization _localization;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels = new ImmutableArray<AsyncLazy<IDMChannel>>(); private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels = new ImmutableArray<AsyncLazy<IDMChannel>>();
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) BotConfig bc, ILocalization localization, NadekoStrings strings, IBotCredentials creds)
{ {
_bot = bot; _bot = bot;
@ -39,12 +39,8 @@ namespace NadekoBot.Services.Administration
_client = client; _client = client;
_creds = creds; _creds = creds;
using (var uow = _db.UnitOfWork) ForwardDMs = bc.ForwardMessages;
{ ForwardDMsToAllOwners = bc.ForwardToAllOwners;
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages;
ForwardDMsToAllOwners = config.ForwardToAllOwners;
}
var _ = Task.Run(async () => var _ = Task.Run(async () =>
{ {
@ -67,12 +63,8 @@ namespace NadekoBot.Services.Administration
_client.Guilds.SelectMany(g => g.Users); _client.Guilds.SelectMany(g => g.Users);
LoadOwnerChannels(); if(client.ShardId == 0)
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.");
}); });
} }
@ -81,11 +73,9 @@ namespace NadekoBot.Services.Administration
var hs = new HashSet<ulong>(_creds.OwnerIds); var hs = new HashSet<ulong>(_creds.OwnerIds);
var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>(); var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>();
foreach (var s in _client.Shards) if (hs.Count > 0)
{ {
if (hs.Count == 0) foreach (var g in _client.Guilds)
break;
foreach (var g in s.Guilds)
{ {
if (hs.Count == 0) if (hs.Count == 0)
break; break;
@ -105,10 +95,15 @@ namespace NadekoBot.Services.Administration
ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key)) ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key))
.Select(x => x.Value) .Select(x => x.Value)
.ToImmutableArray(); .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 // 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) if (msg.Channel is IDMChannel && ForwardDMs && ownerChannels.Length > 0)
{ {

View File

@ -14,11 +14,11 @@ namespace NadekoBot.Services.Administration
{ {
private readonly Logger _log; private readonly Logger _log;
private readonly DbService _db; private readonly DbService _db;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; } public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
public VcRoleService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, DbService db) public VcRoleService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_db = db; _db = db;

View File

@ -20,12 +20,12 @@ namespace NadekoBot.Services.Administration
public readonly ConcurrentHashSet<ulong> VoicePlusTextCache; public readonly ConcurrentHashSet<ulong> VoicePlusTextCache;
private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>(); private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly DbService _db; private readonly DbService _db;
private readonly Logger _log; private readonly Logger _log;
public VplusTService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, NadekoStrings strings, public VplusTService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, NadekoStrings strings,
DbService db) DbService db)
{ {
_client = client; _client = client;

View File

@ -1,6 +1,7 @@
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -17,7 +18,7 @@ namespace NadekoBot.Services.ClashOfClans
// shouldn't be here // shouldn't be here
public class ClashOfClansService public class ClashOfClansService
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
private readonly ILocalization _localization; private readonly ILocalization _localization;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
@ -25,28 +26,27 @@ namespace NadekoBot.Services.ClashOfClans
public ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } public ConcurrentDictionary<ulong, List<ClashWar>> 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<long> guilds)
{ {
_client = client; _client = client;
_db = db; _db = db;
_localization = localization; _localization = localization;
_strings = strings; _strings = strings;
using (var uow = _db.UnitOfWork) ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
{ uow.ClashOfClans
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>( .GetAllWars(guilds)
uow.ClashOfClans .Select(cw =>
.GetAllWars() {
.Select(cw => cw.Channel = _client.GetGuild(cw.GuildId)?
{ .GetTextChannel(cw.ChannelId);
cw.Channel = _client.GetGuild(cw.GuildId)? return cw;
.GetTextChannel(cw.ChannelId); })
return cw; .Where(cw => cw.Channel != null)
}) .GroupBy(cw => cw.GuildId)
.Where(cw => cw.Channel != null) .ToDictionary(g => g.Key, g => g.ToList()));
.GroupBy(cw => cw.GuildId)
.ToDictionary(g => g.Key, g => g.ToList()));
}
checkWarTimer = new Timer(async _ => checkWarTimer = new Timer(async _ =>
{ {

View File

@ -28,7 +28,7 @@ namespace NadekoBot.Services
{ {
public const int GlobalCommandsCooldown = 750; public const int GlobalCommandsCooldown = 750;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly CommandService _commandService; private readonly CommandService _commandService;
private readonly Logger _log; private readonly Logger _log;
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
@ -48,7 +48,7 @@ namespace NadekoBot.Services
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>(); public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
private readonly Timer _clearUsersOnShortCooldown; private readonly Timer _clearUsersOnShortCooldown;
public CommandHandler(DiscordShardedClient client, DbService db, BotConfig bc, IEnumerable<GuildConfig> gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot) public CommandHandler(DiscordSocketClient client, DbService db, BotConfig bc, IEnumerable<GuildConfig> gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot)
{ {
_client = client; _client = client;
_commandService = commandService; _commandService = commandService;

View File

@ -10,6 +10,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Services.Permissions; using NadekoBot.Services.Permissions;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services.Database;
namespace NadekoBot.Services.CustomReactions namespace NadekoBot.Services.CustomReactions
{ {
@ -22,13 +23,14 @@ namespace NadekoBot.Services.CustomReactions
private readonly Logger _log; private readonly Logger _log;
private readonly DbService _db; private readonly DbService _db;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly PermissionService _perms; private readonly PermissionService _perms;
private readonly CommandHandler _cmd; private readonly CommandHandler _cmd;
private readonly BotConfig _bc; private readonly BotConfig _bc;
private readonly NadekoStrings _strings;
public CustomReactionsService(PermissionService perms, DbService db, public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings,
DiscordShardedClient client, CommandHandler cmd, BotConfig bc) DiscordSocketClient client, CommandHandler cmd, BotConfig bc, IUnitOfWork uow)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_db = db; _db = db;
@ -36,16 +38,11 @@ namespace NadekoBot.Services.CustomReactions
_perms = perms; _perms = perms;
_cmd = cmd; _cmd = cmd;
_bc = bc; _bc = bc;
_strings = strings;
var sw = Stopwatch.StartNew();
using (var uow = _db.UnitOfWork) var items = uow.CustomReactions.GetAll();
{ GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
var items = uow.CustomReactions.GetAll(); GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
}
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
} }
public void ClearStats() => ReactionStats.Clear(); public void ClearStats() => ReactionStats.Clear();
@ -98,7 +95,7 @@ namespace NadekoBot.Services.CustomReactions
return greaction; return greaction;
} }
public async Task<bool> TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg) public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{ {
// maybe this message is a custom reaction // maybe this message is a custom reaction
var cr = await Task.Run(() => TryGetCustomReaction(msg)).ConfigureAwait(false); var cr = await Task.Run(() => TryGetCustomReaction(msg)).ConfigureAwait(false);
@ -114,7 +111,7 @@ namespace NadekoBot.Services.CustomReactions
{ {
if (pc.Verbose) 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 { } try { await msg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
_log.Info(returnMsg); _log.Info(returnMsg);
} }

View File

@ -40,7 +40,7 @@ namespace NadekoBot.Services.CustomReactions
} }, } },
}; };
public static Dictionary<string, Func<IUserMessage, DiscordShardedClient, string>> placeholders = new Dictionary<string, Func<IUserMessage, DiscordShardedClient, string>>() public static Dictionary<string, Func<IUserMessage, DiscordSocketClient, string>> placeholders = new Dictionary<string, Func<IUserMessage, DiscordSocketClient, string>>()
{ {
{"%mention%", (ctx, client) => { return $"<@{client.CurrentUser.Id}>"; } }, {"%mention%", (ctx, client) => { return $"<@{client.CurrentUser.Id}>"; } },
{"%user%", (ctx, client) => { return ctx.Author.Mention; } }, {"%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) foreach (var ph in placeholders)
{ {
@ -104,7 +104,7 @@ namespace NadekoBot.Services.CustomReactions
return str; return str;
} }
private static async Task<string> ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordShardedClient client, string resolvedTrigger) private static async Task<string> ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordSocketClient client, string resolvedTrigger)
{ {
foreach (var ph in placeholders) foreach (var ph in placeholders)
{ {
@ -127,13 +127,13 @@ namespace NadekoBot.Services.CustomReactions
return str; 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); => cr.Trigger.ResolveTriggerString(ctx, client);
public static Task<string > ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client) public static Task<string > ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client)
=> cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client)); => cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client));
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context, DiscordShardedClient client, CustomReactionsService crs) public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context, DiscordSocketClient client, CustomReactionsService crs)
{ {
var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel; var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel;

View File

@ -19,7 +19,9 @@ namespace NadekoBot.Services.Database
{ {
var optionsBuilder = new DbContextOptionsBuilder(); var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db");
return new NadekoContext(optionsBuilder.Options); var ctx = new NadekoContext(optionsBuilder.Options);
ctx.Database.SetCommandTimeout(60);
return ctx;
} }
} }

View File

@ -5,6 +5,6 @@ namespace NadekoBot.Services.Database.Repositories
{ {
public interface IClashOfClansRepository : IRepository<ClashWar> public interface IClashOfClansRepository : IRepository<ClashWar>
{ {
IEnumerable<ClashWar> GetAllWars(); IEnumerable<ClashWar> GetAllWars(List<long> guilds);
} }
} }

View File

@ -11,10 +11,10 @@ namespace NadekoBot.Services.Database.Repositories
GuildConfig For(ulong guildId, Func<DbSet<GuildConfig>, IQueryable<GuildConfig>> includes = null); GuildConfig For(ulong guildId, Func<DbSet<GuildConfig>, IQueryable<GuildConfig>> includes = null);
GuildConfig LogSettingsFor(ulong guildId); GuildConfig LogSettingsFor(ulong guildId);
IEnumerable<GuildConfig> OldPermissionsForAll(); IEnumerable<GuildConfig> OldPermissionsForAll();
IEnumerable<GuildConfig> GetAllGuildConfigs(); IEnumerable<GuildConfig> GetAllGuildConfigs(List<long> availableGuilds);
IEnumerable<FollowedStream> GetAllFollowedStreams(); IEnumerable<FollowedStream> GetAllFollowedStreams(List<long> included);
void SetCleverbotEnabled(ulong id, bool cleverbotEnabled); void SetCleverbotEnabled(ulong id, bool cleverbotEnabled);
IEnumerable<GuildConfig> Permissionsv2ForAll(); IEnumerable<GuildConfig> Permissionsv2ForAll(List<long> include);
GuildConfig GcWithPermissionsv2For(ulong guildId); GuildConfig GcWithPermissionsv2For(ulong guildId);
} }
} }

View File

@ -1,9 +1,11 @@
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Collections;
using System.Collections.Generic;
namespace NadekoBot.Services.Database.Repositories namespace NadekoBot.Services.Database.Repositories
{ {
public interface IReminderRepository : IRepository<Reminder> public interface IReminderRepository : IRepository<Reminder>
{ {
IEnumerable<Reminder> GetIncludedReminders(List<long> guildIds);
} }
} }

View File

@ -11,9 +11,11 @@ namespace NadekoBot.Services.Database.Repositories.Impl
{ {
} }
public IEnumerable<ClashWar> GetAllWars() public IEnumerable<ClashWar> GetAllWars(List<long> guilds)
{ {
var toReturn = _set.Include(cw => cw.Bases) var toReturn = _set
.Where(cw => guilds.Contains((long)cw.GuildId))
.Include(cw => cw.Bases)
.ToList(); .ToList();
toReturn.ForEach(cw => cw.Bases = cw.Bases.Where(w => w.SequenceNumber != null).OrderBy(w => w.SequenceNumber).ToList()); toReturn.ForEach(cw => cw.Bases = cw.Bases.Where(w => w.SequenceNumber != null).OrderBy(w => w.SequenceNumber).ToList());
return toReturn; return toReturn;

View File

@ -24,8 +24,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
} }
}; };
public IEnumerable<GuildConfig> GetAllGuildConfigs() => public IEnumerable<GuildConfig> GetAllGuildConfigs(List<long> availableGuilds) =>
_set.Include(gc => gc.LogSetting) _set
.Where(gc => availableGuilds.Contains((long)gc.GuildId))
.Include(gc => gc.LogSetting)
.ThenInclude(ls => ls.IgnoredChannels) .ThenInclude(ls => ls.IgnoredChannels)
.Include(gc => gc.MutedUsers) .Include(gc => gc.MutedUsers)
.Include(gc => gc.CommandAliases) .Include(gc => gc.CommandAliases)
@ -42,6 +44,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl
.Include(gc => gc.SlowmodeIgnoredUsers) .Include(gc => gc.SlowmodeIgnoredUsers)
.Include(gc => gc.AntiSpamSetting) .Include(gc => gc.AntiSpamSetting)
.ThenInclude(x => x.IgnoredChannels) .ThenInclude(x => x.IgnoredChannels)
.Include(gc => gc.FollowedStreams)
.ToList(); .ToList();
/// <summary> /// <summary>
@ -134,9 +137,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
return query.ToList(); return query.ToList();
} }
public IEnumerable<GuildConfig> Permissionsv2ForAll() public IEnumerable<GuildConfig> Permissionsv2ForAll(List<long> include)
{ {
var query = _set var query = _set
.Where(x => include.Contains((long)x.GuildId))
.Include(gc => gc.Permissions); .Include(gc => gc.Permissions);
return query.ToList(); return query.ToList();
@ -167,8 +171,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
return config; return config;
} }
public IEnumerable<FollowedStream> GetAllFollowedStreams() => public IEnumerable<FollowedStream> GetAllFollowedStreams(List<long> included) =>
_set.Include(gc => gc.FollowedStreams) _set
.Where(gc => included.Contains((long)gc.GuildId))
.Include(gc => gc.FollowedStreams)
.SelectMany(gc => gc.FollowedStreams) .SelectMany(gc => gc.FollowedStreams)
.ToList(); .ToList();

View File

@ -1,5 +1,8 @@
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Services.Database.Repositories.Impl namespace NadekoBot.Services.Database.Repositories.Impl
{ {
@ -8,5 +11,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
public ReminderRepository(DbContext context) : base(context) public ReminderRepository(DbContext context) : base(context)
{ {
} }
public IEnumerable<Reminder> GetIncludedReminders(List<long> guildIds)
{
return _set.Where(x => guildIds.Contains((long)x.ServerId)).ToList();
}
} }
} }

View File

@ -32,9 +32,21 @@ namespace NadekoBot.Services
public NadekoContext GetDbContext() public NadekoContext GetDbContext()
{ {
var context = new NadekoContext(options); var context = new NadekoContext(options);
context.Database.SetCommandTimeout(60);
context.Database.Migrate(); context.Database.Migrate();
context.EnsureSeedData(); 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; return context;
} }

View File

@ -12,7 +12,7 @@ namespace NadekoBot.Services.Discord
public event Action<SocketReaction> OnReactionRemoved = delegate { }; public event Action<SocketReaction> OnReactionRemoved = delegate { };
public event Action OnReactionsCleared = 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)); Message = msg ?? throw new ArgumentNullException(nameof(msg));
_client = client; _client = client;
@ -69,7 +69,7 @@ namespace NadekoBot.Services.Discord
} }
private bool disposing = false; private bool disposing = false;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
public void Dispose() public void Dispose()
{ {

View File

@ -15,19 +15,22 @@ namespace NadekoBot.Services.Games
{ {
public class ChatterBotService : IEarlyBlockingExecutor public class ChatterBotService : IEarlyBlockingExecutor
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly Logger _log; private readonly Logger _log;
private readonly PermissionService _perms; private readonly PermissionService _perms;
private readonly CommandHandler _cmd; private readonly CommandHandler _cmd;
private readonly NadekoStrings _strings;
public ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> ChatterBotGuilds { get; } public ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> ChatterBotGuilds { get; }
public ChatterBotService(DiscordShardedClient client, PermissionService perms, IEnumerable<GuildConfig> gcs, CommandHandler cmd) public ChatterBotService(DiscordSocketClient client, PermissionService perms, IEnumerable<GuildConfig> gcs,
CommandHandler cmd, NadekoStrings strings)
{ {
_client = client; _client = client;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_perms = perms; _perms = perms;
_cmd = cmd; _cmd = cmd;
_strings = strings;
ChatterBotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>( ChatterBotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
gcs.Where(gc => gc.CleverbotEnabled) gcs.Where(gc => gc.CleverbotEnabled)
@ -83,7 +86,7 @@ namespace NadekoBot.Services.Games
return true; return true;
} }
public async Task<bool> TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage usrMsg) public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage usrMsg)
{ {
if (!(guild is SocketGuild sg)) if (!(guild is SocketGuild sg))
return false; return false;
@ -102,7 +105,7 @@ namespace NadekoBot.Services.Games
if (pc.Verbose) if (pc.Verbose)
{ {
//todo move this to permissions //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 { } try { await usrMsg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
_log.Info(returnMsg); _log.Info(returnMsg);
} }

View File

@ -23,7 +23,7 @@ namespace NadekoBot.Services.Games
public readonly ImmutableArray<string> EightBallResponses; public readonly ImmutableArray<string> EightBallResponses;
private readonly Timer _t; private readonly Timer _t;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private readonly IImagesService _images; private readonly IImagesService _images;
private readonly Logger _log; private readonly Logger _log;
@ -33,7 +33,7 @@ namespace NadekoBot.Services.Games
public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>(); public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
public GamesService(DiscordShardedClient client, BotConfig bc, IEnumerable<GuildConfig> gcs, public GamesService(DiscordSocketClient client, BotConfig bc, IEnumerable<GuildConfig> gcs,
NadekoStrings strings, IImagesService images, CommandHandler cmdHandler) NadekoStrings strings, IImagesService images, CommandHandler cmdHandler)
{ {
_bc = bc; _bc = bc;

View File

@ -18,16 +18,13 @@ namespace NadekoBot.Services.Games
private string[] answers { get; } private string[] answers { get; }
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>(); private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
private readonly string _question; private readonly string _question;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
private bool running = false; private bool running = false;
private HashSet<ulong> _guildUsers;
public event Action<ulong> OnEnded = delegate { }; public event Action<ulong> OnEnded = delegate { };
public bool IsPublic { get; } public Poll(DiscordSocketClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable<string> enumerable)
public Poll(DiscordShardedClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false)
{ {
_client = client; _client = client;
_strings = strings; _strings = strings;
@ -36,7 +33,6 @@ namespace NadekoBot.Services.Games
_guild = ((ITextChannel)umsg.Channel).Guild; _guild = ((ITextChannel)umsg.Channel).Guild;
_question = question; _question = question;
answers = enumerable as string[] ?? enumerable.ToArray(); answers = enumerable as string[] ?? enumerable.ToArray();
IsPublic = isPublic;
} }
public EmbedBuilder GetStats(string title) 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 msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n";
var num = 1; var num = 1;
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
if (!IsPublic) msgToSend += "\n" + Format.Bold(GetText("poll_vote_public"));
msgToSend += "\n" + Format.Bold(GetText("poll_vote_private"));
else
msgToSend += "\n" + Format.Bold(GetText("poll_vote_public"));
if (!IsPublic)
_guildUsers = new HashSet<ulong>((await _guild.GetUsersAsync().ConfigureAwait(false)).Select(x => x.Id));
await _originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false); await _originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false);
running = true; running = true;
@ -114,36 +104,16 @@ namespace NadekoBot.Services.Games
return false; return false;
IMessageChannel ch; IMessageChannel ch;
if (IsPublic) //if public, channel must be the same the poll started in
{ if (_originalMessage.Channel.Id != msg.Channel.Id)
//if public, channel must be the same the poll started in return false;
if (_originalMessage.Channel.Id != msg.Channel.Id) ch = msg.Channel;
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;
}
//user can vote only once //user can vote only once
if (_participants.TryAdd(msg.Author.Id, vote)) if (_participants.TryAdd(msg.Author.Id, vote))
{ {
if (!IsPublic) var toDelete = await ch.SendConfirmAsync(GetText("poll_voted", Format.Bold(msg.Author.ToString()))).ConfigureAwait(false);
{ toDelete.DeleteAfter(5);
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);
}
return true; return true;
} }
return false; return false;

View File

@ -13,17 +13,17 @@ namespace NadekoBot.Services.Games
{ {
public ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>(); public ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
public PollService(DiscordShardedClient client, NadekoStrings strings) public PollService(DiscordSocketClient client, NadekoStrings strings)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client; _client = client;
_strings = strings; _strings = strings;
} }
public async Task<bool?> StartPoll(ITextChannel channel, IUserMessage msg, string arg, bool isPublic = false) public async Task<bool?> StartPoll(ITextChannel channel, IUserMessage msg, string arg)
{ {
if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";")) if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";"))
return null; return null;
@ -31,7 +31,7 @@ namespace NadekoBot.Services.Games
if (data.Length < 3) if (data.Length < 3)
return null; 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)) if (ActivePolls.TryAdd(channel.Guild.Id, poll))
{ {
poll.OnEnded += (gid) => poll.OnEnded += (gid) =>
@ -45,20 +45,10 @@ namespace NadekoBot.Services.Games
return false; return false;
} }
public async Task<bool> TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg) public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{ {
if (guild == null) if (guild == null)
{
foreach (var kvp in ActivePolls)
{
if (!kvp.Value.IsPublic)
{
if (await kvp.Value.TryVote(msg).ConfigureAwait(false))
return true;
}
}
return false; return false;
}
if (!ActivePolls.TryGetValue(guild.Id, out var poll)) if (!ActivePolls.TryGetValue(guild.Id, out var poll))
return false; return false;

View File

@ -17,10 +17,10 @@ namespace NadekoBot.Services
private readonly DbService _db; private readonly DbService _db;
public readonly ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache; public readonly ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly Logger _log; private readonly Logger _log;
public GreetSettingsService(DiscordShardedClient client, IEnumerable<GuildConfig> guildConfigs, DbService db) public GreetSettingsService(DiscordSocketClient client, IEnumerable<GuildConfig> guildConfigs, DbService db)
{ {
_db = db; _db = db;
_client = client; _client = client;

View File

@ -24,7 +24,7 @@ namespace NadekoBot.Services.Help
_strings = strings; _strings = strings;
} }
public async Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg) public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{ {
try try
{ {

View File

@ -19,6 +19,9 @@ namespace NadekoBot.Services
string OsuApiKey { get; } string OsuApiKey { get; }
bool IsOwner(IUser u); bool IsOwner(IUser u);
int TotalShards { get; }
string ShardRunCommand { get; }
string ShardRunArguments { get; }
} }
public class DBConfig public class DBConfig

View File

@ -18,6 +18,6 @@ namespace NadekoBot.Services
ImmutableArray<byte> WifeMatrix { get; } ImmutableArray<byte> WifeMatrix { get; }
ImmutableArray<byte> RategirlDot { get; } ImmutableArray<byte> RategirlDot { get; }
TimeSpan Reload(); void Reload();
} }
} }

View File

@ -13,6 +13,7 @@ namespace NadekoBot.Services
double MessagesPerSecond { get; } double MessagesPerSecond { get; }
long TextChannels { get; } long TextChannels { get; }
long VoiceChannels { get; } long VoiceChannels { get; }
int GuildCount { get; }
TimeSpan GetUptime(); TimeSpan GetUptime();
string GetUptimeString(string separator = ", "); string GetUptimeString(string separator = ", ");

View File

@ -30,20 +30,23 @@ namespace NadekoBot.Services.Impl
public int TotalShards { get; } public int TotalShards { get; }
public string CarbonKey { 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 PatreonAccessToken { get; }
public string ShardRunCommand { get; }
public string ShardRunArguments { get; }
public int ShardRunPort { get; }
public BotCredentials() public BotCredentials()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
try { File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); } catch { } 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")}"); _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 try
{ {
var configBuilder = new ConfigurationBuilder(); var configBuilder = new ConfigurationBuilder();
configBuilder.AddJsonFile(credsFileName, true) configBuilder.AddJsonFile(_credsFileName, true)
.AddEnvironmentVariables("NadekoBot_"); .AddEnvironmentVariables("NadekoBot_");
var data = configBuilder.Build(); var data = configBuilder.Build();
@ -61,13 +64,24 @@ namespace NadekoBot.Services.Impl
MashapeKey = data[nameof(MashapeKey)]; MashapeKey = data[nameof(MashapeKey)];
OsuApiKey = data[nameof(OsuApiKey)]; OsuApiKey = data[nameof(OsuApiKey)];
PatreonAccessToken = data[nameof(PatreonAccessToken)]; 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 ts = 1;
int.TryParse(data[nameof(TotalShards)], out ts); int.TryParse(data[nameof(TotalShards)], out ts);
TotalShards = ts < 1 ? 1 : ts; TotalShards = ts < 1 ? 1 : ts;
ulong clId = 0; ulong.TryParse(data[nameof(ClientId)], out ulong clId);
ulong.TryParse(data[nameof(ClientId)], out clId);
ClientId = clId; ClientId = clId;
//var scId = data[nameof(SoundCloudClientId)]; //var scId = data[nameof(SoundCloudClientId)];
@ -81,7 +95,7 @@ namespace NadekoBot.Services.Impl
? "sqlite" ? "sqlite"
: dbSection["Type"], : dbSection["Type"],
string.IsNullOrWhiteSpace(dbSection["ConnectionString"]) string.IsNullOrWhiteSpace(dbSection["ConnectionString"])
? "Filename=./data/NadekoBot.db" ? "Filename=./data/NadekoBot.db"
: dbSection["ConnectionString"]); : dbSection["ConnectionString"]);
} }
catch (Exception ex) catch (Exception ex)
@ -107,6 +121,10 @@ namespace NadekoBot.Services.Impl
public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db"); public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db");
public int TotalShards { get; set; } = 1; public int TotalShards { get; set; } = 1;
public string PatreonAccessToken { get; set; } = ""; public string PatreonAccessToken { get; set; } = "";
public string ShardRunCommand { get; set; } = "";
public string ShardRunArguments { get; set; } = "";
public int? ShardRunPort { get; set; } = null;
} }
private class DbModel private class DbModel

View File

@ -47,12 +47,10 @@ namespace NadekoBot.Services.Impl
this.Reload(); this.Reload();
} }
public TimeSpan Reload() public void Reload()
{ {
try try
{ {
_log.Info("Loading images...");
var sw = Stopwatch.StartNew();
Heads = File.ReadAllBytes(_headsPath).ToImmutableArray(); Heads = File.ReadAllBytes(_headsPath).ToImmutableArray();
Tails = File.ReadAllBytes(_tailsPath).ToImmutableArray(); Tails = File.ReadAllBytes(_tailsPath).ToImmutableArray();
@ -79,10 +77,6 @@ namespace NadekoBot.Services.Impl
WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray(); WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray();
RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray(); RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray();
sw.Stop();
_log.Info($"Images loaded after {sw.Elapsed.TotalSeconds:F2}s!");
return sw.Elapsed;
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -13,11 +13,11 @@ namespace NadekoBot.Services.Impl
{ {
public class StatsService : IStatsService public class StatsService : IStatsService
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly DateTime _started; private readonly DateTime _started;
public const string BotVersion = "1.43"; public const string BotVersion = "1.5";
public string Author => "Kwoth#2560"; public string Author => "Kwoth#2560";
public string Library => "Discord.Net"; public string Library => "Discord.Net";
@ -35,11 +35,16 @@ namespace NadekoBot.Services.Impl
public long CommandsRan => Interlocked.Read(ref _commandsRan); public long CommandsRan => Interlocked.Read(ref _commandsRan);
private readonly Timer _carbonitexTimer; 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; _client = client;
_creds = creds; _creds = creds;
_sc = sc;
_started = DateTime.UtcNow; _started = DateTime.UtcNow;
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter)); _client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
@ -121,31 +126,34 @@ namespace NadekoBot.Services.Impl
return Task.CompletedTask; return Task.CompletedTask;
}; };
_carbonitexTimer = new Timer(async (state) => if (sc != null)
{ {
if (string.IsNullOrWhiteSpace(_creds.CarbonKey)) _carbonitexTimer = new Timer(async (state) =>
return;
try
{ {
using (var http = new HttpClient()) if (string.IsNullOrWhiteSpace(_creds.CarbonKey))
return;
try
{ {
using (var content = new FormUrlEncodedContent( using (var http = new HttpClient())
new Dictionary<string, string> {
{ "servercount", _client.Guilds.Count.ToString() },
{ "key", _creds.CarbonKey }}))
{ {
content.Headers.Clear(); using (var content = new FormUrlEncodedContent(
content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); new Dictionary<string, string> {
{ "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
catch {
{ // ignored
// ignored }
} }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
}, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); }
} }
public void Initialize() public void Initialize()

View File

@ -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;
}
}
}

View File

@ -21,7 +21,7 @@ namespace NadekoBot.Services.Permissions
v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns))); v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns)));
} }
public Task<bool> TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, public Task<bool> TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild,
IMessageChannel channel, IUser user, string moduleName, string commandName) IMessageChannel channel, IUser user, string moduleName, string commandName)
{ {
if (guild == null) if (guild == null)

View File

@ -41,7 +41,7 @@ namespace NadekoBot.Services.Permissions
return words; return words;
} }
public FilterService(DiscordShardedClient _client, IEnumerable<GuildConfig> gcs) public FilterService(DiscordSocketClient _client, IEnumerable<GuildConfig> gcs)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();

View File

@ -19,7 +19,7 @@ namespace NadekoBot.Services.Permissions
BlockedCommands = new ConcurrentHashSet<string>(bc.BlockedCommands.Select(x => x.Name)); BlockedCommands = new ConcurrentHashSet<string>(bc.BlockedCommands.Select(x => x.Name));
} }
public async Task<bool> TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) public async Task<bool> TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName)
{ {
await Task.Yield(); await Task.Yield();
commandName = commandName.ToLowerInvariant(); commandName = commandName.ToLowerInvariant();

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore; 
using Microsoft.EntityFrameworkCore;
using NadekoBot.DataStructures.ModuleBehaviors; using NadekoBot.DataStructures.ModuleBehaviors;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog; using NLog;
@ -10,6 +11,7 @@ using System.Threading.Tasks;
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
namespace NadekoBot.Services.Permissions namespace NadekoBot.Services.Permissions
{ {
@ -18,22 +20,26 @@ namespace NadekoBot.Services.Permissions
private readonly DbService _db; private readonly DbService _db;
private readonly Logger _log; private readonly Logger _log;
private readonly CommandHandler _cmd; private readonly CommandHandler _cmd;
private readonly NadekoStrings _strings;
//guildid, root permission //guildid, root permission
public ConcurrentDictionary<ulong, PermissionCache> Cache { get; } = public ConcurrentDictionary<ulong, PermissionCache> Cache { get; } =
new ConcurrentDictionary<ulong, PermissionCache>(); new ConcurrentDictionary<ulong, PermissionCache>();
public PermissionService(DbService db, BotConfig bc, CommandHandler cmd) public PermissionService(DiscordSocketClient client, DbService db, BotConfig bc, CommandHandler cmd, NadekoStrings strings)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_db = db; _db = db;
_cmd = cmd; _cmd = cmd;
_strings = strings;
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
TryMigratePermissions(bc); if (client.ShardId == 0)
TryMigratePermissions(bc);
using (var uow = _db.UnitOfWork) 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() Cache.TryAdd(x.GuildId, new PermissionCache()
{ {
@ -68,10 +74,9 @@ namespace NadekoBot.Services.Permissions
private void TryMigratePermissions(BotConfig bc) private void TryMigratePermissions(BotConfig bc)
{ {
var log = LogManager.GetCurrentClassLogger(); var log = LogManager.GetCurrentClassLogger();
using (var uow = _db.UnitOfWork) if (bc.PermissionVersion <= 1)
{ {
var _bc = uow.BotConfig.GetOrCreate(); using (var uow = _db.UnitOfWork)
if (_bc.PermissionVersion <= 1)
{ {
log.Info("Permission version is 1, upgrading to 2."); log.Info("Permission version is 1, upgrading to 2.");
var oldCache = new ConcurrentDictionary<ulong, OldPermissionCache>(uow.GuildConfigs var oldCache = new ConcurrentDictionary<ulong, OldPermissionCache>(uow.GuildConfigs
@ -126,9 +131,13 @@ namespace NadekoBot.Services.Permissions
log.Info("Permission migration to v2 is done."); 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", "!", "+", "-", "$", ">" }; var oldPrefixes = new[] { ".", ";", "!!", "!m", "!", "+", "-", "$", ">" };
uow._context.Database.ExecuteSqlCommand( uow._context.Database.ExecuteSqlCommand(
@ -144,9 +153,9 @@ WHERE secondaryTargetName LIKE '.%' OR
secondaryTargetName LIKE '>%' OR secondaryTargetName LIKE '>%' OR
secondaryTargetName LIKE '-%' OR secondaryTargetName LIKE '-%' OR
secondaryTargetName LIKE '!%';"); secondaryTargetName LIKE '!%';");
_bc.PermissionVersion = 3; bc.PermissionVersion = 3;
uow.Complete();
} }
uow.Complete();
} }
} }
@ -183,7 +192,7 @@ WHERE secondaryTargetName LIKE '.%' OR
}); });
} }
public async Task<bool> TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName) public async Task<bool> TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild, IMessageChannel channel, IUser user, string moduleName, string commandName)
{ {
await Task.Yield(); await Task.Yield();
if (guild == null) if (guild == null)
@ -198,11 +207,9 @@ WHERE secondaryTargetName LIKE '.%' OR
PermissionCache pc = GetCache(guild.Id); PermissionCache pc = GetCache(guild.Id);
if (!resetCommand && !pc.Permissions.CheckPermissions(msg, commandName, moduleName, out int index)) 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) 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 true;
//return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, returnMsg));
} }
@ -215,7 +222,6 @@ WHERE secondaryTargetName LIKE '.%' OR
if (pc.Verbose) if (pc.Verbose)
try { await channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { } try { await channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
return true; return true;
//return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"You need the **{pc.PermRole}** role in order to use permission commands."));
} }
} }
} }

View File

@ -1,52 +1,18 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog; using NLog;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Services.Searches namespace NadekoBot.Services.Searches
{ {
public class AnimeSearchService public class AnimeSearchService
{ {
private readonly Timer _anilistTokenRefresher;
private readonly Logger _log; private readonly Logger _log;
private static string anilistToken { get; set; }
public AnimeSearchService() public AnimeSearchService()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_anilistTokenRefresher = new Timer(async (state) =>
{
try
{
var headers = new Dictionary<string, string>
{
{"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<AnimeResult> GetAnimeData(string query) public async Task<AnimeResult> GetAnimeData(string query)
@ -56,19 +22,15 @@ namespace NadekoBot.Services.Searches
try 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()) using (var http = new HttpClient())
{ {
var res = await http.GetStringAsync(link + $"?access_token={anilistToken}").ConfigureAwait(false); var res = await http.GetStringAsync(link).ConfigureAwait(false);
var smallObj = JArray.Parse(res)[0]; return JsonConvert.DeserializeObject<AnimeResult>(res);
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<AnimeResult>(aniData); } catch { return null; } }).ConfigureAwait(false);
} }
} }
catch (Exception ex) catch
{ {
_log.Warn(ex, "Failed anime search for {0}", query);
return null; return null;
} }
} }
@ -79,18 +41,16 @@ namespace NadekoBot.Services.Searches
throw new ArgumentNullException(nameof(query)); throw new ArgumentNullException(nameof(query));
try try
{ {
var link = "https://aniapi.nadekobot.me/manga/" + Uri.EscapeDataString(query.Replace("/", " "));
using (var http = new HttpClient()) 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 res = await http.GetStringAsync(link).ConfigureAwait(false);
var smallObj = JArray.Parse(res)[0]; return JsonConvert.DeserializeObject<MangaResult>(res);
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<MangaResult>(aniData); } catch { return null; } }).ConfigureAwait(false);
} }
} }
catch (Exception ex) catch
{ {
_log.Warn(ex, "Failed anime search for {0}", query);
return null; return null;
} }
} }

View File

@ -15,7 +15,7 @@ namespace NadekoBot.Services.Searches
{ {
public class SearchesService public class SearchesService
{ {
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly IGoogleApiService _google; private readonly IGoogleApiService _google;
private readonly DbService _db; private readonly DbService _db;
private readonly Logger _log; private readonly Logger _log;
@ -31,7 +31,7 @@ namespace NadekoBot.Services.Searches
public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>(); public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>();
public List<MagicItem> MagicItems { get; } = new List<MagicItem>(); public List<MagicItem> MagicItems { get; } = new List<MagicItem>();
public SearchesService(DiscordShardedClient client, IGoogleApiService google, DbService db) public SearchesService(DiscordSocketClient client, IGoogleApiService google, DbService db)
{ {
_client = client; _client = client;
_google = google; _google = google;

View File

@ -1,6 +1,8 @@
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
@ -20,10 +22,10 @@ namespace NadekoBot.Services.Searches
private readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>(); private readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private readonly DbService _db; private readonly DbService _db;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings; private readonly NadekoStrings _strings;
public StreamNotificationService(DbService db, DiscordShardedClient client, NadekoStrings strings) public StreamNotificationService(DbService db, DiscordSocketClient client, NadekoStrings strings)
{ {
_db = db; _db = db;
_client = client; _client = client;
@ -35,7 +37,7 @@ namespace NadekoBot.Services.Searches
IEnumerable<FollowedStream> streams; IEnumerable<FollowedStream> streams;
using (var uow = _db.UnitOfWork) 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 => await Task.WhenAll(streams.Select(async fs =>
@ -73,7 +75,7 @@ namespace NadekoBot.Services.Searches
})); }));
firstStreamNotifPass = false; firstStreamNotifPass = false;
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(60)); }, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
} }
public async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true) public async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)

View File

@ -1,4 +1,6 @@
using NadekoBot.Services.Database.Models; using Discord.WebSocket;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; using NLog;
using System; using System;
@ -13,24 +15,26 @@ namespace NadekoBot.Services.Utility
{ {
public class ConverterService public class ConverterService
{ {
public List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>(); public List<ConvertUnit> Units { get; } = new List<ConvertUnit>();
private readonly Logger _log; private readonly Logger _log;
private Timer _timer; private readonly Timer _currencyUpdater;
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0); private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
private readonly DbService _db; private readonly DbService _db;
public ConverterService(DbService db) public ConverterService(DiscordSocketClient client, DbService db)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_db = db; _db = db;
try try
{ {
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit() var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(
{ File.ReadAllText("data/units.json"))
Modifier = u.Modifier, .Select(u => new ConvertUnit()
UnitType = u.UnitType, {
InternalTrigger = string.Join("|", u.Triggers) Modifier = u.Modifier,
}).ToArray(); UnitType = u.UnitType,
InternalTrigger = string.Join("|", u.Triggers)
}).ToArray();
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
{ {
@ -47,10 +51,10 @@ namespace NadekoBot.Services.Utility
_log.Warn("Could not load units: " + ex.Message); _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<Rates> UpdateCurrencyRates() private async Task<Rates> GetCurrencyRates()
{ {
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
@ -59,38 +63,48 @@ namespace NadekoBot.Services.Utility
} }
} }
public async Task UpdateCurrency() private async Task UpdateCurrency(bool shouldLoad)
{ {
try try
{ {
var currencyRates = await UpdateCurrencyRates();
var unitTypeString = "currency"; var unitTypeString = "currency";
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit() if (shouldLoad)
{ {
InternalTrigger = u.Key, var currencyRates = await GetCurrencyRates();
Modifier = u.Value, var baseType = new ConvertUnit()
UnitType = unitTypeString {
}).ToArray(); Triggers = new[] { currencyRates.Base },
var baseType = new ConvertUnit() Modifier = decimal.One,
{ UnitType = unitTypeString
Triggers = new[] { currencyRates.Base }, };
Modifier = decimal.One, var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
UnitType = unitTypeString {
}; InternalTrigger = u.Key,
var toRemove = Units.Where(u => u.UnitType == unitTypeString); Modifier = u.Value,
UnitType = unitTypeString
}).ToArray();
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
{ {
uow.ConverterUnits.RemoveRange(toRemove.ToArray()); uow.ConverterUnits.RemoveRange(toRemove.ToArray());
uow.ConverterUnits.Add(baseType); uow.ConverterUnits.Add(baseType);
uow.ConverterUnits.AddRange(range); 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 catch
{ {

View File

@ -15,7 +15,7 @@ namespace NadekoBot.Services.Utility
public ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; } public ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
public bool RepeaterReady { get; private set; } public bool RepeaterReady { get; private set; }
public MessageRepeaterService(NadekoBot bot, DiscordShardedClient client, IEnumerable<GuildConfig> gcs) public MessageRepeaterService(NadekoBot bot, DiscordSocketClient client, IEnumerable<GuildConfig> gcs)
{ {
var _ = Task.Run(async () => var _ = Task.Run(async () =>
{ {

View File

@ -1,12 +1,15 @@
using NadekoBot.Services.Database.Models; using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Utility.Patreon; using NadekoBot.Services.Utility.Patreon;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; using NLog;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -23,12 +26,15 @@ namespace NadekoBot.Services.Utility
private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1);
private readonly Logger _log; private readonly Logger _log;
public readonly TimeSpan Interval = TimeSpan.FromMinutes(15); public readonly TimeSpan Interval = TimeSpan.FromMinutes(3);
private IBotCredentials _creds; private readonly IBotCredentials _creds;
private readonly DbService _db; private readonly DbService _db;
private readonly CurrencyService _currency; 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; _creds = creds;
_db = db; _db = db;
@ -36,58 +42,65 @@ namespace NadekoBot.Services.Utility
if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken)) if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
return; return;
_log = LogManager.GetCurrentClassLogger(); _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; if (shouldLoad)
await getPledgesLocker.WaitAsync(1000).ConfigureAwait(false);
try
{ {
var rewards = new List<PatreonPledge>(); LastUpdate = DateTime.UtcNow;
var users = new List<PatreonUser>(); await getPledgesLocker.WaitAsync().ConfigureAwait(false);
using (var http = new HttpClient()) try
{ {
http.DefaultRequestHeaders.Clear(); var rewards = new List<PatreonPledge>();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken); var users = new List<PatreonUser>();
var data = new PatreonData() 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" Links = new PatreonDataLinks()
} {
}; next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges"
do }
};
do
{
var res = await http.GetStringAsync(data.Links.next)
.ConfigureAwait(false);
data = JsonConvert.DeserializeObject<PatreonData>(res);
var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge");
rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject<PatreonPledge>(x.ToString()))
.Where(x => x.attributes.declined_since == null));
users.AddRange(data.Included
.Where(x => x["type"].ToString() == "user")
.Select(x => JsonConvert.DeserializeObject<PatreonUser>(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) User = y,
.ConfigureAwait(false); Reward = x,
data = JsonConvert.DeserializeObject<PatreonData>(res); }).ToImmutableArray();
var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge"); File.WriteAllText("./patreon_rewards.json", JsonConvert.SerializeObject(Pledges));
rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject<PatreonPledge>(x.ToString()))
.Where(x => x.attributes.declined_since == null));
users.AddRange(data.Included
.Where(x => x["type"].ToString() == "user")
.Select(x => JsonConvert.DeserializeObject<PatreonUser>(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() catch (Exception ex)
{ {
User = y, _log.Warn(ex);
Reward = x, }
}).ToImmutableArray(); finally
}
catch (Exception ex)
{
_log.Warn(ex);
}
finally
{
var _ = Task.Run(async () =>
{ {
await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
getPledgesLocker.Release(); getPledgesLocker.Release();
}); }
}
else
{
if(File.Exists(cacheFileName))
Pledges = JsonConvert.DeserializeObject<PatreonUserAndReward[]>(File.ReadAllText("./patreon_rewards.json"))
.ToImmutableArray();
} }
} }

View File

@ -1,6 +1,7 @@
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
@ -30,10 +31,11 @@ namespace NadekoBot.Services.Utility
private readonly CancellationTokenSource cancelSource; private readonly CancellationTokenSource cancelSource;
private readonly CancellationToken cancelAllToken; private readonly CancellationToken cancelAllToken;
private readonly BotConfig _config; private readonly BotConfig _config;
private readonly DiscordShardedClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
public RemindService(DiscordShardedClient client, BotConfig config, DbService db) public RemindService(DiscordSocketClient client, BotConfig config, DbService db,
List<long> guilds, IUnitOfWork uow)
{ {
_config = config; _config = config;
_client = client; _client = client;
@ -42,11 +44,8 @@ namespace NadekoBot.Services.Utility
cancelSource = new CancellationTokenSource(); cancelSource = new CancellationTokenSource();
cancelAllToken = cancelSource.Token; cancelAllToken = cancelSource.Token;
List<Reminder> reminders;
using (var uow = _db.UnitOfWork) var reminders = uow.Reminders.GetIncludedReminders(guilds).ToList();
{
reminders = uow.Reminders.GetAll().ToList();
}
RemindMessageFormat = _config.RemindMessageFormat; RemindMessageFormat = _config.RemindMessageFormat;
foreach (var r in reminders) foreach (var r in reminders)

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Services.Utility
private IUserMessage oldMsg = null; private IUserMessage oldMsg = null;
private Timer _t; private Timer _t;
public RepeatRunner(DiscordShardedClient client, Repeater repeater) public RepeatRunner(DiscordSocketClient client, Repeater repeater)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
Repeater = repeater; Repeater = repeater;

View File

@ -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<int, ConcurrentHashSet<ITextChannel>> Subscribers =
new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
private DiscordShardedClient _client;
public CrossServerTextService(IEnumerable<GuildConfig> 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();
}
}

View File

@ -50,12 +50,12 @@ namespace NadekoBot.Services.Utility
public bool ToggleVerboseErrors(ulong guildId) public bool ToggleVerboseErrors(ulong guildId)
{ {
bool enabled;
using (var uow = _db.UnitOfWork) using (var uow = _db.UnitOfWork)
{ {
var gc = uow.GuildConfigs.For(guildId, set => set); var gc = uow.GuildConfigs.For(guildId, set => set);
gc.VerboseErrors = !gc.VerboseErrors; enabled = gc.VerboseErrors = !gc.VerboseErrors;
uow.Complete(); uow.Complete();
@ -65,15 +65,12 @@ namespace NadekoBot.Services.Utility
guildsEnabled.TryRemove(guildId); guildsEnabled.TryRemove(guildId);
} }
if (guildsEnabled.Add(guildId)) if (enabled)
{ guildsEnabled.Add(guildId);
return true;
}
else else
{
guildsEnabled.TryRemove(guildId); guildsEnabled.TryRemove(guildId);
return false;
} return enabled;
} }
} }

View File

@ -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 { }
}
}
}
}

View File

@ -70,7 +70,7 @@ namespace NadekoBot.Extensions
/// <summary> /// <summary>
/// danny kamisama /// danny kamisama
/// </summary> /// </summary>
public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordShardedClient client, int currentPage, Func<int, EmbedBuilder> pageFunc, int? lastPage = null, bool addPaginatedFooter = true) public static async Task SendPaginatedConfirmAsync(this IMessageChannel channel, DiscordSocketClient client, int currentPage, Func<int, EmbedBuilder> pageFunc, int? lastPage = null, bool addPaginatedFooter = true)
{ {
var embed = pageFunc(currentPage); var embed = pageFunc(currentPage);
@ -134,7 +134,7 @@ namespace NadekoBot.Extensions
return embed.WithFooter(efb => efb.WithText(curPage.ToString())); return embed.WithFooter(efb => efb.WithText(curPage.ToString()));
} }
public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordShardedClient client, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null) public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordSocketClient client, Action<SocketReaction> reactionAdded, Action<SocketReaction> reactionRemoved = null)
{ {
if (reactionRemoved == null) if (reactionRemoved == null)
reactionRemoved = delegate { }; reactionRemoved = delegate { };

View File

@ -154,6 +154,7 @@
"administration_old_topic": "Old topic", "administration_old_topic": "Old topic",
"administration_perms": "Error. Most likely I don't have sufficient permissions.", "administration_perms": "Error. Most likely I don't have sufficient permissions.",
"permissions_perms_reset": "Permissions for this server are reset.", "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_active": "Active protections",
"administration_prot_disable": "{0} has been **disabled** on this server.", "administration_prot_disable": "{0} has been **disabled** on this server.",
"administration_prot_enable": "{0} Enabled", "administration_prot_enable": "{0} Enabled",
@ -446,7 +447,7 @@
"music_skipped_to": "Skipped to `{0}:{1}`", "music_skipped_to": "Skipped to `{0}:{1}`",
"music_songs_shuffled": "Songs shuffled", "music_songs_shuffled": "Songs shuffled",
"music_song_moved": "Song moved", "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_time_format": "{0}h {1}m {2}s",
"music_to_position": "To position", "music_to_position": "To position",
"music_unlimited": "unlimited", "music_unlimited": "unlimited",
@ -523,7 +524,7 @@
"searches_cost": "Cost", "searches_cost": "Cost",
"searches_date": "Date", "searches_date": "Date",
"searches_define": "Define:", "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_dropped": "Dropped",
"searches_episodes": "Episodes", "searches_episodes": "Episodes",
"searches_error_occured": "Error occurred.", "searches_error_occured": "Error occurred.",
@ -534,7 +535,7 @@
"searches_hashtag_error": "Failed finding a definition for that tag.", "searches_hashtag_error": "Failed finding a definition for that tag.",
"searches_height_weight": "Height/Weight", "searches_height_weight": "Height/Weight",
"searches_height_weight_val": "{0}m/{1}kg", "searches_height_weight_val": "{0}m/{1}kg",
"searches_hex_invalid": "Invalid color specified.", "searches_hex_invalid": "Invalid color specified.",
"searches_humidity": "Humidity", "searches_humidity": "Humidity",
"searches_image_search_for": "Image search for:", "searches_image_search_for": "Image search for:",
"searches_imdb_fail": "Failed to find that movie.", "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_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_convert_type_error": "Cannot convert {0} to {1}: types of unit are not equal",
"utility_created_at": "Created at", "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_custom_emojis": "Custom emojis",
"utility_error": "Error", "utility_error": "Error",
"utility_features": "Features", "utility_features": "Features",

View File

@ -15,5 +15,9 @@
"Type": "sqlite", "Type": "sqlite",
"ConnectionString": "Filename=./data/NadekoBot.db" "ConnectionString": "Filename=./data/NadekoBot.db"
}, },
"TotalShards": 1 "TotalShards": 1,
"PatreonAccessToken": "",
"ShardRunCommand": "",
"ShardRunArguments": "",
"ShardRunPort": null
} }