254 lines
6.2 KiB
C++
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");
|
|
}
|