diff --git a/Dalamud/Configuration/DalamudConfiguration.cs b/Dalamud/Configuration/DalamudConfiguration.cs index d373d99d1..5d11508f2 100644 --- a/Dalamud/Configuration/DalamudConfiguration.cs +++ b/Dalamud/Configuration/DalamudConfiguration.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using Dalamud.Configuration; -using Dalamud.DiscordBot; using Dalamud.Game.Chat; using Newtonsoft.Json; using Serilog; @@ -12,8 +10,6 @@ namespace Dalamud [Serializable] internal class DalamudConfiguration { - public DiscordFeatureConfiguration DiscordFeatureConfig { get; set; } - public bool OptOutMbCollection { get; set; } = false; public List BadWords { get; set; } diff --git a/Dalamud/DiscordBot/DiscordBotManager.cs b/Dalamud/DiscordBot/DiscordBotManager.cs deleted file mode 100644 index a61f72b0f..000000000 --- a/Dalamud/DiscordBot/DiscordBotManager.cs +++ /dev/null @@ -1,354 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO.Ports; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Dalamud.Game.Chat; -using Dalamud.Game.Chat.SeStringHandling; -using Dalamud.Game.Chat.SeStringHandling.Payloads; -using Dalamud.Game.Internal.Libc; -using Discord; -using Discord.WebSocket; -using Lumina.Excel.GeneratedSheets; -using Newtonsoft.Json.Linq; -using Serilog; - -namespace Dalamud.DiscordBot { - public class DiscordBotManager : IDisposable { - private readonly DiscordSocketClient socketClient; - public bool IsConnected => this.socketClient.ConnectionState == ConnectionState.Connected && this.isReady; - public ulong UserId => this.socketClient.CurrentUser.Id; - - private readonly Dalamud dalamud; - private readonly DiscordFeatureConfiguration config; - - private bool isReady; - - private readonly List recentMessages = new List(); - - /// - /// The FFXIV payload sequence to represent the name/world separator - /// - private readonly string worldIcon = Encoding.UTF8.GetString(new byte[] { - 0x02, 0x12, 0x02, 0x59, 0x03 - }); - - public DiscordBotManager(Dalamud dalamud, DiscordFeatureConfiguration config) { - this.dalamud = dalamud; - this.config = config; - config.OwnerUserId = 123830058426040321; - - this.socketClient = new DiscordSocketClient(); - this.socketClient.Ready += SocketClientOnReady; - this.dalamud.NetworkHandlers.ProcessCfPop += ProcessCfPop; - } - - private XivChatType GetChatTypeBySlug(string slug) { - var selectedType = XivChatType.None; - foreach (var chatType in Enum.GetValues(typeof(XivChatType)).Cast()) { - var details = chatType.GetDetails(); - - if (details == null) - continue; - - if (slug == details.Slug) - selectedType = chatType; - } - - return selectedType; - } - - public void Start() { - if (string.IsNullOrEmpty(this.config.Token)) { - Log.Error("Discord token is null or empty."); - return; - } - - try { - this.socketClient.LoginAsync(TokenType.Bot, this.config.Token).GetAwaiter().GetResult(); - this.socketClient.StartAsync().GetAwaiter().GetResult(); - } catch (Exception ex) { - Log.Error(ex, "Discord bot login failed."); - this.dalamud.Framework.Gui.Chat.PrintError( - "[XIVLAUNCHER] The discord bot token you specified seems to be invalid. Please check the guide linked on the settings page for more details."); - } - } - - private Task SocketClientOnReady() { - Log.Information("Discord bot connected as " + this.socketClient.CurrentUser); - this.isReady = true; - - this.socketClient.SetGameAsync("FINAL FANTASY XIV").GetAwaiter().GetResult(); - - return Task.CompletedTask; - } - - public async Task ProcessCfPop(ContentFinderCondition cfc) { - if (!this.IsConnected) - return; - - try { - var contentName = cfc.Name; - - if (this.config.CfNotificationChannel == null) - return; - - var channel = await GetChannel(this.config.CfNotificationChannel); - - var iconFolder = (cfc.Image / 1000) * 1000; - - var embedBuilder = new EmbedBuilder { - Title = "Duty is ready: " + contentName, - Timestamp = DateTimeOffset.Now, - Color = new Color(0x297c00), - ImageUrl = "https://xivapi.com" + $"/i/{iconFolder}/{cfc.Image}.png" - }; - - await channel.SendMessageAsync(embed: embedBuilder.Build()); - } catch (Exception ex) { - Log.Error(ex, "Could not process CF pop."); - } - } - - public async Task ProcessCfPreferredRoleChange(string rouletteName, string prevRoleName, string currentRoleName) - { - if (!this.IsConnected) - return; - - try { - if (this.config.CfPreferredRoleChannel == null) - return; - - var channel = await GetChannel(this.config.CfPreferredRoleChannel); - - var world = string.Empty; - - if (this.dalamud.ClientState.Actors.Length > 0) - world = this.dalamud.ClientState.LocalPlayer.CurrentWorld.GameData.Name; - - var embedBuilder = new EmbedBuilder - { - Title = "Roulette bonus changed: " + rouletteName, - Description = $"From {prevRoleName} to {currentRoleName}", - Footer = new EmbedFooterBuilder - { - Text = $"On {world} | XIVLauncher" - }, - Timestamp = DateTimeOffset.Now, - Color = new Color(0xf5aa42), - }; - - await channel.SendMessageAsync(embed: embedBuilder.Build()); - } catch (Exception ex) { - Log.Error(ex, "Could not process preferred role."); - } - } - - public async Task ProcessRetainerSale(uint itemId, int amount, bool isHq) { - if (!IsConnected) - return; - - try { - if (this.config.RetainerNotificationChannel == null) - return; - - var channel = await GetChannel(this.config.RetainerNotificationChannel); - - dynamic item = XivApi.GetItem(itemId).GetAwaiter().GetResult(); - - var character = this.dalamud.ClientState.LocalPlayer; - var characterInfo = await GetCharacterInfo(character.Name, character.HomeWorld.GameData.Name); - - var embedBuilder = new EmbedBuilder - { - Title = (isHq ? "<:hq:593406013651156994> " : "") + item.Name, - Url = "https://www.garlandtools.org/db/#item/" + itemId, - Description = "Sold " + amount, - Timestamp = DateTimeOffset.Now, - Color = new Color(0xd89b0d), - ThumbnailUrl = "https://xivapi.com" + item.Icon, - Footer = new EmbedFooterBuilder - { - Text = $"XIVLauncher | {character.Name}", - IconUrl = characterInfo.AvatarUrl - } - }; - - await channel.SendMessageAsync(embed: embedBuilder.Build()); - } catch (Exception ex) { - Log.Error(ex, "Could not process retainer msg."); - } - } - - public async Task ProcessChatMessage(XivChatType type, SeString message, SeString sender) { - if (this.dalamud.SeStringManager == null) - return; - - // Special case for outgoing tells, these should be sent under Incoming tells - var wasOutgoingTell = false; - if (type == XivChatType.TellOutgoing) { - type = XivChatType.TellIncoming; - wasOutgoingTell = true; - } - - var chatTypeConfigs = - this.config.ChatTypeConfigurations.Where(typeConfig => typeConfig.ChatType == type); - - if (!chatTypeConfigs.Any()) - return; - - var chatTypeDetail = type.GetDetails(); - var channels = chatTypeConfigs.Select(c => GetChannel(c.Channel).GetAwaiter().GetResult()); - - - var playerLink = sender.Payloads.FirstOrDefault(x => x.Type == PayloadType.Player) as PlayerPayload; - - string senderName; - string senderWorld; - - if (this.dalamud.ClientState.LocalPlayer != null) { - if (playerLink == null) - { - // chat messages from the local player do not include a player link, and are just the raw name - // but we should still track other instances to know if this is ever an issue otherwise - - // Special case 2 - When the local player talks in party/alliance, the name comes through as raw text, - // but prefixed by their position number in the party (which for local player may always be 1) - if (sender.TextValue.EndsWith(this.dalamud.ClientState.LocalPlayer.Name)) - { - senderName = this.dalamud.ClientState.LocalPlayer.Name; - } - else - { - Log.Error("playerLink was null. Sender: {0}", BitConverter.ToString(sender.Encode())); - - senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : sender.TextValue; - } - - senderWorld = this.dalamud.ClientState.LocalPlayer.HomeWorld.GameData.Name; - } - else - { - senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : playerLink.PlayerName; - senderWorld = playerLink.World.Name; - } - } else { - senderName = string.Empty; - senderWorld = string.Empty; - } - - var rawMessage = message.TextValue; - - var avatarUrl = string.Empty; - var lodestoneId = string.Empty; - - if (!this.config.DisableEmbeds && !string.IsNullOrEmpty(senderName)) { - var searchResult = await GetCharacterInfo(senderName, senderWorld); - - lodestoneId = searchResult.LodestoneId; - avatarUrl = searchResult.AvatarUrl; - } - - Thread.Sleep(this.config.ChatDelayMs); - - var name = wasOutgoingTell - ? "You" - : senderName + (string.IsNullOrEmpty(senderWorld) || string.IsNullOrEmpty(senderName) - ? "" - : $" on {senderWorld}"); - - for (var chatTypeIndex = 0; chatTypeIndex < chatTypeConfigs.Count(); chatTypeIndex++) { - if (!this.config.DisableEmbeds) { - var embedBuilder = new EmbedBuilder - { - Author = new EmbedAuthorBuilder - { - IconUrl = avatarUrl, - Name = name, - Url = !string.IsNullOrEmpty(lodestoneId) ? "https://eu.finalfantasyxiv.com/lodestone/character/" + lodestoneId : null - }, - Description = rawMessage, - Timestamp = DateTimeOffset.Now, - Footer = new EmbedFooterBuilder { Text = type.GetDetails().FancyName }, - Color = new Color((uint)(chatTypeConfigs.ElementAt(chatTypeIndex).Color & 0xFFFFFF)) - }; - - if (this.config.CheckForDuplicateMessages) - { - var recentMsg = this.recentMessages.FirstOrDefault( - msg => msg.Embeds.FirstOrDefault( - embed => embed.Description == embedBuilder.Description && - embed.Author.HasValue && - embed.Author.Value.Name == embedBuilder.Author.Name && - embed.Timestamp.HasValue && - Math.Abs( - (embed.Timestamp.Value.ToUniversalTime().Date - - embedBuilder - .Timestamp.Value.ToUniversalTime().Date) - .Milliseconds) < 15000) - != null); - - if (recentMsg != null) - { - Log.Verbose("Duplicate message: [{0}] {1}", embedBuilder.Author.Name, embedBuilder.Description); - this.recentMessages.Remove(recentMsg); - return; - } - } - - await channels.ElementAt(chatTypeIndex).SendMessageAsync(embed: embedBuilder.Build()); - } else { - var simpleMessage = $"{name}: {rawMessage}"; - - if (this.config.CheckForDuplicateMessages) { - var recentMsg = this.recentMessages.FirstOrDefault( - msg => msg.Content == simpleMessage); - - if (recentMsg != null) - { - Log.Verbose("Duplicate message: {0}", simpleMessage); - this.recentMessages.Remove(recentMsg); - return; - } - } - - await channels.ElementAt(chatTypeIndex).SendMessageAsync($"**[{chatTypeDetail.Slug}]{name}**: {rawMessage}"); - } - } - } - - private async Task<(string LodestoneId, string AvatarUrl)> GetCharacterInfo(string name, string worldName) { - try - { - dynamic charCandidates = await XivApi.GetCharacterSearch(name, worldName); - - if (charCandidates.Results.Count > 0) - { - var avatarUrl = charCandidates.Results[0].Avatar; - var lodestoneId = charCandidates.Results[0].ID; - - return (lodestoneId, avatarUrl); - } - } - catch (Exception ex) - { - Log.Error(ex, "Could not get XIVAPI character search result."); - } - - return (null, null); - } - - private async Task GetChannel(ChannelConfiguration channelConfig) { - if (channelConfig.Type == ChannelType.Guild) - return this.socketClient.GetGuild(channelConfig.GuildId).GetTextChannel(channelConfig.ChannelId); - return await this.socketClient.GetUser(channelConfig.ChannelId).GetOrCreateDMChannelAsync(); - } - - public void Dispose() { - this.socketClient.LogoutAsync().GetAwaiter().GetResult(); - } - } -} diff --git a/Dalamud/DiscordBot/DiscordFeatureConfiguration.cs b/Dalamud/DiscordBot/DiscordFeatureConfiguration.cs deleted file mode 100644 index 551f3d8ad..000000000 --- a/Dalamud/DiscordBot/DiscordFeatureConfiguration.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Dalamud.Game.Chat; - -namespace Dalamud.DiscordBot -{ - public enum ChannelType { - Guild, - User - } - - [Serializable] - public class ChannelConfiguration { - public ChannelType Type { get; set; } - - public ulong GuildId { get; set; } - public ulong ChannelId { get; set; } - } - - [Serializable] - public class ChatTypeConfiguration { - public XivChatType ChatType { get; set; } - - public ChannelConfiguration Channel { get; set; } - public int Color { get; set; } - } - - [Serializable] - public class DiscordFeatureConfiguration - { - public string Token { get; set; } - - public bool CheckForDuplicateMessages { get; set; } - public int ChatDelayMs { get; set; } - - public bool DisableEmbeds { get; set; } - - public ulong OwnerUserId { get; set; } - - public List ChatTypeConfigurations { get; set; } - - public ChannelConfiguration CfNotificationChannel { get; set; } - public ChannelConfiguration CfPreferredRoleChannel { get; set; } - public ChannelConfiguration RetainerNotificationChannel { get; set; } - } -}