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

495 lines
16 KiB

#include "IRC.h"
#include "MdiManager.h"
#include "IRCClient/IRCMember.h"
#include "IRCClient/IRCMemberEntry.h"
#include "IRCClient/IRCChannel.h"
#include "IWin/IWinStatus.h"
#include "IWin/IWinChannel.h"
#include "MdiManager.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 <QDebug>
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.";
}
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::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()
{
const auto& config = ConfigMgr::instance();
if (config.common("RejoinChannelsOnConnect") == "1")
rejoinChannels();
}
void IRC::onConnected()
{
emit connected();
contextualScriptEvent(&m_status, ScriptEvent::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);
}
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)
{
for (const auto& channelName : channelsAffected) {
auto channel = getChannel(channelName);
auto memberOpt = channel->getMember(sender.nickname());
if (!memberOpt)
continue;
if (!ignoreVerbosity(Command::IRC::NICK)) {
const std::string msg = fmt::format("{} is now known as {}", sender.nickname(), newNickname);
m_status.printTo(channelName.c_str(), PrintType::ServerInfo, msg.c_str());
}
const auto& member = memberOpt->get();
emit memberChanged(sender.nickname().c_str(), member);
}
contextualScriptEvent(&m_status, ScriptEvent::Nick, sender.nickname(), 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::ServerInfo, 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);
}
}
}
if (!ignoreVerbosity(Command::IRC::MODE)) {
/* We changed our own usermode */
if (sender.toString() == target && target == getNickname()) {
const std::string msg = fmt::format("Your usermode changed: {}", modemsg);
getStatus().print(PrintType::ServerInfo, 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::ServerInfo, 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)
{
for (const auto& channelName : channelsAffected) {
auto channel = getChannel(channelName);
auto memberOpt = channel->getMember(sender.nickname());
if (!memberOpt)
continue;
if (!ignoreVerbosity(Command::IRC::QUIT)) {
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());
m_status.printTo(channelName.c_str(), PrintType::ServerInfo, 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();
}
/* 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::ServerInfo, 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::ServerInfo, 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("{} set the topic to: {}", sender.toString(), topic);
else
msg = fmt::format("{} removed the topic", sender.toString());
getStatus().printTo(target.c_str(), PrintType::ServerInfo, msg.c_str());
}
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);
getStatus().printToActive(PrintType::ServerInfo, msg.c_str());
}
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 memberListReloaded(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))
getStatus().printTo(target.c_str(), PrintType::ServerInfo, msg.c_str());
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;
const std::string msg = fmt::format("<{}> {}", sender.toString(), message);
/* Message to a channel */
if (chantypes.find_first_of(target[0]) != std::string::npos)
getStatus().printTo(target.c_str(), ptype, msg.c_str());
/* Private message */
else {
auto* window = getStatus().createPrivateWindow(sender.toString().c_str(), false);
window->print(ptype, msg.c_str());
}
}
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)) {
const std::string msg = fmt::format("-{}- {}", sender.toString(), message);
getStatus().printToActive(PrintType::Notice, msg.c_str());
}
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());
}
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());
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());
}
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 (!ignoreVerbosity(num)) {
if (message.empty())
getStatus().print(PrintType::Normal, argmsg.c_str());
else if (argmsg.empty())
getStatus().print(PrintType::Normal, message.c_str());
else {
std::string msg = fmt::format("{}: {}", argmsg, message);
getStatus().print(PrintType::Normal, msg.c_str());
}
}
if (num == Numeric::RPL_ENDOFNAMES) {
emit memberListReloaded(args[1].c_str());
}
/* Script event */
{
ScriptArray pval;
int i = 0;
for (const std::string& arg : args) {
const std::string 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)
{
if (!ignoreVerbosity(Command::Internal::CTCP)) {
std::string msg;
const std::string& chantypes = isupport().at("CHANTYPES");
/* Sent private */
if (std::find(chantypes.begin(), chantypes.end(), target[0]) == chantypes.end()) {
msg = fmt::format("[CTCP {}] from {}", command, sender.nickname());
}
/* Sent to channel */
else {
msg = fmt::format("[CTCP {}]:{} from {}", command, target, sender.nickname());
}
auto* win = getStatus().getActiveWindow();
win->print(PrintType::CTCP, msg.c_str());
}
if (command == "VERSION") {
ctcp(sender.nickname(), "VERSION", fmt::format("IdealIRC {} by Tomatix - http://idealirc.org/", VERSION_STRING));
}
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)
{
if (!ignoreVerbosity(Command::Internal::CTCP)) {
const auto msg = fmt::format("[CTCP {}] reply from {}: {}", command, sender.nickname(), message);
auto* win = getStatus().getActiveWindow();
win->print(PrintType::CTCP, msg.c_str());
}
contextualScriptEvent(&m_status, ScriptEvent::CtcpReply, sender.toString(), target, command, message);
}
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)
{
std::string msg;
if (message.empty()) {
msg = fmt::format("{} is now away: {}", sender.nickname(), message);
}
else {
msg = fmt::format("{} is no longer away", sender.nickname(), message);
}
for (const auto& channel : channelsAffected)
getStatus().printTo(channel.c_str(), PrintType::ServerInfo, msg.c_str());
}
void IRC::v3onMsgJoin(const IRCPrefix& sender, const std::string& channel, const std::string& useraccount, const std::string& realname)
{
onMsgJoin(sender, channel);
}