The complete source code of IdealIRC
http://www.idealirc.org/
858 lines
30 KiB
858 lines
30 KiB
/*
|
|
* IdealIRC - Internet Relay Chat client
|
|
* Copyright (C) 2021 Tom-Andre Barstad.
|
|
* This software is licensed under the Software Attribution License.
|
|
* See LICENSE for more information.
|
|
*/
|
|
|
|
#include "IRC.h"
|
|
#include "IRCClient/Utilities.h"
|
|
#include "MdiManager.h"
|
|
#include "IRCClient/IRCMember.h"
|
|
#include "IRCClient/IRCMemberEntry.h"
|
|
#include "IRCClient/IRCChannel.h"
|
|
#include "IRCClient/Commands.h"
|
|
#include "IWin/IWinStatus.h"
|
|
#include "IWin/IWinChannel.h"
|
|
#include "ConfigMgr.h"
|
|
#include "ICommand/Commands.h"
|
|
#include "Numeric.h"
|
|
#include "Script/Manager.h"
|
|
#include "ScriptEvent.h"
|
|
#include "Script/Builtin/ListUtils.h" // Needed for LIST_END_MAGIC
|
|
#include "config.h"
|
|
#include <fmt/format.h>
|
|
#include <QDateTime>
|
|
#include <QTimer>
|
|
|
|
IRC::IRC(IWinStatus& status)
|
|
: m_status(status)
|
|
{
|
|
static int idCount{ 0 };
|
|
m_id = ++idCount;
|
|
}
|
|
|
|
void IRC::disconnectForExit(const QString& quitMessage)
|
|
{
|
|
m_disconnectForExit = true;
|
|
std::string quitMessageConv = quitMessage.toStdString();
|
|
if (disconnectFromServer(quitMessageConv) == IRCError::NotConnected)
|
|
qWarning() << "Trying to disconnect from an already closed connection.";
|
|
}
|
|
|
|
int IRC::channelAutoComplete(int index, QString& pattern)
|
|
{
|
|
if (pattern.isEmpty() || !isOnline())
|
|
return 0;
|
|
|
|
int searchIdx = 0;
|
|
bool changed = false;
|
|
|
|
Retry:
|
|
for (const auto& chan : channels()) {
|
|
const auto name = QString::fromStdString(chan->name());
|
|
if (pattern.length() > name.length())
|
|
continue;
|
|
|
|
if (name.left(pattern.length()).compare(pattern, Qt::CaseInsensitive) == 0) {
|
|
if (searchIdx == index) {
|
|
pattern = name;
|
|
changed = true;
|
|
}
|
|
++searchIdx;
|
|
}
|
|
}
|
|
|
|
if (!changed && searchIdx > 0) {
|
|
changed = false;
|
|
searchIdx = 0;
|
|
index = 0;
|
|
goto Retry;
|
|
}
|
|
|
|
return searchIdx;
|
|
}
|
|
|
|
void IRC::setIgnoreVerbosity(const QStringList& commandsAndNumerics)
|
|
{
|
|
for (const QString& entry : commandsAndNumerics)
|
|
m_ignoreVerbosity.push_back(entry.toUpper());
|
|
m_ignoreVerbosity.removeDuplicates();
|
|
}
|
|
|
|
void IRC::unsetIgnoreVerbosity(const QStringList& commandsAndNumerics)
|
|
{
|
|
for (const QString& entry : commandsAndNumerics)
|
|
m_ignoreVerbosity.removeOne(entry.toUpper());
|
|
}
|
|
|
|
bool IRC::ignoreVerbosity(const std::string& cmd)
|
|
{
|
|
QString qcmd{ cmd.c_str() };
|
|
return m_ignoreVerbosity.contains(qcmd.toUpper());
|
|
}
|
|
|
|
void IRC::setReconnectDetails(bool ssl, const std::string& host, const std::string& port)
|
|
{
|
|
m_reconnectDetails.emplace();
|
|
m_reconnectDetails->ssl = ssl;
|
|
m_reconnectDetails->host = host;
|
|
m_reconnectDetails->port = port;
|
|
}
|
|
|
|
void IRC::rejoinChannels()
|
|
{
|
|
QStringList targetList;
|
|
for (IWin* subwin : m_status.subWindows())
|
|
targetList << subwin->getButtonText();
|
|
if (targetList.isEmpty())
|
|
return;
|
|
QString targets = targetList.join(',');
|
|
command(Command::IRC::JOIN, { targets.toStdString() });
|
|
}
|
|
|
|
void IRC::onRegistered()
|
|
{
|
|
m_expectDisconnect = false;
|
|
emit registered();
|
|
const auto& config = ConfigMgr::instance();
|
|
if (config.common("RejoinChannelsOnConnect") == "1")
|
|
rejoinChannels();
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Connected);
|
|
}
|
|
|
|
void IRC::onConnected()
|
|
{
|
|
emit connected();
|
|
}
|
|
|
|
void IRC::onConnectedWithSSLExceptions(const std::vector<IRCError>& codes)
|
|
{
|
|
for (const auto code : codes) {
|
|
const auto msg = fmt::format("{} - IGNORED", IRCErrorToString(code));
|
|
m_status.print(PrintType::ProgramInfo, msg.c_str());
|
|
}
|
|
onConnected();
|
|
}
|
|
|
|
void IRC::onDisconnected()
|
|
{
|
|
emit disconnected();
|
|
emit memberListClearedForAll();
|
|
if (m_disconnectForExit)
|
|
emit readyForExit();
|
|
contextualScriptEvent(&m_status, ScriptEvent::Disconnected);
|
|
|
|
if (m_reconnectDetails.has_value()) {
|
|
setHostname(m_reconnectDetails->host, m_reconnectDetails->ssl);
|
|
setPort(m_reconnectDetails->port);
|
|
m_reconnectDetails.reset();
|
|
tryConnect();
|
|
}
|
|
else {
|
|
auto& conf = ConfigMgr::instance();
|
|
bool reconnect = conf.common("Reconnect").toInt();
|
|
if (reconnect && !m_expectDisconnect) {
|
|
auto delayReconnectSecs = conf.common("ReconnectDelay").toInt();
|
|
QTimer::singleShot(delayReconnectSecs * 1000, [this] {
|
|
if (!m_expectDisconnect) // Note: This flag might be changed during the timer. If reconnection is aborted, it'll change.
|
|
tryConnect();
|
|
});
|
|
}
|
|
}
|
|
m_expectDisconnect = false;
|
|
}
|
|
|
|
void IRC::onConnectionError(IRCError e)
|
|
{
|
|
m_status.print(PrintType::ProgramInfo, IRCErrorToString(e).c_str());
|
|
}
|
|
|
|
void IRC::onMsgNick(const IRCPrefix& sender, const std::string& newNickname, const std::vector<std::string>& channelsAffected)
|
|
{
|
|
std::string msg;
|
|
if (newNickname == getNickname()) {
|
|
msg = fmt::format("You are now known as {}", newNickname);
|
|
m_status.print(PrintType::Nick, msg.c_str());
|
|
m_status.refreshWindowTitle();
|
|
}
|
|
else
|
|
msg = fmt::format("{} is now known as {}", sender.toString(), newNickname);
|
|
|
|
for (const auto& channelName : channelsAffected) {
|
|
auto channel = getChannel(channelName);
|
|
auto memberOpt = channel->getMember(newNickname);
|
|
if (!memberOpt)
|
|
continue;
|
|
|
|
if (!ignoreVerbosity(Command::IRC::NICK))
|
|
m_status.printTo(channelName.c_str(), PrintType::Nick, msg.c_str());
|
|
|
|
const auto& member = memberOpt->get();
|
|
emit memberChanged(sender.toString().c_str(), member);
|
|
}
|
|
|
|
auto pm = MdiManager::instance().findWindow(&m_status, QString::fromStdString(sender.toString()));
|
|
auto pmNewExisting = MdiManager::instance().findWindow(&m_status, QString::fromStdString(newNickname));
|
|
if (pm)
|
|
pm->print(PrintType::Nick, msg.c_str());
|
|
if (pmNewExisting)
|
|
pmNewExisting->print(PrintType::Nick, msg.c_str());
|
|
|
|
if (pm && !pmNewExisting) {
|
|
MdiManager::instance().renameWindowButton(pm, QString::fromStdString(newNickname));
|
|
pm->refreshWindowTitle();
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Nick, sender.toString(), newNickname);
|
|
}
|
|
|
|
void IRC::onMsgMode(const IRCPrefix& sender, const std::string& target, const std::string& modes, const std::vector<std::string>& args)
|
|
{
|
|
auto modemsg = modes;
|
|
std::for_each(args.begin(), args.end(),
|
|
[&modemsg](const std::string& arg){
|
|
modemsg += " " + arg;
|
|
});
|
|
|
|
const auto& is = isupport();
|
|
const auto& chantypes = is.at("CHANTYPES"); // Chantypes is always present.
|
|
|
|
/* Mode changed in a channel */
|
|
if (chantypes.find_first_of(target[0]) != std::string::npos) {
|
|
if (!ignoreVerbosity(Command::IRC::MODE)) {
|
|
const std::string msg = fmt::format("{} changed mode: {}", sender.toString(), modemsg);
|
|
getStatus().printTo(target.c_str(), PrintType::Mode, msg.c_str());
|
|
}
|
|
|
|
auto channel = getChannel(target);
|
|
|
|
int argp = 0;
|
|
char sign = modes[0]; // First character is _always_ either + or -
|
|
for (const char m : modes) {
|
|
if (m == '-' || m == '+') {
|
|
sign = m;
|
|
continue;
|
|
}
|
|
|
|
char group = channelModeGroup(m);
|
|
if (group == 'A' || group == 'B' || (group == 'C' && sign == '+'))
|
|
++argp;
|
|
else if (group == 'M') {
|
|
auto& member = channel->getMember(args[argp]).value().get();
|
|
++argp;
|
|
emit memberChanged(member.member()->prefix().nickname().c_str(), member);
|
|
}
|
|
}
|
|
|
|
auto subwin = MdiManager::instance().findWindow(&getStatus(), QString::fromStdString(target));
|
|
if (subwin)
|
|
subwin->refreshWindowTitle();
|
|
}
|
|
|
|
/* User-mode changed */
|
|
else if (!ignoreVerbosity(Command::IRC::MODE)) {
|
|
/* We changed our own usermode */
|
|
if (target == getNickname()) {
|
|
const std::string msg = fmt::format("Your usermode changed: {}", modemsg);
|
|
getStatus().print(PrintType::Mode, msg.c_str());
|
|
}
|
|
|
|
/* Probably not possible, but someone set someone elses usermode and we got the message about it */
|
|
else {
|
|
const std::string msg = fmt::format("{} changed usermode for {}: {}", sender.toString(), target, modemsg);
|
|
getStatus().print(PrintType::Mode, msg.c_str());
|
|
}
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Mode, sender.toString(), target, modemsg);
|
|
}
|
|
|
|
void IRC::onMsgQuit(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected)
|
|
{
|
|
std::string msg;
|
|
if (!message.empty())
|
|
msg = fmt::format("Quit: {} ({}@{}) ({})", sender.nickname(), sender.user(), sender.host(), message);
|
|
else
|
|
msg = fmt::format("Quit: {} ({}@{})", sender.nickname(), sender.user(), sender.host());
|
|
|
|
if (channelsAffected.empty() && !ignoreVerbosity(Command::IRC::QUIT))
|
|
m_status.print(PrintType::Quit, msg.c_str());
|
|
|
|
for (const auto& channelName : channelsAffected) {
|
|
auto channel = getChannel(channelName);
|
|
auto memberOpt = channel->getMember(sender.nickname());
|
|
if (!memberOpt)
|
|
continue;
|
|
|
|
if (!ignoreVerbosity(Command::IRC::QUIT)) {
|
|
m_status.printTo(channelName.c_str(), PrintType::Quit, msg.c_str());
|
|
}
|
|
|
|
const auto& member = memberOpt->get();
|
|
emit memberRemoved(channelName.c_str(), member);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Quit, sender.nickname(), message);
|
|
}
|
|
|
|
void IRC::onMsgJoin(const IRCPrefix& sender, const std::string& target)
|
|
{
|
|
/* We joined a channel */
|
|
if (sender.nickname() == getNickname()) {
|
|
auto* subwin = getStatus().createChannelWindow(target.c_str());
|
|
if (!ignoreVerbosity(Command::IRC::JOIN)) {
|
|
const std::string msg = fmt::format("Now talking in {}", target);
|
|
subwin->print(PrintType::ServerInfo, msg.c_str());
|
|
}
|
|
subwin->refreshWindowTitle();
|
|
command(Command::IRC::MODE, { target });
|
|
}
|
|
|
|
/* Someone else joined a channel */
|
|
else {
|
|
if (!ignoreVerbosity(Command::IRC::JOIN)) {
|
|
const std::string msg = fmt::format("Join: {} ({}@{})", sender.nickname(), sender.user(), sender.host());
|
|
getStatus().printTo(target.c_str(), PrintType::Join, msg.c_str());
|
|
}
|
|
|
|
auto channel = getChannel(target);
|
|
auto memberOpt = channel->getMember(sender.nickname());
|
|
if (memberOpt)
|
|
emit memberAdded(target.c_str(), memberOpt->get());
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Join, sender.nickname(), target);
|
|
}
|
|
|
|
void IRC::onMsgPart(const IRCPrefix& sender, const std::string& target, const std::string& message)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::PART)) {
|
|
std::string msg;
|
|
if (msg.empty())
|
|
msg = fmt::format("Part: {} ({}@{})", sender.nickname(), sender.user(), sender.host());
|
|
else
|
|
msg = fmt::format("Part: {} ({}@{}) ({})", sender.nickname(), sender.user(), sender.host(), message);
|
|
getStatus().printTo(target.c_str(), PrintType::Part, msg.c_str());
|
|
}
|
|
|
|
/* We left a channel */
|
|
if (sender.nickname() == getNickname())
|
|
emit memberListReloaded(target.c_str());
|
|
|
|
/* Someone else left a channel */
|
|
else {
|
|
auto channel = getChannel(target);
|
|
auto memberOpt = channel->getMember(sender.nickname());
|
|
if (memberOpt)
|
|
emit memberRemoved(target.c_str(), memberOpt->get());
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Part, sender.nickname(), target, message);
|
|
}
|
|
|
|
void IRC::onMsgTopic(const IRCPrefix& sender, const std::string& target, const std::string& topic)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::TOPIC)) {
|
|
std::string msg;
|
|
if (topic.empty())
|
|
msg = fmt::format("{} removed the topic", sender.toString());
|
|
else
|
|
msg = fmt::format("{} set the topic to: {}", sender.toString(), topic);
|
|
getStatus().printTo(target.c_str(), PrintType::Topic, msg.c_str());
|
|
auto subwin = MdiManager::instance().findWindow(&getStatus(), QString::fromStdString(target));
|
|
if (subwin)
|
|
subwin->refreshWindowTitle();
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Topic, sender.toString(), target, topic);
|
|
}
|
|
|
|
void IRC::onMsgInvite(const IRCPrefix& sender, const std::string& target)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::INVITE)) {
|
|
const std::string msg = fmt::format("{} invited you to join {}", sender.toString(), target);
|
|
auto* win = getStatus().getActiveWindow();
|
|
win->print(PrintType::Invite, msg.c_str());
|
|
MdiManager::instance().highlight(win, HL_Attention);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Invite, sender.toString(), target);
|
|
}
|
|
|
|
void IRC::onMsgKick(const IRCPrefix& sender, const std::string& target, const std::string& who, const std::string& reason)
|
|
{
|
|
std::string msg;
|
|
|
|
/* We were kicked */
|
|
if (who == getNickname()) {
|
|
msg = fmt::format("You were kicked out by {} ({})", sender.toString(), reason);
|
|
emit memberListReset(target.c_str());
|
|
}
|
|
|
|
/* Someone else were kicked */
|
|
else {
|
|
msg = fmt::format("{} kicked out {} ({})", sender.toString(), who, reason);
|
|
|
|
auto channel = getChannel(target);
|
|
auto memberOpt = channel->getMember(who);
|
|
if (memberOpt)
|
|
emit memberRemoved(target.c_str(), memberOpt->get());
|
|
}
|
|
|
|
if (!ignoreVerbosity(Command::IRC::KICK)) {
|
|
auto* win = MdiManager::instance().findWindow(&getStatus(), target.c_str());
|
|
win->print(PrintType::Kick, msg.c_str());
|
|
if (who == getNickname())
|
|
MdiManager::instance().highlight(win, HL_Attention);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Kick, sender.toString(), who, target, reason);
|
|
}
|
|
|
|
void IRC::onMsgPrivmsg(const IRCPrefix& sender, const std::string& target, const std::string& message)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::PRIVMSG)) {
|
|
const auto& is = isupport();
|
|
const auto& chantypes = is.at("CHANTYPES"); // Chantypes is always present.
|
|
|
|
PrintType ptype = PrintType::Normal;
|
|
if (sender.nickname() == getNickname())
|
|
ptype = PrintType::OwnText;
|
|
|
|
auto highlightState = HL_Message;
|
|
if (message.find(getNickname()) != std::string::npos)
|
|
highlightState = HL_Attention;
|
|
|
|
/* Message to a channel */
|
|
if (chantypes.find_first_of(target[0]) != std::string::npos) {
|
|
auto* window = MdiManager::instance().findWindow(&getStatus(), target.c_str());
|
|
auto& conf = ConfigMgr::instance();
|
|
std::string msg;
|
|
if (conf.common("ShowModeInMessage").toInt()) {
|
|
auto memberOpt = getChannel(target)->getMember(sender.toString());
|
|
if (memberOpt) {
|
|
auto& member = memberOpt->get();
|
|
if (member.modes().empty())
|
|
msg = fmt::format("<{}> {}", sender.toString(), message);
|
|
else {
|
|
std::string symbols = toMemberPrefix(member.modes());
|
|
msg = fmt::format("<{}{}> {}", symbols[0], sender.toString(), message);
|
|
}
|
|
}
|
|
else
|
|
msg = fmt::format("<{}> {}", sender.toString(), message);
|
|
}
|
|
else
|
|
msg = fmt::format("<{}> {}", sender.toString(), message);
|
|
window->print(ptype, msg.c_str());
|
|
MdiManager::instance().highlight(window, highlightState);
|
|
}
|
|
|
|
/* Private message */
|
|
else {
|
|
auto* window = getStatus().createPrivateWindow(sender.toString().c_str(), false);
|
|
const std::string msg = fmt::format("<{}> {}", sender.toString(), message);
|
|
window->print(ptype, msg.c_str());
|
|
MdiManager::instance().highlight(window, highlightState);
|
|
}
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Privmsg, sender.toString(), target, message);
|
|
}
|
|
|
|
void IRC::onMsgNotice(const IRCPrefix& sender, const std::string& target, const std::string& message)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::NOTICE)) {
|
|
IWin* window;
|
|
std::string msg;
|
|
if (isChannelSymbol(target[0])) {
|
|
msg = fmt::format("-{}({})- {}", sender.toString(), target, message);
|
|
window = MdiManager::instance().findWindow(&getStatus(), QString::fromStdString(target));
|
|
}
|
|
else {
|
|
msg = fmt::format("-{}- {}", sender.toString(), message);
|
|
window = getStatus().getActiveWindow();
|
|
}
|
|
window->print(PrintType::Notice, msg.c_str());
|
|
MdiManager::instance().highlight(window, HL_Attention);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Notice, sender.toString(), target, message);
|
|
}
|
|
|
|
void IRC::onMsgKill(const IRCPrefix& sender, const std::string& reason)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::KILL)) {
|
|
std::string msg;
|
|
|
|
if (sender.type() == IRCPrefix::Type::user)
|
|
msg = fmt::format("You were forcibly disconnected by {} ({}@{}) ({})",
|
|
sender.nickname(), sender.user(), sender.host(), reason);
|
|
else
|
|
msg = fmt::format("You were forcibly disconnected by {} ({})",
|
|
sender.servername(), reason);
|
|
|
|
getStatus().print(PrintType::ServerInfo, msg.c_str());
|
|
MdiManager::instance().highlight(&getStatus(), HL_Attention);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Kill, sender.toString(), reason);
|
|
}
|
|
|
|
void IRC::onMsgPing(const std::string& message)
|
|
{
|
|
|
|
}
|
|
|
|
void IRC::onMsgPong(const std::string& message)
|
|
{
|
|
|
|
}
|
|
|
|
void IRC::onMsgError(const std::string& message)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::ERROR)) {
|
|
getStatus().print(PrintType::ServerInfo, message.c_str());
|
|
MdiManager::instance().highlight(&getStatus(), HL_Attention);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Error, message);
|
|
}
|
|
|
|
void IRC::onMsgWallops(const IRCPrefix& sender, const std::string& message)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::WALLOPS)) {
|
|
const std::string msg = fmt::format("!{}! {}", sender.toString(), message);
|
|
getStatus().print(PrintType::Wallops, msg.c_str());
|
|
MdiManager::instance().highlight(&getStatus(), HL_Attention);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::Wallops, sender.toString(), message);
|
|
}
|
|
|
|
void IRC::onMsgNumeric(const IRCPrefix& /*sender*/, const std::string& num, const std::vector<std::string>& args, const std::string& message)
|
|
{
|
|
std::string argmsg;
|
|
|
|
/*
|
|
* Skip showing our own nickname if it's the first argument received.
|
|
* It usually is.
|
|
*/
|
|
size_t argsOffset = 0;
|
|
if (!args.empty() && args[0] == getNickname())
|
|
argsOffset = 1;
|
|
|
|
std::for_each(args.begin() + argsOffset, args.end(),
|
|
[&argmsg](const std::string& arg){
|
|
if (!argmsg.empty())
|
|
argmsg += ' ';
|
|
argmsg += arg;
|
|
});
|
|
|
|
if (num == Numeric::RPL_ENDOFNAMES) {
|
|
emit memberListReloaded(args[1].c_str());
|
|
if (!ignoreVerbosity(num)) {
|
|
std::string msg = fmt::format("{}: {}", argmsg, message);
|
|
getStatus().print(PrintType::Normal, msg.c_str());
|
|
}
|
|
}
|
|
|
|
else if (num == Numeric::RPL_TOPIC) {
|
|
const auto channel = QString::fromStdString(args[1]);
|
|
auto* subwin = MdiManager::instance().findWindow(&getStatus(), channel);
|
|
if (subwin) {
|
|
subwin->refreshWindowTitle();
|
|
}
|
|
|
|
if (!ignoreVerbosity(num)) {
|
|
if (subwin)
|
|
subwin->print(PrintType::Topic, tr("Topic is: %1").arg(QString::fromStdString(message)));
|
|
else
|
|
getStatus().print(PrintType::Topic, tr("Topic for %1 is: %2")
|
|
.arg(QString::fromStdString(args[1]))
|
|
.arg(QString::fromStdString(message)));
|
|
}
|
|
}
|
|
|
|
else if (num == Numeric::RPL_TOPICBY) {
|
|
const auto channel = QString::fromStdString(args[1]);
|
|
auto* subwin = MdiManager::instance().findWindow(&getStatus(), channel);
|
|
if (!ignoreVerbosity(num)) {
|
|
const auto ts = QString::fromStdString(args[3]);
|
|
const auto dt = QDateTime::fromMSecsSinceEpoch(ts.toLongLong() * 1000ll);
|
|
if (subwin)
|
|
subwin->print(PrintType::Topic, tr("Topic set by: %1 (%2)")
|
|
.arg(QString::fromStdString(args[2]))
|
|
.arg(dt.toString()));
|
|
else
|
|
getStatus().print(PrintType::Topic, tr("Topic for %1 set by: %2 (%3)")
|
|
.arg(QString::fromStdString(args[1]))
|
|
.arg(QString::fromStdString(args[2]))
|
|
.arg(dt.toString()));
|
|
}
|
|
}
|
|
|
|
else if (num == Numeric::RPL_CHANNELMODEIS) {
|
|
const auto channel = QString::fromStdString(args[1]);
|
|
auto* subwin = MdiManager::instance().findWindow(&getStatus(), channel);
|
|
if (subwin) {
|
|
subwin->refreshWindowTitle();
|
|
}
|
|
|
|
if (!ignoreVerbosity(num)) {
|
|
auto chan = getChannel(args[1]);
|
|
const auto modes = concatenateModes(chan->modes());
|
|
if (!modes.empty()) {
|
|
if (subwin)
|
|
subwin->print(PrintType::Mode, tr("Modes: +%1").arg(QString::fromStdString(modes)));
|
|
else
|
|
getStatus().print(PrintType::Mode, tr("Modes for %1: +%2")
|
|
.arg(QString::fromStdString(args[1]))
|
|
.arg(QString::fromStdString(modes)));
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (num == Numeric::RPL_CREATION) {
|
|
const auto channel = QString::fromStdString(args[1]);
|
|
auto* subwin = MdiManager::instance().findWindow(&getStatus(), channel);
|
|
if (subwin) {
|
|
subwin->refreshWindowTitle();
|
|
}
|
|
|
|
if (!ignoreVerbosity(num)) {
|
|
const auto ts = QString::fromStdString(args[2]);
|
|
const auto dt = QDateTime::fromMSecsSinceEpoch(ts.toLongLong() * 1000ll);
|
|
if (subwin)
|
|
subwin->print(PrintType::Topic, tr("Channel created at %1").arg(dt.toString()));
|
|
else
|
|
getStatus().print(PrintType::Topic, tr("%1 created at %2")
|
|
.arg(QString::fromStdString(args[1]))
|
|
.arg(dt.toString()));
|
|
}
|
|
}
|
|
|
|
else if (!ignoreVerbosity(num)) {
|
|
if (num == Numeric::RPL_WHOISUSER) {
|
|
// This is always the first message received in /WHOIS response.
|
|
auto& conf = ConfigMgr::instance();
|
|
m_receiveWhoisToCurrentWindow = conf.common("ShowWhoisActiveWindow").toInt();
|
|
IWin* window = m_receiveWhoisToCurrentWindow ? getStatus().getActiveWindow()
|
|
: &getStatus();
|
|
|
|
std::string msg = fmt::format("{}: {}@{}: {}", args[1], args[2], args[3], message);
|
|
window->print(PrintType::Normal, msg.c_str());
|
|
}
|
|
|
|
else if (num == Numeric::RPL_WHOISSERVER) {
|
|
IWin* window = m_receiveWhoisToCurrentWindow ? getStatus().getActiveWindow()
|
|
: &getStatus();
|
|
|
|
std::string msg = fmt::format("{}: {} ({})", args[1], args[2], message);
|
|
window->print(PrintType::Normal, msg.c_str());
|
|
}
|
|
|
|
else if (num == Numeric::RPL_WHOISIDLE) {
|
|
IWin* window = m_receiveWhoisToCurrentWindow ? getStatus().getActiveWindow()
|
|
: &getStatus();
|
|
auto list = QString::fromStdString(message).split(", ");
|
|
std::string msg = fmt::format("{}:", args[1]);
|
|
int i = 2; // Relevant information starts on the 2nd argument
|
|
for (const auto& il : list) {
|
|
auto item = il.toLower();
|
|
auto arg = QString::fromStdString(args[i]);
|
|
if (item == "seconds idle") {
|
|
quint64 now = QDateTime::currentSecsSinceEpoch();
|
|
quint64 seconds = arg.toLongLong();
|
|
QString date = QDateTime::fromTime_t(now-seconds).toString();
|
|
msg.append(fmt::format(" Last active: {}", date.toStdString()));
|
|
}
|
|
else if (item == "signon time") {
|
|
quint64 seconds = arg.toLongLong();
|
|
QString date = QDateTime::fromTime_t(seconds).toString();
|
|
msg.append(fmt::format(" Signed on: {}", date.toStdString()));
|
|
}
|
|
++i;
|
|
}
|
|
window->print(PrintType::Normal, msg.c_str());
|
|
}
|
|
|
|
else if (num == Numeric::RPL_WHOISLOGGEDIN) {
|
|
IWin* window = m_receiveWhoisToCurrentWindow ? getStatus().getActiveWindow()
|
|
: &getStatus();
|
|
|
|
std::string msg = fmt::format("{}: {} {}", args[1], message, args[2]);
|
|
window->print(PrintType::Normal, msg.c_str());
|
|
}
|
|
|
|
else if (num == Numeric::RPL_LISTSTART) {
|
|
getStatus().print(PrintType::ProgramInfo, "Listing public channels...");
|
|
}
|
|
|
|
else if (num == Numeric::RPL_LIST) {
|
|
std::string msg = fmt::format("{} [{} users] {}", args[1], args[2], message);
|
|
getStatus().print(PrintType::Normal, QString::fromStdString(msg));
|
|
}
|
|
|
|
else {
|
|
IWin* window = nullptr;
|
|
|
|
if (m_receiveWhoisToCurrentWindow) {
|
|
window = getStatus().getActiveWindow();
|
|
}
|
|
else {
|
|
if (args.size() > 1)
|
|
window = MdiManager::instance().findWindow(&getStatus(), QString::fromStdString(args[1]));
|
|
if (!window)
|
|
window = &getStatus();
|
|
}
|
|
|
|
if (message.empty())
|
|
window->print(PrintType::Normal, argmsg.c_str());
|
|
else if (argmsg.empty())
|
|
window->print(PrintType::Normal, message.c_str());
|
|
else {
|
|
std::string msg = fmt::format("{}: {}", argmsg, message);
|
|
window->print(PrintType::Normal, msg.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Numeric responses handled separately from message displaying
|
|
*/
|
|
if (num == Numeric::ERR_NICKNAMEINUSE && !isOnline()) {
|
|
auto& conf = ConfigMgr::instance();
|
|
auto altNick = conf.connection("AltNickname");
|
|
if (!altNick.isEmpty() && altNick.toStdString() != getNickname()) {
|
|
setNickname(altNick.toStdString());
|
|
}
|
|
else {
|
|
getStatus().getInputBox().setText("/NICK ");
|
|
}
|
|
}
|
|
|
|
else if (num == Numeric::ERR_ERRORNEUSNICKNAME && !isOnline()) {
|
|
getStatus().getInputBox().setText("/NICK ");
|
|
}
|
|
|
|
else if (num == Numeric::RPL_ENDOFWHOIS) {
|
|
m_receiveWhoisToCurrentWindow = false;
|
|
}
|
|
|
|
/* Script event */
|
|
{
|
|
ScriptArray pval;
|
|
int i = 0;
|
|
for (const std::string& arg : args) {
|
|
const auto key = std::to_string(i);
|
|
pval.emplace(key, arg);
|
|
++i;
|
|
}
|
|
pval.emplace(LIST_END_MAGIC, i);
|
|
contextualScriptEvent(&m_status, ScriptEvent::Numeric, num, pval, message);
|
|
}
|
|
}
|
|
|
|
void IRC::onMsgCTCPRequest(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message)
|
|
{
|
|
const std::string& chantypes = isupport().at("CHANTYPES");
|
|
|
|
if (command == "ACTION") {
|
|
auto msg = fmt::format("{} {}", sender.toString(), message);
|
|
IWin* win = MdiManager::instance().findWindow(&getStatus(), QString::fromStdString(target));
|
|
if (!win) {
|
|
if (std::find(chantypes.begin(), chantypes.end(), target[0]) == chantypes.end())
|
|
win = getStatus().createPrivateWindow(QString::fromStdString(sender.toString()), false);
|
|
else {
|
|
msg = fmt::format("({}) {} {}", target, sender.toString(), message);
|
|
win = &getStatus();
|
|
}
|
|
}
|
|
win->print(PrintType::Action, msg.c_str());
|
|
}
|
|
else if (!ignoreVerbosity(Command::Internal::CTCP)) {
|
|
std::string printmsg;
|
|
std::string cmdMsg;
|
|
if (!message.empty() && command != "PING")
|
|
cmdMsg = fmt::format("{} {}", command, message);
|
|
else
|
|
cmdMsg = command;
|
|
|
|
/* Sent private */
|
|
if (std::find(chantypes.begin(), chantypes.end(), target[0]) == chantypes.end()) {
|
|
printmsg = fmt::format("[CTCP {}] from {}", cmdMsg, sender.nickname());
|
|
}
|
|
|
|
/* Sent to channel */
|
|
else {
|
|
printmsg = fmt::format("[CTCP {}]:{} from {}", cmdMsg, target, sender.nickname());
|
|
}
|
|
|
|
getStatus().print(PrintType::CTCP, printmsg.c_str());
|
|
}
|
|
|
|
if (command == "VERSION") {
|
|
ctcpResponse(sender.nickname(), "VERSION",
|
|
fmt::format("IdealIRC {} by Tomatix - http://www.idealirc.org/", VERSION_STRING));
|
|
}
|
|
else if (command == "PING") {
|
|
ctcpResponse(sender.toString(), command, message);
|
|
}
|
|
|
|
contextualScriptEvent(&m_status, ScriptEvent::CtcpRequest, sender.toString(), target, command, message);
|
|
}
|
|
|
|
void IRC::onMsgCTCPResponse(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message)
|
|
{
|
|
std::string msg = message;
|
|
|
|
if (command == "PING") {
|
|
const qint64 curr = QDateTime::currentMSecsSinceEpoch();
|
|
const qint64 prev = std::stoll(message);
|
|
const qint64 diffMs = curr - prev;
|
|
msg = fmt::format("{}ms", diffMs);
|
|
}
|
|
|
|
if (!ignoreVerbosity(Command::Internal::CTCP)) {
|
|
std::string printmsg;
|
|
if (message.empty())
|
|
printmsg = fmt::format("[CTCP {}] reply from {}", command, sender.nickname());
|
|
else
|
|
printmsg = fmt::format("[CTCP {}] reply from {}: {}", command, sender.nickname(), msg);
|
|
|
|
auto* win = getStatus().getActiveWindow();
|
|
win->print(PrintType::CTCP, printmsg.c_str());
|
|
}
|
|
contextualScriptEvent(&m_status, ScriptEvent::CtcpReply, sender.toString(), target, command, msg);
|
|
}
|
|
|
|
void IRC::onMsgDCCRequest(std::shared_ptr<DCC> dcc, const IRCPrefix& sender, const std::string& target, const std::string& type, const std::string& message)
|
|
{
|
|
|
|
}
|
|
|
|
void IRC::v3onMsgAway(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected)
|
|
{
|
|
if (!ignoreVerbosity(Command::IRC::AWAY)) {
|
|
std::string msg;
|
|
if (message.empty())
|
|
msg = fmt::format("{} is no longer away", sender.nickname(), message);
|
|
else
|
|
msg = fmt::format("{} is now away: {}", sender.nickname(), message);
|
|
|
|
if (channelsAffected.empty())
|
|
getStatus().print(PrintType::ServerInfo, msg.c_str());
|
|
|
|
for (const auto& channel : channelsAffected)
|
|
getStatus().printTo(channel.c_str(), PrintType::ServerInfo, msg.c_str());
|
|
}
|
|
contextualScriptEvent(&m_status, ScriptEvent::Away, sender.toString(), message);
|
|
}
|
|
|
|
void IRC::v3onMsgJoin(const IRCPrefix& sender, const std::string& channel, const std::string& useraccount, const std::string& realname)
|
|
{
|
|
onMsgJoin(sender, channel);
|
|
}
|
|
|