starting work on websocket chat reader
This commit is contained in:
@@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Network)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS WebSockets)
|
||||
|
||||
set(PROJECT_SOURCES
|
||||
main.cpp
|
||||
@@ -37,6 +38,11 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
settings.hpp settings.cpp
|
||||
invalididexception.hpp invalididexception.cpp
|
||||
tierplaceholder.hpp tierplaceholder.cpp
|
||||
chatreader.hpp chatreader.cpp
|
||||
chatmessage.hpp chatmessage.cpp
|
||||
messagetype.hpp messagetype.cpp
|
||||
ircmessagetype.hpp ircmessagetype.cpp
|
||||
ircexception.hpp ircexception.cpp
|
||||
|
||||
)
|
||||
# Define target properties for Android with Qt 6 as:
|
||||
@@ -59,6 +65,7 @@ endif()
|
||||
|
||||
target_link_libraries(qt-app PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
||||
target_link_libraries(qt-app PRIVATE Qt${QT_VERISON_MAJOR}::Network)
|
||||
target_link_libraries(qt-app PRIVATE Qt${QT_VERISON_MAJOR}::WebSockets)
|
||||
|
||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||
# If you are developing for iOS or macOS you should consider setting an
|
||||
|
||||
253
chatmessage.cpp
Normal file
253
chatmessage.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#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");
|
||||
}
|
||||
67
chatmessage.hpp
Normal file
67
chatmessage.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef CHATMESSAGE_HPP
|
||||
#define CHATMESSAGE_HPP
|
||||
|
||||
// #include "messagetype.hpp"
|
||||
#include "ircmessagetype.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
class ChatMessage
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
static ChatMessage* Parse(QString message);
|
||||
QString rawMessage() const;
|
||||
const std::unordered_map<QString, QString>* tags() const;
|
||||
QString sender() const;
|
||||
const QList<QString>* parameters() const;
|
||||
QString prefix() const;
|
||||
IrcMessageType type() const;
|
||||
// explicit ChatMessage(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
explicit ChatMessage(QString message);
|
||||
|
||||
signals:
|
||||
|
||||
private:
|
||||
ChatMessage(QString rawMessage, std::unordered_map<QString, QString> tags,
|
||||
QString sender, QList<QString> parameters, QString prefix,
|
||||
IrcMessageType type);
|
||||
const QString _rawMessage;
|
||||
const std::unordered_map<QString, QString> _tags;
|
||||
const QString _sender;
|
||||
const QList<QString> _parameters;
|
||||
const QString _prefix;
|
||||
const IrcMessageType _type;
|
||||
static std::unordered_map<QString, QString> parseTags(QString tagStr);
|
||||
|
||||
};
|
||||
|
||||
class PrivmsgView
|
||||
{
|
||||
public:
|
||||
PrivmsgView(const ChatMessage* msg);
|
||||
QString message() const;
|
||||
QString user() const;
|
||||
static constexpr IrcMessageType Expected = IrcMessageType::PRIVMSG;
|
||||
|
||||
private:
|
||||
const ChatMessage* const parent;
|
||||
};
|
||||
|
||||
class PingView
|
||||
{
|
||||
public:
|
||||
PingView(const ChatMessage* msg);
|
||||
QString toPong() const;
|
||||
static constexpr IrcMessageType Expected = IrcMessageType::PING;
|
||||
|
||||
private:
|
||||
const ChatMessage* const parent;
|
||||
};
|
||||
|
||||
#endif // CHATMESSAGE_HPP
|
||||
53
chatreader.cpp
Normal file
53
chatreader.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "chatreader.hpp"
|
||||
|
||||
ChatReader::ChatReader()
|
||||
: QObject(nullptr), socket()
|
||||
{
|
||||
QObject::connect(&socket, SIGNAL(socketConnect),
|
||||
this, SLOT(socketConnect));
|
||||
QObject::connect(&socket, SIGNAL(socketDisconnect),
|
||||
this, SLOT(socketDisconnect));
|
||||
}
|
||||
|
||||
bool ChatReader::getConnected() const
|
||||
{
|
||||
return _connected;
|
||||
}
|
||||
|
||||
void ChatReader::socketConnect()
|
||||
{
|
||||
_connected = true;
|
||||
}
|
||||
|
||||
void ChatReader::socketDisconnect()
|
||||
{
|
||||
_connected = false;
|
||||
}
|
||||
|
||||
void ChatReader::open()
|
||||
{
|
||||
socket.open(URL);
|
||||
}
|
||||
|
||||
qint64 ChatReader::send(QString message)
|
||||
{
|
||||
return socket.sendTextMessage(message);
|
||||
}
|
||||
|
||||
void ChatReader::messageReceived(QString message)
|
||||
{
|
||||
auto chat = ChatMessage::Parse(message);
|
||||
if (chat->type() == PingView::Expected)
|
||||
{
|
||||
const PingView ping(chat);
|
||||
send(ping.toPong());
|
||||
}
|
||||
emit chatMessageReceived(chat);
|
||||
}
|
||||
|
||||
ChatReader::~ChatReader()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const QUrl ChatReader::URL = QUrl("wss://irc-ws.chat.twitch.tv:443");
|
||||
30
chatreader.hpp
Normal file
30
chatreader.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef CHATREADER_HPP
|
||||
#define CHATREADER_HPP
|
||||
|
||||
#include "chatmessage.hpp"
|
||||
|
||||
#include <QWebSocket>
|
||||
|
||||
class ChatReader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const QUrl URL;
|
||||
|
||||
ChatReader();
|
||||
~ChatReader();
|
||||
bool getConnected() const;
|
||||
void open();
|
||||
qint64 send(QString message);
|
||||
signals:
|
||||
void chatMessageReceived(const ChatMessage* message);
|
||||
protected slots:
|
||||
void socketConnect();
|
||||
void socketDisconnect();
|
||||
void messageReceived(QString message);
|
||||
private:
|
||||
QWebSocket socket;
|
||||
bool _connected = false;
|
||||
};
|
||||
|
||||
#endif // CHATREADER_HPP
|
||||
@@ -6,7 +6,7 @@ InvalidIdException::InvalidIdException()
|
||||
}
|
||||
|
||||
InvalidRowIdException::InvalidRowIdException(TierRow::IdType id)
|
||||
: _id(id), _what(std::format("id: {}", _id))
|
||||
: _id(id), _what(QString("id: {}").arg(QString::number(id)).toLocal8Bit())
|
||||
{
|
||||
|
||||
}
|
||||
@@ -18,11 +18,11 @@ TierRow::IdType InvalidRowIdException::id() const
|
||||
|
||||
const char* InvalidRowIdException::what() const noexcept
|
||||
{
|
||||
return _what.c_str();
|
||||
return _what.data();
|
||||
}
|
||||
|
||||
InvalidCardIdException::InvalidCardIdException(TierCard::IdType id)
|
||||
: _id(id), _what(std::format("id: {}", _id))
|
||||
: _id(id), _what(QString("id: {}").arg(QString::number(id)).toLocal8Bit())
|
||||
{
|
||||
|
||||
}
|
||||
@@ -34,5 +34,5 @@ TierCard::IdType InvalidCardIdException::id() const
|
||||
|
||||
const char* InvalidCardIdException::what() const noexcept
|
||||
{
|
||||
return _what.c_str();
|
||||
return _what.data();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
|
||||
private:
|
||||
const TierRow::IdType _id;
|
||||
const std::string _what;
|
||||
const QByteArray _what;
|
||||
};
|
||||
|
||||
class InvalidCardIdException : public InvalidIdException
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
|
||||
private:
|
||||
const TierCard::IdType _id;
|
||||
const std::string _what;
|
||||
const QByteArray _what;
|
||||
};
|
||||
|
||||
#endif // INVALIDIDEXCEPTION_HPP
|
||||
|
||||
40
ircexception.cpp
Normal file
40
ircexception.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "ircexception.hpp"
|
||||
|
||||
IrcException::IrcException(QString _what)
|
||||
: _what(_what.toLocal8Bit())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const char* IrcException::what() const noexcept
|
||||
{
|
||||
return _what.data();
|
||||
}
|
||||
|
||||
IrcTagNotFoundException::IrcTagNotFoundException(QString tag)
|
||||
: IrcException(QString("missing tag: %1").arg(tag)), tag(tag)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
IrcParseException::IrcParseException(QString message, QString description)
|
||||
: IrcException(_makeWhat(message, description)),
|
||||
message(message), description(description)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString IrcParseException::_makeWhat(QString message, QString description)
|
||||
{
|
||||
QString s = "";
|
||||
if (!message.isEmpty())
|
||||
s = QString("Description: %1\n").arg(description);
|
||||
return s + QString("Message: %1").arg(message);
|
||||
}
|
||||
|
||||
IrcIncorrectTypeException::IrcIncorrectTypeException(IrcMessageType expected, IrcMessageType actual)
|
||||
: IrcException(QString("Expected: %1 Actual: %2").arg(expected.toString(), actual.toString())),
|
||||
expected(expected), actual(actual)
|
||||
{
|
||||
|
||||
}
|
||||
43
ircexception.hpp
Normal file
43
ircexception.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef IRCEXCEPTIONS_HPP
|
||||
#define IRCEXCEPTIONS_HPP
|
||||
|
||||
#include "ircmessagetype.hpp"
|
||||
|
||||
#include <QException>
|
||||
|
||||
class IrcException : public QException
|
||||
{
|
||||
public:
|
||||
const char* what() const noexcept override;
|
||||
protected:
|
||||
IrcException(QString _what);
|
||||
const QByteArray _what;
|
||||
};
|
||||
|
||||
class IrcParseException : public IrcException
|
||||
{
|
||||
public:
|
||||
IrcParseException(QString message, QString description);
|
||||
const QString message;
|
||||
const QString description;
|
||||
|
||||
private:
|
||||
static QString _makeWhat(QString message, QString description="");
|
||||
};
|
||||
|
||||
class IrcTagNotFoundException : public IrcException
|
||||
{
|
||||
public:
|
||||
IrcTagNotFoundException(QString tag);
|
||||
const QString tag;
|
||||
};
|
||||
|
||||
class IrcIncorrectTypeException : public IrcException
|
||||
{
|
||||
public:
|
||||
IrcIncorrectTypeException(IrcMessageType expected, IrcMessageType actual);
|
||||
const IrcMessageType expected;
|
||||
const IrcMessageType actual;
|
||||
};
|
||||
|
||||
#endif // IRCEXCEPTIONS_HPP
|
||||
45
ircmessagetype.cpp
Normal file
45
ircmessagetype.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "ircmessagetype.hpp"
|
||||
|
||||
#include <QMetaObject>
|
||||
#include <QMetaEnum>
|
||||
|
||||
IrcMessageType::MessageType IrcMessageType::getType() const
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
|
||||
IrcMessageType IrcMessageType::parse(QString str)
|
||||
{
|
||||
// QVariant var = QVariant::fromValue(str);
|
||||
// if (var.convert(QMetaType::fromType<IrcMessageType>()))
|
||||
// return var.value<IrcMessageType>();
|
||||
// return IrcMessageType::MessageType::UNKNOWN;
|
||||
auto&& meta = QMetaEnum::fromType<IrcMessageType::MessageType>();
|
||||
bool ok = false;
|
||||
auto value = static_cast<IrcMessageType>(meta.keyToValue(str.toLocal8Bit().data(), &ok));
|
||||
if (ok)
|
||||
return value;
|
||||
auto num = str.toInt(&ok);
|
||||
if (ok)
|
||||
return num;
|
||||
return Unknown();
|
||||
}
|
||||
|
||||
IrcMessageType::IrcMessageType(const IrcMessageType& other)
|
||||
: _type(other._type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
IrcMessageType::IrcMessageType(int type)
|
||||
: _type(static_cast<MessageType>(type))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString IrcMessageType::toString() const
|
||||
{
|
||||
auto&& metaEnum = QMetaEnum::fromType<MessageType>();
|
||||
return metaEnum.valueToKey(static_cast<int>(_type));
|
||||
}
|
||||
34
ircmessagetype.hpp
Normal file
34
ircmessagetype.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef IRCMESSAGETYPE_HPP
|
||||
#define IRCMESSAGETYPE_HPP
|
||||
|
||||
#include <QMetaObject>
|
||||
#include <QMetaType>
|
||||
|
||||
class IrcMessageType
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
enum MessageType
|
||||
{
|
||||
UNKNOWN,
|
||||
PRIVMSG,
|
||||
PING
|
||||
};
|
||||
Q_ENUM(MessageType);
|
||||
Q_PROPERTY(MessageType type READ getType)
|
||||
static IrcMessageType Unknown() { return UNKNOWN; }
|
||||
constexpr IrcMessageType(MessageType type) : _type(type) { }
|
||||
IrcMessageType(int type);
|
||||
IrcMessageType(const IrcMessageType& other);
|
||||
bool operator ==(const IrcMessageType& other)
|
||||
{ return this->_type == other._type; }
|
||||
static IrcMessageType parse(QString str);
|
||||
MessageType getType() const;
|
||||
QString toString() const;
|
||||
|
||||
private:
|
||||
const MessageType _type;
|
||||
};
|
||||
Q_DECLARE_METATYPE(IrcMessageType)
|
||||
|
||||
#endif // IRCMESSAGETYPE_HPP
|
||||
17
messagetype.cpp
Normal file
17
messagetype.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "messagetype.hpp"
|
||||
|
||||
MessageType::MessageType(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MessageType::setType(MsgType type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MessageType::MsgType MessageType::type() const
|
||||
{
|
||||
return UNKNOWN;
|
||||
}
|
||||
25
messagetype.hpp
Normal file
25
messagetype.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef MESSAGETYPE_HPP
|
||||
#define MESSAGETYPE_HPP
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class MessageType : public QObject
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
explicit MessageType(QObject *parent = nullptr);
|
||||
MessageType(MessageType& other);
|
||||
enum MsgType
|
||||
{
|
||||
UNKNOWN,
|
||||
PRIVMSG,
|
||||
};
|
||||
Q_ENUM(MsgType)
|
||||
MessageType(MsgType type);
|
||||
void setType(MsgType type);
|
||||
MsgType type() const;
|
||||
|
||||
signals:
|
||||
};
|
||||
|
||||
#endif // MESSAGETYPE_HPP
|
||||
Reference in New Issue
Block a user