[Saylinks] Refactor saylink injection (#2315)

If a message was longer than 50 characters with "00000" somewhere in the
message (such as messages with hex numbers) then the saylink injection
method was sending a blank message to the chat window.

This refactors the saylink injection method using a crude state machine
to build the output. It should function the same:

  - Inner-most brackets generate saylinks when nested
  - Saylinks are not generated in brackets that already have a saylink
  - Existing saylinks are preserved
  - Existing saylinks that contain text with brackets do not attempt to
    generate saylinks
  - Saylinks are not generated if brackets contains leading or trailing
    spaces (e.g. [ spaces ])
This commit is contained in:
hg 2022-07-27 09:50:00 -04:00 committed by GitHub
parent f07e3f4d3b
commit 1089f8139b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -322,145 +322,54 @@ std::string EQ::SayLinkEngine::GenerateQuestSaylink(std::string saylink_text, bo
std::string EQ::SayLinkEngine::InjectSaylinksIfNotExist(const char *message)
{
std::string new_message = message;
int link_index = 0;
int saylink_index = 0;
std::vector<std::string> links = {};
std::vector<std::string> saylinks = {};
int saylink_length = 50;
std::string saylink_separator = "\u0012";
std::string saylink_partial = "00000";
LogSaylinkDetail("message [{}]", message);
LogSaylinkDetail("new_message pre pass 1 [{}]", new_message);
std::string new_message;
new_message.reserve(strlen(message));
// first pass - strip existing saylinks by putting placeholder anchors on them
for (auto &saylink: Strings::Split(new_message, saylink_separator)) {
if (!saylink.empty() && saylink.length() > saylink_length &&
saylink.find(saylink_partial) != std::string::npos) {
saylinks.emplace_back(saylink);
bool in_bracket_state = false;
bool in_link_state = false;
LogSaylinkDetail("Found saylink [{}]", saylink);
const char* ch = message;
const char* startpos = message;
for (; *ch != '\0'; ++ch)
{
// saylinks not added if spaces touch brackets
bool abort_space = in_bracket_state && *ch == ' ' && (*(ch-1) == '[' || *(ch+1) == ']');
// replace with anchor
Strings::FindReplace(
new_message,
fmt::format("{}", saylink),
fmt::format("<saylink:{}>", saylink_index)
);
saylink_index++;
if (in_bracket_state && (*ch == '[' || *ch == '\x12' || abort_space))
{
// abort due to nested bracket (which starts another) or existing saylink
new_message.append(startpos, ch - startpos);
in_bracket_state = false;
}
}
LogSaylinkDetail("new_message post pass 1 [{}]", new_message);
LogSaylinkDetail("saylink separator count [{}]", std::count(new_message.begin(), new_message.end(), '\u0012'));
// loop through brackets until none exist
if (new_message.find('[') != std::string::npos) {
for (auto &b: Strings::Split(new_message, "[")) {
if (!b.empty() && b.find(']') != std::string::npos) {
std::vector<std::string> right_split = Strings::Split(b, "]");
if (!right_split.empty()) {
std::string bracket_message = Strings::Trim(right_split[0]);
// we shouldn't see a saylink fragment here, ignore this bracket
if (bracket_message.find(saylink_partial) != std::string::npos) {
continue;
}
// skip where multiple saylinks are within brackets
if (bracket_message.find(saylink_separator) != std::string::npos &&
std::count(bracket_message.begin(), bracket_message.end(), '\u0012') > 1) {
continue;
}
// if non empty bracket contents
if (!bracket_message.empty()) {
LogSaylinkDetail("Found bracket_message [{}]", bracket_message);
// already a saylink
// todo: improve this later
if (!bracket_message.empty() &&
(bracket_message.length() > saylink_length ||
bracket_message.find(saylink_separator) != std::string::npos)) {
links.emplace_back(bracket_message);
}
else {
links.emplace_back(
EQ::SayLinkEngine::GenerateQuestSaylink(
bracket_message,
false,
bracket_message
)
);
}
// replace with anchor
Strings::FindReplace(
new_message,
fmt::format("[{}]", bracket_message),
fmt::format("<prelink:{}>", link_index)
);
link_index++;
}
}
else if (in_bracket_state && *ch == ']')
{
if (ch != startpos)
{
std::string str(startpos, ch - startpos);
new_message += EQ::SayLinkEngine::GenerateQuestSaylink(str, false, str);
}
in_bracket_state = false;
}
if (!in_bracket_state)
{
new_message.push_back(*ch);
}
if (*ch == '[' && !in_link_state)
{
startpos = ch + 1;
in_bracket_state = true;
}
else if (*ch == '\x12')
{
in_link_state = !in_link_state;
}
}
LogSaylinkDetail("new_message post pass 2 (post brackets) [{}]", new_message);
// strip any current delimiters of saylinks
Strings::FindReplace(new_message, saylink_separator, "");
// pop links onto anchors
link_index = 0;
for (auto &link: links) {
// strip any current delimiters of saylinks
Strings::FindReplace(link, saylink_separator, "");
Strings::FindReplace(
new_message,
fmt::format("<prelink:{}>", link_index),
fmt::format("[\u0012{}\u0012]", link)
);
link_index++;
}
LogSaylinkDetail("new_message post pass 3 (post prelink anchor pop) [{}]", new_message);
// pop links onto anchors
saylink_index = 0;
for (auto &link: saylinks) {
// strip any current delimiters of saylinks
Strings::FindReplace(link, saylink_separator, "");
// check to see if we did a double anchor pass (existing saylink that was also inside brackets)
// this means we found a saylink and we're checking to see if we're already encoded before double encoding
if (new_message.find(fmt::format("\u0012<saylink:{}>\u0012", saylink_index)) != std::string::npos) {
LogSaylinkDetail("Found encoded saylink at index [{}]", saylink_index);
Strings::FindReplace(
new_message,
fmt::format("\u0012<saylink:{}>\u0012", saylink_index),
fmt::format("\u0012{}\u0012", link)
);
saylink_index++;
continue;
}
Strings::FindReplace(
new_message,
fmt::format("<saylink:{}>", saylink_index),
fmt::format("\u0012{}\u0012", link)
);
saylink_index++;
}
LogSaylinkDetail("new_message post pass 4 (post saylink anchor pop) [{}]", new_message);
LogSaylinkDetail("new_message [{}]", new_message);
return new_message;
}