The complete source code of IdealIRC
http://www.idealirc.org/
495 lines
16 KiB
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);
|
|
}
|
|
|