The complete source code of IdealIRC http://www.idealirc.org/
 
 
 
 
idealirc/IdealIRC/IRC.cpp

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);
}