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
patreon_rewards.json
command_errors*.txt
src/NadekoBot/Command Errors*.txt

View File

@ -1,7 +1,7 @@

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

View File

@ -18,7 +18,8 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl
"OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6",
"PatreonAccessToken": "",
"Db": null,
"TotalShards": 1
"TotalShards": 1,
"ShardRunCommand": ""
}
```
-----
@ -155,14 +156,28 @@ It should look like:
- **TotalShards**
- Required if the bot will be connected to more than 1500 servers.
- Most likely unnecessary to change until your bot is added to more than 1500 servers.
- **ShardRunCommand**
- Command with which to run shards 1+
- Required if you're sharding your bot on windows using .exe, or in a custom way.
- This internally defaults to `dotnet`
- For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to something like this: `C:\Program Files\NadekoBot\system\NadekoBot.exe`
- **ShardRunArguments**
- Arguments to the shard run command
- Required if you're sharding your bot on windows using .exe, or in a custom way.
- This internally defaults to `run -c Release -- {0} {1} {2}` which will be enough to run linux and other 'from source' setups
- {0} will be replaced by the `shard ID` of the shard being ran, {1} by the shard 0's process id, and {2} by the port shard communication is happening on
- If shard0 (main window) is closed, all other shards will close too
- For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to `{0} {1} {2}`
- **ShardRunPort**
- Bot uses a random UDP port in [5000, 6000) range for communication between shards
-----
## DB files
Nadeko saves all the settings and infomations in `NadekoBot.db` file here:
`NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data/NadekoBot.db` (macOS and Linux)
`NadekoBot\system\data` (Windows)
`NadekoBot\system\data` (Windows)
in order to open the database file you will need [DB Browser for SQLite](http://sqlitebrowser.org/).
*NOTE: You don't have to worry if you don't have `NadekoBot.db` file, it gets auto created once you run the bot successfully.*

View File

@ -13,6 +13,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
/// Try to execute some logic within some module's service.
/// </summary>
/// <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
{
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);
}
}

View File

@ -9,6 +9,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
/// </summary>
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
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public GuildTypeReader(DiscordShardedClient client)
public GuildTypeReader(DiscordSocketClient client)
{
_client = client;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,13 +20,13 @@ namespace NadekoBot.Modules.Games.Models
public bool IsActive { get; private set; }
private readonly Stopwatch sw;
private readonly List<ulong> finishedUserIds;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly GamesService _games;
private readonly string _prefix;
private Logger _log { get; }
public TypingGame(GamesService games, DiscordShardedClient client, ITextChannel channel, string prefix) //kek@prefix
public TypingGame(GamesService games, DiscordSocketClient client, ITextChannel channel, string prefix) //kek@prefix
{
_log = LogManager.GetCurrentClassLogger();
_games = games;

View File

@ -116,7 +116,7 @@ namespace NadekoBot.Modules.Games
bool enabled;
using (var uow = _db.UnitOfWork)
{
var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
var guildConfig = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
var toAdd = new GCChannelId() { ChannelId = channel.Id };
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))

View File

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

View File

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

View File

@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Games
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public TicTacToeCommands(DiscordShardedClient client)
public TicTacToeCommands(DiscordSocketClient client)
{
_client = client;
}
@ -87,9 +87,9 @@ namespace NadekoBot.Modules.Games
private IUserMessage _previousMessage;
private Timer _timeoutTimer;
private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public TicTacToe(NadekoStrings strings, DiscordShardedClient client, ITextChannel channel, IGuildUser firstUser)
public TicTacToe(NadekoStrings strings, DiscordSocketClient client, ITextChannel channel, IGuildUser firstUser)
{
_channel = channel;
_strings = strings;

View File

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

View File

@ -18,12 +18,12 @@ namespace NadekoBot.Modules.Games
public class TriviaCommands : NadekoSubmodule
{
private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly BotConfig _bc;
public static ConcurrentDictionary<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;
_client = client;

View File

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

View File

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

View File

@ -86,13 +86,12 @@ namespace NadekoBot.Modules
var text = GetText(textKey, replacements);
return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + text);
}
// todo maybe make this generic and use
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType);
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
{
var userInputTask = new TaskCompletionSource<string>();
var dsc = (DiscordShardedClient)Context.Client;
var dsc = (DiscordSocketClient)Context.Client;
try
{
dsc.MessageReceived += MessageReceived;

View File

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

View File

@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Utility
{
private readonly CommandMapService _service;
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public CommandMapCommands(CommandMapService service, DbService db, DiscordShardedClient client)
public CommandMapCommands(CommandMapService service, DbService db, DiscordSocketClient client)
{
_service = service;
_db = db;

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]
public class InfoCommands : NadekoSubmodule
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly IStatsService _stats;
public InfoCommands(DiscordShardedClient client, IStatsService stats, CommandHandler ch)
public InfoCommands(DiscordSocketClient client, IStatsService stats, CommandHandler ch)
{
_client = client;
_stats = stats;

View File

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

View File

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

View File

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

View File

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

View File

@ -18,99 +18,25 @@ using Discord.WebSocket;
using System.Diagnostics;
using Color = Discord.Color;
using NadekoBot.Services;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.Utility
{
public partial class Utility : NadekoTopLevelModule
{
private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly IStatsService _stats;
private readonly IBotCredentials _creds;
private readonly NadekoBot _bot;
public Utility(DiscordShardedClient client, IStatsService stats, IBotCredentials creds)
public Utility(NadekoBot bot, DiscordSocketClient client, IStatsService stats, IBotCredentials creds)
{
_client = client;
_stats = stats;
_creds = creds;
}
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//public async Task Midorina([Remainder] string arg)
//{
// var channel = (ITextChannel)Context.Channel;
// var roleNames = arg?.Split(';');
// if (roleNames == null || roleNames.Length == 0)
// return;
// var j = 0;
// var roles = roleNames.Select(x => Context.Guild.Roles.FirstOrDefault(r => String.Compare(r.Name, x, StringComparison.OrdinalIgnoreCase) == 0))
// .Where(x => x != null)
// .Take(10)
// .ToArray();
// var rnd = new NadekoRandom();
// var reactions = new[] { "🎬", "🐧", "🌍", "🌺", "🚀", "☀", "🌲", "🍒", "🐾", "🏀" }
// .OrderBy(x => rnd.Next())
// .ToArray();
// var roleStrings = roles
// .Select(x => $"{reactions[j++]} -> {x.Name}");
// var msg = await Context.Channel.SendConfirmAsync("Pick a Role",
// string.Join("\n", roleStrings)).ConfigureAwait(false);
// for (int i = 0; i < roles.Length; i++)
// {
// try { await msg.AddReactionAsync(reactions[i]).ConfigureAwait(false); }
// catch (Exception ex) { _log.Warn(ex); }
// await Task.Delay(1000).ConfigureAwait(false);
// }
// msg.OnReaction((r) => Task.Run(async () =>
// {
// try
// {
// var usr = r.User.GetValueOrDefault() as IGuildUser;
// if (usr == null)
// return;
// var index = Array.IndexOf<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);
// }
// }));
//}
_bot = bot;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -354,23 +280,25 @@ namespace NadekoBot.Modules.Utility
}
[NadekoCommand, Usage, Description, Aliases]
[Shard0Precondition]
public async Task ShardStats(int page = 1)
{
if (--page < 0)
return;
var statuses = _bot.ShardCoord.Statuses.ToArray()
.Where(x => x != null);
var status = string.Join(", ", _client.Shards.GroupBy(x => x.ConnectionState)
var status = string.Join(", ", statuses
.GroupBy(x => x.ConnectionState)
.Select(x => $"{x.Count()} {x.Key}")
.ToArray());
var allShardStrings = _client.Shards
var allShardStrings = statuses
.Select(x =>
GetText("shard_stats_txt", x.ShardId.ToString(),
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.ToString())))
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.ToString())))
.ToArray();
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
{
@ -387,21 +315,9 @@ namespace NadekoBot.Modules.Utility
}, allShardStrings.Length / 25);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ShardId(IGuild guild)
{
var shardId = _client.GetShardIdFor(guild);
await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Stats()
{
var shardId = Context.Guild != null
? _client.GetShardIdFor(Context.Guild)
: 0;
{
await Context.Channel.EmbedAsync(
new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
@ -409,7 +325,7 @@ namespace NadekoBot.Modules.Utility
.WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg"))
.AddField(efb => efb.WithName(GetText("author")).WithValue(_stats.Author).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {_client.Shards.Count}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{_bot.ShardId} / {_creds.TotalShards}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(_stats.CommandsRan.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").WithIsInline(true))
@ -417,13 +333,7 @@ namespace NadekoBot.Modules.Utility
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("presence")).WithValue(
GetText("presence_txt",
_client.Guilds.Count, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true))
#if !GLOBAL_NADEKO
//.WithFooter(efb => efb.WithText(GetText("stats_songs",
// _music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
// _music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
#endif
);
_stats.GuildCount, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)));
}
[NadekoCommand, Usage, Description, Aliases]

View File

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

View File

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

View File

@ -2,7 +2,17 @@
{
public class Program
{
public static void Main(string[] args) =>
new NadekoBot().RunAndBlockAsync(args).GetAwaiter().GetResult();
public static void Main(string[] args)
{
if (args.Length == 3 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId))
{
int? port = null;
if (int.TryParse(args[2], out var outPort))
port = outPort;
new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args).GetAwaiter().GetResult();
}
else
new NadekoBot(0, 0).RunAndBlockAsync(args).GetAwaiter().GetResult();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,9 +19,9 @@ namespace NadekoBot.Services.Administration
public ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>();
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();
_client = client;

View File

@ -23,11 +23,11 @@ namespace NadekoBot.Services.Administration
private readonly Logger _log;
private readonly ILocalization _localization;
private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private ImmutableArray<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)
{
_bot = bot;
@ -39,12 +39,8 @@ namespace NadekoBot.Services.Administration
_client = client;
_creds = creds;
using (var uow = _db.UnitOfWork)
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages;
ForwardDMsToAllOwners = config.ForwardToAllOwners;
}
ForwardDMs = bc.ForwardMessages;
ForwardDMsToAllOwners = bc.ForwardToAllOwners;
var _ = Task.Run(async () =>
{
@ -67,12 +63,8 @@ namespace NadekoBot.Services.Administration
_client.Guilds.SelectMany(g => g.Users);
LoadOwnerChannels();
if (!ownerChannels.Any())
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
else
_log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels.");
if(client.ShardId == 0)
LoadOwnerChannels();
});
}
@ -81,11 +73,9 @@ namespace NadekoBot.Services.Administration
var hs = new HashSet<ulong>(_creds.OwnerIds);
var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>();
foreach (var s in _client.Shards)
if (hs.Count > 0)
{
if (hs.Count == 0)
break;
foreach (var g in s.Guilds)
foreach (var g in _client.Guilds)
{
if (hs.Count == 0)
break;
@ -105,10 +95,15 @@ namespace NadekoBot.Services.Administration
ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key))
.Select(x => x.Value)
.ToImmutableArray();
if (!ownerChannels.Any())
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
else
_log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels.");
}
// forwards dms
public async Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg)
public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{
if (msg.Channel is IDMChannel && ForwardDMs && ownerChannels.Length > 0)
{

View File

@ -14,11 +14,11 @@ namespace NadekoBot.Services.Administration
{
private readonly Logger _log;
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public ConcurrentDictionary<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();
_db = db;

View File

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

View File

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

View File

@ -28,7 +28,7 @@ namespace NadekoBot.Services
{
public const int GlobalCommandsCooldown = 750;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly CommandService _commandService;
private readonly Logger _log;
private readonly IBotCredentials _creds;
@ -48,7 +48,7 @@ namespace NadekoBot.Services
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
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;
_commandService = commandService;

View File

@ -10,6 +10,7 @@ using System;
using System.Threading.Tasks;
using NadekoBot.Services.Permissions;
using NadekoBot.Extensions;
using NadekoBot.Services.Database;
namespace NadekoBot.Services.CustomReactions
{
@ -22,13 +23,14 @@ namespace NadekoBot.Services.CustomReactions
private readonly Logger _log;
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly PermissionService _perms;
private readonly CommandHandler _cmd;
private readonly BotConfig _bc;
private readonly NadekoStrings _strings;
public CustomReactionsService(PermissionService perms, DbService db,
DiscordShardedClient client, CommandHandler cmd, BotConfig bc)
public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings,
DiscordSocketClient client, CommandHandler cmd, BotConfig bc, IUnitOfWork uow)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
@ -36,16 +38,11 @@ namespace NadekoBot.Services.CustomReactions
_perms = perms;
_cmd = cmd;
_bc = bc;
var sw = Stopwatch.StartNew();
using (var uow = _db.UnitOfWork)
{
var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<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");
_strings = strings;
var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
}
public void ClearStats() => ReactionStats.Clear();
@ -98,7 +95,7 @@ namespace NadekoBot.Services.CustomReactions
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
var cr = await Task.Run(() => TryGetCustomReaction(msg)).ConfigureAwait(false);
@ -114,7 +111,7 @@ namespace NadekoBot.Services.CustomReactions
{
if (pc.Verbose)
{
var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action.";
var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)));
try { await msg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
_log.Info(returnMsg);
}

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}>"; } },
{"%user%", (ctx, client) => { return ctx.Author.Mention; } },
@ -94,7 +94,7 @@ namespace NadekoBot.Services.CustomReactions
} }
};
private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordShardedClient client)
private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordSocketClient client)
{
foreach (var ph in placeholders)
{
@ -104,7 +104,7 @@ namespace NadekoBot.Services.CustomReactions
return str;
}
private static async Task<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)
{
@ -127,13 +127,13 @@ namespace NadekoBot.Services.CustomReactions
return str;
}
public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client)
public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client)
=> cr.Trigger.ResolveTriggerString(ctx, client);
public static Task<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));
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;

View File

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

View File

@ -5,6 +5,6 @@ namespace NadekoBot.Services.Database.Repositories
{
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 LogSettingsFor(ulong guildId);
IEnumerable<GuildConfig> OldPermissionsForAll();
IEnumerable<GuildConfig> GetAllGuildConfigs();
IEnumerable<FollowedStream> GetAllFollowedStreams();
IEnumerable<GuildConfig> GetAllGuildConfigs(List<long> availableGuilds);
IEnumerable<FollowedStream> GetAllFollowedStreams(List<long> included);
void SetCleverbotEnabled(ulong id, bool cleverbotEnabled);
IEnumerable<GuildConfig> Permissionsv2ForAll();
IEnumerable<GuildConfig> Permissionsv2ForAll(List<long> include);
GuildConfig GcWithPermissionsv2For(ulong guildId);
}
}

View File

@ -1,9 +1,11 @@
using NadekoBot.Services.Database.Models;
using System.Collections;
using System.Collections.Generic;
namespace NadekoBot.Services.Database.Repositories
{
public interface IReminderRepository : IRepository<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();
toReturn.ForEach(cw => cw.Bases = cw.Bases.Where(w => w.SequenceNumber != null).OrderBy(w => w.SequenceNumber).ToList());
return toReturn;

View File

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

View File

@ -1,5 +1,8 @@
using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Services.Database.Repositories.Impl
{
@ -8,5 +11,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
public ReminderRepository(DbContext context) : base(context)
{
}
public IEnumerable<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()
{
var context = new NadekoContext(options);
context.Database.SetCommandTimeout(60);
context.Database.Migrate();
context.EnsureSeedData();
//set important sqlite stuffs
var conn = context.Database.GetDbConnection();
conn.Open();
context.Database.ExecuteSqlCommand("PRAGMA journal_mode=WAL");
using (var com = conn.CreateCommand())
{
com.CommandText = "PRAGMA journal_mode=WAL; PRAGMA synchronous=OFF";
com.ExecuteNonQuery();
}
return context;
}

View File

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

View File

@ -15,19 +15,22 @@ namespace NadekoBot.Services.Games
{
public class ChatterBotService : IEarlyBlockingExecutor
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly Logger _log;
private readonly PermissionService _perms;
private readonly CommandHandler _cmd;
private readonly NadekoStrings _strings;
public ConcurrentDictionary<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;
_log = LogManager.GetCurrentClassLogger();
_perms = perms;
_cmd = cmd;
_strings = strings;
ChatterBotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
gcs.Where(gc => gc.CleverbotEnabled)
@ -83,7 +86,7 @@ namespace NadekoBot.Services.Games
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))
return false;
@ -102,7 +105,7 @@ namespace NadekoBot.Services.Games
if (pc.Verbose)
{
//todo move this to permissions
var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action.";
var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)));
try { await usrMsg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
_log.Info(returnMsg);
}

View File

@ -23,7 +23,7 @@ namespace NadekoBot.Services.Games
public readonly ImmutableArray<string> EightBallResponses;
private readonly Timer _t;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings;
private readonly IImagesService _images;
private readonly Logger _log;
@ -33,7 +33,7 @@ namespace NadekoBot.Services.Games
public List<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)
{
_bc = bc;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,20 +30,23 @@ namespace NadekoBot.Services.Impl
public int TotalShards { get; }
public string CarbonKey { get; }
public string credsFileName { get; } = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
private readonly string _credsFileName = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
public string PatreonAccessToken { get; }
public string ShardRunCommand { get; }
public string ShardRunArguments { get; }
public int ShardRunPort { get; }
public BotCredentials()
{
_log = LogManager.GetCurrentClassLogger();
try { File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); } catch { }
if(!File.Exists(credsFileName))
if(!File.Exists(_credsFileName))
_log.Warn($"credentials.json is missing. Attempting to load creds from environment variables prefixed with 'NadekoBot_'. Example is in {Path.GetFullPath("./credentials_example.json")}");
try
{
var configBuilder = new ConfigurationBuilder();
configBuilder.AddJsonFile(credsFileName, true)
configBuilder.AddJsonFile(_credsFileName, true)
.AddEnvironmentVariables("NadekoBot_");
var data = configBuilder.Build();
@ -61,13 +64,24 @@ namespace NadekoBot.Services.Impl
MashapeKey = data[nameof(MashapeKey)];
OsuApiKey = data[nameof(OsuApiKey)];
PatreonAccessToken = data[nameof(PatreonAccessToken)];
ShardRunCommand = data[nameof(ShardRunCommand)];
ShardRunArguments = data[nameof(ShardRunArguments)];
if (string.IsNullOrWhiteSpace(ShardRunCommand))
ShardRunCommand = "dotnet";
if (string.IsNullOrWhiteSpace(ShardRunArguments))
ShardRunArguments = "run -c Release -- {0} {1} {2}";
var portStr = data[nameof(ShardRunPort)];
if (string.IsNullOrWhiteSpace(portStr))
ShardRunPort = new NadekoRandom().Next(5000, 6000);
else
ShardRunPort = int.Parse(portStr);
int ts = 1;
int.TryParse(data[nameof(TotalShards)], out ts);
TotalShards = ts < 1 ? 1 : ts;
ulong clId = 0;
ulong.TryParse(data[nameof(ClientId)], out clId);
ulong.TryParse(data[nameof(ClientId)], out ulong clId);
ClientId = clId;
//var scId = data[nameof(SoundCloudClientId)];
@ -81,7 +95,7 @@ namespace NadekoBot.Services.Impl
? "sqlite"
: dbSection["Type"],
string.IsNullOrWhiteSpace(dbSection["ConnectionString"])
? "Filename=./data/NadekoBot.db"
? "Filename=./data/NadekoBot.db"
: dbSection["ConnectionString"]);
}
catch (Exception ex)
@ -107,6 +121,10 @@ namespace NadekoBot.Services.Impl
public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db");
public int TotalShards { get; set; } = 1;
public string PatreonAccessToken { get; set; } = "";
public string ShardRunCommand { get; set; } = "";
public string ShardRunArguments { get; set; } = "";
public int? ShardRunPort { get; set; } = null;
}
private class DbModel

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ namespace NadekoBot.Services.Permissions
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();
commandName = commandName.ToLowerInvariant();

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json;
using System;
@ -20,10 +22,10 @@ namespace NadekoBot.Services.Searches
private readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings;
public StreamNotificationService(DbService db, DiscordShardedClient client, NadekoStrings strings)
public StreamNotificationService(DbService db, DiscordSocketClient client, NadekoStrings strings)
{
_db = db;
_client = client;
@ -35,7 +37,7 @@ namespace NadekoBot.Services.Searches
IEnumerable<FollowedStream> streams;
using (var uow = _db.UnitOfWork)
{
streams = uow.GuildConfigs.GetAllFollowedStreams();
streams = uow.GuildConfigs.GetAllFollowedStreams(client.Guilds.Select(x => (long)x.Id).ToList());
}
await Task.WhenAll(streams.Select(async fs =>
@ -73,7 +75,7 @@ namespace NadekoBot.Services.Searches
}));
firstStreamNotifPass = false;
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
}, null, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
}
public async Task<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 NLog;
using System;
@ -13,24 +15,26 @@ namespace NadekoBot.Services.Utility
{
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 Timer _timer;
private readonly Timer _currencyUpdater;
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
private readonly DbService _db;
public ConverterService(DbService db)
public ConverterService(DiscordSocketClient client, DbService db)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
try
{
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit()
{
Modifier = u.Modifier,
UnitType = u.UnitType,
InternalTrigger = string.Join("|", u.Triggers)
}).ToArray();
var data = JsonConvert.DeserializeObject<List<MeasurementUnit>>(
File.ReadAllText("data/units.json"))
.Select(u => new ConvertUnit()
{
Modifier = u.Modifier,
UnitType = u.UnitType,
InternalTrigger = string.Join("|", u.Triggers)
}).ToArray();
using (var uow = _db.UnitOfWork)
{
@ -47,10 +51,10 @@ namespace NadekoBot.Services.Utility
_log.Warn("Could not load units: " + ex.Message);
}
_timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
_currencyUpdater = new Timer(async (shouldLoad) => await UpdateCurrency((bool)shouldLoad), client.ShardId == 0, _updateInterval, _updateInterval);
}
public static async Task<Rates> UpdateCurrencyRates()
private async Task<Rates> GetCurrencyRates()
{
using (var http = new HttpClient())
{
@ -59,38 +63,48 @@ namespace NadekoBot.Services.Utility
}
}
public async Task UpdateCurrency()
private async Task UpdateCurrency(bool shouldLoad)
{
try
{
var currencyRates = await UpdateCurrencyRates();
var unitTypeString = "currency";
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
if (shouldLoad)
{
InternalTrigger = u.Key,
Modifier = u.Value,
UnitType = unitTypeString
}).ToArray();
var baseType = new ConvertUnit()
{
Triggers = new[] { currencyRates.Base },
Modifier = decimal.One,
UnitType = unitTypeString
};
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
var currencyRates = await GetCurrencyRates();
var baseType = new ConvertUnit()
{
Triggers = new[] { currencyRates.Base },
Modifier = decimal.One,
UnitType = unitTypeString
};
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
{
InternalTrigger = u.Key,
Modifier = u.Value,
UnitType = unitTypeString
}).ToArray();
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
using (var uow = _db.UnitOfWork)
{
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
uow.ConverterUnits.Add(baseType);
uow.ConverterUnits.AddRange(range);
using (var uow = _db.UnitOfWork)
{
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
uow.ConverterUnits.Add(baseType);
uow.ConverterUnits.AddRange(range);
await uow.CompleteAsync().ConfigureAwait(false);
await uow.CompleteAsync().ConfigureAwait(false);
}
Units.RemoveAll(u => u.UnitType == unitTypeString);
Units.Add(baseType);
Units.AddRange(range);
}
else
{
using (var uow = _db.UnitOfWork)
{
Units.RemoveAll(u => u.UnitType == unitTypeString);
Units.AddRange(uow.ConverterUnits.GetAll().ToArray());
}
}
Units.RemoveAll(u => u.UnitType == unitTypeString);
Units.Add(baseType);
Units.AddRange(range);
_log.Info("Updated Currency");
}
catch
{

View File

@ -15,7 +15,7 @@ namespace NadekoBot.Services.Utility
public ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; 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 () =>
{

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 Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -23,12 +26,15 @@ namespace NadekoBot.Services.Utility
private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1);
private readonly Logger _log;
public readonly TimeSpan Interval = TimeSpan.FromMinutes(15);
private IBotCredentials _creds;
public readonly TimeSpan Interval = TimeSpan.FromMinutes(3);
private readonly IBotCredentials _creds;
private readonly DbService _db;
private readonly CurrencyService _currency;
public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency)
private readonly string cacheFileName = "./patreon-rewards.json";
public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency,
DiscordSocketClient client)
{
_creds = creds;
_db = db;
@ -36,58 +42,65 @@ namespace NadekoBot.Services.Utility
if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
return;
_log = LogManager.GetCurrentClassLogger();
Updater = new Timer(async (_) => await LoadPledges(), null, TimeSpan.Zero, Interval);
Updater = new Timer(async (load) => await RefreshPledges((bool)load),
client.ShardId == 0, client.ShardId == 0 ? TimeSpan.Zero : TimeSpan.FromMinutes(2), Interval);
}
public async Task LoadPledges()
public async Task RefreshPledges(bool shouldLoad)
{
LastUpdate = DateTime.UtcNow;
await getPledgesLocker.WaitAsync(1000).ConfigureAwait(false);
try
if (shouldLoad)
{
var rewards = new List<PatreonPledge>();
var users = new List<PatreonUser>();
using (var http = new HttpClient())
LastUpdate = DateTime.UtcNow;
await getPledgesLocker.WaitAsync().ConfigureAwait(false);
try
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
var data = new PatreonData()
var rewards = new List<PatreonPledge>();
var users = new List<PatreonUser>();
using (var http = new HttpClient())
{
Links = new PatreonDataLinks()
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
var data = new PatreonData()
{
next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges"
}
};
do
Links = new PatreonDataLinks()
{
next = "https://api.patreon.com/oauth2/api/campaigns/334038/pledges"
}
};
do
{
var res = await http.GetStringAsync(data.Links.next)
.ConfigureAwait(false);
data = JsonConvert.DeserializeObject<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)
.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));
User = y,
Reward = x,
}).ToImmutableArray();
File.WriteAllText("./patreon_rewards.json", JsonConvert.SerializeObject(Pledges));
}
Pledges = rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward()
catch (Exception ex)
{
User = y,
Reward = x,
}).ToImmutableArray();
}
catch (Exception ex)
{
_log.Warn(ex);
}
finally
{
var _ = Task.Run(async () =>
_log.Warn(ex);
}
finally
{
await Task.Delay(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
getPledgesLocker.Release();
});
}
}
else
{
if(File.Exists(cacheFileName))
Pledges = JsonConvert.DeserializeObject<PatreonUserAndReward[]>(File.ReadAllText("./patreon_rewards.json"))
.ToImmutableArray();
}
}

View File

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

View File

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

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

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>
/// danny kamisama
/// </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);
@ -134,7 +134,7 @@ namespace NadekoBot.Extensions
return embed.WithFooter(efb => efb.WithText(curPage.ToString()));
}
public static ReactionEventWrapper OnReaction(this IUserMessage msg, DiscordShardedClient client, Action<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)
reactionRemoved = delegate { };

View File

@ -154,6 +154,7 @@
"administration_old_topic": "Old topic",
"administration_perms": "Error. Most likely I don't have sufficient permissions.",
"permissions_perms_reset": "Permissions for this server are reset.",
"permissions_trigger": "Permission number #{0} {1} is preventing this action.",
"administration_prot_active": "Active protections",
"administration_prot_disable": "{0} has been **disabled** on this server.",
"administration_prot_enable": "{0} Enabled",
@ -446,7 +447,7 @@
"music_skipped_to": "Skipped to `{0}:{1}`",
"music_songs_shuffled": "Songs shuffled",
"music_song_moved": "Song moved",
"music_song_not_found": "No song found.",
"music_song_not_found": "No song found.",
"music_time_format": "{0}h {1}m {2}s",
"music_to_position": "To position",
"music_unlimited": "unlimited",
@ -523,7 +524,7 @@
"searches_cost": "Cost",
"searches_date": "Date",
"searches_define": "Define:",
"searches_define_unknown": "Can't find the definition for that term.",
"searches_define_unknown": "Can't find the definition for that term.",
"searches_dropped": "Dropped",
"searches_episodes": "Episodes",
"searches_error_occured": "Error occurred.",
@ -534,7 +535,7 @@
"searches_hashtag_error": "Failed finding a definition for that tag.",
"searches_height_weight": "Height/Weight",
"searches_height_weight_val": "{0}m/{1}kg",
"searches_hex_invalid": "Invalid color specified.",
"searches_hex_invalid": "Invalid color specified.",
"searches_humidity": "Humidity",
"searches_image_search_for": "Image search for:",
"searches_imdb_fail": "Failed to find that movie.",
@ -613,9 +614,6 @@
"utility_convert_not_found": "Cannot convert {0} to {1}: units not found",
"utility_convert_type_error": "Cannot convert {0} to {1}: types of unit are not equal",
"utility_created_at": "Created at",
"utility_csc_join": "Joined cross server channel.",
"utility_csc_leave": "Left cross server channel.",
"utility_csc_token": "This is your CSC token",
"utility_custom_emojis": "Custom emojis",
"utility_error": "Error",
"utility_features": "Features",

View File

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