#include "chatmessage.hpp" #include "ircexception.hpp" #include std::unordered_map ChatMessage::parseTags(QString tagStr) { enum { FindingKey, FindingValue, ValueEscaped, } state; QString s = tagStr; if (s.startsWith('@')) s = s.mid(1); std::unordered_map tags; QString key = ""; QString value = ""; state = FindingKey; for(const auto c : std::as_const(s)) { switch (state) { case FindingKey: if (c == '=') state = FindingValue; else if (c == ';') { state = FindingKey; tags[key] = ""; key = ""; } else if (c == ' ') { tags[key] = ""; goto EndParse; } else key += c; break; case FindingValue: if (c == '\\') { state = ValueEscaped; } else if (c == ';') { tags[key] = value; key = value = ""; state = FindingKey; } else if (c == ' ') { tags[key] = value; goto EndParse; } else if (c == '\r' || c == '\n' || c == '\0') throw IrcParseException("Invalid character in tag string", s); else { value += c; } break; case ValueEscaped: if (c == ':') { value += ';'; state = FindingValue; } else if (c == 's') { value += ' '; state = FindingValue; } else if (c == '\\') { value += '\\'; state = FindingValue; } else if (c == 'r') { value += '\r'; state = FindingValue; } else if (c == 'n') { value += '\n'; state = FindingValue; } else if (c == ';') { tags[key] = value; key = value = ""; state = FindingKey; } //spaces should already be stripped, but handle this as end of tags just in case else if (c == ' ') { tags[key] = value; key = value = ""; goto EndParse; } else if (c == '\r' || c == '\n' || c == '\0') throw IrcParseException("Invalid character in tag string", s); else { value += c; state = FindingValue; } break; default: throw new QException(); } } //this is reached after processing the last character without hitting a space tags[key] = value; EndParse: return tags; } ChatMessage* ChatMessage::Parse(QString message) { QString rawMessage = message; std::unordered_map tags; QString sender = ""; QList parameters; QString prefix = ""; QString s = message; if (s.startsWith('@')) { s = s.mid(1); //first ' ' acts as the delimeter auto split = s.split(' '); auto tagString = split.takeFirst(); s = split.join(' '); tags = parseTags(tagString); } //message has source if (s.startsWith(':')) { s = s.mid(1); auto split = s.split(' '); prefix = split.takeFirst(); s = split.join(' '); while (s[0] == ' ') s = s.mid(1); } auto spl_command = s.split(' '); IrcMessageType type = IrcMessageType::parse(spl_command.takeFirst()); //message has parameters if (!spl_command.isEmpty()) { s = spl_command.join(' '); //message has single parameter marked as the final parameter //this needs to be handled specially because the leading ' ' //is stripped if (s.startsWith(':')) { parameters.append(s.mid(1)); } else { auto spl_final = s.split(" :"); auto spl_initial = spl_final.takeFirst().split(' ', Qt::SkipEmptyParts); for (auto& part : spl_initial) { part = part.trimmed(); } parameters.append(spl_initial); if (!spl_final.isEmpty()) parameters.append(spl_final.join(" :")); } } return new ChatMessage(rawMessage, tags, sender, parameters, prefix, type); } ChatMessage::ChatMessage(QString rawMessage, std::unordered_map tags, QString sender, QList parameters, QString prefix, IrcMessageType type) : _rawMessage(rawMessage), _tags(tags), _sender(sender), _parameters(parameters), _prefix(prefix), _type(type) { } QString ChatMessage::rawMessage() const { return _rawMessage; } const std::unordered_map* ChatMessage::tags() const { return &_tags; } QString ChatMessage::sender() const { return _sender; } const QList* ChatMessage::parameters() const { return &_parameters; } QString ChatMessage::prefix() const { return _prefix; } IrcMessageType ChatMessage::type() const { return _type; } PrivmsgView::PrivmsgView(const ChatMessage* msg) : parent(msg) { if (parent->type() != Expected) throw IrcIncorrectTypeException(Expected, parent->type()); } QString PrivmsgView::message() const { return parent->parameters()->last(); } QString PrivmsgView::user() const { static constexpr const char* KEY = "display-name"; if (parent->tags()->contains(KEY)) return parent->tags()->at(KEY); throw IrcTagNotFoundException(KEY); } PingView::PingView(const ChatMessage* msg) : parent(msg) { if (parent->type() != Expected) throw IrcIncorrectTypeException(Expected, parent->type()); } QString PingView::toPong() const { return parent->rawMessage().replace("PING", "PONG"); }