feat: improve chat string parsing - thanks meli!

This commit is contained in:
goat 2020-02-07 02:31:26 +09:00
parent 02a9c64a78
commit 0c4febb680
10 changed files with 690 additions and 191 deletions

View file

@ -1,11 +1,15 @@
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 Newtonsoft.Json.Linq;
@ -157,12 +161,11 @@ namespace Dalamud.DiscordBot {
await channel.SendMessageAsync(embed: embedBuilder.Build());
}
public async Task ProcessChatMessage(XivChatType type, string message, string sender) {
public async Task ProcessChatMessage(XivChatType type, StdString message, StdString sender) {
// Special case for outgoing tells, these should be sent under Incoming tells
var wasOutgoingTell = false;
if (type == XivChatType.TellOutgoing) {
type = XivChatType.TellIncoming;
sender = this.dalamud.ClientState.LocalPlayer.Name;
wasOutgoingTell = true;
}
@ -173,32 +176,34 @@ namespace Dalamud.DiscordBot {
return;
var chatTypeDetail = type.GetDetails();
var channels = chatTypeConfigs.Select(c => GetChannel(c.Channel).GetAwaiter().GetResult());
var senderSplit = sender.Split(new[] {this.worldIcon}, StringSplitOptions.None);
var parsedSender = SeString.Parse(sender.RawData);
var playerLink = parsedSender.Payloads.FirstOrDefault(x => x.Type == PayloadType.Player) as PlayerPayload;
var world = string.Empty;
var senderName = string.Empty;
var senderWorld = string.Empty;
if (this.dalamud.ClientState.Actors.Length > 0)
world = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Name;
if (playerLink == null) {
Log.Error("playerLink was null. Sender: {0}", BitConverter.ToString(sender.RawData));
if (senderSplit.Length == 2) {
world = senderSplit[1];
sender = senderSplit[0];
senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : parsedSender.TextValue;
senderWorld = this.dalamud.ClientState.LocalPlayer.HomeWorld.Name;
} else {
playerLink.Resolve();
senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : playerLink.PlayerName;
senderWorld = playerLink.ServerName;
}
sender = SeString.Parse(sender).Output;
message = SeString.Parse(message).Output;
var rawMessage = SeString.Parse(message.RawData).TextValue;
sender = RemoveAllNonLanguageCharacters(sender);
var avatarUrl = "";
var lodestoneId = "";
var avatarUrl = string.Empty;
var lodestoneId = string.Empty;
if (!this.config.DisableEmbeds) {
var searchResult = await GetCharacterInfo(sender, world);
var searchResult = await GetCharacterInfo(senderName, senderWorld);
lodestoneId = searchResult.LodestoneId;
avatarUrl = searchResult.AvatarUrl;
@ -208,9 +213,9 @@ namespace Dalamud.DiscordBot {
var name = wasOutgoingTell
? "You"
: sender + (string.IsNullOrEmpty(world) || string.IsNullOrEmpty(sender)
: senderName + (string.IsNullOrEmpty(senderWorld) || string.IsNullOrEmpty(senderName)
? ""
: $" on {world}");
: $" on {senderWorld}");
for (var chatTypeIndex = 0; chatTypeIndex < chatTypeConfigs.Count(); chatTypeIndex++) {
if (!this.config.DisableEmbeds) {
@ -222,7 +227,7 @@ namespace Dalamud.DiscordBot {
Name = name,
Url = !string.IsNullOrEmpty(lodestoneId) ? "https://eu.finalfantasyxiv.com/lodestone/character/" + lodestoneId : null
},
Description = message,
Description = rawMessage,
Timestamp = DateTimeOffset.Now,
Footer = new EmbedFooterBuilder { Text = type.GetDetails().FancyName },
Color = new Color((uint)(chatTypeConfigs.ElementAt(chatTypeIndex).Color & 0xFFFFFF))
@ -253,7 +258,7 @@ namespace Dalamud.DiscordBot {
await channels.ElementAt(chatTypeIndex).SendMessageAsync(embed: embedBuilder.Build());
} else {
var simpleMessage = $"{name}: {message}";
var simpleMessage = $"{name}: {rawMessage}";
if (this.config.CheckForDuplicateMessages) {
var recentMsg = this.recentMessages.FirstOrDefault(
@ -267,7 +272,7 @@ namespace Dalamud.DiscordBot {
}
}
await channels.ElementAt(chatTypeIndex).SendMessageAsync($"**[{chatTypeDetail.Slug}]{name}**: {message}");
await channels.ElementAt(chatTypeIndex).SendMessageAsync($"**[{chatTypeDetail.Slug}]{name}**: {rawMessage}");
}
}
}
@ -299,10 +304,6 @@ namespace Dalamud.DiscordBot {
return await this.socketClient.GetUser(channelConfig.ChannelId).GetOrCreateDMChannelAsync();
}
private string RemoveAllNonLanguageCharacters(string input) {
return Regex.Replace(input, @"[^\p{L} ']", "");
}
public void Dispose() {
this.socketClient.LogoutAsync().GetAwaiter().GetResult();
}

View file

@ -1,157 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Dalamud.Game.Chat {
// TODO: This class does not work - it's a hack, needs a revamp and better handling for payloads used in player chat
public class SeString {
public enum PlayerLinkType {
ItemLink = 0x03
}
public enum SeStringPayloadType {
PlayerLink = 0x27
}
// in all likelihood these are flags of some kind, but these are the only 2 values I've noticed
public enum ItemQuality {
NormalQuality = 0xF2,
HighQuality = 0xF6
}
private const int START_BYTE = 0x02;
private const int END_BYTE = 0x03;
public static (string Output, List<SeStringPayloadContainer> Payloads) Parse(byte[] bytes)
{
var output = new List<byte>();
var payloads = new List<SeStringPayloadContainer>();
using (var stream = new MemoryStream(bytes))
using (var reader = new BinaryReader(stream))
{
while (stream.Position < bytes.Length)
{
var b = stream.ReadByte();
if (b == START_BYTE)
ProcessPacket(reader, output, payloads);
else
output.Add((byte)b);
}
}
return (Encoding.UTF8.GetString(output.ToArray()), payloads);
}
public static (string Output, List<SeStringPayloadContainer> Payloads) Parse(string input) {
var bytes = Encoding.UTF8.GetBytes(input);
return Parse(bytes);
}
private static void ProcessPacket(BinaryReader reader, List<byte> output,
List<SeStringPayloadContainer> payloads) {
var type = reader.ReadByte();
var payloadSize = GetInteger(reader);
var payload = new byte[payloadSize];
reader.Read(payload, 0, payloadSize);
var orphanByte = reader.Read();
// If the end of the tag isn't what we predicted, let's ignore it for now
while (orphanByte != END_BYTE) orphanByte = reader.Read();
//output.AddRange(Encoding.UTF8.GetBytes($"<{type.ToString("X")}:{BitConverter.ToString(payload)}>"));
switch ((SeStringPayloadType) type) {
case SeStringPayloadType.PlayerLink:
if (payload[0] == (byte)PlayerLinkType.ItemLink)
{
int itemId;
bool isHQ = payload[1] == (byte)ItemQuality.HighQuality;
if (isHQ)
{
// hq items have an extra 0x0F byte before the ID, and the ID is 0x4240 above the actual item ID
// This _seems_ consistent but I really don't know
itemId = (payload[3] << 8 | payload[4]) - 0x4240;
}
else
{
itemId = (payload[2] << 8 | payload[3]);
}
payloads.Add(new SeStringPayloadContainer
{
Type = SeStringPayloadType.PlayerLink,
Param1 = (itemId, isHQ)
});
}
break;
}
}
public class SeStringPayloadContainer {
public SeStringPayloadType Type { get; set; }
public object Param1 { get; set; }
}
#region Shared
public enum IntegerType {
Byte = 0xF0,
ByteTimes256 = 0xF1,
Int16 = 0xF2,
Int24 = 0xFA,
Int32 = 0xFE
}
protected static int GetInteger(BinaryReader input) {
var t = input.ReadByte();
var type = (IntegerType) t;
return GetInteger(input, type);
}
protected static int GetInteger(BinaryReader input, IntegerType type) {
const byte ByteLengthCutoff = 0xF0;
var t = (byte) type;
if (t < ByteLengthCutoff)
return t - 1;
switch (type) {
case IntegerType.Byte:
return input.ReadByte();
case IntegerType.ByteTimes256:
return input.ReadByte() * 256;
case IntegerType.Int16: {
var v = 0;
v |= input.ReadByte() << 8;
v |= input.ReadByte();
return v;
}
case IntegerType.Int24: {
var v = 0;
v |= input.ReadByte() << 16;
v |= input.ReadByte() << 8;
v |= input.ReadByte();
return v;
}
case IntegerType.Int32: {
var v = 0;
v |= input.ReadByte() << 24;
v |= input.ReadByte() << 16;
v |= input.ReadByte() << 8;
v |= input.ReadByte();
return v;
}
default:
throw new NotSupportedException();
}
}
#endregion
}
}

View file

@ -0,0 +1,221 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Serilog;
namespace Dalamud.Game.Chat.SeStringHandling
{
/// <summary>
/// This class represents a parsed SeString payload.
/// </summary>
public abstract class Payload
{
public abstract PayloadType Type { get; }
public abstract void Resolve();
public abstract byte[] Encode();
protected abstract void ProcessChunkImpl(BinaryReader reader, long endOfStream);
public static Payload Process(BinaryReader reader)
{
if ((byte)reader.PeekChar() != START_BYTE)
{
return ProcessText(reader);
}
else
{
return ProcessChunk(reader);
}
}
private static Payload ProcessChunk(BinaryReader reader)
{
Payload payload = null;
reader.ReadByte(); // START_BYTE
var chunkType = (SeStringChunkType)reader.ReadByte();
var chunkLen = GetInteger(reader);
var packetStart = reader.BaseStream.Position;
switch (chunkType)
{
case SeStringChunkType.Interactable:
{
var subType = (EmbeddedInfoType)reader.ReadByte();
switch (subType)
{
case EmbeddedInfoType.PlayerName:
payload = new PlayerPayload();
break;
case EmbeddedInfoType.ItemLink:
payload = new ItemPayload();
break;
case EmbeddedInfoType.Status:
payload = new StatusPayload();
break;
case EmbeddedInfoType.LinkTerminator:
// Does not need to be handled
break;
default:
Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType);
break;
}
}
break;
default:
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
break;
}
payload?.ProcessChunkImpl(reader, reader.BaseStream.Position + chunkLen - 1);
// read through the rest of the packet
var readBytes = (int)(reader.BaseStream.Position - packetStart);
reader.ReadBytes(chunkLen - readBytes + 1); // +1 for the END_BYTE marker
return payload;
}
private static Payload ProcessText(BinaryReader reader)
{
var payload = new TextPayload();
payload.ProcessChunkImpl(reader, reader.BaseStream.Length);
return payload;
}
#region parse constants and helpers
protected const byte START_BYTE = 0x02;
protected const byte END_BYTE = 0x03;
protected enum SeStringChunkType
{
Interactable = 0x27
}
protected enum EmbeddedInfoType
{
PlayerName = 0x01,
ItemLink = 0x03,
Status = 0x09,
LinkTerminator = 0xCF // not clear but seems to always follow a link
}
protected enum IntegerType
{
Byte = 0xF0,
ByteTimes256 = 0xF1,
Int16 = 0xF2,
Int16Plus1Million = 0xF6,
Int24 = 0xFA,
Int32 = 0xFE
}
// made protected, unless we actually want to use it externally
// in which case it should probably go live somewhere else
protected static int GetInteger(BinaryReader input)
{
var t = input.ReadByte();
var type = (IntegerType)t;
return GetInteger(input, type);
}
private static int GetInteger(BinaryReader input, IntegerType type)
{
const byte ByteLengthCutoff = 0xF0;
var t = (byte)type;
if (t < ByteLengthCutoff)
return t - 1;
switch (type)
{
case IntegerType.Byte:
return input.ReadByte();
case IntegerType.ByteTimes256:
return input.ReadByte() * 256;
case IntegerType.Int16:
{
var v = 0;
v |= input.ReadByte() << 8;
v |= input.ReadByte();
return v;
}
case IntegerType.Int16Plus1Million:
{
var v = 0;
v |= input.ReadByte() << 16;
v |= input.ReadByte() << 8;
v |= input.ReadByte();
// need the actual value since it's used as a flag
// v -= 1000000;
return v;
}
case IntegerType.Int24:
{
var v = 0;
v |= input.ReadByte() << 16;
v |= input.ReadByte() << 8;
v |= input.ReadByte();
return v;
}
case IntegerType.Int32:
{
var v = 0;
v |= input.ReadByte() << 24;
v |= input.ReadByte() << 16;
v |= input.ReadByte() << 8;
v |= input.ReadByte();
return v;
}
default:
throw new NotSupportedException();
}
}
protected static byte[] MakeInteger(int value)
{
// clearly the epitome of efficiency
var bytesPadded = BitConverter.GetBytes(value);
Array.Reverse(bytesPadded);
return bytesPadded.SkipWhile(b => b == 0x00).ToArray();
}
protected static IntegerType GetTypeForIntegerBytes(byte[] bytes)
{
// not the most scientific, exists mainly for laziness
if (bytes.Length == 1)
{
return IntegerType.Byte;
}
else if (bytes.Length == 2)
{
return IntegerType.Int16;
}
else if (bytes.Length == 3)
{
return IntegerType.Int24;
}
else if (bytes.Length == 4)
{
return IntegerType.Int32;
}
throw new NotSupportedException();
}
#endregion
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling
{
/// <summary>
/// All parsed types of SeString payloads.
/// </summary>
public enum PayloadType
{
/// <summary>
/// An SeString payload representing a player link.
/// </summary>
Player,
/// <summary>
/// An SeString payload representing an Item link.
/// </summary>
Item,
/// <summary>
/// An SeString payload representing an Status Effect link.
/// </summary>
Status,
/// <summary>
/// An SeString payload representing raw, typed text.
/// </summary>
RawText
}
}

View file

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public class ItemPayload : Payload
{
public override PayloadType Type => PayloadType.Item;
public int ItemId { get; private set; }
public string ItemName { get; private set; } = string.Empty;
public bool IsHQ { get; private set; } = false;
public ItemPayload() { }
public ItemPayload(int itemId, bool isHQ)
{
ItemId = itemId;
IsHQ = isHQ;
}
public override void Resolve()
{
if (string.IsNullOrEmpty(ItemName))
{
dynamic item = XivApi.GetItem(ItemId).GetAwaiter().GetResult();
ItemName = item.Name;
}
}
public override byte[] Encode()
{
var actualItemId = IsHQ ? ItemId + 1000000 : ItemId;
var idBytes = MakeInteger(actualItemId);
var itemIdFlag = IsHQ ? IntegerType.Int16Plus1Million : IntegerType.Int16;
var chunkLen = idBytes.Length + 5;
var bytes = new List<byte>()
{
START_BYTE,
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink,
(byte)itemIdFlag
};
bytes.AddRange(idBytes);
// unk
bytes.AddRange(new byte[] { 0x02, 0x01, END_BYTE });
return bytes.ToArray();
}
public override string ToString()
{
return $"{Type} - ItemId: {ItemId}, ItemName: {ItemName}, IsHQ: {IsHQ}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
ItemId = GetInteger(reader);
if (ItemId > 1000000)
{
ItemId -= 1000000;
IsHQ = true;
}
if (reader.BaseStream.Position + 3 < endOfStream)
{
// unk
reader.ReadBytes(3);
var itemNameLen = GetInteger(reader);
ItemName = Encoding.UTF8.GetString(reader.ReadBytes(itemNameLen));
}
}
}
}

View file

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public class PlayerPayload : Payload
{
public override PayloadType Type => PayloadType.Player;
public string PlayerName { get; private set; }
public int ServerId { get; private set; }
public string ServerName { get; private set; } = String.Empty;
public PlayerPayload() { }
public PlayerPayload(string playerName, int serverId)
{
PlayerName = playerName;
ServerId = serverId;
}
public override void Resolve()
{
if (string.IsNullOrEmpty(ServerName))
{
dynamic server = XivApi.Get($"World/{ServerId}").GetAwaiter().GetResult();
ServerName = server.Name;
}
}
public override byte[] Encode()
{
var chunkLen = PlayerName.Length + 7;
var bytes = new List<byte>()
{
START_BYTE,
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName,
/* unk */ 0x01,
(byte)(ServerId+1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually
/* unk */0x01, /* unk */0xFF, // these sometimes vary but are frequently this
(byte)(PlayerName.Length+1)
};
bytes.AddRange(Encoding.UTF8.GetBytes(PlayerName));
bytes.Add(END_BYTE);
// encoded names are followed by the name in plain text again
// use the payload parsing for consistency, as this is technically a new chunk
bytes.AddRange(new TextPayload(PlayerName).Encode());
// unsure about this entire packet, but it seems to always follow a name
bytes.AddRange(new byte[]
{
START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator,
0x01, 0x01, 0x01, 0xFF, 0x01,
END_BYTE
});
return bytes.ToArray();
}
public override string ToString()
{
return $"{Type} - PlayerName: {PlayerName}, ServerId: {ServerId}, ServerName: {ServerName}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
// unk
reader.ReadByte();
ServerId = GetInteger(reader);
// unk
reader.ReadBytes(2);
var nameLen = GetInteger(reader);
PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen));
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public class StatusPayload : Payload
{
public override PayloadType Type => PayloadType.Status;
public int StatusId { get; private set; }
public string StatusName { get; private set; } = string.Empty;
public StatusPayload() { }
public StatusPayload(int statusId)
{
StatusId = statusId;
}
public override void Resolve()
{
if (string.IsNullOrEmpty(StatusName))
{
dynamic status = XivApi.Get($"Status/{StatusId}").GetAwaiter().GetResult();
//Console.WriteLine($"Resolved status {StatusId} to {status.Name}");
StatusName = status.Name;
}
}
public override byte[] Encode()
{
var idBytes = MakeInteger(StatusId);
var idPrefix = GetTypeForIntegerBytes(idBytes);
var chunkLen = idBytes.Length + 8;
var bytes = new List<byte>()
{
START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status,
(byte)idPrefix
};
bytes.AddRange(idBytes);
// unk
bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE });
return bytes.ToArray();
}
public override string ToString()
{
return $"{Type} - StatusId: {StatusId}, StatusName: {StatusName}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
StatusId = GetInteger(reader);
}
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
public class TextPayload : Payload
{
public override PayloadType Type => PayloadType.RawText;
public string Text { get; private set; }
public TextPayload() { }
public TextPayload(string text)
{
Text = text;
}
public override void Resolve()
{
// nothing to do
}
public override byte[] Encode()
{
return Encoding.UTF8.GetBytes(Text);
}
public override string ToString()
{
return $"{Type} - Text: {Text}";
}
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
{
var text = new List<byte>();
while (reader.BaseStream.Position < endOfStream)
{
if ((byte)reader.PeekChar() == START_BYTE)
break;
// not the most efficient, but the easiest
text.Add(reader.ReadByte());
}
if (text.Count > 0)
{
// TODO: handling of the game's assorted special unicode characters
Text = Encoding.UTF8.GetString(text.ToArray());
}
}
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
namespace Dalamud.Game.Chat.SeStringHandling
{
/// <summary>
/// This class represents a parsed SeString.
/// </summary>
public class SeString
{
private Dictionary<PayloadType, List<Payload>> mappedPayloads_ = null;
public List<Payload> Payloads { get; }
public Dictionary<PayloadType, List<Payload>> MappedPayloads
{
get
{
if (mappedPayloads_ == null)
{
mappedPayloads_ = new Dictionary<PayloadType, List<Payload>>();
foreach (var p in Payloads)
{
if (!mappedPayloads_.ContainsKey(p.Type))
{
mappedPayloads_[p.Type] = new List<Payload>();
}
mappedPayloads_[p.Type].Add(p);
}
}
return mappedPayloads_;
}
}
public SeString(List<Payload> payloads)
{
Payloads = payloads;
}
/// <summary>
/// Helper function to get all raw text from a message as a single joined string
/// </summary>
/// <returns>
/// All the raw text from the contained payloads, joined into a single string
/// </returns>
public string TextValue
{
get {
var sb = new StringBuilder();
foreach (var p in Payloads)
{
if (p.Type == PayloadType.RawText)
{
sb.Append(((TextPayload)p).Text);
}
}
return sb.ToString();
}
}
/// <summary>
/// Parse an array of bytes to a SeString.
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static SeString Parse(byte[] bytes)
{
var payloads = new List<Payload>();
using (var stream = new MemoryStream(bytes)) {
using var reader = new BinaryReader(stream);
while (stream.Position < bytes.Length)
{
var payload = Payload.Process(reader);
if (payload != null)
payloads.Add(payload);
}
}
return new SeString(payloads);
}
/// <summary>
/// Encode a parsed/created SeString to an array of bytes, to be used for injection.
/// </summary>
/// <param name="payloads"></param>
/// <returns>The bytes of the message.</returns>
public static byte[] Encode(List<Payload> payloads)
{
var messageBytes = new List<byte>();
foreach (var p in payloads)
{
messageBytes.AddRange(p.Encode());
}
return messageBytes.ToArray();
}
}
}

View file

@ -7,6 +7,8 @@ using System.Text;
using System.Text.RegularExpressions;
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 Serilog;
@ -149,10 +151,16 @@ namespace Dalamud.Game {
var itemInfo = matchInfo.Groups["item"];
if (!itemInfo.Success)
continue;
//var itemName = SeString.Parse(itemInfo.Value).Output;
var (itemId, isHQ) = (ValueTuple<int, bool>)(SeString.Parse(message.RawData).Payloads[0].Param1);
Log.Debug($"Probable retainer sale: {message}, decoded item {itemId}, HQ {isHQ}");
var itemLink =
SeString.Parse(message.RawData).Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload;
if (itemLink == null) {
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.RawData));
break;
}
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.ItemId}, HQ {itemLink.IsHQ}");
int itemValue = 0;
var valueInfo = matchInfo.Groups["value"];
@ -160,16 +168,16 @@ namespace Dalamud.Game {
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out itemValue))
continue;
Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemId, itemValue, isHQ));
Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.ItemId, itemValue, itemLink.IsHQ));
break;
}
}
var messageCopy = message;
var senderCopy = sender;
this.dalamud.BotManager.ProcessChatMessage(type, messageCopy, senderCopy);
Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, messageVal, senderVal).GetAwaiter()
.GetResult());
// Handle all of this with SeString some day
if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout ||
type == XivChatType.Alliance || type == XivChatType.TellOutgoing || type == XivChatType.Yell) && !message.Value.Contains((char)0x02)) {
var italicsStart = message.Value.IndexOf("*");