mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
refactor: remove Discord bot
This commit is contained in:
parent
5ae756577c
commit
969e935f27
3 changed files with 0 additions and 407 deletions
|
|
@ -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<string> BadWords { get; set; }
|
||||
|
|
|
|||
|
|
@ -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<SocketMessage> recentMessages = new List<SocketMessage>();
|
||||
|
||||
/// <summary>
|
||||
/// The FFXIV payload sequence to represent the name/world separator
|
||||
/// </summary>
|
||||
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<XivChatType>()) {
|
||||
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<IMessageChannel> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ChatTypeConfiguration> ChatTypeConfigurations { get; set; }
|
||||
|
||||
public ChannelConfiguration CfNotificationChannel { get; set; }
|
||||
public ChannelConfiguration CfPreferredRoleChannel { get; set; }
|
||||
public ChannelConfiguration RetainerNotificationChannel { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue