From 8957f284f5cd6cf0c0c50f0bb6ac6ee5d51cf3a3 Mon Sep 17 00:00:00 2001 From: Advocaite Date: Thu, 8 Sep 2022 04:02:44 +0800 Subject: [PATCH] Discord Bot Intergration Discord bot added to server some general info commands a example event, also ability to register account with discord bot and link discord user to that account and give them role for registering. --- .../AccountsSystem/AccountManager.cs | 29 ++ src/DiIiS-NA/Blizzless.csproj | 4 +- src/DiIiS-NA/Core/Discord/Bot.cs | 284 ++++++++++++++++++ src/DiIiS-NA/Core/Discord/Config.cs | 31 ++ .../Core/Discord/Modules/AuthModule.cs | 115 +++++++ .../Core/Discord/Modules/EventsModule.cs | 39 +++ .../Core/Discord/Modules/InfoModule.cs | 116 +++++++ .../Services/CommandHandlingService.cs | 84 ++++++ .../AccountDataBase/Entities/DBAccount.cs | 4 +- .../AccountDataBase/Mapper/DBAccountMapper.cs | 2 + .../D3-GameServer/ClientSystem/GameServer.cs | 3 +- src/DiIiS-NA/Program.cs | 13 +- src/DiIiS-NA/config.ini | 148 +++++++++ 13 files changed, 868 insertions(+), 4 deletions(-) create mode 100644 src/DiIiS-NA/Core/Discord/Bot.cs create mode 100644 src/DiIiS-NA/Core/Discord/Config.cs create mode 100644 src/DiIiS-NA/Core/Discord/Modules/AuthModule.cs create mode 100644 src/DiIiS-NA/Core/Discord/Modules/EventsModule.cs create mode 100644 src/DiIiS-NA/Core/Discord/Modules/InfoModule.cs create mode 100644 src/DiIiS-NA/Core/Discord/Services/CommandHandlingService.cs create mode 100644 src/DiIiS-NA/config.ini diff --git a/src/DiIiS-NA/BGS-Server/AccountsSystem/AccountManager.cs b/src/DiIiS-NA/BGS-Server/AccountsSystem/AccountManager.cs index 186435f..0f132f4 100644 --- a/src/DiIiS-NA/BGS-Server/AccountsSystem/AccountManager.cs +++ b/src/DiIiS-NA/BGS-Server/AccountsSystem/AccountManager.cs @@ -75,6 +75,35 @@ namespace DiIiS_NA.LoginServer.AccountsSystem Logger.Warn("Created account {0}", email); return GetAccountByEmail(email); } + public static bool BindDiscordAccount(string email, ulong discordId, string discordTag) + { + try + { + if (DBSessions.SessionQueryWhere(dba => dba.DiscordId == discordId).Count() > 0) + return false; + + var account = GetAccountByEmail(email); + account.DBAccount.DiscordTag = discordTag; + account.DBAccount.DiscordId = discordId; + DBSessions.SessionUpdate(account.DBAccount); + return true; + } + catch (Exception e) + { + Logger.DebugException(e, "BindDiscordAccount() exception: "); + return false; + } + } + public static Account GetAccountByDiscordId(ulong discordId) + { + List dbAcc = DBSessions.SessionQueryWhere(dba => dba.DiscordId == discordId).ToList(); + if (dbAcc.Count() == 0) + { + Logger.Warn("GetAccountByDiscordId {0}: DBAccount is null!", discordId); + return null; + } + return GetAccountByDBAccount(dbAcc.First()); + } public static bool GenerateReferralCode(string email) { diff --git a/src/DiIiS-NA/Blizzless.csproj b/src/DiIiS-NA/Blizzless.csproj index 1dc558c..40819d8 100644 --- a/src/DiIiS-NA/Blizzless.csproj +++ b/src/DiIiS-NA/Blizzless.csproj @@ -64,6 +64,7 @@ + @@ -85,6 +86,7 @@ + @@ -95,7 +97,7 @@ Always - Never + PreserveNewest Always diff --git a/src/DiIiS-NA/Core/Discord/Bot.cs b/src/DiIiS-NA/Core/Discord/Bot.cs new file mode 100644 index 0000000..00f421d --- /dev/null +++ b/src/DiIiS-NA/Core/Discord/Bot.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using DiIiS_NA.Core.Discord.Services; +using DiIiS_NA.LoginServer.AccountsSystem; +using DiIiS_NA.Core.Logging; +using DiIiS_NA.Core.Storage; +using DiIiS_NA.Core.Storage.AccountDataBase.Entities; + +namespace DiIiS_NA.Core.Discord +{ + public class Bot + { + private static readonly Logger Logger = LogManager.CreateLogger(); + + public static void Init() + => new Bot().MainAsync().GetAwaiter().GetResult(); + + public DiscordSocketClient Client = null; + private DiscordSocketConfig _config = new DiscordSocketConfig{MessageCacheSize = 100}; + + public async Task MainAsync() + { + var services = ConfigureServices(); + this.Client = services.GetService(); + await services.GetService().InitializeAsync(); + + await this.Client.LoginAsync(TokenType.Bot, Config.Instance.Token); + await this.Client.StartAsync(); + + this.Client.ReactionAdded += HandleReactionAsync;// ReactionAdded; + + //await Task.Delay(-1); + } + + private IServiceProvider ConfigureServices() + { + return new ServiceCollection() + // Base + .AddSingleton(new DiscordSocketClient(_config)) + .AddSingleton() + .AddSingleton() + // Add additional services here... + .BuildServiceProvider(); + } + private async Task HandleReactionAsync(Cacheable message, Cacheable channel, SocketReaction reaction) + { + if (this.Client.GetUser(reaction.UserId).IsBot) return; + + // If the message was not in the cache, downloading it will result in getting a copy of it. + var msg = await message.GetOrDownloadAsync(); + if (channel.Id == (ulong)DiIiS_NA.Core.Discord.Config.Instance.EventsChannelId) + { + var user = reaction.User.Value; + var guild_user = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetUser(user.Id); + + if (!guild_user.Roles.Select(r => r.Id).Contains((ulong)DiIiS_NA.Core.Discord.Config.Instance.BaseRoleId) && !guild_user.IsBot) + { + await msg.RemoveReactionAsync(reaction.Emote, reaction.User.Value); + await user.SendMessageAsync("**Your #💎events entry has been removed because your Blizzless account isn't linked to Discord!**\nYou can do that if you send me:\n\n`!email my_d3r_email@something.com`\n\n**(Replace** `my_d3r_email@something.com` **with your D3 Reflection email)**"); + } + } + + } + + public async Task AddCollectorRole(ulong userId) + { + try + { + var user = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetUser(userId); + await user.AddRoleAsync(this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetRole((ulong)DiIiS_NA.Core.Discord.Config.Instance.CollectorRoleId)); + await user.SendMessageAsync("Congratulations! You are now a **Collector**."); + } + catch (Exception e) + { + Logger.WarnException(e, "AddCollectorRole() exception: "); + } + } + + public async Task AddPremiumRole(ulong userId) + { + try + { + var user = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetUser(userId); + await user.AddRoleAsync(this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetRole((ulong)DiIiS_NA.Core.Discord.Config.Instance.PremiumRoleId)); + await user.SendMessageAsync("Congratulations! You are now a **Premium** user."); + } + catch (Exception e) + { + Logger.WarnException(e, "AddPremiumRole() exception: "); + } + } + + public async Task UpdateBattleTag(ulong userId, string btag) + { + try + { + var user = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetUser(userId); + await user.AddRoleAsync(this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetRole((ulong)DiIiS_NA.Core.Discord.Config.Instance.PremiumRoleId)); + await user.ModifyAsync(x => {x.Nickname = btag;}); + } + catch (Exception e) + { + Logger.WarnException(e, "UpdateBattleTag() exception: "); + } + } + + public async Task ClearPremiumRoles(List userIds) + { + try + { + var guild = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId); + var role = guild.GetRole((ulong)DiIiS_NA.Core.Discord.Config.Instance.PremiumRoleId); + foreach (var userId in userIds) + { + try + { + var user = guild.GetUser(userId); + await user.RemoveRoleAsync(role); + } catch {} + } + } + catch (Exception e) + { + Logger.WarnException(e, "ClearPremiumRoles() exception: "); + } + } + + public async Task ShowServerStats() + { + try + { + var guild = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId); + var channelId = (ulong)DiIiS_NA.Core.Discord.Config.Instance.StatsChannelId; + string opened = DBSessions.SessionQueryWhere(dbgp => dbgp.Name == "ServerOpened").First().Value > 0 ? "ENABLED" : "DISABLED"; + // int gs_count = Program.MooNetServer.MooNetBackend.GameServers.Count; + // int ccu = DiIiS_NA.Core.MooNet.Online.PlayerManager.OnlinePlayers.Count; + // int games = DiIiS_NA.Core.MooNet.Games.GameFactoryManager.GamesOnline; + var messages = await guild.GetTextChannel(channelId).GetMessagesAsync(10).FlattenAsync(); + await guild.GetTextChannel(channelId).DeleteMessagesAsync(messages); + // await guild.GetTextChannel(channelId).SendMessageAsync($"Login availability: **{opened}**\nGame servers available: {gs_count}\nPlayers online: {ccu}\nGames online: {games}"); + } + catch (Exception e) + { + Logger.WarnException(e, "ShowStats() exception: "); + } + } + + + public async Task StartGiveaway() + { + try + { + var guild = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId); + var channelId = (ulong)DiIiS_NA.Core.Discord.Config.Instance.EventsChannelId; + var param_message = DBSessions.SessionQueryWhere(dbgp => dbgp.Name == "DiscordGiveawayPostId"); + if (param_message.Count < 1) + { + var messages = await guild.GetTextChannel(channelId).GetMessagesAsync(10).FlattenAsync(); + await guild.GetTextChannel(channelId).DeleteMessagesAsync(messages); + } + else + { + await guild.GetTextChannel(channelId).DeleteMessageAsync(param_message.First().Value); + } + + var eb = new EmbedBuilder(); + eb.WithTitle("Reward: 7 days of Premium"); + eb.WithDescription("Click <:wolfRNG:607868292979490816> to join.\nEnds at 18:00 UTC"); + eb.WithFooter("You must bind your D3R account email to be able to join!"); + eb.WithColor(Color.Blue); + var mes = await guild.GetTextChannel(channelId).SendMessageAsync("@here \n <:wolfRNG:607868292979490816> **GIVEAWAY ROULETTE!** <:wolfRNG:607868292979490816>", false, eb.Build()); + + //var giveaway_message = await guild.GetTextChannel(channelId).GetMessagesAsync(1).FlattenAsync(); + //foreach (var mes in giveaway_message) + //{ + var param = DBSessions.SessionQueryWhere(dbgp => dbgp.Name == "DiscordGiveawayPostId"); + if (param.Count < 1) + { + var new_param = new DBGlobalParams{ + Name = "DiscordGiveawayPostId", + Value = mes.Id + }; + DBSessions.SessionSave(new_param); + } + else + { + var postId = param.First(); + postId.Value = mes.Id; + DBSessions.SessionUpdate(postId); + } + await (mes as IUserMessage).AddReactionAsync(Emote.Parse("<:wolfRNG:607868292979490816>")); + //} + } + catch (Exception e) + { + Logger.WarnException(e, "StartGiveaway() exception: "); + } + } + + public async Task FinishGiveaway() + { + try + { + var guild = this.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId); + var channelId = (ulong)DiIiS_NA.Core.Discord.Config.Instance.EventsChannelId; + bool haveWinner = true; + string winnerName = ""; + var param = DBSessions.SessionQueryWhere(dbgp => dbgp.Name == "DiscordGiveawayPostId"); + if (param.Count < 1) + { + haveWinner = false; + } + else + { + if (param.First().Value > 0) + { + var message = await guild.GetTextChannel(channelId).GetMessageAsync(param.First().Value); + var reactedUsers = await (message as IUserMessage).GetReactionUsersAsync(Emote.Parse("<:wolfRNG:607868292979490816>"), 100).FlattenAsync(); + var contestants = reactedUsers.Where(u => !u.IsBot).ToList(); + if (contestants.Count() > 0) + { + var winner = reactedUsers.Where(u => !u.IsBot).ToList()[DiIiS_NA.Core.Helpers.Math.FastRandom.Instance.Next(0, reactedUsers.Count() - 1)]; + winnerName = guild.GetUser(winner.Id).Nickname; + await winner.SendMessageAsync("Congratulations! You have won **7 days of D3 Reflection Premium**!.\nYour account has already had its Premium prolonged. Have a nice game!"); + var acc = AccountManager.GetAccountByDiscordId(winner.Id); + if (acc != null) { + //acc.UpdatePremiumTime(7); + } + } + else + haveWinner = false; + } + else + haveWinner = false; + } + + if (param.Count < 1) + { + var messages = await guild.GetTextChannel(channelId).GetMessagesAsync(10).FlattenAsync(); + await guild.GetTextChannel(channelId).DeleteMessagesAsync(messages); + } + else + { + await guild.GetTextChannel(channelId).DeleteMessageAsync(param.First().Value); + } + + var eb = new EmbedBuilder(); + eb.WithTitle("Giveaway ended"); + eb.WithDescription(haveWinner ? string.Format("Winner: {0}", winnerName) : "We have no winner this time :("); + eb.WithFooter("Free Premium giveaways - starts every Friday and Saturday!"); + eb.WithColor(Color.Red); + var mes = await guild.GetTextChannel(channelId).SendMessageAsync("<:wolfRNG:607868292979490816> **GIVEAWAY ROULETTE!** <:wolfRNG:607868292979490816>", false, eb.Build()); + + var db_param = DBSessions.SessionQueryWhere(dbgp => dbgp.Name == "DiscordGiveawayPostId"); + if (db_param.Count < 1) + { + var new_param = new DBGlobalParams{ + Name = "DiscordGiveawayPostId", + Value = mes.Id + }; + DBSessions.SessionSave(new_param); + } + else + { + var postId = db_param.First(); + postId.Value = mes.Id; + DBSessions.SessionUpdate(postId); + } + } + catch (Exception e) + { + Logger.WarnException(e, "FinishGiveaway() exception: "); + } + } + } +} \ No newline at end of file diff --git a/src/DiIiS-NA/Core/Discord/Config.cs b/src/DiIiS-NA/Core/Discord/Config.cs new file mode 100644 index 0000000..eb9cb04 --- /dev/null +++ b/src/DiIiS-NA/Core/Discord/Config.cs @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 - 2012 DiIiS_NA project - http://www.DiIiS_NA.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * + */ + +namespace DiIiS_NA.Core.Discord +{ + public sealed class Config : Core.Config.Config + { + public bool Enabled { get { return this.GetBoolean("Enabled", false); } set { this.Set("Enabled", value); } } + public bool MonitorEnabled { get { return this.GetBoolean("MonitorEnabled", true); } set { this.Set("MonitorEnabled", value); } } + public string Token { get { return this.GetString("Token", ""); } set { this.Set("Token", value); } } + public long GuildId { get { return this.GetLong("GuildId", 0); } set { this.Set("GuildId", value); } } + public long AnnounceChannelId { get { return this.GetLong("AnnounceChannelId", 0); } set { this.Set("AnnounceChannelId", value); } } + public long StatsChannelId { get { return this.GetLong("StatsChannelId", 0); } set { this.Set("StatsChannelId", value); } } + public long EventsChannelId { get { return this.GetLong("EventsChannelId", 0); } set { this.Set("EventsChannelId", value); } } + public long BaseRoleId { get { return this.GetLong("BaseRoleId", 0); } set { this.Set("BaseRoleId", value); } } + public long PremiumRoleId { get { return this.GetLong("PremiumRoleId", 0); } set { this.Set("PremiumRoleId", value); } } + public long CollectorRoleId { get { return this.GetLong("CollectorRoleId", 0); } set { this.Set("CollectorRoleId", value); } } + + private static readonly Config _instance = new Config(); + public static Config Instance { get { return _instance; } } + private Config() : base("Discord") { } + } +} diff --git a/src/DiIiS-NA/Core/Discord/Modules/AuthModule.cs b/src/DiIiS-NA/Core/Discord/Modules/AuthModule.cs new file mode 100644 index 0000000..52297aa --- /dev/null +++ b/src/DiIiS-NA/Core/Discord/Modules/AuthModule.cs @@ -0,0 +1,115 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Net.Mail; +using DiIiS_NA.LoginServer.AccountsSystem; +using DiIiS_NA.Core.Storage; +using DiIiS_NA.Core.Storage.AccountDataBase.Entities; +using Discord; +using Discord.Commands; + +namespace DiIiS_NA.Core.Discord.Modules +{ + public class AuthModule : ModuleBase + { + [Command("register")] + public async Task Register([Remainder] string args) + { + string dtag = Context.User.Username + "#" + Context.User.Discriminator; + string[] registerInfo = args.Split(null); + + var email = registerInfo[0]; + var password = registerInfo[1]; + var battleTagName = registerInfo[2]; + var userLevel = Account.UserLevels.User; + + if (!(Context.Channel is IDMChannel)) + { + await Context.Guild.GetTextChannel(Context.Channel.Id).DeleteMessageAsync(Context.Message); + await ReplyAsync($"<@{Context.User.Id}> that command could be used only via direct message!\nDon't show your e-mail to anyone, don't be a fool! <:200iq:538833204421984286>"); + return; + } + + + if (registerInfo.Count() == 3) + { + if (!email.Contains('@')) + { + await ReplyAsync($"<@{Context.User.Id}> " + string.Format("'{0}' is not a valid email address.", email)); + return; + } + if (!IsValid(email)) + { + await ReplyAsync("Your e-mail address is invalid!"); + return; + } + if (battleTagName.Contains('#')) + { + await ReplyAsync($"<@{Context.User.Id}> BattleTag must not contain '#' or HashCode."); + return; + } + + + if (password.Length < 8 || password.Length > 16) + { + await ReplyAsync($"<@{Context.User.Id}> Password should be a minimum of 8 and a maximum of 16 characters."); + return; + } + + if (AccountManager.GetAccountByEmail(email) != null) + { + await ReplyAsync($"<@{Context.User.Id}> " + string.Format("An account already exists for email address {0}.", email)); + return; + } + + var account = AccountManager.CreateAccount(email, password, battleTagName, userLevel); + var gameAccount = GameAccountManager.CreateGameAccount(account); + + var guild_user = Context.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetUser(Context.User.Id); + + if (guild_user == null) + { + await ReplyAsync("Your Discord account is not participated channel!"); + return; + } + + if (AccountManager.BindDiscordAccount(email, Context.User.Id, dtag)) + { + var accountcheck = AccountManager.GetAccountByEmail(email); + string battle_tag = account.DBAccount.BattleTagName + "#" + account.DBAccount.HashCode; + await ReplyAsync($"Account registered.\nYour Discord account has been successfully bound to {battle_tag}!"); + await guild_user.AddRoleAsync(Context.Client.GetGuild((ulong)DiIiS_NA.Core.Discord.Config.Instance.GuildId).GetRole((ulong)DiIiS_NA.Core.Discord.Config.Instance.BaseRoleId)); + await ReplyAsync("You are now **DemonSlayer**!"); + try + { + await guild_user.ModifyAsync(x => { x.Nickname = battle_tag; }); + } + catch + { } + } + else + await ReplyAsync("An error occured: make sure your Discord account hasn't already been bound to another account.!"); + } + else + { + await ReplyAsync("Incorrect usage: !register .!"); + } + } + + private bool IsValid(string emailaddress) + { + try + { + MailAddress m = new MailAddress(emailaddress); + + return true; + } + catch (FormatException) + { + return false; + } + } + } +} diff --git a/src/DiIiS-NA/Core/Discord/Modules/EventsModule.cs b/src/DiIiS-NA/Core/Discord/Modules/EventsModule.cs new file mode 100644 index 0000000..1360d99 --- /dev/null +++ b/src/DiIiS-NA/Core/Discord/Modules/EventsModule.cs @@ -0,0 +1,39 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using DiIiS_NA.Core.Storage; +using DiIiS_NA.Core.Storage.AccountDataBase.Entities; + +namespace DiIiS_NA.Core.Discord.Modules +{ + public class EventsModule : ModuleBase + { + private ulong EventsChannelId + { + get + { + return (ulong)DiIiS_NA.Core.Discord.Config.Instance.EventsChannelId; + } + set{} + } + + + [Command("announce_event")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task SalesAnnounce() + { + var eb = new EmbedBuilder(); + eb.WithTitle("New Event"); + eb.WithDescription("Event Description."); + eb.WithFooter("Ends 4th Dec 2022.\nStay at home!"); + eb.WithColor(Color.Green); + await Context.Guild.GetTextChannel(EventsChannelId).SendMessageAsync("<:party:> **NEW EVENT!** <:party:>", false, eb.Build()); + } + + } +} diff --git a/src/DiIiS-NA/Core/Discord/Modules/InfoModule.cs b/src/DiIiS-NA/Core/Discord/Modules/InfoModule.cs new file mode 100644 index 0000000..02055cd --- /dev/null +++ b/src/DiIiS-NA/Core/Discord/Modules/InfoModule.cs @@ -0,0 +1,116 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using DiIiS_NA.Core.Extensions; +using DiIiS_NA.Core.Storage; +using DiIiS_NA.Core.Storage.AccountDataBase.Entities; +using DiIiS_NA.Core.Storage; +using DiIiS_NA.Core.Storage.AccountDataBase.Entities; +using DiIiS_NA.LoginServer.Battle; +using DiIiS_NA.LoginServer.GamesSystem; + +namespace DiIiS_NA.Core.Discord.Modules +{ + public class InfoModule : ModuleBase + { + private ulong AnnounceChannelId + { + get + { + return (ulong)DiIiS_NA.Core.Discord.Config.Instance.AnnounceChannelId; + } + set{} + } + + private ulong StatsChannelId + { + get + { + return (ulong)DiIiS_NA.Core.Discord.Config.Instance.StatsChannelId; + } + set{} + } + + [Command("about")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task Info() + { + await ReplyAsync($"Hello, I am a bot called {Context.Client.CurrentUser.Username} written for Blizzless Server\nSpecial Thanks to those who want it."); + } + + [Command("ping")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task PingAsync() + { + await ReplyAsync("pong!"); + } + + [Command("list_online")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task ListOnline() + { + int ccu = PlayerManager.OnlinePlayers.Count; + string players = ""; + foreach (var plr in PlayerManager.OnlinePlayers) + { + players += plr.Account.BattleTag; + players += "\n"; + } + + await ReplyAsync(string.Format("Total players online: {0}\n{1}", ccu, players)); + } + + [Command("lock")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task Lock() + { + var param = DBSessions.SessionQueryWhere(dbgp => dbgp.Name == "ServerOpened").First(); + param.Value = 0; + DBSessions.SessionUpdate(param); + await ReplyAsync("Login availability now **DISABLED**"); + } + + [Command("unlock")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task Unlock() + { + var param = DBSessions.SessionQueryWhere(dbgp => dbgp.Name == "ServerOpened").First(); + param.Value = 1; + DBSessions.SessionUpdate(param); + await ReplyAsync("Login availability now **ENABLED**"); + } + + [Command("maintenance")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task MaintenanceAnnounce([Remainder]int minutes) + { + //notify players maintenance is enabled + var messages = await Context.Guild.GetTextChannel(AnnounceChannelId).GetMessagesAsync(10).FlattenAsync(); + await Context.Guild.GetTextChannel(AnnounceChannelId).DeleteMessagesAsync(messages); + await Context.Guild.GetTextChannel(AnnounceChannelId).SendMessageAsync("Servers status: :tools: **PLANNED MAINTENANCE**."); + await Context.Guild.GetTextChannel(AnnounceChannelId).SendMessageAsync($"@here Servers will be restarted in **{minutes}** minutes for a planned maintenance.\n----\nСерверы будут перезагружены через **{minutes}** минут для плановых профилактических работ."); + } + + [Command("online")] + [RequireContext(ContextType.Guild)] + [RequireUserPermission(GuildPermission.Administrator)] + public async Task OnlineAnnounce() + { + var messages = await Context.Guild.GetTextChannel(AnnounceChannelId).GetMessagesAsync(10).FlattenAsync(); + await Context.Guild.GetTextChannel(AnnounceChannelId).DeleteMessagesAsync(messages); + await Context.Guild.GetTextChannel(AnnounceChannelId).SendMessageAsync("Servers status: :white_check_mark: **ONLINE**."); + await Context.Guild.GetTextChannel(AnnounceChannelId).SendMessageAsync("@here Servers are up and running.\n"); + } + + } +} diff --git a/src/DiIiS-NA/Core/Discord/Services/CommandHandlingService.cs b/src/DiIiS-NA/Core/Discord/Services/CommandHandlingService.cs new file mode 100644 index 0000000..41d765c --- /dev/null +++ b/src/DiIiS-NA/Core/Discord/Services/CommandHandlingService.cs @@ -0,0 +1,84 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using DiIiS_NA.Core.Logging; + +namespace DiIiS_NA.Core.Discord.Services +{ + public class CommandHandlingService + { + private static readonly Logger Logger = LogManager.CreateLogger(); + private readonly CommandService _commands; + private readonly DiscordSocketClient _discord; + private readonly IServiceProvider _services; + + public CommandHandlingService(IServiceProvider services) + { + _commands = services.GetService(); + _discord = services.GetService(); + _services = services; + + // Hook CommandExecuted to handle post-command-execution logic. + _commands.CommandExecuted += CommandExecutedAsync; + // Hook MessageReceived so we can process each message to see + // if it qualifies as a command. + _discord.MessageReceived += MessageReceivedAsync; + } + + public async Task InitializeAsync() + { + // Register modules that are public and inherit ModuleBase. + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + } + + public async Task MessageReceivedAsync(SocketMessage rawMessage) + { + try + { + // Ignore system messages, or messages from other bots + if (!(rawMessage is SocketUserMessage message)) return; + if (message.Source != MessageSource.User) return; + + //if (!(message.Channel is SocketTextChannel textChannel)) return; + //if (textChannel.Name != DiIiS_NA.Core.Discord.Config.Instance.ConsoleChannel) return; + // This value holds the offset where the prefix ends + var argPos = 0; + // Perform prefix check. You may want to replace this with + if (!message.HasCharPrefix('!', ref argPos)) return; + // for a more traditional command format like !help. + //if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) return; + + var context = new SocketCommandContext(_discord, message); + // Perform the execution of the command. In this method, + // the command service will perform precondition and parsing check + // then execute the command if one is matched. + Logger.BotCommand("[{0}]: {1}", message.Author.Username, message.Content); + await _commands.ExecuteAsync(context, argPos, _services); + // Note that normally a result will be returned by this format, but here + // we will handle the result in CommandExecutedAsync, + } + catch (Exception e) + { + Logger.DebugException(e, "Discord exception: "); + } + } + + public async Task CommandExecutedAsync(Optional command, ICommandContext context, IResult result) + { + // command is unspecified when there was a search failure (command not found); we don't care about these errors + if (!command.IsSpecified) + return; + + // the command was successful, we don't care about this result, unless we want to log that a command succeeded. + if (result.IsSuccess) + return; + + // the command failed, let's notify the user that something happened. + await context.Channel.SendMessageAsync($"error: {result}"); + } + } +} diff --git a/src/DiIiS-NA/Core/Storage/AccountDataBase/Entities/DBAccount.cs b/src/DiIiS-NA/Core/Storage/AccountDataBase/Entities/DBAccount.cs index c05f164..a26344f 100644 --- a/src/DiIiS-NA/Core/Storage/AccountDataBase/Entities/DBAccount.cs +++ b/src/DiIiS-NA/Core/Storage/AccountDataBase/Entities/DBAccount.cs @@ -23,5 +23,7 @@ namespace DiIiS_NA.Core.Storage.AccountDataBase.Entities public virtual ulong LastOnline { get; set; } public virtual bool HasRename { get; set; } public virtual ulong RenameCooldown { get; set; } - } + public virtual ulong DiscordId { get; set; } + public virtual string DiscordTag { get; set; } + } } diff --git a/src/DiIiS-NA/Core/Storage/AccountDataBase/Mapper/DBAccountMapper.cs b/src/DiIiS-NA/Core/Storage/AccountDataBase/Mapper/DBAccountMapper.cs index a968d4f..c2eb73e 100644 --- a/src/DiIiS-NA/Core/Storage/AccountDataBase/Mapper/DBAccountMapper.cs +++ b/src/DiIiS-NA/Core/Storage/AccountDataBase/Mapper/DBAccountMapper.cs @@ -12,6 +12,8 @@ namespace DiIiS_NA.Core.Storage.AccountDataBase.Mapper Table("accounts"); Id(e => e.Id).CustomType().GeneratedBy.Sequence("accounts_seq").UnsavedValue(null); Map(e => e.Email); + Map(e => e.DiscordTag); + Map(e => e.DiscordId).CustomType().Default("0"); Map(e => e.Banned).Not.Nullable().Default("false"); Map(e => e.Salt)/*.CustomSqlType("VarBinary(32)")*/.Length(32); Map(e => e.PasswordVerifier)/*.CustomSqlType("VarBinary")*/.Length(128); diff --git a/src/DiIiS-NA/D3-GameServer/ClientSystem/GameServer.cs b/src/DiIiS-NA/D3-GameServer/ClientSystem/GameServer.cs index d88ac0e..d7f495c 100644 --- a/src/DiIiS-NA/D3-GameServer/ClientSystem/GameServer.cs +++ b/src/DiIiS-NA/D3-GameServer/ClientSystem/GameServer.cs @@ -1,4 +1,5 @@ //Blizzless Project 2022 +using DiIiS_NA.Core.Discord; using DiIiS_NA.Core.Logging; //Blizzless Project 2022 using DiIiS_NA.GameServer.ClientSystem.Base; @@ -24,7 +25,7 @@ namespace DiIiS_NA.GameServer.ClientSystem public static GSBackend GSBackend { get; set; } public static int MaintenanceTime = -1; - + public Bot DiscordBot { get; set; } public GameServer() { this.OnConnect += ClientManager.Instance.OnConnect; diff --git a/src/DiIiS-NA/Program.cs b/src/DiIiS-NA/Program.cs index e9ffc44..aade652 100644 --- a/src/DiIiS-NA/Program.cs +++ b/src/DiIiS-NA/Program.cs @@ -1,5 +1,6 @@ //Blizzless Project 2022 //Blizzless Project 2022 +using DiIiS_NA.Core.Discord.Modules; using DiIiS_NA.Core.Logging; //Blizzless Project 2022 using DiIiS_NA.Core.MPQ; @@ -164,6 +165,7 @@ namespace DiIiS_NA Logger.Info("REST server started - " + REST.Config.Instance.IP + ":{0}", REST.Config.Instance.PORT); } + //BGS ServerBootstrap b = new ServerBootstrap(); IEventLoopGroup boss = new MultithreadEventLoopGroup(1); @@ -268,7 +270,16 @@ namespace DiIiS_NA GameServer = new DiIiS_NA.GameServer.ClientSystem.GameServer(); GameServerThread = new Thread(GameServer.Run) { Name = "GameServerThread", IsBackground = true }; GameServerThread.Start(); - + if (DiIiS_NA.Core.Discord.Config.Instance.Enabled) + { + Logger.Info("Starting Discord bot handler.."); + GameServer.DiscordBot = new Core.Discord.Bot(); + GameServer.DiscordBot.MainAsync().GetAwaiter().GetResult(); + } + else + { + Logger.Info("Discord bot Disabled.."); + } DiIiS_NA.GameServer.GSSystem.GeneratorsSystem.SpawnGenerator.RegenerateDensity(); DiIiS_NA.GameServer.ClientSystem.GameServer.GSBackend = new GSBackend(DiIiS_NA.LoginServer.Config.Instance.BindIP, DiIiS_NA.LoginServer.Config.Instance.WebPort); return true; diff --git a/src/DiIiS-NA/config.ini b/src/DiIiS-NA/config.ini new file mode 100644 index 0000000..ad34b29 --- /dev/null +++ b/src/DiIiS-NA/config.ini @@ -0,0 +1,148 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; ; +; blizzless Configuration File ; +; ; +;-----------------------------------------------------------------------------------------------------------------; +; ; +; This file is an example configuration and may require modification to suit your needs. ; +; ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Settings for Bnet server +[Battle-Server] +Enabled = true +BindIP = 127.0.0.1 +BindIPv6 = ::1 +Port = 1119 +MOTD = Welcome to Diablo 3 Emulator! +RoSEnabled = true +SeasonEnabled = true + +; Settings for game server +[Game-Server] +Enabled = true +PvP = false +BindIP = 127.0.0.1 +WebIP = 127.0.0.1 +WebPort = 9001 +BindIPv6 = ::1 +Port = 1999 +DRLGemu = true + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Authentication settings +[Authentication] +DisablePasswordChecks=true + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Discord bot settings +[Discord] +Enabled=true +MonitorEnabled=false +Token=secret token +GuildId=0 +AnnounceChannelId=0 +StatsChannelId=0 +EventsChannelId=0 +BaseRoleId=0 +PremiumRoleId=0 +CollectorRoleId=0 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; You can set here the command-prefix. Note: You can't use slash as a prefix. +[Commands] +CommandPrefix = ! + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; You can enable web-services here and use the provided contrib/web/LibMooge.php for communicating mooege over http. +[REST] +Enabled = true +Public = 127.0.0.1 +PublicIP = 127.0.0.1 +PORT = 8081 + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Networking settings. +[Networking] +EnableIPv6 = false ; experimental!! + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Network address translation settings. +; You only need to change this if you're running behind a dsl router or so. +; Important: If you enable NAT, LAN-clients will not able to connect in gs. +; (Will be fixed later with a proper implementation similar to one in pvpgn). +[NAT] +Enabled = false +PublicIP = 101.185.137.121 ; You need to change this to your router's public interface IP if you'd like to use NAT. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; General logging settings +[Logging] +Root=logs + +; Settings for console logger +[ConsoleLog] +Enabled=true +Target=Console +IncludeTimeStamps=true +MinimumLevel=Trace +MaximumLevel=Fatal + +; Settings for server log file. +[ServerLog] +Enabled=true +Target=File +FileName="blizzless.log" +IncludeTimeStamps=true +MinimumLevel=Trace +MaximumLevel=Fatal +ResetOnStartup=true + +; Settings for chat log file. +[ChatLog] +Enabled=true +Target=File +FileName="blizzless-chat.log" +IncludeTimeStamps=true +MinimumLevel=ChatMessage +MaximumLevel=ChatMessage +ResetOnStartup=true + +; Settings for discord log file. +[DiscordLog] +Enabled=false +Target=File +FileName="discord-bot.log" +IncludeTimeStamps=true +MinimumLevel=BotCommand +MaximumLevel=BotCommand +ResetOnStartup=true + +; Settings for renames log file. +[RenameAccountLog] +Enabled=true +Target=File +FileName="blizzless-btag-rename.log" +IncludeTimeStamps=true +MinimumLevel=RenameAccountLog +MaximumLevel=RenameAccountLog +ResetOnStartup=true + +; Settings for packet logger, only recommended for developers! +[PacketLog] +Enabled=true +Target=File +FileName="packet-dump.log" +IncludeTimeStamps=true +MinimumLevel=PacketDump +MaximumLevel=PacketDump +ResetOnStartup=true