NadekoBot/src/NadekoBot/NadekoBot.cs

321 lines
11 KiB
C#
Raw Normal View History

2016-08-15 14:57:40 +00:00
using Discord;
using Discord.Commands;
2016-08-13 18:45:08 +00:00
using Discord.WebSocket;
2016-08-15 14:57:40 +00:00
using NadekoBot.Services;
using NadekoBot.Services.Impl;
2016-08-18 21:00:54 +00:00
using NLog;
2016-08-13 18:45:08 +00:00
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using NadekoBot.Modules.Permissions;
2017-02-14 14:53:51 +00:00
using System.Collections.Immutable;
using System.Diagnostics;
2016-10-26 14:30:49 +00:00
using NadekoBot.Services.Database.Models;
2017-04-15 00:54:19 +00:00
using System.Threading;
2017-06-04 09:40:34 +00:00
using System.IO;
using NadekoBot.Extensions;
using System.Collections.Generic;
2017-07-17 19:42:36 +00:00
using NadekoBot.Common;
using NadekoBot.Common.ShardCom;
using NadekoBot.Common.TypeReaders;
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Services.Database;
2016-08-13 18:45:08 +00:00
namespace NadekoBot
{
public class NadekoBot
{
2016-08-18 21:00:54 +00:00
private Logger _log;
public BotCredentials Credentials { get; }
public DiscordSocketClient Client { get; }
public CommandService CommandService { get; }
public DbService Db { get; }
public BotConfig BotConfig { get; }
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
2017-05-22 23:59:31 +00:00
/* I don't know how to make this not be static
* and keep the convenience of .WithOkColor
* and .WithErrorColor extensions methods.
* I don't want to pass botconfig every time I
* want to send a confirm or error message, so
* I'll keep this for now */
public static Color OkColor { get; private set; }
public static Color ErrorColor { get; private set; }
public TaskCompletionSource<bool> Ready { get; private set; } = new TaskCompletionSource<bool>();
2016-10-26 14:30:49 +00:00
public INServiceProvider Services { get; private set; }
2017-05-10 14:08:31 +00:00
2017-06-20 02:23:11 +00:00
public ShardsCoordinator ShardCoord { get; private set; }
private readonly ShardComClient _comClient;
public NadekoBot(int shardId, int parentProcessId, int? port = null)
2016-10-26 14:30:49 +00:00
{
if (shardId < 0)
throw new ArgumentOutOfRangeException(nameof(shardId));
LogSetup.SetupLogger();
2017-05-22 23:59:31 +00:00
_log = LogManager.GetCurrentClassLogger();
2017-06-04 09:40:34 +00:00
TerribleElevatedPermissionCheck();
Credentials = new BotCredentials();
Db = new DbService(Credentials);
Client = new DiscordSocketClient(new DiscordSocketConfig
2016-08-13 18:45:08 +00:00
{
2016-09-06 21:34:00 +00:00
MessageCacheSize = 10,
2016-08-15 14:57:40 +00:00
LogLevel = LogSeverity.Warning,
2017-02-01 16:50:14 +00:00
ConnectionTimeout = int.MaxValue,
TotalShards = Credentials.TotalShards,
ShardId = shardId,
2017-06-17 12:07:15 +00:00
AlwaysDownloadUsers = false,
2016-08-13 18:45:08 +00:00
});
CommandService = new CommandService(new CommandServiceConfig()
2017-05-22 23:59:31 +00:00
{
2017-01-28 23:38:09 +00:00
CaseSensitiveCommands = false,
DefaultRunMode = RunMode.Sync,
2016-12-31 16:34:21 +00:00
});
port = port ?? Credentials.ShardRunPort;
_comClient = new ShardComClient(port.Value);
using (var uow = Db.UnitOfWork)
{
BotConfig = uow.BotConfig.GetOrCreate();
OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
}
2017-06-01 08:11:05 +00:00
2017-07-15 03:23:46 +00:00
SetupShard(parentProcessId, port.Value);
#if GLOBAL_NADEKO
Client.Log += Client_Log;
#endif
}
2016-08-13 18:45:08 +00:00
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,
Time = DateTime.UtcNow,
});
await Task.Delay(5000);
}
});
}
private void AddServices()
{
2017-06-20 02:23:11 +00:00
var startingGuildIdList = Client.Guilds.Select(x => (long)x.Id).ToList();
//this unit of work will be used for initialization of all modules too, to prevent multiple queries from running
using (var uow = Db.UnitOfWork)
{
2017-06-20 02:23:11 +00:00
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db);
//initialize Services
Services = new NServiceProvider.ServiceProviderBuilder()
.AddManual<IBotCredentials>(Credentials)
.AddManual(Db)
.AddManual(BotConfig)
.AddManual(Client)
.AddManual(CommandService)
.AddManual<ILocalization>(localization)
.AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this
.AddManual<NadekoBot>(this)
.AddManual<IUnitOfWork>(uow)
.LoadFrom(Assembly.GetEntryAssembly())
.Build();
var commandHandler = Services.GetService<CommandHandler>();
commandHandler.AddServices(Services);
//setup typereaders
CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader());
CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService));
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService));
CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader());
}
2017-05-22 23:59:31 +00:00
}
private async Task LoginAsync(string token)
2017-05-22 23:59:31 +00:00
{
var clientReady = new TaskCompletionSource<bool>();
Task SetClientReady()
{
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;
}
2016-08-15 14:57:40 +00:00
//connect
2017-07-15 03:23:46 +00:00
_log.Info("Shard {0} logging in ...", Client.ShardId);
2017-06-25 06:00:52 +00:00
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
await Client.StartAsync().ConfigureAwait(false);
Client.Ready += SetClientReady;
await clientReady.Task.ConfigureAwait(false);
Client.Ready -= SetClientReady;
Client.JoinedGuild += Client_JoinedGuild;
Client.LeftGuild += Client_LeftGuild;
2017-07-15 03:23:46 +00:00
_log.Info("Shard {0} logged in.", Client.ShardId);
}
private Task Client_LeftGuild(SocketGuild arg)
{
_log.Info("Left server: {0} [{1}]", arg?.Name, arg?.Id);
return Task.CompletedTask;
}
private Task Client_JoinedGuild(SocketGuild arg)
{
_log.Info("Joined server: {0} [{1}]", arg?.Name, arg?.Id);
return Task.CompletedTask;
}
public async Task RunAsync(params string[] args)
{
2017-07-15 03:23:46 +00:00
if(Client.ShardId == 0)
_log.Info("Starting NadekoBot v" + StatsService.BotVersion);
var sw = Stopwatch.StartNew();
await LoginAsync(Credentials.Token).ConfigureAwait(false);
2017-07-15 03:23:46 +00:00
_log.Info($"Shard {Client.ShardId} loading services...");
AddServices();
2016-08-18 21:00:54 +00:00
sw.Stop();
2017-07-15 03:23:46 +00:00
_log.Info($"Shard {Client.ShardId} connected in {sw.Elapsed.TotalSeconds:F2}s");
2016-08-18 21:00:54 +00:00
var stats = Services.GetService<IStatsService>();
stats.Initialize();
var commandHandler = Services.GetService<CommandHandler>();
var CommandService = Services.GetService<CommandService>();
// start handling messages received in commandhandler
2017-05-22 23:59:31 +00:00
await commandHandler.StartHandling().ConfigureAwait(false);
var _ = await CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly);
2017-06-15 17:16:50 +00:00
bool isPublicNadeko = false;
#if GLOBAL_NADEKO
isPublicNadeko = true;
#endif
2017-07-02 11:53:09 +00:00
//_log.Info(string.Join(", ", CommandService.Commands
// .Distinct(x => x.Name + x.Module.Name)
// .SelectMany(x => x.Aliases)
// .GroupBy(x => x)
// .Where(x => x.Count() > 1)
// .Select(x => x.Key + $"({x.Count()})")));
//unload modules which are not available on the public bot
if(isPublicNadeko)
CommandService
.Modules
.ToArray()
.Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot)))
.ForEach(x => CommandService.RemoveModuleAsync(x));
2017-06-21 21:34:29 +00:00
Ready.TrySetResult(true);
2017-07-15 03:23:46 +00:00
_log.Info($"Shard {Client.ShardId} ready.");
//_log.Info(await stats.Print().ConfigureAwait(false));
}
2016-08-13 18:45:08 +00:00
2017-01-15 01:08:14 +00:00
private Task Client_Log(LogMessage arg)
{
_log.Warn(arg.Source + " | " + arg.Message);
2017-01-15 01:08:14 +00:00
if (arg.Exception != null)
_log.Warn(arg.Exception);
return Task.CompletedTask;
}
public async Task RunAndBlockAsync(params string[] args)
{
await RunAsync(args).ConfigureAwait(false);
2017-06-22 01:39:26 +00:00
StartSendingData();
2017-06-20 02:23:11 +00:00
if (ShardCoord != null)
await ShardCoord.RunAndBlockAsync();
else
2017-06-22 01:39:26 +00:00
{
2017-06-20 02:23:11 +00:00
await Task.Delay(-1).ConfigureAwait(false);
2017-06-22 01:39:26 +00:00
}
2016-08-13 18:45:08 +00:00
}
2016-08-15 14:57:40 +00:00
2017-06-04 09:40:34 +00:00
private void TerribleElevatedPermissionCheck()
{
try
{
File.WriteAllText("test", "test");
File.Delete("test");
}
catch
{
_log.Error("You must run the application as an ADMINISTRATOR.");
Console.ReadKey();
Environment.Exit(2);
}
}
2017-06-20 02:23:11 +00:00
2017-07-15 03:23:46 +00:00
private void SetupShard(int parentProcessId, int port)
2017-06-20 02:23:11 +00:00
{
2017-07-15 03:23:46 +00:00
if (Client.ShardId == 0)
2017-06-20 02:23:11 +00:00
{
ShardCoord = new ShardsCoordinator(port);
2017-07-15 03:23:46 +00:00
return;
2017-06-20 02:23:11 +00:00
}
2017-07-15 03:23:46 +00:00
new Thread(new ThreadStart(() =>
{
try
{
var p = Process.GetProcessById(parentProcessId);
if (p == null)
return;
p.WaitForExit();
}
finally
{
Environment.Exit(10);
}
})).Start();
2017-06-20 02:23:11 +00:00
}
2016-08-13 18:45:08 +00:00
}
}