Files
qTier/chatmessage.cpp
2024-05-27 13:53:31 -05:00

254 lines
6.2 KiB
C++

#include "chatmessage.hpp"
#include "ircexception.hpp"
#include <QException>
std::unordered_map<QString, QString> ChatMessage::parseTags(QString tagStr)
{
enum
{
FindingKey,
FindingValue,
ValueEscaped,
} state;
QString s = tagStr;
if (s.startsWith('@'))
s = s.mid(1);
std::unordered_map<QString, QString> 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<QString, QString> tags;
QString sender = "";
QList<QString> 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<QString, QString> tags,
QString sender, QList<QString> 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<QString, QString>* ChatMessage::tags() const
{
return &_tags;
}
QString ChatMessage::sender() const
{
return _sender;
}
const QList<QString>* 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");
}