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

692 lines
17 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 "ICommand.h"
#include "Commands.h"
#include "IRCClient/IRCMember.h"
#include "IRCClient/IRCChannel.h"
#include "MdiManager.h"
#include "Script/Manager.h"
#include "fmt/format.h"
#include "ICommandPriv.h"
#include <algorithm>
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
#define QT_SPLIT_SKIP_EMPTY_PARTS QString::SkipEmptyParts
#else
#define QT_SPLIT_SKIP_EMPTY_PARTS Qt::SkipEmptyParts
#endif
ICommand::ICommand(IRC& connection_)
: mp(new ICommandPriv(*this, connection_, connection_.getStatus()))
{}
ICommand::~ICommand() = default;
void ICommand::parse(QString text)
{
auto& connection = mp->connection;
if (text.isEmpty())
return;
if (text[0] == '/')
text = text.mid(1);
if (tryParseIRC(text) || tryParseInternal(text))
return;
else if (ScriptManager::instance()->runCommand(text))
return;
else if (!connection.isOnline()) {
const auto command = text.split(' ')[0];
print_notConnectedToServer(command);
}
else {
const int nextSpace = text.indexOf(' ');
if (nextSpace == -1)
connection.raw(text.toUpper().toStdString());
else {
QString command = text.mid(0, nextSpace).toUpper();
QString out = QString("%1 %2")
.arg(command)
.arg(text.mid(nextSpace + 1));
connection.raw(out.toStdString());
}
}
}
IRC& ICommand::getConnection()
{
return mp->connection;
}
bool ICommand::tryParseIRC(QString cmdLine)
{
auto& connection = mp->connection;
int spaceidx = cmdLine.indexOf(' ');
spaceidx = spaceidx > -1 ? spaceidx : cmdLine.length();
const QString command = cmdLine.mid(0, spaceidx).toUpper();
cmdLine.remove(0, spaceidx);
using namespace Command::IRC; // For the string constants below.
if (command == PASS) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(PASS, *argv[0]);
return true;
}
else if (command == NICK) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!argv[0])
print_notEnoughParameters(command);
else {
if (!connection.isOnline())
connection.setNickname(*argv[0]);
connection.command(NICK, *argv[0]);
}
return true;
}
else if (command == OPER) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (argv.size() < 2)
print_notEnoughParameters(command);
else
connection.command(OPER, { *argv[0], *argv[1] });
return true;
}
else if (command == MODE) {
if (!connection.isOnline())
print_notConnectedToServer(command);
else {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message });
connection.raw(fmt::format("{} {}", MODE, argv.joinString(' ')));
}
return true;
}
else if (command == QUIT) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
connection.expectDisconnect();
connection.command(QUIT, *argv[0]);
return true;
}
else if (command == SQUIT) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(SQUIT, { *argv[0] }, *argv[1]);
return true;
}
else if (command == JOIN) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(JOIN, { *argv[0], argv[1].value_or("") });
return true;
}
else if (command == PART) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(PART, { *argv[0] }, argv[1].value_or(""));
return true;
}
else if (command == TOPIC) {
CommandData argv(cmdLine, { &ICommand::prd_Switch, &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[1])
print_notEnoughParameters(command);
else {
if (argv[0].value_or("") == "-c") {
connection.command(TOPIC, { *argv[1] }, "");
return true;
}
if (argv[2])
connection.command(TOPIC, { *argv[1] }, *argv[2]);
else
connection.command(TOPIC, { *argv[1] });
}
return true;
}
else if (command == NAMES) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(NAMES, { *argv[0] });
return true;
}
else if (command == LIST) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(LIST, { argv[0].value_or("") });
return true;
}
else if (command == INVITE) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0] || !argv[1])
print_notEnoughParameters(command);
else
connection.command(INVITE, { *argv[0], *argv[1] });
return true;
}
else if (command == KICK) {
CommandData argv(cmdLine, { &ICommand::prd_OptionalChannel, &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[1])
print_notEnoughParameters(command);
else
mp->cmd_kick(*argv[0], *argv[1], argv[2].value_or(""));
return true;
}
else if (command == PRIVMSG || command == Command::Internal::MSG) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
mp->cmd_privmsg(*argv[0], argv[1].value_or(""));
return true;
}
else if (command == NOTICE) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
mp->cmd_notice(*argv[0], argv[1].value_or(""));
return true;
}
else if (command == MOTD) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(MOTD, { argv[0].value_or("") });
return true;
}
else if (command == LUSERS) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(LUSERS, { argv[0].value_or("") });
return true;
}
else if (command == VERSION) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(VERSION, { argv[0].value_or("") });
return true;
}
else if (command == STATS) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(STATS, { argv[0].value_or("") });
return true;
}
else if (command == LINKS) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(LINKS, { argv[0].value_or("") });
return true;
}
else if (command == TIME) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(TIME, { argv[0].value_or("") });
return true;
}
else if (command == CONNECT) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(CONNECT, { argv[0].value_or("") });
return true;
}
else if (command == TRACE) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(TRACE, { argv[0].value_or("") });
return true;
}
else if (command == ADMIN) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(ADMIN, { argv[0].value_or("") });
return true;
}
else if (command == INFO) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(INFO, { argv[0].value_or("") });
return true;
}
else if (command == SERVLIST) {
}
else if (command == SQUERY) {
}
else if (command == WHO) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else
connection.command(WHO, { argv[0].value_or("") });
return true;
}
else if (command == WHOIS) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(WHOIS, { *argv[0] });
return true;
}
else if (command == WHOWAS) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(WHOWAS, { *argv[0] });
return true;
}
else if (command == KILL) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0]) {
print_notEnoughParameters(command);
return true;
}
connection.command(KILL, { *argv[0] }, argv[1].value_or(""));
return true;
}
else if (command == PING) {
}
else if (command == PONG) {
}
else if (command == AWAY) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (argv[0])
connection.command(AWAY, *argv[0]);
else
connection.raw(AWAY);
return true;
}
else if (command == REHASH) {
}
else if (command == DIE) {
}
else if (command == RESTART) {
}
else if (command == SUMMON) {
}
else if (command == USERS) {
}
else if (command == WALLOPS) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(WALLOPS, *argv[0]);
return true;
}
else if (command == USERHOST) {
}
else if (command == ISON) {
}
return false;
}
bool ICommand::tryParseInternal(QString cmdLine)
{
auto& connection = mp->connection;
auto& status = mp->status;
int spaceidx = cmdLine.indexOf(' ');
spaceidx = spaceidx > -1 ? spaceidx : cmdLine.length();
const QString command = cmdLine.mid(0, spaceidx).toUpper();
cmdLine.remove(0, spaceidx);
using namespace Command::Internal;
using namespace Command::IRC;
if (command == CTCP || command == CTCPREPLY) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWordToUpper, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0] || !argv[1])
print_notEnoughParameters(command);
else {
if (command == CTCP)
mp->cmd_ctcp(*argv[0], *argv[1], argv[2].value_or(""));
else
mp->cmd_ctcpreply(*argv[0], *argv[1], argv[2].value_or(""));
}
return true;
}
else if (command == ME) {
IWin* cw = status.getActiveWindow();
if (cw->getType() != IWin::Type::Channel && cw->getType() != IWin::Type::Private) {
print_invalidWindowTypeForCommand(command);
return true;
}
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else {
const auto target = cw->getButtonText().toStdString();
mp->cmd_me(target, argv[0].value_or(""));
}
return true;
}
else if (command == ECHO) {
CommandData argv(cmdLine, { &ICommand::prd_Switch, &ICommand::prd_Switch, &ICommand::prd_Message });
if (!argv[2]) {
print_notEnoughParameters(command);
return true;
}
std::string arg1, arg2;
if (argv[0])
arg1 = *argv[0];
if (argv[1])
arg2 = *argv[1];
mp->cmd_echo(arg1, arg2, *argv[2]);
return true;
}
else if (command == QUERY) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0])
print_notEnoughParameters(command);
else
status.createPrivateWindow(argv[0]->c_str());
return true;
}
else if (command == MUTERESP) {
QStringList list = cmdLine.split(' ', QT_SPLIT_SKIP_EMPTY_PARTS);
if (list.isEmpty()) {
print_notEnoughParameters(command);
return true;
}
connection.setIgnoreVerbosity(list);
return true;
}
else if (command == UNMUTERESP) {
QStringList list = cmdLine.split(' ', QT_SPLIT_SKIP_EMPTY_PARTS);
if (list.isEmpty()) {
print_notEnoughParameters(command);
return true;
}
connection.unsetIgnoreVerbosity(list);
return true;
}
else if (command == CLEAR) {
auto& mdi = MdiManager::instance();
mdi.currentWindow()->clear();
return true;
}
else if (command == QUOTE || command == RAW) {
CommandData argv(cmdLine, { &ICommand::prd_Message });
if (!argv[0]) {
print_notEnoughParameters(command);
return true;
}
// don't do "isOnline" since BNCs asks to do "/quote pass blah" and such before connection registration.
// ... as well as other services that would require something similar.
if (!connection.isConnected()) {
print_notConnectedToServer(command);
return true;
}
connection.raw(*argv[0]);
return true;
}
else if (command == SERVER) {
CommandData argv(cmdLine, { &ICommand::prd_Switch, &ICommand::prd_AnyWord });
if (!argv[1]) {
print_notEnoughParameters(command);
return true;
}
bool ssl{ false };
bool newstatus{ false };
bool activate{ false };
if (argv[0]) {
ssl = argv[0]->find('s') != std::string::npos;
newstatus = argv[0]->find('n') != std::string::npos;
activate = argv[0]->find('a') != std::string::npos;
}
QStringList serverport = QString::fromStdString(*argv[1]).split(':');
auto host = serverport[0].toStdString();
auto port = serverport.size() > 1 ? serverport[1].toStdString() : "6667";
mp->cmd_server(ssl, newstatus, activate, host, port);
return true;
}
return false;
}
void ICommand::print_notEnoughParameters(const QString& command)
{
const auto& mdi = MdiManager::instance();
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, tr("/%1: Not enough parameters.").arg(command.toLower()));
}
void ICommand::print_invalidCommandSwitch(const QString& command)
{
const auto& mdi = MdiManager::instance();
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, tr("/%1: Invalid command switch.").arg(command.toLower()));
}
void ICommand::print_invalidWindowTypeForCommand(const QString& command)
{
const auto& mdi = MdiManager::instance();
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, tr("/%1: Invalid window type for that command.").arg(command.toLower()));
}
void ICommand::print_notConnectedToServer(const QString& command)
{
const auto& mdi = MdiManager::instance();
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, tr("/%1: Not connected to a server.").arg(command.toLower()));
}
std::optional<std::string> ICommand::prd_AnyWord(int& idx, const QString& line)
{
std::string ret;
std::string stdline = line.toStdString();
for (; idx < stdline.size(); ++idx) {
const char c = stdline[idx];
if (c == ' ')
break;
ret += c;
}
if (ret.empty())
return {};
return ret;
}
std::optional<std::string> ICommand::prd_AnyWordToUpper(int& idx, const QString& line)
{
auto ret = prd_AnyWord(idx, line);
if (ret) {
std::transform(ret->begin(), ret->end(), ret->begin(),
[](unsigned char c) -> unsigned char { return std::toupper(c); });
}
return ret;
}
std::optional<std::string> ICommand::prd_OptionalChannel(int& idx, const QString& line)
{
const auto& mdi = MdiManager::instance();
const auto& isupport = mdi.currentStatus()->getConnection().isupport();
const auto& chantypes = isupport.at("CHANTYPES");
if (chantypes.find_first_of(line.toStdString()[idx]) == std::string::npos)
return std::nullopt;
return prd_AnyWord(idx, line);
}
std::optional<std::string> ICommand::prd_NotSwitch(int& idx, const QString& line)
{
if (line[idx] == '-' || line[idx] == '+')
return std::nullopt;
return prd_AnyWord(idx, line);
}
std::optional<std::string> ICommand::prd_Switch(int& idx, const QString& line)
{
if (line[idx] != '-' && line[idx] != '+')
return std::nullopt;
return prd_AnyWord(idx, line);
}
std::optional<std::string> ICommand::prd_Message(int& idx, const QString& line)
{
QString ret = line.mid(idx);
if (ret.isEmpty())
return std::nullopt;
return ret.toStdString();
}