/*************************************************************************** * Copyright (C) 2008-2017 The Communi Project * * Copyright (C) 2017 by Fae - itsthefae@gmail.com * * Copyright (C) 2020 by Stephen Lyons - slysven@virginmedia.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ircmessageformatter.h" #include QString IrcMessageFormatter::formatMessage(IrcMessage* message, bool isForLua) { QString formatted; QString color = "#f29010"; IrcNumericMessage* nMsg; switch (message->type()) { case IrcMessage::Away: formatted = formatAwayMessage(static_cast(message), isForLua); break; case IrcMessage::Invite: formatted = formatInviteMessage(static_cast(message), isForLua); break; case IrcMessage::Join: formatted = formatJoinMessage(static_cast(message), isForLua); break; case IrcMessage::Mode: formatted = formatModeMessage(static_cast(message), isForLua); break; case IrcMessage::Motd: formatted = formatMotdMessage(static_cast(message), isForLua); break; case IrcMessage::Names: formatted = formatNamesMessage(static_cast(message), isForLua); break; case IrcMessage::Nick: formatted = formatNickMessage(static_cast(message), isForLua); break; case IrcMessage::Notice: formatted = formatNoticeMessage(static_cast(message), isForLua); break; case IrcMessage::Part: formatted = formatPartMessage(static_cast(message), isForLua); break; case IrcMessage::Pong: formatted = formatPongMessage(static_cast(message), isForLua); break; case IrcMessage::Private: formatted = formatPrivateMessage(static_cast(message), isForLua); break; case IrcMessage::Quit: formatted = formatQuitMessage(static_cast(message), isForLua); break; case IrcMessage::Topic: formatted = formatTopicMessage(static_cast(message), isForLua); color = "#3283bc"; break; case IrcMessage::Whois: formatted = formatWhoisMessage(static_cast(message), isForLua); break; case IrcMessage::Whowas: formatted = formatWhowasMessage(static_cast(message), isForLua); break; case IrcMessage::WhoReply: formatted = formatWhoReplyMessage(static_cast(message), isForLua); break; case IrcMessage::Error: formatted = formatErrorMessage(static_cast(message), isForLua); color = "indianred"; break; case IrcMessage::Unknown: formatted = formatUnknownMessage(message, isForLua); break; case IrcMessage::Numeric: nMsg = static_cast(message); formatted = formatNumericMessage(nMsg, isForLua); // if you change this, change formatErrorMessage too if (Irc::codeToString(nMsg->code()).startsWith("ERR_")) { color = "indianred"; } break; default: break; } return formatMessage(formatted, color, isForLua); } QString IrcMessageFormatter::formatMessage(const QString& message, QString color, bool isForLua) { if (!message.isEmpty()) { if (isForLua) { // lua has no need for the timestamp or HTML added here. return message; } QString formatted = QString("[%1] %2").arg(QTime::currentTime().toString(), message); if (message.startsWith("!")) { formatted = QString("%1").arg(formatted); } else if (message.startsWith("*")) { formatted = QString("%1").arg(formatted); } else if (message.startsWith("$")) { formatted = QString("%1").arg(formatted); } else if (message.startsWith("[")) { if (color.isEmpty()) { color = "#f29010"; } formatted = QString("%1").arg(formatted, color); } return formatted; } return QString(); } QString IrcMessageFormatter::formatAwayMessage(IrcAwayMessage* message, bool isForLua) { QString content; if (isForLua) { content = IrcTextFormat().toPlainText(message->content()); } else { content = IrcTextFormat().toHtml(message->content()); } if (message->flags() & IrcMessage::Own) { return QObject::tr("! %1").arg(content); } if (!message->content().isEmpty()) { return QObject::tr("! %1 is away (%2)").arg(message->nick(), content); } return QObject::tr("! %1 is back").arg(message->nick()); } QString IrcMessageFormatter::formatInviteMessage(IrcInviteMessage* message, bool isForLua) { Q_UNUSED(isForLua) if (message->isReply()) { return QObject::tr("! invited %1 to %2").arg(message->user(), message->channel()); } return QObject::tr("! %2 invited to %3").arg(message->nick(), message->channel()); } QString IrcMessageFormatter::formatJoinMessage(IrcJoinMessage* message, bool isForLua) { Q_UNUSED(isForLua) if (message->flags() & IrcMessage::Own) { return QObject::tr("! You have joined %1 as %2").arg(message->channel(), message->nick()); } return QObject::tr("! %1 has joined %2").arg(message->nick(), message->channel()); } QString IrcMessageFormatter::formatKickMessage(IrcKickMessage* message, bool isForLua) { Q_UNUSED(isForLua) return QObject::tr("! %1 kicked %2").arg(message->nick(), message->user()); } QString IrcMessageFormatter::formatModeMessage(IrcModeMessage* message, bool isForLua) { Q_UNUSED(isForLua) const QString args = message->arguments().join(" "); if (message->isReply()) { return QObject::tr("! %1 mode is %2 %3").arg(message->target(), message->mode(), args); } return QObject::tr("! %1 sets mode %2 %3 %4").arg(message->nick(), message->target(), message->mode(), args); } QString IrcMessageFormatter::formatMotdMessage(IrcMotdMessage* message, bool isForLua) { QString motdData; for (const auto& line : message->lines()) { QString content, lineEnd; if (isForLua) { lineEnd = "\n"; content = IrcTextFormat().toPlainText(line); } else { lineEnd = "
\n"; content = IrcTextFormat().toHtml(line); } motdData += QObject::tr("[MOTD] %1%2").arg(content, lineEnd); } return motdData; } QString IrcMessageFormatter::formatNamesMessage(IrcNamesMessage* message, bool isForLua) { const QString count = QString::number(message->names().count()); if (isForLua) { // lua actually needs the names for parsing, since getting a names // list from the UI userModel alone would be limiting to the IRC commands. const QString nameList = message->names().join(" "); return QObject::tr("! %1 has %2 users: %3").arg(message->channel(), count, nameList); } return QObject::tr("! %1 has %2 users").arg(message->channel(), count); } QString IrcMessageFormatter::formatNickMessage(IrcNickMessage* message, bool isForLua) { Q_UNUSED(isForLua) return QObject::tr("! %1 has changed nick to %2").arg(message->oldNick(), message->newNick()); } QString IrcMessageFormatter::formatNoticeMessage(IrcNoticeMessage* message, bool isForLua) { if (message->isReply()) { const QStringList params = message->content().split(" ", Qt::SkipEmptyParts); const QString cmd = params.value(0); if (cmd.toUpper() == "PING") { const QString secs = formatSeconds(params.value(1).toInt()); return QObject::tr("! %1 replied in %2").arg(message->nick(), secs); } if (cmd.toUpper() == "TIME") { const QString rest = QStringList(params.mid(1)).join(" "); return QObject::tr("! %1 time is %2").arg(message->nick(), rest); } if (cmd.toUpper() == "VERSION") { const QString rest = QStringList(params.mid(1)).join(" "); return QObject::tr("! %1 version is %2").arg(message->nick(), rest); } } QString pfx = message->statusPrefix(); if (!pfx.isEmpty()) { pfx = ":" + pfx; } if (message->isPrivate()) { QString content; if (isForLua) { content = IrcTextFormat().toPlainText(message->content()); } else { content = IrcTextFormat().toHtml(message->content()); } return QObject::tr("[%1%2] %3").arg(message->nick(), pfx, content); } if (isForLua) { // lua only needs the message text. return IrcTextFormat().toPlainText(message->content()); } const QString content = IrcTextFormat().toHtml(message->content()); return QObject::tr("<%1%2> [%3] %4").arg(message->nick(), pfx, message->target(), content); } QString IrcMessageFormatter::formatNumericMessage(IrcNumericMessage* message, bool isForLua) { if (message->code() < 300) { const QString info = QStringList(message->parameters().mid(1)).join(" "); QString content; if (isForLua) { content = IrcTextFormat().toPlainText(info); } else { content = IrcTextFormat().toHtml(info); } return QObject::tr("[INFO] %1").arg(info); } switch (message->code()) { case Irc::RPL_VERSION: return QObject::tr("! %1 version is %2").arg(message->nick(), message->parameters().value(1)); case Irc::RPL_TIME: return QObject::tr("! %1 time is %2").arg(message->parameters().value(1), message->parameters().value(2)); default: break; } if (message->isComposed() || message->flags() & IrcMessage::Implicit) { return QString(); } // if you change this, change formatErrorMessage too if (Irc::codeToString(message->code()).startsWith("ERR_")) { const QString info = QStringList(message->parameters().mid(1)).join(" "); QString content; if (isForLua) { content = IrcTextFormat().toPlainText(info); } else { content = IrcTextFormat().toHtml(info); } return QObject::tr("[ERROR] %1").arg(content); } if (message->code() == Irc::RPL_CHANNEL_URL) { const QString info = QStringList(message->parameters().mid(1)).join(" "); QString content; if (isForLua) { content = IrcTextFormat().toPlainText(info); } else { content = IrcTextFormat().toHtml(info); } return QObject::tr("[Channel URL] %1").arg(content); } const QString info = QStringList(message->parameters().mid(1)).join(" "); QString content; if (isForLua) { content = IrcTextFormat().toPlainText(info); } else { content = IrcTextFormat().toHtml(info); } return QObject::tr("[%1] %2").arg(QString::number(message->code()), content); } QString IrcMessageFormatter::formatErrorMessage(IrcErrorMessage* message, bool isForLua) { Q_UNUSED(isForLua) // if you change this, change ERR_ in formatNumericMessage too return QObject::tr("[ERROR] %1").arg(message->error()); } QString IrcMessageFormatter::formatPartMessage(IrcPartMessage* message, bool isForLua) { Q_UNUSED(isForLua) if (message->reason().isEmpty()) { return QObject::tr("! %1 has left %2").arg(message->nick(), message->channel()); } return QObject::tr("! %1 has left %2 (%3)").arg(message->nick(), message->channel(), message->reason()); } QString IrcMessageFormatter::formatPongMessage(IrcPongMessage* message, bool isForLua) { Q_UNUSED(isForLua) quint64 const msec = message->timeStamp().toMSecsSinceEpoch(); quint64 const dms = (QDateTime::currentMSecsSinceEpoch() - msec); return QObject::tr("! %1 replied in %2 seconds").arg(message->nick()).arg(dms / 1000.0, 4, 'f', 3, QLatin1Char('0')); } // Normal messages sent to channels are processed by our client as if they are private messages. QString IrcMessageFormatter::formatPrivateMessage(IrcPrivateMessage* message, bool isForLua) { QString content; if (isForLua) { content = IrcTextFormat().toPlainText(message->content()); } else { content = IrcTextFormat().toHtml(message->content()); } if (message->isAction()) { return QObject::tr("* %1 %2").arg(message->nick(), content); } if (isForLua) { // lua only needs the message text here. Nick and target are sent as arguments to postIrcMessage() return content; } return QObject::tr("<%1> %2").arg(message->nick(), content); } QString IrcMessageFormatter::formatQuitMessage(IrcQuitMessage* message, bool isForLua) { Q_UNUSED(isForLua) if (message->reason().isEmpty()) { return QObject::tr("! %1 has quit").arg(message->nick()); } return QObject::tr("! %1 has quit (%2)").arg(message->nick(), message->reason()); } QString IrcMessageFormatter::formatTopicMessage(IrcTopicMessage* message, bool isForLua) { if (message->isReply()) { if (message->topic().isEmpty()) { return QObject::tr("! no topic"); } QString topic; if (isForLua) { topic = IrcTextFormat().toPlainText(message->topic()); } else { topic = IrcTextFormat().toHtml(message->topic()); } return QObject::tr("[TOPIC] %1").arg(topic); } if (message->topic().isEmpty()) { return QObject::tr("! %2 cleared topic").arg(message->nick()); } return QObject::tr("! %2 changed topic").arg(message->nick()); } QString IrcMessageFormatter::formatUnknownMessage(IrcMessage* message, bool isForLua) { Q_UNUSED(isForLua) return QObject::tr("? %2 %3 %4").arg(message->nick(), message->command(), message->parameters().join(" ")); } QString IrcMessageFormatter::formatWhoisMessage(IrcWhoisMessage* message, bool isForLua) { Q_UNUSED(isForLua) QString wData; wData = QObject::tr("[WHOIS] %1 is %2@%3 (%4)").arg(message->nick(), message->ident(), message->host(), message->realName()); wData += QObject::tr("[WHOIS] %1 is connected via %2 (%3)").arg(message->nick(), message->server(), message->info()); wData += QObject::tr("[WHOIS] %1 is connected since %2 (idle %3)").arg(message->nick(), message->since().toString(), formatDuration(message->idle())); if (!message->awayReason().isEmpty()) { wData += QObject::tr("[WHOIS] %1 is away: %2").arg(message->nick(), message->awayReason()); } if (!message->account().isEmpty()) { wData += QObject::tr("[WHOIS] %1 is logged in as %2").arg(message->nick(), message->account()); } if (!message->address().isEmpty()) { wData += QObject::tr("[WHOIS] %1 is connected from %2").arg(message->nick(), message->address()); } if (message->isSecure()) { wData += QObject::tr("[WHOIS] %1 is using a secure connection").arg(message->nick()); } if (!message->channels().isEmpty()) { wData += QObject::tr("[WHOIS] %1 is on %2").arg(message->nick(), message->channels().join(" ")); } return wData; } QString IrcMessageFormatter::formatWhowasMessage(IrcWhowasMessage* message, bool isForLua) { Q_UNUSED(isForLua) QString wData; wData = QObject::tr("[WHOWAS] %1 was %2@%3 (%4)").arg(message->nick(), message->ident(), message->host(), message->realName()); wData += QObject::tr("[WHOWAS] %1 was connected via %2 (%3)").arg(message->nick(), message->server(), message->info()); if (!message->account().isEmpty()) { wData += QObject::tr("[WHOWAS] %1 was logged in as %2").arg(message->nick(), message->account()); } return wData; } QString IrcMessageFormatter::formatWhoReplyMessage(IrcWhoReplyMessage* message, bool isForLua) { Q_UNUSED(isForLua) QString format = QObject::tr("[WHO] %1 (%2)").arg(message->nick(), message->realName()); if (message->isAway()) { format += QObject::tr(" - away"); } if (message->isServOp()) { format += QObject::tr(" - server operator"); } return format; } QString IrcMessageFormatter::formatSeconds(int secs) { const QDateTime time = QDateTime::fromSecsSinceEpoch(secs); return QObject::tr("%1s").arg(time.secsTo(QDateTime::currentDateTime())); } QString IrcMessageFormatter::formatDuration(int secs) { QStringList idle; if (const int days = secs / 86400) { idle += QObject::tr("%1 days").arg(days); } secs %= 86400; if (const int hours = secs / 3600) { idle += QObject::tr("%1 hours").arg(hours); } secs %= 3600; if (const int mins = secs / 60) { idle += QObject::tr("%1 mins").arg(mins); } idle += QObject::tr("%1 secs").arg(secs % 60); return idle.join(" "); }