Compare commits
81 Commits
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (c) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "ICommand/ICommandPriv.h" |
||||
#include "IRCClient/DCC.h" |
||||
#include <fmt/format.h> |
||||
|
||||
void ICommandPriv::cmd_dcc(const std::string& command, const std::string& target, const std::string& message) |
||||
{ |
||||
auto& mdi = MdiManager::instance(); |
||||
|
||||
if (command == "CHAT") { |
||||
auto result = connection.initiateDCC(); |
||||
if (result.second != IRCError::NoError) { |
||||
const auto code = static_cast<int>(result.second); |
||||
const auto errorStr = QString::fromStdString(IRCErrorToString(result.second)); |
||||
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, QObject::tr("/DCC: Unable to initiate DCC Chat to %1 [%2 (%3)]") |
||||
.arg( QString::fromStdString(target), errorStr, QString::number(code)) ); |
||||
} |
||||
else { |
||||
auto dcc = result.first; |
||||
const auto portno = dcc->isReversed() ? 0 |
||||
: dcc->port(); |
||||
|
||||
auto* window = mdi.createDCCSubwindow(mdi.currentStatus(), IWin::Type::DCCChat, result.first, IRCPrefix(target)); |
||||
|
||||
if (dcc->isReversed()) |
||||
window->print(PrintType::ProgramInfo, QObject::tr("Initiating CHAT with %1, please wait...") |
||||
.arg( QString::fromStdString(target) )); |
||||
else |
||||
window->print(PrintType::ProgramInfo, QObject::tr("Initiating CHAT with %1 on port %2, please wait...") |
||||
.arg( QString::fromStdString(target) ) |
||||
.arg(portno)); |
||||
|
||||
const auto myIp = findOwnIpAddress(); |
||||
const auto msg = fmt::format( "CHAT chat {} {}", |
||||
normalIpToLong(myIp), |
||||
portno |
||||
); |
||||
|
||||
cmd_ctcp(target, "DCC", msg); |
||||
} |
||||
} |
||||
else { |
||||
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, QObject::tr("/DCC: Unknown DCC method: %1") |
||||
.arg( QString::fromStdString(command) )); |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "AddServer.h" |
||||
#include "ui_AddServer.h" |
||||
#include <QFile> |
||||
#include <QJsonDocument> |
||||
#include <QJsonObject> |
||||
#include <QJsonArray> |
||||
|
||||
AddServer::AddServer(const QStringList& networkList, QWidget *parent) : |
||||
QDialog(parent), |
||||
ui(new Ui::AddServer) |
||||
{ |
||||
ui->setupUi(this); |
||||
setAttribute(Qt::WA_DeleteOnClose); |
||||
|
||||
auto EnableOrDisableSave = [this](const QString&) { |
||||
auto isDisabled = ui->edName->text().isEmpty() || ui->edAddress->text().isEmpty(); |
||||
ui->btnSave->setEnabled(!isDisabled); |
||||
}; |
||||
|
||||
connect(ui->edName, &QLineEdit::textChanged, EnableOrDisableSave); |
||||
connect(ui->edAddress, &QLineEdit::textChanged, EnableOrDisableSave); |
||||
|
||||
for (const auto& networkName : networkList) |
||||
ui->edNetwork->addItem(networkName); |
||||
} |
||||
|
||||
AddServer::~AddServer() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
QString AddServer::name() const |
||||
{ |
||||
return ui->edName->text(); |
||||
} |
||||
|
||||
QString AddServer::address() const |
||||
{ |
||||
return ui->edAddress->text(); |
||||
} |
||||
|
||||
QString AddServer::password() const |
||||
{ |
||||
return ui->edPassword->text(); |
||||
} |
||||
|
||||
QString AddServer::sasl() const |
||||
{ |
||||
return ui->edSASL->text(); |
||||
} |
||||
|
||||
bool AddServer::ssl() const |
||||
{ |
||||
return ui->chkSSL->isChecked(); |
||||
} |
||||
|
||||
bool AddServer::isServer() const |
||||
{ |
||||
return ui->rdServer->isChecked(); |
||||
} |
||||
|
||||
int AddServer::networkIndex() const |
||||
{ |
||||
// 0th index is the "No network" selection, make that into a "-1" index instead... and the rest must also follow.
|
||||
return ui->edNetwork->currentIndex() - 1; |
||||
} |
||||
|
||||
void AddServer::on_btnCancel_clicked() |
||||
{ |
||||
close(); |
||||
} |
||||
|
||||
void AddServer::on_btnSave_clicked() |
||||
{ |
||||
emit saved(); |
||||
close(); |
||||
} |
||||
|
||||
void AddServer::on_rdNetwork_toggled(bool checked) |
||||
{ |
||||
ui->edNetwork->setEnabled(!checked); |
||||
|
||||
// Network servers (child servers) uses their parent server's SASL.
|
||||
// The individual child servers will not have own SASL credentials.
|
||||
ui->edSASL->setEnabled(!checked && ui->edNetwork->currentIndex() == 0 || checked); |
||||
} |
||||
|
||||
void AddServer::on_edNetwork_currentIndexChanged(int index) |
||||
{ |
||||
ui->edSASL->setEnabled(index == 0); |
||||
} |
@ -0,0 +1,46 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#ifndef ADDSERVER_H |
||||
#define ADDSERVER_H |
||||
|
||||
#include <QDialog> |
||||
|
||||
namespace Ui { |
||||
class AddServer; |
||||
} |
||||
|
||||
class AddServer : public QDialog |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit AddServer(const QStringList& networkList, QWidget *parent = nullptr); |
||||
~AddServer(); |
||||
|
||||
QString name() const; |
||||
QString address() const; |
||||
QString password() const; |
||||
QString sasl() const; |
||||
bool ssl() const; |
||||
bool isServer() const; |
||||
int networkIndex() const; |
||||
|
||||
signals: |
||||
void saved(); |
||||
|
||||
private slots: |
||||
void on_btnCancel_clicked(); |
||||
void on_btnSave_clicked(); |
||||
void on_rdNetwork_toggled(bool checked); |
||||
void on_edNetwork_currentIndexChanged(int index); |
||||
|
||||
private: |
||||
Ui::AddServer *ui; |
||||
}; |
||||
|
||||
#endif // ADDSERVER_H
|
@ -0,0 +1,183 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>AddServer</class> |
||||
<widget class="QDialog" name="AddServer"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>400</width> |
||||
<height>200</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Add server</string> |
||||
</property> |
||||
<property name="windowIcon"> |
||||
<iconset resource="../Resources/resources.qrc"> |
||||
<normaloff>:/Icons/options.png</normaloff>:/Icons/options.png</iconset> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
<item> |
||||
<widget class="QRadioButton" name="rdNetwork"> |
||||
<property name="text"> |
||||
<string>Network</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QRadioButton" name="rdServer"> |
||||
<property name="text"> |
||||
<string>Server</string> |
||||
</property> |
||||
<property name="checked"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QComboBox" name="edNetwork"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="placeholderText"> |
||||
<string/> |
||||
</property> |
||||
<item> |
||||
<property name="text"> |
||||
<string><No network></string> |
||||
</property> |
||||
</item> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_2"> |
||||
<item> |
||||
<widget class="QLabel" name="label"> |
||||
<property name="text"> |
||||
<string>Name</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edName"/> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_3"> |
||||
<item> |
||||
<widget class="QLabel" name="label_2"> |
||||
<property name="text"> |
||||
<string>Address</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edAddress"/> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkSSL"> |
||||
<property name="text"> |
||||
<string>SSL</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_4"> |
||||
<item> |
||||
<widget class="QLabel" name="label_3"> |
||||
<property name="text"> |
||||
<string>Password</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edPassword"> |
||||
<property name="echoMode"> |
||||
<enum>QLineEdit::Password</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_6"> |
||||
<item> |
||||
<widget class="QLabel" name="label_4"> |
||||
<property name="text"> |
||||
<string>SASL credential</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edSASL"> |
||||
<property name="echoMode"> |
||||
<enum>QLineEdit::Password</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_5"> |
||||
<item> |
||||
<spacer name="horizontalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnCancel"> |
||||
<property name="text"> |
||||
<string>Cancel</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnSave"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Save</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<tabstops> |
||||
<tabstop>rdNetwork</tabstop> |
||||
<tabstop>rdServer</tabstop> |
||||
<tabstop>edNetwork</tabstop> |
||||
<tabstop>edName</tabstop> |
||||
<tabstop>edAddress</tabstop> |
||||
<tabstop>chkSSL</tabstop> |
||||
<tabstop>edPassword</tabstop> |
||||
<tabstop>edSASL</tabstop> |
||||
<tabstop>btnCancel</tabstop> |
||||
<tabstop>btnSave</tabstop> |
||||
</tabstops> |
||||
<resources> |
||||
<include location="../Resources/resources.qrc"/> |
||||
</resources> |
||||
<connections/> |
||||
</ui> |
@ -1,313 +0,0 @@ |
||||
/*
|
||||
* 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 "ServerEditor.h" |
||||
#include "ui_ServerEditor.h" |
||||
#include <QDebug> |
||||
#include <QMessageBox> |
||||
#include <QInputDialog> |
||||
|
||||
ServerEditor::ServerEditor(ServerModel& model, QWidget* parent) |
||||
: QDialog(parent) |
||||
, ui(new Ui::ServerEditor) |
||||
, smodel(model) |
||||
{ |
||||
ui->setupUi(this); |
||||
addMenu = new QMenu(this); |
||||
addServerAction = addMenu->addAction(tr("Server"), this, &ServerEditor::on_addServerAction_triggered); |
||||
addNetworkAction = addMenu->addAction(tr("Network"), this, &ServerEditor::on_addNetworkAction_triggered); |
||||
ui->btnAdd->setMenu(addMenu); |
||||
ui->serverView->setModel(&smodel); |
||||
ui->serverView->header()->setSectionResizeMode(QHeaderView::Interactive); |
||||
|
||||
QStringList networks = smodel.networkList(); |
||||
ui->edNetwork->addItem(""); |
||||
for (const QString& network : networks) { |
||||
if (network == "NONE") |
||||
continue; |
||||
ui->edNetwork->addItem(network); |
||||
} |
||||
} |
||||
|
||||
ServerEditor::~ServerEditor() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
void ServerEditor::on_addServerAction_triggered() |
||||
{ |
||||
QString network = "NONE"; |
||||
if (editMode == EditMode::Network) { |
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
network = spair.first; |
||||
} |
||||
else if (editMode == EditMode::Server) { |
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
if (current.parent().isValid()) { |
||||
auto spair = smodel.fromIndex(current.parent()); |
||||
network = spair.first; |
||||
} |
||||
} |
||||
|
||||
QString name; |
||||
QString host; |
||||
for (int i = 1 ;; ++i) { |
||||
name = QStringLiteral("New server %1").arg(i); |
||||
host = QStringLiteral("host%1.name:6667").arg(i); |
||||
QString det = smodel.details(name); |
||||
if (det.isEmpty()) |
||||
break; |
||||
} |
||||
QModelIndex idx = smodel.addServer(name, host, network); |
||||
selectItem(idx); |
||||
} |
||||
|
||||
void ServerEditor::on_addNetworkAction_triggered() |
||||
{ |
||||
QString name; |
||||
QString host; |
||||
for (int i = 1 ;; ++i) { |
||||
name = QStringLiteral("New network %1").arg(i); |
||||
host = QStringLiteral("irc%1.host.name:6667").arg(i); |
||||
QString det = smodel.details("DEFAULT", name); |
||||
if (det.isEmpty()) |
||||
break; |
||||
} |
||||
QModelIndex idx = smodel.addNetwork(name, host); |
||||
ui->edNetwork->addItem(name); |
||||
selectItem(idx); |
||||
} |
||||
|
||||
void ServerEditor::on_btnDel_clicked() |
||||
{ |
||||
if (editMode == EditMode::Off) |
||||
return; |
||||
|
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
QString network = spair.first; |
||||
QString name = spair.second; |
||||
|
||||
if (editMode == EditMode::Server) { |
||||
if (QMessageBox::question(this, tr("Delete server"), tr("Do you want to delete the server '%1'?").arg(name)) == QMessageBox::No) |
||||
return; |
||||
smodel.delServer(name, network); |
||||
} |
||||
|
||||
if (editMode == EditMode::Network) { |
||||
name = spair.first; |
||||
auto answer = QMessageBox::question(this, tr("Delete network"), tr("Do you want to delete the network '%1'?\nHitting 'Yes' will keep the servers as orphans.").arg(name), QMessageBox::Yes | QMessageBox::YesAll | QMessageBox::No); |
||||
if (answer == QMessageBox::No) |
||||
return; |
||||
bool keepServers = answer != QMessageBox::YesAll; |
||||
smodel.delNetwork(name, keepServers); |
||||
} |
||||
|
||||
ui->serverView->clearSelection(); |
||||
disableAll(); |
||||
} |
||||
|
||||
void ServerEditor::on_btnShowPassword_toggled(bool checked) |
||||
{ |
||||
ui->edPassword->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password); |
||||
} |
||||
|
||||
void ServerEditor::on_btnSave_clicked() |
||||
{ |
||||
if (editMode == EditMode::Off) |
||||
return; |
||||
|
||||
if (ui->edName->text().isEmpty()) { |
||||
QMessageBox::information(this, tr("Field missing"), tr("No name is specified.")); |
||||
return; |
||||
} |
||||
|
||||
if (ui->edHostname->text().isEmpty()) { |
||||
QMessageBox::information(this, tr("Field missing"), tr("No hostname is specified.")); |
||||
return; |
||||
} |
||||
|
||||
if (editMode == EditMode::Server) |
||||
saveServerEdit(); |
||||
|
||||
else if (editMode == EditMode::Network) |
||||
saveNetworkEdit(); |
||||
} |
||||
|
||||
void ServerEditor::enableForServer() |
||||
{ |
||||
enableAll(); |
||||
ui->lbNetwork->show(); |
||||
ui->edNetwork->show(); |
||||
editMode = EditMode::Server; |
||||
} |
||||
|
||||
void ServerEditor::enableForNetwork() |
||||
{ |
||||
enableAll(); |
||||
ui->lbNetwork->hide(); |
||||
ui->edNetwork->hide(); |
||||
editMode = EditMode::Network; |
||||
} |
||||
|
||||
void ServerEditor::enableAll() |
||||
{ |
||||
ui->edName->setEnabled(true); |
||||
ui->edHostname->setEnabled(true); |
||||
ui->edPort->setEnabled(true); |
||||
ui->edPassword->setEnabled(true); |
||||
ui->btnShowPassword->setEnabled(true); |
||||
ui->btnShowPassword->setChecked(false); |
||||
ui->edNetwork->setEnabled(true); |
||||
ui->btnSave->setEnabled(true); |
||||
} |
||||
|
||||
void ServerEditor::disableAll() |
||||
{ |
||||
ui->edName->clear(); |
||||
ui->edHostname->clear(); |
||||
ui->edPort->setValue(6667); |
||||
ui->edPassword->clear(); |
||||
ui->btnShowPassword->setChecked(false); |
||||
ui->edNetwork->clearEditText(); |
||||
|
||||
ui->edName->setEnabled(false); |
||||
ui->edHostname->setEnabled(false); |
||||
ui->edPort->setEnabled(false); |
||||
ui->edPassword->setEnabled(false); |
||||
ui->btnShowPassword->setEnabled(false); |
||||
ui->edNetwork->setEnabled(false); |
||||
ui->btnSave->setEnabled(false); |
||||
ui->lbNetwork->show(); |
||||
ui->edNetwork->show(); |
||||
|
||||
editMode = EditMode::Off; |
||||
} |
||||
|
||||
void ServerEditor::saveNetworkEdit() |
||||
{ |
||||
qDebug() << "Save network"; |
||||
|
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
QString network = spair.first; |
||||
|
||||
/* Network name changed */ |
||||
if (network != ui->edName->text()) { |
||||
QString newNetwork = ui->edName->text(); |
||||
if (ui->edNetwork->findText(newNetwork) > -1) { |
||||
QMessageBox::information(this, tr("Duplicate network name"), tr("The specified network name already exist.")); |
||||
return; |
||||
} |
||||
smodel.renameNetwork(network, newNetwork); |
||||
int networkIdx = ui->edNetwork->findText(network); |
||||
ui->edNetwork->setItemText(networkIdx, newNetwork); |
||||
network = newNetwork; |
||||
} |
||||
|
||||
QString details = ui->edHostname->text() + ":" + QString::number(ui->edPort->value()); |
||||
QString password = ui->edPassword->text(); |
||||
if (!password.isEmpty()) |
||||
details += "|" + password; |
||||
smodel.setNetworkServer(network, details); |
||||
} |
||||
|
||||
void ServerEditor::saveServerEdit() |
||||
{ |
||||
qDebug() << "Save server"; |
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
|
||||
QString name = ui->edName->text(); |
||||
QString host = ui->edHostname->text(); |
||||
QString port = QString::number(ui->edPort->value()); |
||||
QString password = ui->edPassword->text(); |
||||
QString network = ui->edNetwork->currentText(); |
||||
if (ui->edNetwork->currentIndex() == 0) |
||||
network = "NONE"; |
||||
if (spair.first != network) { |
||||
/* New network */ |
||||
if (network != "NONE" && ui->edNetwork->findText(network) == -1) { |
||||
bool ok = false; |
||||
QString hostname = QInputDialog::getText(this, tr("New network"), tr("Set hostname:port for the new network %1").arg(network), QLineEdit::Normal, "", &ok); |
||||
if (!ok || hostname.isEmpty()) |
||||
return; |
||||
|
||||
smodel.addNetwork(network, hostname); |
||||
ui->edNetwork->addItem(network); |
||||
} |
||||
|
||||
QString details = smodel.details(spair.second, spair.first); |
||||
smodel.delServer(spair.second, spair.first); |
||||
QModelIndex idx = smodel.addServer(spair.second, details, network); |
||||
selectItem(idx); |
||||
} |
||||
|
||||
smodel.setServer(spair.second, host + ":" + port, password, network); |
||||
if (spair.second != name) { |
||||
QString details = smodel.details(spair.second, network); |
||||
smodel.delServer(spair.second, network); |
||||
smodel.resetModel(); |
||||
QModelIndex idx = smodel.addServer(name, details, network); |
||||
selectItem(idx); |
||||
} |
||||
} |
||||
|
||||
void ServerEditor::selectItem(const QModelIndex& index) |
||||
{ |
||||
ui->serverView->clearSelection(); |
||||
ui->serverView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); |
||||
on_serverView_clicked(index); |
||||
} |
||||
|
||||
void ServerEditor::on_serverView_clicked(const QModelIndex &index) |
||||
{ |
||||
if (!index.isValid()) { |
||||
disableAll(); |
||||
return; |
||||
} |
||||
|
||||
auto spair = smodel.fromIndex(index); |
||||
if (spair.first.isEmpty()) { |
||||
disableAll(); |
||||
return; |
||||
} |
||||
|
||||
QString name; |
||||
if (spair.second == "DEFAULT") { |
||||
name = spair.first; |
||||
enableForNetwork(); |
||||
} |
||||
else { |
||||
name = spair.second; |
||||
enableForServer(); |
||||
} |
||||
|
||||
QString details = smodel.details(spair.second, spair.first); |
||||
QString host, port, password; |
||||
// Value format: host:port|password
|
||||
if (details.contains('|')) { |
||||
password = details.split('|')[1]; |
||||
details = details.remove("|"+password); |
||||
} |
||||
if (details.contains(':')) { |
||||
port = details.split(':')[1]; |
||||
details = details.remove(":"+port); |
||||
} |
||||
host = details; |
||||
|
||||
|
||||
ui->edName->setText(name); |
||||
ui->edHostname->setText(host); |
||||
ui->edPort->setValue(port.toInt()); |
||||
ui->edPassword->setText(password); |
||||
int networkUiIndex = ui->edNetwork->findText(spair.first); |
||||
if (networkUiIndex == -1) |
||||
networkUiIndex = 0; |
||||
ui->edNetwork->setCurrentIndex(networkUiIndex); |
||||
} |
@ -1,59 +0,0 @@ |
||||
/*
|
||||
* 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. |
||||
*/ |
||||
|
||||
#ifndef SERVEREDITOR_H |
||||
#define SERVEREDITOR_H |
||||
|
||||
#include "ServerModel.h" |
||||
#include <QDialog> |
||||
#include <QMenu> |
||||
|
||||
namespace Ui { |
||||
class ServerEditor; |
||||
} |
||||
|
||||
class ServerEditor : public QDialog |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerEditor(ServerModel& model, QWidget *parent = nullptr); |
||||
~ServerEditor(); |
||||
|
||||
private slots: |
||||
void on_addServerAction_triggered(); |
||||
void on_addNetworkAction_triggered(); |
||||
void on_btnDel_clicked(); |
||||
void on_btnShowPassword_toggled(bool checked); |
||||
void on_btnSave_clicked(); |
||||
void on_serverView_clicked(const QModelIndex &index); |
||||
|
||||
private: |
||||
enum class EditMode { |
||||
Off, |
||||
Server, |
||||
Network |
||||
} editMode = EditMode::Off; |
||||
|
||||
void enableForServer(); |
||||
void enableForNetwork(); |
||||
void enableAll(); |
||||
void disableAll(); |
||||
|
||||
void saveNetworkEdit(); |
||||
void saveServerEdit(); |
||||
|
||||
void selectItem(const QModelIndex& index); |
||||
|
||||
Ui::ServerEditor* ui; |
||||
QMenu* addMenu; |
||||
QAction* addServerAction; |
||||
QAction* addNetworkAction; |
||||
ServerModel& smodel; |
||||
}; |
||||
|
||||
#endif // SERVEREDITOR_H
|
@ -1,251 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>ServerEditor</class> |
||||
<widget class="QDialog" name="ServerEditor"> |
||||
<property name="windowModality"> |
||||
<enum>Qt::WindowModal</enum> |
||||
</property> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>370</width> |
||||
<height>450</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Server editor</string> |
||||
</property> |
||||
<layout class="QGridLayout" name="gridLayout_2"> |
||||
<item row="0" column="0" rowspan="3" colspan="2"> |
||||
<widget class="QTreeView" name="serverView"> |
||||
<property name="editTriggers"> |
||||
<set>QAbstractItemView::NoEditTriggers</set> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="2"> |
||||
<widget class="QToolButton" name="btnAdd"> |
||||
<property name="text"> |
||||
<string>+</string> |
||||
</property> |
||||
<property name="popupMode"> |
||||
<enum>QToolButton::InstantPopup</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="2"> |
||||
<widget class="QToolButton" name="btnDel"> |
||||
<property name="text"> |
||||
<string>-</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="2" column="2"> |
||||
<spacer name="verticalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Vertical</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>20</width> |
||||
<height>178</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="3" column="0" colspan="2"> |
||||
<widget class="QFrame" name="frame"> |
||||
<property name="frameShape"> |
||||
<enum>QFrame::StyledPanel</enum> |
||||
</property> |
||||
<property name="frameShadow"> |
||||
<enum>QFrame::Raised</enum> |
||||
</property> |
||||
<layout class="QGridLayout" name="gridLayout"> |
||||
<item row="0" column="0"> |
||||
<widget class="QLabel" name="label_4"> |
||||
<property name="text"> |
||||
<string>Name</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="0"> |
||||
<widget class="QLabel" name="label"> |
||||
<property name="text"> |
||||
<string>Hostname</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="2" column="0"> |
||||
<widget class="QLabel" name="label_2"> |
||||
<property name="text"> |
||||
<string>Port</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="3" column="0"> |
||||
<widget class="QLabel" name="label_3"> |
||||
<property name="text"> |
||||
<string>Password</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="3" column="1" colspan="2"> |
||||
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
<item> |
||||
<widget class="QLineEdit" name="edPassword"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="echoMode"> |
||||
<enum>QLineEdit::Password</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QToolButton" name="btnShowPassword"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Show</string> |
||||
</property> |
||||
<property name="checkable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item row="4" column="0"> |
||||
<widget class="QLabel" name="lbNetwork"> |
||||
<property name="text"> |
||||
<string>Network</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="5" column="1"> |
||||
<spacer name="horizontalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="5" column="2"> |
||||
<widget class="QPushButton" name="btnSave"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Save</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="2" column="1" colspan="2"> |
||||
<widget class="QSpinBox" name="edPort"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="minimum"> |
||||
<number>1</number> |
||||
</property> |
||||
<property name="maximum"> |
||||
<number>65535</number> |
||||
</property> |
||||
<property name="value"> |
||||
<number>6667</number> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="1" colspan="2"> |
||||
<widget class="QLineEdit" name="edHostname"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="1" colspan="2"> |
||||
<widget class="QLineEdit" name="edName"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="4" column="1" colspan="2"> |
||||
<widget class="QComboBox" name="edNetwork"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="editable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<property name="insertPolicy"> |
||||
<enum>QComboBox::InsertAlphabetically</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</item> |
||||
<item row="4" column="0"> |
||||
<spacer name="horizontalSpacer_2"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>230</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="4" column="1"> |
||||
<widget class="QPushButton" name="btnClose"> |
||||
<property name="text"> |
||||
<string>Close</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<tabstops> |
||||
<tabstop>serverView</tabstop> |
||||
<tabstop>btnAdd</tabstop> |
||||
<tabstop>btnDel</tabstop> |
||||
<tabstop>edName</tabstop> |
||||
<tabstop>edHostname</tabstop> |
||||
<tabstop>edPort</tabstop> |
||||
<tabstop>edPassword</tabstop> |
||||
<tabstop>edNetwork</tabstop> |
||||
<tabstop>btnSave</tabstop> |
||||
<tabstop>btnClose</tabstop> |
||||
<tabstop>btnShowPassword</tabstop> |
||||
</tabstops> |
||||
<resources/> |
||||
<connections> |
||||
<connection> |
||||
<sender>btnClose</sender> |
||||
<signal>clicked()</signal> |
||||
<receiver>ServerEditor</receiver> |
||||
<slot>close()</slot> |
||||
<hints> |
||||
<hint type="sourcelabel"> |
||||
<x>272</x> |
||||
<y>327</y> |
||||
</hint> |
||||
<hint type="destinationlabel"> |
||||
<x>358</x> |
||||
<y>288</y> |
||||
</hint> |
||||
</hints> |
||||
</connection> |
||||
</connections> |
||||
</ui> |
@ -0,0 +1,51 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "ServerItem.h" |
||||
|
||||
namespace Key = ServerJsonKey; |
||||
|
||||
ServerItem::ServerItem(const QJsonObject& data, int row, ServerItem* parentItem, bool isNetwork) |
||||
: m_data(data) |
||||
, m_row(row) |
||||
, m_parent(parentItem) |
||||
, m_isNetwork{ isNetwork } |
||||
{} |
||||
|
||||
QString ServerItem::name() const |
||||
{ |
||||
return m_data[Key::Name].toString(); |
||||
} |
||||
|
||||
QString ServerItem::address() const |
||||
{ |
||||
auto host{ m_data[Key::Host].toString() }; |
||||
auto port{ m_data[Key::Port].toInt() }; |
||||
|
||||
if (host.isEmpty()) |
||||
host = "no.host"; |
||||
|
||||
if (port == 0) |
||||
port = ssl() ? 6697 : 6667; |
||||
|
||||
return QStringLiteral("%1:%2").arg(host).arg(port); |
||||
} |
||||
|
||||
QString ServerItem::password() const |
||||
{ |
||||
return m_data[Key::Password].toString(); |
||||
} |
||||
|
||||
QString ServerItem::sasl() const |
||||
{ |
||||
return m_data[Key::SASL].toString(); |
||||
} |
||||
|
||||
bool ServerItem::ssl() const |
||||
{ |
||||
return m_data[Key::SSL].toBool(); |
||||
} |
@ -0,0 +1,76 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#ifndef SERVERITEM_H |
||||
#define SERVERITEM_H |
||||
|
||||
#include <QList> |
||||
#include <QString> |
||||
#include <QJsonObject> |
||||
|
||||
namespace ServerJsonKey |
||||
{ |
||||
constexpr auto Servers = "Servers"; |
||||
constexpr auto Networks = "Networks"; |
||||
constexpr auto Name = "Name"; |
||||
constexpr auto Host = "Host"; |
||||
constexpr auto Port = "Port"; |
||||
constexpr auto Password = "Password"; |
||||
constexpr auto SASL = "SASL"; |
||||
constexpr auto SSL = "SSL"; |
||||
} |
||||
|
||||
class ServerItem |
||||
{ |
||||
public: |
||||
ServerItem(const QJsonObject& data, int row, ServerItem* parentItem, bool isNetwork); |
||||
|
||||
QString name() const; |
||||
void setName(const QString& name) { m_data[ServerJsonKey::Name] = name; } |
||||
|
||||
QString address() const; |
||||
void setAddress(const QString& address) |
||||
{ |
||||
auto hostport = address.split(':'); |
||||
|
||||
if (hostport.size() == 1) |
||||
hostport << (ssl() ? "6697" : "6667"); |
||||
|
||||
if (hostport[0].isEmpty()) |
||||
hostport[0] = "server.name"; |
||||
|
||||
m_data[ServerJsonKey::Host] = hostport[0]; |
||||
m_data[ServerJsonKey::Port] = hostport[1].toUShort(); |
||||
} |
||||
|
||||
QString password() const; |
||||
void setPassword(const QString& password) { m_data[ServerJsonKey::Password] = password; } |
||||
|
||||
QString sasl() const; |
||||
void setSasl(const QString& credentials) { m_data[ServerJsonKey::SASL] = credentials; } |
||||
|
||||
bool ssl() const; |
||||
void setSsl(bool enable) { m_data[ServerJsonKey::SSL] = enable; } |
||||
|
||||
ServerItem* parent() const { return m_parent; } |
||||
int row() const { return m_row; } |
||||
void incRow() { ++m_row; } |
||||
void decRow() { --m_row; } |
||||
|
||||
QList<ServerItem>& children() { return m_children; } |
||||
|
||||
bool isNetwork() const { return m_isNetwork; } |
||||
|
||||
private: |
||||
QJsonObject m_data; |
||||
QList<ServerItem> m_children; |
||||
int m_row; |
||||
ServerItem* m_parent; |
||||
bool m_isNetwork; |
||||
}; |
||||
|
||||
#endif // SERVERITEM_H
|
@ -1,133 +0,0 @@ |
||||
/*
|
||||
* 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 "ServerMgr.h" |
||||
#include "config.h" |
||||
#include <fmt/format.h> |
||||
|
||||
ServerMgr::ServerMgr(QObject *parent) : |
||||
QObject(parent), |
||||
ini( QString(LOCAL_PATH+"/servers.ini").toStdString() ) |
||||
{ |
||||
} |
||||
|
||||
QStringList ServerMgr::networkList() |
||||
{ |
||||
int count = ini.count(); |
||||
QStringList r; |
||||
|
||||
for (int i = 0; i < count; i++) { |
||||
const auto name = QString::fromStdString( ini.section(i) ); |
||||
r.push_back(name); |
||||
} |
||||
|
||||
return r; |
||||
} |
||||
|
||||
QHash<QString,QString> ServerMgr::serverList(const QString& network) |
||||
{ |
||||
const auto networkStr = network.toStdString(); |
||||
|
||||
int count = ini.count(networkStr); |
||||
QHash<QString,QString> r; |
||||
|
||||
for (int i = 0; i < count; i++) { |
||||
const auto servername = QString::fromStdString( ini.item(networkStr, i) ); |
||||
const auto serverdetails = QString::fromStdString( ini.read(networkStr, i) ); |
||||
r.insert(servername, serverdetails); |
||||
} |
||||
|
||||
return r; |
||||
} |
||||
|
||||
QString ServerMgr::defaultServer(const QString& network) |
||||
{ |
||||
return QString::fromStdString( ini.read(network.toStdString(), "DEFAULT") ); |
||||
} |
||||
|
||||
bool ServerMgr::addNetwork(const QString& name) |
||||
{ |
||||
const auto nameStr = name.toStdString(); |
||||
|
||||
if (nameStr == "NONE" || ini.exist(nameStr)) |
||||
return false; |
||||
|
||||
ini.write(nameStr, "DEFAULT", "server.name"); |
||||
return true; |
||||
} |
||||
|
||||
bool ServerMgr::renameNetwork(const QString& o_name, const QString& n_name) |
||||
{ |
||||
if ((o_name == "NONE") || (n_name == "NONE")) |
||||
return false; |
||||
|
||||
auto err = ini.rename(o_name.toStdString(), n_name.toStdString()); |
||||
return err == IniFile::Error::NoError; |
||||
} |
||||
|
||||
void ServerMgr::delNetwork(const QString& name, bool keep_servers) |
||||
{ |
||||
// If servers=true, we will keep the servers by moving them to the NONE section.
|
||||
|
||||
const auto nameStr = name.toStdString(); |
||||
if (! ini.exist(nameStr)) |
||||
return; |
||||
|
||||
if (keep_servers) { |
||||
int max = ini.count(nameStr); |
||||
for (int i = 0; i < max; i++) { |
||||
auto item = fmt::format("{}_{}", name.toStdString(), ini.item(nameStr, i)); |
||||
auto value = ini.read(nameStr, i); |
||||
|
||||
ini.write("NONE", item, value); |
||||
} |
||||
} |
||||
|
||||
ini.remove(nameStr); |
||||
} |
||||
|
||||
bool ServerMgr::addServer(const QString& name, const QString& host, const QString& pw, const QString& network) |
||||
{ |
||||
QString details; |
||||
|
||||
if (pw.isEmpty()) { |
||||
details = host; |
||||
} |
||||
else { |
||||
details = QString("%1|%2") |
||||
.arg(host) |
||||
.arg(pw); |
||||
} |
||||
|
||||
const auto networkStr = network.toStdString(); |
||||
|
||||
if (!ini.exist(networkStr) && networkStr != "NONE") |
||||
return false; |
||||
|
||||
ini.write(networkStr, name.toStdString(), details.toStdString()); |
||||
return true; |
||||
} |
||||
|
||||
void ServerMgr::delServer(const QString& name, const QString& network) |
||||
{ |
||||
ini.remove(network.toStdString(), name.toStdString()); |
||||
} |
||||
|
||||
bool ServerMgr::hasNetwork(const QString& name) |
||||
{ |
||||
return ini.exist(name.toStdString()); |
||||
} |
||||
|
||||
bool ServerMgr::hasServer(const QString& name, const QString& network) |
||||
{ |
||||
return ini.exist(network.toStdString(), name.toStdString()); |
||||
} |
||||
|
||||
QString ServerMgr::getServerDetails(const QString& name, const QString& network) |
||||
{ |
||||
return QString::fromStdString( ini.read(network.toStdString(), name.toStdString()) ); |
||||
} |
@ -1,66 +0,0 @@ |
||||
/*
|
||||
* 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. |
||||
*/ |
||||
|
||||
#ifndef SERVERMGR_H |
||||
#define SERVERMGR_H |
||||
|
||||
#include <QObject> |
||||
#include <QStringList> |
||||
#include <QHash> |
||||
|
||||
#include "IniFile.h" |
||||
|
||||
/**
|
||||
* @brief Manages servers.ini |
||||
* @details |
||||
* This class is from IdealIRC 0.x series and is going to be removed/replaced at any time. |
||||
*/ |
||||
class ServerMgr : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerMgr(QObject *parent = nullptr); |
||||
|
||||
// All networks in a string list (Also counts in the NONE network)
|
||||
QStringList networkList(); |
||||
|
||||
// All servers from a network in a hash map <"name","server:port|passwd">
|
||||
QHash<QString,QString> serverList(const QString& network = "NONE"); |
||||
|
||||
// Return default server of given network (The "NONE" network have no default!) - returns empty if no default server is set.
|
||||
QString defaultServer(const QString& network); |
||||
|
||||
// Add new network to servers.ini - returns false if network exist
|
||||
bool addNetwork(const QString& name); |
||||
|
||||
// Rename a network - returns false if new network name already exist
|
||||
bool renameNetwork(const QString& o_name, const QString& n_name); |
||||
|
||||
// Delete network
|
||||
void delNetwork(const QString& name, bool keep_servers = false); |
||||
|
||||
// Add (or update) a server to network - returns false if network doesn't exsist
|
||||
bool addServer(const QString& name, const QString& host /*host:port*/, const QString& pw = "", const QString& network = "NONE"); |
||||
|
||||
// Delete a server from network
|
||||
void delServer(const QString& name, const QString& network = "NONE"); |
||||
|
||||
// Check of we have the given network name
|
||||
bool hasNetwork(const QString& name); |
||||
|
||||
// Check if we have the given server name inside the network
|
||||
bool hasServer(const QString& name, const QString& network = "NONE"); |
||||
|
||||
// Get server details
|
||||
QString getServerDetails(const QString& name, const QString& network = "NONE"); |
||||
|
||||
private: |
||||
IniFile ini;
|
||||
}; |
||||
|
||||
#endif // SERVERMGR_H
|
@ -1,358 +1,430 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* Copyright (C) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "ServerModel.h" |
||||
#include <QHashIterator> |
||||
#include "config.h" |
||||
#include <QJsonArray> |
||||
#include <QFile> |
||||
#include <QDebug> |
||||
#include <QTimer> |
||||
#include <QIcon> |
||||
#include <QFile> |
||||
|
||||
ServerModel::ServerModel(QObject *parent) : |
||||
QStandardItemModel(parent) |
||||
namespace { |
||||
QJsonObject createJsonObject(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl) |
||||
{ |
||||
resetModel(); |
||||
} |
||||
|
||||
QModelIndex ServerModel::indexFromHost(QString hostname) |
||||
{ |
||||
return hostmap.value(hostname, QModelIndex()); |
||||
} |
||||
|
||||
QPair<QString, QString> ServerModel::fromIndex(const QModelIndex& index) |
||||
{ |
||||
/* Locate netmap */ |
||||
{ |
||||
QHashIterator<QString,QModelIndex> it(netmap); |
||||
while (it.hasNext()) { |
||||
const QString& network = it.next().key(); |
||||
const QModelIndex& iidx = it.value(); |
||||
if (index == iidx || index == iidx.siblingAtColumn(1)) |
||||
return { network, "DEFAULT" }; |
||||
|
||||
for (int i = 0 ;; ++i) { |
||||
QModelIndex cidx = iidx.child(i, 0); |
||||
if (!cidx.isValid()) |
||||
break; |
||||
QString name = cidx.data().toString(); |
||||
if (index == cidx || index == cidx.siblingAtColumn(1)) |
||||
return { network, name }; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Locate nonemap */ |
||||
{ |
||||
QHashIterator<QString,QModelIndex> it(hostmap); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
const QModelIndex& iidx = it.value(); |
||||
if (index == iidx || index == iidx.siblingAtColumn(1)) |
||||
return { "NONE", iidx.data().toString() }; |
||||
} |
||||
} |
||||
|
||||
return {}; |
||||
} |
||||
namespace Key = ServerJsonKey; |
||||
|
||||
QModelIndex ServerModel::addNetwork(QString name, QString server) |
||||
{ |
||||
QStandardItem *root = invisibleRootItem(); |
||||
auto hostport{ address.split(':') }; |
||||
if (hostport.size() == 1) { |
||||
hostport[0] = "no.host"; |
||||
hostport << (isSsl ? "6697" : "6667"); |
||||
} |
||||
|
||||
QStandardItem *pname = new QStandardItem(QIcon(":/options/gfx/network.png"), name); |
||||
QStandardItem *phost = new QStandardItem(server); |
||||
QList<QStandardItem*> list; |
||||
list << pname << phost; |
||||
const auto host{ hostport[0] }; |
||||
const auto port{ hostport.count() > 1 ? hostport[1].toShort() |
||||
: (isSsl ? 6697 : 6667) }; |
||||
QJsonObject obj; |
||||
|
||||
root->appendRow(list); |
||||
hostmap.insert(server, pname->index()); |
||||
netmap.insert(name, pname->index()); |
||||
obj[Key::Name] = name; |
||||
obj[Key::Host] = host; |
||||
obj[Key::Port] = port; |
||||
obj[Key::SSL] = isSsl; |
||||
obj[Key::Password] = password; |
||||
|
||||
if (smgr.addNetwork(name)) |
||||
smgr.addServer("DEFAULT", server, "", name); |
||||
if (!sasl.isEmpty()) |
||||
obj[Key::SASL] = sasl; |
||||
|
||||
return pname->index(); |
||||
return obj; |
||||
} |
||||
} |
||||
|
||||
void ServerModel::setNetworkServer(QString name, QString server) |
||||
ServerModel::ServerModel(QObject* parent) : |
||||
QAbstractItemModel(parent) |
||||
{ |
||||
QModelIndex current = netmap.value(name); |
||||
int row = current.row(); |
||||
QModelIndex serverIndex = index(row, 1, current.parent()); |
||||
|
||||
QStandardItem *item = itemFromIndex(serverIndex); |
||||
item->setText(server); |
||||
smgr.addServer("DEFAULT", server, "", name); |
||||
loadFromFile(); |
||||
} |
||||
|
||||
void ServerModel::renameNetwork(QString name, QString newname) |
||||
bool ServerModel::saveToFile() |
||||
{ |
||||
QModelIndex current = netmap.value(name); |
||||
int row = current.row(); |
||||
QModelIndex nameIndex = index(row, 0, current.parent()); |
||||
namespace Key = ServerJsonKey; |
||||
|
||||
QJsonArray servers, networks; |
||||
for (auto& item : m_items) { |
||||
if (item.isNetwork()) { |
||||
QJsonArray networkServers; |
||||
for (auto& child : item.children()) { |
||||
QJsonObject server{ createJsonObject(child.name(), child.address(), child.password(), "", child.ssl()) }; |
||||
networkServers << server; |
||||
} |
||||
|
||||
QJsonObject network{ createJsonObject(item.name(), item.address(), item.password(), item.sasl(), item.ssl()) }; |
||||
network[Key::Servers] = networkServers; |
||||
networks << network; |
||||
} |
||||
else { |
||||
QJsonObject server{ createJsonObject(item.name(), item.address(), item.password(), item.sasl(), item.ssl()) }; |
||||
servers << server; |
||||
} |
||||
} |
||||
|
||||
QJsonObject root; |
||||
root[Key::Servers] = servers; |
||||
root[Key::Networks] = networks; |
||||
|
||||
QJsonDocument doc(root); |
||||
|
||||
QStandardItem *item = itemFromIndex(nameIndex); |
||||
item->setText(newname); |
||||
const auto path{ QStringLiteral("%1/servers.json").arg(LOCAL_PATH) }; |
||||
QFile f{ path }; |
||||
if (!f.open(QIODevice::WriteOnly)) |
||||
return false; |
||||
|
||||
netmap.remove(name); |
||||
netmap.insert(newname, current); |
||||
smgr.renameNetwork(name, newname); |
||||
f.write(doc.toJson(QJsonDocument::Indented)); |
||||
f.flush(); |
||||
f.close(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void ServerModel::delNetwork(QString name, bool keepServers) |
||||
void ServerModel::reloadModel() |
||||
{ |
||||
smgr.delNetwork(name, keepServers); |
||||
resetModel(); |
||||
beginResetModel(); |
||||
m_items.clear(); |
||||
loadFromFile(); |
||||
endResetModel(); |
||||
} |
||||
|
||||
QModelIndex ServerModel::addServer(QString name, QString server, QString network) |
||||
Qt::ItemFlags ServerModel::flags(const QModelIndex& index) const |
||||
{ |
||||
QStandardItem *parent; |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
/* Don't care about invalid indexes */ |
||||
if (!index.isValid()) |
||||
return Qt::NoItemFlags; |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
/* Columns containing widgets for editing */ |
||||
if (index.column() > 1) |
||||
return Qt::ItemIsEnabled | Qt::ItemIsEditable; |
||||
|
||||
QStandardItem *sname = new QStandardItem(QIcon(":/options/gfx/server.png"), name); |
||||
QStandardItem *shost = new QStandardItem(server); |
||||
QList<QStandardItem*> list; |
||||
list << sname << shost; |
||||
/* Directly editable columns */ |
||||
Qt::ItemFlags flags{ Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled }; |
||||
|
||||
parent->appendRow(list); |
||||
hostmap.insert(server, indexFromItem(sname)); |
||||
if (network == "NONE") |
||||
nonemap.insert(name, indexFromItem(sname)); |
||||
const auto* item = static_cast<const ServerItem*>(index.internalPointer()); |
||||
if (!item->isNetwork()) |
||||
flags |= Qt::ItemNeverHasChildren; |
||||
|
||||
smgr.addServer(name, server, "", network); |
||||
|
||||
return indexFromItem(sname); |
||||
return flags; |
||||
} |
||||
|
||||
void ServerModel::setServer(QString name, QString server, QString password, QString network) |
||||
QVariant ServerModel::data(const QModelIndex& index, int role) const |
||||
{ |
||||
QStandardItem *parent; |
||||
if (!index.isValid()) |
||||
return {}; |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
if (role == Qt::DisplayRole || role == Qt::EditRole) { |
||||
const auto* item = static_cast<const ServerItem*>(index.internalPointer()); |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
if (index.column() == 0) |
||||
return item->name(); |
||||
|
||||
QModelIndex parentIdx = indexFromItem(parent); // Parent index
|
||||
QModelIndex current; // Item's index
|
||||
else if (index.column() == 1) |
||||
return item->address(); |
||||
|
||||
if (network != "NONE") { |
||||
else |
||||
return {}; |
||||
} |
||||
|
||||
for (int r = 0 ;; r++) { |
||||
QModelIndex idx = parentIdx.child(r, 0); |
||||
if (role == Qt::DecorationRole && index.column() == 0) { |
||||
const auto* item = static_cast<const ServerItem*>(index.internalPointer()); |
||||
if (item->isNetwork()) |
||||
return QIcon(":/Icons/network.png"); |
||||
else |
||||
return QIcon(":/Icons/serverwindow.png"); |
||||
} |
||||
|
||||
if (! idx.isValid()) |
||||
return; // No relevant child found, stop.
|
||||
return {}; |
||||
} |
||||
|
||||
if (idx.data().toString() == name) { |
||||
current = idx; |
||||
break; |
||||
} |
||||
} |
||||
QVariant ServerModel::headerData(int section, Qt::Orientation orientation, int role) const |
||||
{ |
||||
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { |
||||
QStringList labels { |
||||
tr("Name"), |
||||
tr("Address"), |
||||
"", |
||||
"", |
||||
"" |
||||
}; |
||||
|
||||
return labels[section]; |
||||
} |
||||
else { |
||||
current = nonemap.value(name); |
||||
return {}; |
||||
} |
||||
} |
||||
|
||||
bool ServerModel::setData(const QModelIndex& index, const QVariant& value, int role) |
||||
{ |
||||
if (!index.isValid() || role != Qt::EditRole) |
||||
return false; |
||||
|
||||
auto* item = static_cast<ServerItem*>(index.internalPointer()); |
||||
|
||||
bool ret{ true }; |
||||
switch (index.column()) { |
||||
case 0: |
||||
if (value.toString().isEmpty()) |
||||
ret = false; |
||||
else |
||||
item->setName(value.toString()); |
||||
break; |
||||
|
||||
case 1: |
||||
if (value.toString().isEmpty()) |
||||
ret = false; |
||||
else |
||||
item->setAddress(value.toString()); |
||||
break; |
||||
|
||||
default: |
||||
ret = false; |
||||
} |
||||
|
||||
int row = current.row(); |
||||
QModelIndex nameIndex = index(row, 0, current.parent()); |
||||
QModelIndex serverIndex = index(row, 1, current.parent()); |
||||
QStandardItem *serverItem = itemFromIndex(serverIndex); |
||||
serverItem->setText(server); |
||||
return ret; |
||||
} |
||||
|
||||
hostmap.insert(server, nameIndex); |
||||
smgr.addServer(name, server, password, network); |
||||
int ServerModel::rowCount(const QModelIndex& parent) const |
||||
{ |
||||
if (!parent.isValid()) |
||||
return m_items.count(); |
||||
else { |
||||
auto* parentItem = static_cast<ServerItem*>(parent.internalPointer()); |
||||
return parentItem->children().count(); |
||||
} |
||||
} |
||||
|
||||
void ServerModel::renameServer(QString name, QString newname, QString network) |
||||
QModelIndex ServerModel::index(int row, int column, const QModelIndex& parent) const |
||||
{ |
||||
QStandardItem *parent; |
||||
if (!hasIndex(row, column, parent)) |
||||
return {}; |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
ServerItem* item{}; |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
if (!parent.isValid()) |
||||
item = &m_items[row]; |
||||
else { |
||||
auto* parentItem = static_cast<ServerItem*>(parent.internalPointer()); |
||||
item = &parentItem->children()[row]; |
||||
} |
||||
|
||||
QModelIndex parentIdx = indexFromItem(parent); // Parent index
|
||||
QModelIndex current; // Item's index
|
||||
return createIndex(row, column, item); |
||||
} |
||||
|
||||
if (network != "NONE") { |
||||
QModelIndex ServerModel::parent(const QModelIndex& index) const |
||||
{ |
||||
if (!index.isValid()) |
||||
return {}; |
||||
|
||||
for (int r = 0 ;; r++) { |
||||
QModelIndex idx = parentIdx.child(r, 0); |
||||
auto* parentItem = static_cast<ServerItem*>(index.internalPointer())->parent(); |
||||
if (!parentItem) |
||||
return {}; |
||||
|
||||
if (! idx.isValid()) |
||||
return; // No relevant child found, stop.
|
||||
return createIndex(parentItem->row(), 0, parentItem); |
||||
} |
||||
|
||||
if (idx.data().toString() == name) { |
||||
current = idx; |
||||
break; |
||||
} |
||||
} |
||||
void ServerModel::addServer(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl, int networkIdx) |
||||
{ |
||||
QList<ServerItem>* items{}; |
||||
ServerItem* parentNetworkItem{}; |
||||
int pos{}; |
||||
|
||||
QModelIndex modelIdx; |
||||
|
||||
/* Network server */ |
||||
if (networkIdx > -1) { |
||||
modelIdx = networkIndex(networkIdx); |
||||
parentNetworkItem = static_cast<ServerItem*>(modelIdx.internalPointer()); |
||||
items = &(parentNetworkItem->children()); |
||||
pos = items->count(); |
||||
} |
||||
|
||||
/* Non-network server */ |
||||
else { |
||||
current = nonemap.value(name); |
||||
nonemap.remove(name); |
||||
nonemap.insert(newname, current); |
||||
} |
||||
items = &m_items; |
||||
|
||||
int row = current.row(); |
||||
QModelIndex serverIndex = index(row, 0, current.parent()); |
||||
QStandardItem *item = itemFromIndex(serverIndex); |
||||
item->setText(newname); |
||||
for (auto& item : m_items) { |
||||
if (item.isNetwork()) |
||||
item.incRow(); // Increment row number for each network, since any non-network server is inserted before these.
|
||||
else |
||||
++pos; // Finds the end position of non-network servers. Add server there (bottom of non-networks; before networks are listed.)
|
||||
} |
||||
} |
||||
|
||||
QString details = smgr.getServerDetails(name, network); |
||||
smgr.delServer(name, network); |
||||
smgr.addServer(name, details, "", network); |
||||
const auto obj{ createJsonObject(name, address, password, sasl, isSsl) }; |
||||
beginInsertRows(modelIdx, pos, pos); |
||||
items->insert(pos, ServerItem(obj, pos, parentNetworkItem, false)); |
||||
endInsertRows(); |
||||
|
||||
emit newEntry( |
||||
createIndex(pos, 2, &((*items)[pos])), // SSL
|
||||
createIndex(pos, 3, &((*items)[pos])), // Password
|
||||
createIndex(pos, 4, &((*items)[pos])) // SASL
|
||||
); |
||||
|
||||
/*
|
||||
* A hack to make new child items appear if parent's expanded. |
||||
* This also "solves" when a new/empty network gets its first entry, the network item (parent) won't get the "expand/collapse" decoration. |
||||
* Doing this should not be necessary, so there is an issue with this ServerModel, somewhere... |
||||
*/ |
||||
QTimer::singleShot(1, [this] { |
||||
auto pos = m_items.size(); |
||||
beginInsertRows(QModelIndex{}, pos, pos); |
||||
m_items << ServerItem({}, pos, nullptr, false); |
||||
endInsertRows(); |
||||
|
||||
QTimer::singleShot(1, [this] { |
||||
auto pos = m_items.size() - 1; |
||||
beginRemoveRows(QModelIndex{}, pos, pos); |
||||
m_items.removeLast(); |
||||
endRemoveRows(); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
void ServerModel::delServer(QString name, QString network) |
||||
void ServerModel::addNetwork(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl) |
||||
{ |
||||
QStandardItem *parent; |
||||
const auto obj{ createJsonObject(name, address, password, sasl, isSsl) }; |
||||
|
||||
const auto idx{ m_items.count() }; |
||||
beginInsertRows(QModelIndex{}, idx, idx); |
||||
m_items << ServerItem(obj, idx, nullptr, true); |
||||
endInsertRows(); |
||||
|
||||
emit newEntry( |
||||
createIndex(idx, 2, &(m_items.back())), // SSL
|
||||
createIndex(idx, 3, &(m_items.back())), // Password
|
||||
createIndex(idx, 4, &(m_items.back())) // SASL
|
||||
); |
||||
} |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
void ServerModel::deleteEntry(const QModelIndex& index) |
||||
{ |
||||
auto* item = static_cast<ServerItem*>(index.internalPointer()); |
||||
auto* parent = item->parent(); |
||||
QList<ServerItem>* items{}; |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
if (parent == nullptr) |
||||
items = &m_items; |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
items = &(parent->children()); |
||||
|
||||
QModelIndex parentIdx = indexFromItem(parent); // Parent index
|
||||
QModelIndex current; // Item's index
|
||||
beginRemoveRows(index.parent(), index.row(), index.row()); |
||||
|
||||
if (network != "NONE") { |
||||
items->removeAt(item->row()); |
||||
for (int i = item->row(); i < items->count(); ++i) |
||||
(*items)[i].decRow(); |
||||
|
||||
for (int r = 0 ;; r++) { |
||||
QModelIndex idx = parentIdx.child(r, 0); |
||||
endRemoveRows(); |
||||
} |
||||
|
||||
if (! idx.isValid()) |
||||
return; // No relevant child found, stop.
|
||||
QStringList ServerModel::getNetworks() const |
||||
{ |
||||
QStringList networks; |
||||
|
||||
for (const auto& item : m_items) { |
||||
if (item.isNetwork()) |
||||
networks << item.name(); |
||||
} |
||||
|
||||
if (idx.data().toString() == name) { |
||||
current = idx; |
||||
break; |
||||
return networks; |
||||
} |
||||
|
||||
QList<QModelIndex> ServerModel::getEditorColumns() |
||||
{ |
||||
QList<QModelIndex> indexList; |
||||
|
||||
for (auto& topItem : m_items) { |
||||
indexList << createIndex(topItem.row(), 2, &topItem); |
||||
indexList << createIndex(topItem.row(), 3, &topItem); |
||||
indexList << createIndex(topItem.row(), 4, &topItem); |
||||
|
||||
if (topItem.isNetwork()) { |
||||
auto& items = topItem.children(); |
||||
for (auto& item : items) { |
||||
indexList << createIndex(item.row(), 2, &item); |
||||
indexList << createIndex(item.row(), 3, &item); |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
current = nonemap.value(name); |
||||
nonemap.remove(name); |
||||
} |
||||
|
||||
|
||||
int row = current.row(); |
||||
removeRow(row, current.parent()); |
||||
smgr.delServer(name, network); |
||||
return indexList; |
||||
} |
||||
|
||||
void ServerModel::resetModel() |
||||
QModelIndex ServerModel::networkIndex(int row, int col) |
||||
{ |
||||
clear(); |
||||
|
||||
QStandardItem *root = invisibleRootItem(); |
||||
|
||||
QStandardItem *i = new QStandardItem(); |
||||
QStringList l; |
||||
l << tr("Name") << tr("Host"); |
||||
setColumnCount(2); |
||||
setHorizontalHeaderItem(0, i); |
||||
setHorizontalHeaderLabels(l); |
||||
|
||||
hostmap.clear(); |
||||
netmap.clear(); |
||||
nonemap.clear(); |
||||
|
||||
QStringList netlist = smgr.networkList(); |
||||
|
||||
if (netlist.contains("NONE")) { // "None" network is a section with servers not assigned to a network.
|
||||
QHash<QString,QString> sl = smgr.serverList("NONE"); |
||||
QHashIterator<QString,QString> i(sl); |
||||
while (i.hasNext()) { |
||||
i.next(); |
||||
// Key: Server name
|
||||
// Value: host:port|pass
|
||||
QString name = i.key(); |
||||
QString detail = i.value(); |
||||
|
||||
QString host; // hostname with port, e.g. irc.network.org:6667
|
||||
host = detail.split('|')[0]; |
||||
|
||||
QStandardItem *itemname = new QStandardItem(QIcon(":/options/gfx/server.png"), name); |
||||
QStandardItem *itemhost = new QStandardItem(host); |
||||
QList<QStandardItem*> list; |
||||
list << itemname << itemhost; |
||||
|
||||
root->appendRow(list); |
||||
hostmap.insert(host, indexFromItem(itemname)); |
||||
nonemap.insert(name, indexFromItem(itemname)); |
||||
|
||||
} |
||||
} |
||||
|
||||
for (int i = 0; i <= netlist.count()-1; ++i) { |
||||
|
||||
if (netlist[i] == "NONE") |
||||
continue; // The "None" network already taken care of - ignore.
|
||||
|
||||
QString data = smgr.defaultServer(netlist[i]); |
||||
QString host = data.split('|')[0]; |
||||
|
||||
QStandardItem *pname = new QStandardItem(QIcon(":/options/gfx/network.png"), netlist[i]); // parent name
|
||||
QStandardItem *phost = new QStandardItem(host); // parent host
|
||||
QList<QStandardItem*> list; |
||||
list << pname << phost; |
||||
|
||||
root->appendRow(list); |
||||
hostmap.insert(host, pname->index()); |
||||
netmap.insert(netlist[i], pname->index()); |
||||
|
||||
QHash<QString,QString> sl = smgr.serverList(netlist[i]); |
||||
QHashIterator<QString,QString> sli(sl); |
||||
while (sli.hasNext()) { |
||||
sli.next(); |
||||
// Key: Server name
|
||||
// Value: host:port|pass
|
||||
QString name = sli.key(); |
||||
if (name == "DEFAULT") |
||||
continue; // The default value already taken care of, it's the address of parent item.
|
||||
QString detail = sli.value(); |
||||
QString host; // hostname with port, e.g. irc.network.org:6667
|
||||
host = detail.split('|')[0]; |
||||
|
||||
QStandardItem *itemname = new QStandardItem(QIcon(":/options/gfx/server.png"), name); // parent name
|
||||
QStandardItem *itemhost = new QStandardItem(host); // parent host
|
||||
QList<QStandardItem*> list; |
||||
list << itemname << itemhost; |
||||
|
||||
pname->appendRow(list); |
||||
hostmap.insert(host, indexFromItem(itemname)); |
||||
} |
||||
} |
||||
int rc{ 0 }; |
||||
for (auto& item : m_items) { |
||||
if (!item.isNetwork()) |
||||
continue; |
||||
|
||||
if (rc == row) |
||||
return createIndex(row, col, &item); |
||||
else |
||||
++rc; |
||||
} |
||||
|
||||
return {}; |
||||
} |
||||
|
||||
QStringList ServerModel::networkList() |
||||
void ServerModel::createItems(const QJsonObject& json) |
||||
{ |
||||
return smgr.networkList(); |
||||
namespace Key = ServerJsonKey; |
||||
|
||||
const auto servers = json[Key::Servers].toArray(); |
||||
const auto networks = json[Key::Networks].toArray(); |
||||
|
||||
int rootRow{ 0 }; |
||||
|
||||
for (const auto& server : servers) { |
||||
m_items << ServerItem(server.toObject(), rootRow++, nullptr, false); |
||||
} |
||||
|
||||
for (const auto& network : networks) { |
||||
const auto networkObj = network.toObject(); |
||||
const auto childServers = networkObj[Key::Servers].toArray(); |
||||
|
||||
m_items << ServerItem(networkObj, rootRow++, nullptr, true); |
||||
auto& networkItem = m_items.back(); |
||||
|
||||
int childRow{ 0 }; |
||||
for (const auto& cs : childServers) { |
||||
auto& networkServers = networkItem.children(); |
||||
networkServers << ServerItem(cs.toObject(), childRow++, &networkItem, false); |
||||
} |
||||
} |
||||
} |
||||
|
||||
QString ServerModel::details(QString name, QString network) |
||||
void ServerModel::loadFromFile() |
||||
{ |
||||
return smgr.getServerDetails(name, network); |
||||
QJsonDocument jsondoc; |
||||
|
||||
const auto path{ QStringLiteral("%1/servers.json").arg(LOCAL_PATH) }; |
||||
QFile f{ path }; |
||||
if (f.open(QIODevice::ReadOnly)) { |
||||
const auto jsonData{ f.readAll() }; |
||||
qDebug() << "Read" << jsonData.size() << "bytes from" << path; |
||||
jsondoc = QJsonDocument::fromJson(jsonData); |
||||
|
||||
f.close(); |
||||
|
||||
if (!jsondoc.isObject()) |
||||
qWarning() << "Parse error for servers.json"; |
||||
else |
||||
createItems(jsondoc.object()); |
||||
} |
||||
else |
||||
qWarning() << "Unable to open servers.json:" << path; |
||||
} |
||||
|
||||
|
@ -0,0 +1,105 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "ServerOptionsDelegate.h" |
||||
#include "ServerItem.h" |
||||
#include <QInputDialog> |
||||
#include <QPushButton> |
||||
#include <QDebug> |
||||
|
||||
ServerOptionsDelegate::ServerOptionsDelegate(QObject* parent) |
||||
: QStyledItemDelegate(parent) |
||||
{} |
||||
|
||||
QWidget* ServerOptionsDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const |
||||
{ |
||||
if (!index.isValid()) |
||||
return nullptr; |
||||
|
||||
if (index.column() == 2) { |
||||
auto* btn = new QPushButton(tr("SSL"), parent); |
||||
btn->setCheckable(true); |
||||
return btn; |
||||
} |
||||
|
||||
else if (index.column() == 3) { |
||||
auto* item = static_cast<ServerItem*>(index.internalPointer()); |
||||
auto* btn = new QPushButton(tr("Password"), parent); |
||||
connect(btn, &QPushButton::pressed, |
||||
[item, parent] { |
||||
auto password = QInputDialog::getText( |
||||
parent, |
||||
tr("Enter password"), |
||||
tr("Password for %1:").arg(item->name()), |
||||
QLineEdit::Password, |
||||
item->password() |
||||
); |
||||
|
||||
item->setPassword(password); |
||||
}); |
||||
|
||||
return btn; |
||||
} |
||||
|
||||
else if (index.column() == 4) { |
||||
auto* item = static_cast<ServerItem*>(index.internalPointer()); |
||||
if (item->parent()) // Only items with a parent are network servers / child servers. No SASL for them.
|
||||
return nullptr; |
||||
|
||||
auto* btn = new QPushButton(tr("SASL"), parent); |
||||
connect(btn, &QPushButton::pressed, |
||||
[item, parent] { |
||||
auto password = QInputDialog::getText( |
||||
parent, |
||||
tr("SASL credentials"), |
||||
tr("SASL credentials for %1:").arg(item->name()), |
||||
QLineEdit::Password, |
||||
item->sasl() |
||||
); |
||||
|
||||
item->setSasl(password); |
||||
}); |
||||
|
||||
return btn; |
||||
} |
||||
|
||||
else { |
||||
return nullptr; |
||||
} |
||||
} |
||||
|
||||
void ServerOptionsDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const |
||||
{ |
||||
const auto* item = static_cast<const ServerItem*>(index.internalPointer()); |
||||
|
||||
if (index.column() == 2) { |
||||
auto* btn = qobject_cast<QPushButton*>(editor); |
||||
if (btn) |
||||
btn->setChecked(item->ssl()); |
||||
else |
||||
qCritical() << "SSL button is not a QPushButton?!"; |
||||
} |
||||
|
||||
} |
||||
|
||||
void ServerOptionsDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const |
||||
{ |
||||
auto* item = static_cast<ServerItem*>(index.internalPointer()); |
||||
|
||||
if (index.column() == 2) { |
||||
auto* btn = qobject_cast<QPushButton*>(editor); |
||||
if (btn) |
||||
item->setSsl(btn->isChecked()); |
||||
else |
||||
qCritical() << "SSL button is not a QPushButton?!"; |
||||
} |
||||
} |
||||
|
||||
void ServerOptionsDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const |
||||
{ |
||||
editor->setGeometry(option.rect); |
||||
} |
@ -0,0 +1,26 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#ifndef SERVEROPTIONSDELEGATE_H |
||||
#define SERVEROPTIONSDELEGATE_H |
||||
|
||||
#include <QStyledItemDelegate> |
||||
|
||||
class ServerOptionsDelegate : public QStyledItemDelegate |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerOptionsDelegate(QObject* parent = nullptr); |
||||
|
||||
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; |
||||
void setEditorData(QWidget* editor, const QModelIndex& index) const override; |
||||
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; |
||||
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override; |
||||
}; |
||||
|
||||
#endif // SERVEROPTIONSDELEGATE_H
|
@ -1,125 +0,0 @@ |
||||
/*
|
||||
* 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 "ServerItem.h" |
||||
#include <QDebug> |
||||
|
||||
ServerItem::ServerItem() |
||||
: m_type(Type::Root) |
||||
{} |
||||
|
||||
ServerItem::ServerItem(ServerItem* parentItem, const QString& serverName, const QString& host, const quint16 port, const QString& password) |
||||
: m_type(Type::Server) |
||||
, m_serverName(serverName) |
||||
, m_host(host) |
||||
, m_port(port) |
||||
, m_password(password) |
||||
, m_parent(parentItem) |
||||
{} |
||||
|
||||
ServerItem::ServerItem(const QString& networkName, const QString& host, const quint16 port, const QString& password) |
||||
: m_type(Type::Network) |
||||
, m_networkName(networkName) |
||||
, m_host(host) |
||||
, m_port(port) |
||||
, m_password(password) |
||||
{} |
||||
|
||||
ServerItem::~ServerItem() |
||||
{ |
||||
qDeleteAll(m_children); |
||||
} |
||||
|
||||
ServerItem::Type ServerItem::getType() const |
||||
{ |
||||
return m_type; |
||||
} |
||||
|
||||
const QString& ServerItem::getNetworkName() const |
||||
{ |
||||
return m_networkName; |
||||
} |
||||
|
||||
const QString& ServerItem::getServerName() const |
||||
{ |
||||
return m_serverName; |
||||
} |
||||
|
||||
const QString& ServerItem::getHost() const |
||||
{ |
||||
return m_host; |
||||
} |
||||
|
||||
quint16 ServerItem::getPort() const |
||||
{ |
||||
return m_port; |
||||
} |
||||
|
||||
const QString& ServerItem::getPassword() const |
||||
{ |
||||
return m_password; |
||||
} |
||||
|
||||
ServerItem*ServerItem::getParentItem() const |
||||
{ |
||||
return m_parent; |
||||
} |
||||
|
||||
bool ServerItem::isOrphan() const |
||||
{ |
||||
return m_parent == nullptr; |
||||
} |
||||
|
||||
bool ServerItem::addChild(ServerItem* child) |
||||
{ |
||||
if (m_type == Type::Server) { |
||||
qWarning() << "Tried to add a child item on a server entry!"; |
||||
return false; |
||||
} |
||||
m_children.push_back(child); |
||||
return true; |
||||
} |
||||
|
||||
bool ServerItem::delChild(ServerItem* child) |
||||
{ |
||||
if (m_type == Type::Server) { |
||||
qWarning() << "Tried to delete a child item on a server entry!"; |
||||
return false; |
||||
} |
||||
m_children.removeOne(child); |
||||
return true; |
||||
} |
||||
|
||||
ServerItem* ServerItem::getChild(int row) const |
||||
{ |
||||
if (m_type == Type::Server) { |
||||
qWarning() << "Tried to get a child item on a server entry!"; |
||||
return nullptr; |
||||
} |
||||
|
||||
if (row >= m_children.count()) { |
||||
qWarning() << "Tried to get child from row" << row << "- but it's out of index. Network:" << m_networkName; |
||||
return nullptr; |
||||
} |
||||
|
||||
return m_children[row]; |
||||
} |
||||
|
||||
int ServerItem::childCount() const |
||||
{ |
||||
return m_children.count(); |
||||
} |
||||
|
||||
int ServerItem::childRow(ServerItem* child) const |
||||
{ |
||||
return m_children.indexOf(child); |
||||
} |
||||
|
||||
ServerItem*ServerItem::getParent() const |
||||
{ |
||||
return m_parent; |
||||
} |
@ -1,67 +0,0 @@ |
||||
/*
|
||||
* 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. |
||||
*/ |
||||
|
||||
#ifndef SERVERITEM_H |
||||
#define SERVERITEM_H |
||||
|
||||
#include <QString> |
||||
#include <QList> |
||||
|
||||
class ServerItem |
||||
{ |
||||
public: |
||||
enum class Type { |
||||
Root, |
||||
Server, |
||||
Network |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Construct a root item. Should only be constructed once as the "invisible" root item in the tree. |
||||
*/ |
||||
explicit ServerItem(); |
||||
|
||||
/**
|
||||
* @brief Construct a server item. Set parentItem=nullptr for an orphaned server. |
||||
*/ |
||||
ServerItem(ServerItem* parentItem, const QString& serverName, const QString& host, const quint16 port, const QString& password); |
||||
|
||||
/**
|
||||
* @brief Construct a network item. |
||||
*/ |
||||
ServerItem(const QString& networkName, const QString& host, const quint16 port, const QString& password); |
||||
|
||||
~ServerItem(); |
||||
|
||||
Type getType() const; |
||||
const QString& getNetworkName() const; |
||||
const QString& getServerName() const; |
||||
const QString& getHost() const; |
||||
quint16 getPort() const; |
||||
const QString& getPassword() const; |
||||
ServerItem* getParentItem() const; |
||||
bool isOrphan() const; |
||||
|
||||
bool addChild(ServerItem* child); |
||||
bool delChild(ServerItem* child); |
||||
ServerItem* getChild(int row) const; |
||||
int childCount() const; |
||||
int childRow(ServerItem* child) const; |
||||
ServerItem* getParent() const; |
||||
|
||||
private: |
||||
Type m_type; |
||||
QString m_networkName; |
||||
QString m_serverName; |
||||
QString m_host; |
||||
quint16 m_port; |
||||
QString m_password; |
||||
ServerItem* m_parent{ nullptr }; |
||||
QList<ServerItem*> m_children; |
||||
}; |
||||
|
||||
#endif // SERVERITEM_H
|
@ -1,172 +0,0 @@ |
||||
/*
|
||||
* 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 "ServerModel.h" |
||||
#include "config.h" |
||||
#include <QDebug> |
||||
|
||||
ServerModel::ServerModel(QObject *parent) |
||||
: QAbstractItemModel(parent) |
||||
{ |
||||
IniFile ini(LOCAL_PATH+"/servers.ini"); |
||||
buildToplevelItem(ini, "NONE"); // Orphaned servers. Always build first.
|
||||
const int sectionCount = ini.countSections(); |
||||
for (int i = 0; i < sectionCount; ++i) { |
||||
QString section = ini.section(i); |
||||
if (section == "NONE") |
||||
continue; |
||||
|
||||
buildToplevelItem(ini, section); |
||||
} |
||||
|
||||
qDebug() << "Built server tree model."; |
||||
} |
||||
|
||||
ServerModel::~ServerModel() |
||||
{ |
||||
qDeleteAll(toplevelItems); |
||||
} |
||||
|
||||
ServerModel::ItemIniValue::ItemIniValue(QString value) |
||||
{ |
||||
if (value.isEmpty()) |
||||
return; |
||||
|
||||
// Value format: host:port|password
|
||||
if (value.contains('|')) { |
||||
password = value.split('|')[1]; |
||||
value = value.remove("|"+password); |
||||
} |
||||
if (value.contains(':')) { |
||||
port = value.split(':')[1]; |
||||
value = value.remove(":"+port); |
||||
} |
||||
host = value; |
||||
} |
||||
|
||||
QModelIndex ServerModel::index(int row, int column, const QModelIndex &parent) const |
||||
{ |
||||
// TEST
|
||||
//if (!hasIndex(row, column, parent))
|
||||
// return QModelIndex();
|
||||
|
||||
ServerItem* item{ nullptr }; |
||||
if (!parent.isValid()) { |
||||
if (row < toplevelItems.count()) |
||||
item = toplevelItems[row]; |
||||
} |
||||
else |
||||
item = static_cast<ServerItem*>(parent.internalPointer()); |
||||
|
||||
if (item) |
||||
return createIndex(row, column, item); |
||||
else |
||||
return QModelIndex(); |
||||
} |
||||
|
||||
QModelIndex ServerModel::parent(const QModelIndex &index) const |
||||
{ |
||||
// FIXME: Implement me!
|
||||
if (!index.isValid()) |
||||
return QModelIndex(); |
||||
|
||||
ServerItem* childItem = static_cast<ServerItem*>(index.internalPointer()); |
||||
if (!childItem->isOrphan()) { |
||||
ServerItem* parentItem = childItem->getParentItem(); |
||||
return createIndex(parentItem->childRow(childItem), 0, parentItem); |
||||
} |
||||
else { |
||||
return QModelIndex(); |
||||
} |
||||
} |
||||
|
||||
int ServerModel::rowCount(const QModelIndex &parent) const |
||||
{ |
||||
if (!parent.isValid()) |
||||
return toplevelItems.count(); |
||||
|
||||
ServerItem* item = static_cast<ServerItem*>(parent.internalPointer()); |
||||
if (!item) |
||||
return 0; |
||||
else |
||||
return item->childCount(); |
||||
} |
||||
|
||||
int ServerModel::columnCount(const QModelIndex&) const |
||||
{ |
||||
return 1; |
||||
} |
||||
|
||||
QVariant ServerModel::data(const QModelIndex &index, int role) const |
||||
{ |
||||
if (!index.isValid()) |
||||
return QVariant(); |
||||
|
||||
// FIXME: Implement me!
|
||||
return "neger"; |
||||
} |
||||
|
||||
bool ServerModel::setData(const QModelIndex &index, const QVariant &value, int role) |
||||
{ |
||||
if (data(index, role) != value) { |
||||
// FIXME: Implement me!
|
||||
emit dataChanged(index, index, QVector<int>() << role); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
Qt::ItemFlags ServerModel::flags(const QModelIndex &index) const |
||||
{ |
||||
if (!index.isValid()) |
||||
return Qt::NoItemFlags; |
||||
|
||||
return Qt::ItemIsEditable; // FIXME: Implement me!
|
||||
} |
||||
|
||||
bool ServerModel::insertRows(int row, int count, const QModelIndex &parent) |
||||
{ |
||||
beginInsertRows(parent, row, row + count - 1); |
||||
// FIXME: Implement me!
|
||||
endInsertRows(); |
||||
} |
||||
|
||||
bool ServerModel::removeRows(int row, int count, const QModelIndex &parent) |
||||
{ |
||||
beginRemoveRows(parent, row, row + count - 1); |
||||
// FIXME: Implement me!
|
||||
endRemoveRows(); |
||||
} |
||||
|
||||
void ServerModel::buildToplevelItem(IniFile& ini, const QString& section) |
||||
{ |
||||
if (!ini.sectionExists(section)) |
||||
return; |
||||
|
||||
ServerItem* parent{ nullptr }; |
||||
if (section != "NONE") { |
||||
ItemIniValue networkDefault(ini.value(section, "DEFAULT")); |
||||
parent = new ServerItem(section, networkDefault.host, |
||||
networkDefault.port.toUShort(), |
||||
networkDefault.password); |
||||
toplevelItems.push_back(parent); |
||||
} |
||||
|
||||
const int itemCount = ini.countItems(section); |
||||
for (int i = 0; i < itemCount; ++i) { |
||||
QString key = ini.key(section, i); |
||||
if (key == "DEFAULT") |
||||
continue; |
||||
|
||||
ItemIniValue val(ini.value(section, key)); |
||||
ServerItem* item = new ServerItem(parent, key, val.host, val.port.toUShort(), val.password); |
||||
if (parent) |
||||
parent->addChild(item); |
||||
else |
||||
toplevelItems.push_back(item); |
||||
} |
||||
} |
@ -1,54 +0,0 @@ |
||||
/*
|
||||
* 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. |
||||
*/ |
||||
|
||||
#ifndef SERVERMODEL_H |
||||
#define SERVERMODEL_H |
||||
|
||||
#include "ServerItem.h" |
||||
#include "IniFile.h" |
||||
#include <QAbstractItemModel> |
||||
#include <QList> |
||||
|
||||
class ServerModel : public QAbstractItemModel |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerModel(QObject *parent = nullptr); |
||||
~ServerModel() override; |
||||
|
||||
// Basic functionality:
|
||||
QModelIndex index(int row, int column, |
||||
const QModelIndex &parent = QModelIndex()) const override; |
||||
QModelIndex parent(const QModelIndex &index) const override; |
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override; |
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override; |
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; |
||||
|
||||
// Editable:
|
||||
bool setData(const QModelIndex &index, const QVariant &value, |
||||
int role = Qt::EditRole) override; |
||||
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override; |
||||
|
||||
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; |
||||
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; |
||||
|
||||
private: |
||||
struct ItemIniValue { |
||||
ItemIniValue(QString value); |
||||
QString host; |
||||
QString port; |
||||
QString password; |
||||
}; |
||||
void buildToplevelItem(IniFile& ini, const QString& section); |
||||
QList<ServerItem*> toplevelItems; |
||||
}; |
||||
|
||||
#endif // SERVERMODEL_H
|
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::handleUnhandled(const IRCMessage& ircmessage) |
||||
{ |
||||
super.onMsgUnhandled(ircmessage.getSender(), ircmessage.getCommand(), ircmessage.getArgs(), ircmessage.getMessage()); |
||||
} |
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdError(const IRCMessage& ircmessage) |
||||
{ |
||||
super.onMsgError(ircmessage.getMessage()); |
||||
} |
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdInvite(const IRCMessage& ircmessage) |
||||
{ |
||||
super.onMsgInvite(ircmessage.getSender(), ircmessage.getMessage()); |
||||
} |
@ -0,0 +1,43 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdJoin(const IRCMessage& ircmessage) |
||||
{ |
||||
const auto& msg = ircmessage.getMessage(); |
||||
const auto& sender = ircmessage.getSender(); |
||||
|
||||
if (ircmessage.getArgs().size() == 2) { |
||||
std::string accountname = ircmessage[1]; |
||||
if (accountname[0] == '*') |
||||
accountname.clear(); |
||||
|
||||
/* We are joining a channel */ |
||||
if (sender.nickname() == nickname) { |
||||
auto chanentry = super.getChannel(ircmessage[0]); |
||||
if (!chanentry) |
||||
channels.emplace_back(std::make_shared<IRCChannel>(ircmessage[0], super)); |
||||
} |
||||
else |
||||
addMemberToChannel(sender, ircmessage[0]); |
||||
super.v3onMsgJoin(sender, ircmessage[0], accountname, msg); |
||||
} |
||||
else { |
||||
/* We are joining a channel */ |
||||
auto channelName = msg.empty() ? ircmessage[0] : msg; |
||||
if (sender.nickname() == nickname) { |
||||
auto chanentry = super.getChannel(channelName); |
||||
if (!chanentry) |
||||
channels.emplace_back(std::make_shared<IRCChannel>(channelName, super)); |
||||
} |
||||
else |
||||
addMemberToChannel(sender, channelName); |
||||
super.onMsgJoin(sender, channelName); |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdKick(const IRCMessage& ircmessage) |
||||
{ |
||||
super.onMsgKick(ircmessage.getSender(), ircmessage[0], ircmessage[1], ircmessage.getMessage()); |
||||
auto prefix = IRCPrefix::fromNickname(ircmessage[1]); |
||||
delMemberFromChannel(prefix, ircmessage[0]); |
||||
} |
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdKill(const IRCMessage& ircmessage) |
||||
{ |
||||
super.onMsgKill(ircmessage.getSender(), ircmessage.getMessage()); |
||||
} |
@ -0,0 +1,31 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdMode(const IRCMessage& ircmessage) |
||||
{ |
||||
auto args = ircmessage.getArgs(); |
||||
const std::string target = args[0]; |
||||
std::string modes; |
||||
|
||||
if (isChannelSymbol(target[0])) { |
||||
modes = args[1]; |
||||
args.erase(args.begin(), args.begin() + 2); |
||||
|
||||
auto chan = super.getChannel(target); |
||||
if (chan) |
||||
parseChannelModeMessage(chan, modes, args); |
||||
} |
||||
else { |
||||
modes = ircmessage.getMessage(); |
||||
args.erase(args.begin(), args.begin() + 1); |
||||
} |
||||
|
||||
super.onMsgMode(ircmessage.getSender(), target, modes, args); |
||||
} |
@ -0,0 +1,29 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdNick(const IRCMessage& ircmessage) |
||||
{ |
||||
std::vector<std::string> channelsAffected; |
||||
channelsAffected.reserve(16); |
||||
auto member = super.getMember(ircmessage.getSender().toString()); |
||||
|
||||
if (member) { |
||||
const auto& chans = member->channels(); |
||||
for (const auto& c : chans) |
||||
channelsAffected.push_back(c.lock()->name()); |
||||
member->setNickname(ircmessage.getMessage()); |
||||
} |
||||
|
||||
/* We changed our nickname */ |
||||
if (ircmessage.getSender().toString() == nickname) |
||||
nickname = ircmessage.getMessage(); |
||||
|
||||
super.onMsgNick(ircmessage.getSender(), ircmessage.getMessage(), channelsAffected); |
||||
} |
@ -0,0 +1,23 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Utilities.h" |
||||
|
||||
void IRCBasePriv::cmdNotice(const IRCMessage& ircmessage) |
||||
{ |
||||
const auto& sender = ircmessage.getSender(); |
||||
const auto& msg = ircmessage.getMessage(); |
||||
|
||||
if (msg[0] == CTCPflag) { |
||||
auto[cmd,ctcpMsg] = FormatCTCPLine(msg); |
||||
super.onMsgCTCPResponse(sender, ircmessage[0], cmd, ctcpMsg); |
||||
} |
||||
else |
||||
super.onMsgNotice(sender, ircmessage[0], msg); |
||||
} |
@ -0,0 +1,16 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdPart(const IRCMessage& ircmessage) |
||||
{ |
||||
const auto& sender = ircmessage.getSender(); |
||||
super.onMsgPart(sender, ircmessage[0], ircmessage.getMessage()); |
||||
delMemberFromChannel(sender, ircmessage[0]); |
||||
} |
@ -0,0 +1,19 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Commands.h" |
||||
|
||||
void IRCBasePriv::cmdPing(const IRCMessage& ircmessage) |
||||
{ |
||||
using namespace Command::IRC; |
||||
|
||||
const auto& msg = ircmessage.getMessage(); |
||||
write(PONG, msg); |
||||
super.onMsgPing(msg); |
||||
} |
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdPong(const IRCMessage& ircmessage) |
||||
{ |
||||
super.onMsgPong(ircmessage.getMessage()); |
||||
} |
@ -0,0 +1,51 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Utilities.h" |
||||
|
||||
#include <iostream> // Temporary printouts for making DCC work. |
||||
|
||||
void IRCBasePriv::cmdPrivmsg(const IRCMessage& ircmessage) |
||||
{ |
||||
const auto& sender = ircmessage.getSender(); |
||||
const auto& msg = ircmessage.getMessage(); |
||||
|
||||
if (msg[0] == CTCPflag) { |
||||
auto[cmd,ctcpMsg] = FormatCTCPLine(msg); |
||||
|
||||
if (cmd == "DCC") { |
||||
auto[dccCmd,dccMsg] = FormatCTCPLine(ctcpMsg); |
||||
std::vector<std::string> dccMsgTks; |
||||
{ |
||||
std::stringstream ss(dccMsg); |
||||
dccMsgTks = std::vector<std::string>(std::istream_iterator<std::string>{ss}, |
||||
std::istream_iterator<std::string>()); |
||||
} |
||||
|
||||
if (dccCmd == "CHAT" && dccMsgTks.size() >= 3 && dccMsgTks[0] == "chat") { |
||||
const auto& dccLongIp = dccMsgTks[1]; |
||||
const auto& dccPort = dccMsgTks[2]; |
||||
if (port == "0") { |
||||
// nat stuff
|
||||
std::cout << "DCC CHAT TODO" << std::endl; |
||||
} |
||||
else { |
||||
const auto ip = longIpToNormal(dccLongIp); |
||||
dcclist.emplace_back(std::make_shared<DCC>(super, ioctx, ip, dccPort)); |
||||
|
||||
super.onMsgDCCRequest(dcclist.back(), sender, ircmessage[0], dccCmd, dccMsg); |
||||
} |
||||
} |
||||
} |
||||
else |
||||
super.onMsgCTCPRequest(sender, ircmessage[0], cmd, ctcpMsg); |
||||
} |
||||
else |
||||
super.onMsgPrivmsg(sender, ircmessage[0], msg); |
||||
} |
@ -0,0 +1,32 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdQuit(const IRCMessage& ircmessage) |
||||
{ |
||||
std::vector<std::shared_ptr<IRCChannel>> channelsAffected; |
||||
std::vector<std::string> channelsAffectedStr; |
||||
|
||||
auto member = super.getMember(ircmessage.getSender().toString()); |
||||
if (member) { |
||||
const auto& chans = member->channels(); |
||||
for (const auto& c : chans) { |
||||
channelsAffected.push_back(c.lock()); |
||||
channelsAffectedStr.push_back(c.lock()->name()); |
||||
} |
||||
|
||||
auto it = std::find(allMembers.begin(), allMembers.end(), member); |
||||
allMembers.erase(it); |
||||
} |
||||
|
||||
super.onMsgQuit(ircmessage.getSender(), ircmessage.getMessage(), channelsAffectedStr); |
||||
|
||||
for (const auto& c : channelsAffected) |
||||
c->delMember(member); |
||||
} |
@ -0,0 +1,17 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdTopic(const IRCMessage& ircmessage) |
||||
{ |
||||
auto channel = super.getChannel(ircmessage[0]); |
||||
if (channel) |
||||
channel->setTopic(ircmessage.getMessage()); |
||||
super.onMsgTopic(ircmessage.getSender(), ircmessage[0], ircmessage.getMessage()); |
||||
} |
@ -0,0 +1,18 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdV3Account(const IRCMessage& ircmessage) |
||||
{ |
||||
const auto& sender = ircmessage.getSender(); |
||||
if (ircmessage[0] == "*") |
||||
super.v3onMsgAccountLogout(sender); |
||||
else |
||||
super.v3onMsgAccountLogin(sender, ircmessage[0]); |
||||
} |
@ -0,0 +1,20 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (c) 2022 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Commands.h" |
||||
#include "../Utilities.h" |
||||
|
||||
void IRCBasePriv::cmdV3Authenticate(const IRCMessage& ircmessage) |
||||
{ |
||||
using namespace Command::IRC; |
||||
if (saslInProgress && ircmessage[0] == "+") { |
||||
const auto credentials = ident + char(0) + ident + char(0) + v3SASL; |
||||
writeNoMsg(Command::Extension::AUTHENTICATE, { toBase64(credentials) }); |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdV3Away(const IRCMessage& ircmessage) |
||||
{ |
||||
const auto& sender = ircmessage.getSender(); |
||||
std::vector<std::string> channelsAffected; |
||||
auto member = super.getMember(sender.nickname()); |
||||
if (member) { |
||||
const auto& chans = member->channels(); |
||||
for (const auto& c : chans) { |
||||
const auto cptr = c.lock(); |
||||
channelsAffected.push_back(cptr->name()); |
||||
} |
||||
} |
||||
super.v3onMsgAway(sender, ircmessage.getMessage(), channelsAffected); |
||||
} |
@ -0,0 +1,64 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Commands.h" |
||||
|
||||
void IRCBasePriv::cmdV3Cap(const IRCMessage& ircmessage) |
||||
{ |
||||
using namespace Command::IRC; |
||||
|
||||
bool saslSupported{ std::find(registeredV3support.begin(), registeredV3support.end(), "sasl") != registeredV3support.end() }; |
||||
|
||||
/*
|
||||
* List of IRCv3 capabilities from the server. |
||||
* Match it with what we support and request those. |
||||
*/ |
||||
if (ircmessage[1] == Command::IRCv3::LS) { |
||||
std::istringstream ss(ircmessage.getMessage()); |
||||
serverV3support = std::vector<std::string>(std::istream_iterator<std::string>{ss}, |
||||
std::istream_iterator<std::string>()); |
||||
|
||||
for (const auto& cap : serverV3support) { |
||||
auto myIt = std::find(ircv3enabled.begin(), ircv3enabled.end(), cap); |
||||
if (myIt != ircv3enabled.end()) |
||||
registeredV3support.push_back(cap); |
||||
} |
||||
|
||||
std::string requestStr; |
||||
for (const auto& cap : registeredV3support) { |
||||
if (!requestStr.empty()) |
||||
requestStr += ' '; |
||||
requestStr += cap; |
||||
} |
||||
|
||||
write(Command::IRCv3::CAP, { Command::IRCv3::REQ }, requestStr); |
||||
} |
||||
|
||||
/*
|
||||
* Server accepted our desires. |
||||
*/ |
||||
else if (ircmessage[1] == Command::IRCv3::ACK) { |
||||
if (saslSupported && !v3SASL.empty()) { |
||||
saslInProgress = true; |
||||
writeNoMsg(Command::Extension::AUTHENTICATE, { "PLAIN" }); |
||||
} |
||||
else { |
||||
writeNoMsg(Command::IRCv3::CAP, { Command::IRCv3::END }); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Server replied NAK on our IRCv3 capabilities, even though it seems it supported them... |
||||
* Resetting and continuing with our lives. |
||||
*/ |
||||
else if (ircmessage[1] == Command::IRCv3::NAK) { |
||||
registeredV3support.clear(); |
||||
writeNoMsg(Command::IRCv3::CAP, { Command::IRCv3::END }); // TODO erase the offending modules instead.
|
||||
} |
||||
} |
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::cmdWallops(const IRCMessage& ircmessage) |
||||
{ |
||||
super.onMsgWallops(ircmessage.getSender(), ircmessage.getMessage()); |
||||
} |
@ -0,0 +1,135 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Commands.h" |
||||
|
||||
void IRCBasePriv::handleNumeric(const IRCMessage& ircmessage) { |
||||
using namespace Numeric; |
||||
|
||||
if (!isOnline && ircmessage == RPL_ISUPPORT) { |
||||
// Note: skip the first item (argument), it is always our nickname
|
||||
for (auto it = ircmessage.getArgs().cbegin() + 1; it != ircmessage.getArgs().cend(); ++it) { |
||||
const auto& arg = *it; |
||||
std::string key, val; |
||||
auto delim = std::find(arg.begin(), arg.end(), '='); |
||||
if (delim != arg.end()) { |
||||
key = std::string(arg.begin(), delim); |
||||
val = std::string(delim + 1, arg.end()); |
||||
} |
||||
else |
||||
key = arg; |
||||
isupport.insert_or_assign(key, val); |
||||
} |
||||
|
||||
const std::string& prefix = isupport.find("PREFIX")->second; |
||||
|
||||
validPrivilegeModes = std::string(std::find(prefix.begin(), prefix.end(), '(') + 1, |
||||
std::find(prefix.begin(), prefix.end(), ')')); |
||||
|
||||
validPrivilegeSymbols = std::string(std::find(prefix.begin(), prefix.end(), ')') + 1, |
||||
prefix.end()); |
||||
} |
||||
|
||||
else if (!isOnline && (ircmessage == RPL_ENDOFMOTD || ircmessage == ERR_NOMOTD)) { |
||||
nickname = ircmessage[0]; |
||||
isOnline = true; |
||||
if (keepaliveFreq > std::chrono::seconds(0)) |
||||
startKeepaliveTimer(); |
||||
|
||||
// Emplace ourself in the all-members list.
|
||||
allMembers.emplace_back(std::make_shared<IRCMember>(IRCPrefix::fromNickname(nickname))); |
||||
|
||||
super.onRegistered(); |
||||
} |
||||
|
||||
else if (ircmessage == RPL_NAMREPLY) { |
||||
auto chan = super.getChannel(ircmessage[2]); |
||||
if (chan && chan->isPopulating()) { |
||||
std::istringstream ss(ircmessage.getMessage()); |
||||
|
||||
/*
|
||||
* Note these things: |
||||
* A member may contain a usermode. |
||||
* IRCv3 may enable multiple usermodes (multi-prefix). |
||||
* IRCv3 may enable each member entry to be a full hostmask (userhost-in-names) and not only a nickname. |
||||
*/ |
||||
auto members = std::vector<std::string>(std::istream_iterator<std::string>{ ss }, |
||||
std::istream_iterator<std::string>()); |
||||
|
||||
for (std::string& namem : members) { |
||||
/* Extract mode symbols */ |
||||
auto memBegin = std::find_if_not(namem.begin(), namem.end(), |
||||
[this](char ms) { |
||||
return isMemberPrivilegeSymbol(ms); |
||||
}); |
||||
std::string modesymbols = std::string(namem.begin(), memBegin); |
||||
namem.erase(namem.begin(), memBegin); |
||||
|
||||
std::optional<IRCPrefix> memprefix; |
||||
/* IRC standard reply */ |
||||
if (namem.find('!') == std::string::npos) |
||||
memprefix.emplace(IRCPrefix::fromNickname(namem)); |
||||
/* IRCv3 reply */ |
||||
else |
||||
memprefix.emplace(namem); |
||||
|
||||
auto mem = super.getMember(memprefix->nickname()); |
||||
if (!mem) { |
||||
mem = std::make_shared<IRCMember>(*memprefix); |
||||
allMembers.push_back(mem); |
||||
} |
||||
if (mem->prefix().host().empty() && !memprefix->host().empty()) |
||||
mem->setPrefix(*memprefix); |
||||
|
||||
mem->addChannel(chan); |
||||
auto& entry = chan->addMember(mem); |
||||
for (char s : modesymbols) { |
||||
char letter = memberPrivilegeSymbolToMode(s); |
||||
if (letter != '\0') |
||||
entry.addMode(letter); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
else if (ircmessage == RPL_ENDOFNAMES) { |
||||
auto chan = super.getChannel(ircmessage[1]); |
||||
if (chan && chan->isPopulating()) |
||||
chan->donePopulating(); |
||||
} |
||||
|
||||
else if (ircmessage == RPL_TOPIC) { |
||||
auto chan = super.getChannel(ircmessage[1]); |
||||
if (chan) |
||||
chan->setTopic(ircmessage.getMessage()); |
||||
} |
||||
|
||||
else if (ircmessage == RPL_CHANNELMODEIS) { |
||||
auto chan = super.getChannel(ircmessage[1]); |
||||
const std::string& modes = ircmessage[2]; |
||||
|
||||
auto args = ircmessage.getArgs(); |
||||
args.erase(args.begin(), args.begin() + 3); |
||||
|
||||
if (chan) |
||||
parseChannelModeMessage(chan, modes, args); |
||||
} |
||||
|
||||
else if (ircmessage == NumericV3::RPL_SASLSUCCESS && saslInProgress) { |
||||
saslInProgress = false; |
||||
writeNoMsg(Command::IRCv3::CAP, { Command::IRCv3::END }); |
||||
} |
||||
|
||||
else if (ircmessage == NumericV3::ERR_SASLFAIL && saslInProgress) { |
||||
saslInProgress = false; |
||||
super.disconnectFromServer(); |
||||
} |
||||
|
||||
super.onMsgNumeric(ircmessage.getSender(), ircmessage.getCommand(), ircmessage.getArgs(), ircmessage.getMessage()); |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,162 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#ifndef IRCBASEPRIV_H |
||||
#define IRCBASEPRIV_H |
||||
|
||||
#include "Numeric.h" |
||||
#include "IRCError.h" |
||||
#include "IRCPrefix.h" |
||||
#include "IRCMember.h" |
||||
#include "IRCChannel.h" |
||||
#include "IRCMessage.h" |
||||
#include "DCC.h" |
||||
|
||||
#include <asio.hpp> |
||||
#include <asio/ssl.hpp> |
||||
|
||||
#include <ctime> |
||||
|
||||
namespace { |
||||
|
||||
/* Debug/development options */ |
||||
constexpr bool DumpReadData = false; |
||||
constexpr bool DumpWriteData = false; |
||||
|
||||
const std::vector<std::string> V3Support { |
||||
"account-notify", |
||||
"extended-join", |
||||
"away-notify", |
||||
"invite-notify", |
||||
"multi-prefix", |
||||
"userhost-in-names", |
||||
"message-tags", |
||||
"server-time", |
||||
"sasl" |
||||
}; |
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
class IRCBase; |
||||
|
||||
using asio::ip::tcp; |
||||
|
||||
struct IRCBasePriv |
||||
{ |
||||
explicit IRCBasePriv(IRCBase& super_) |
||||
: super(super_) |
||||
, sslctx(asio::ssl::context::tls) |
||||
, resolver(ioctx) |
||||
, keepaliveTimer(ioctx) |
||||
{ |
||||
readbuf.reserve(1024); |
||||
setDefaults(); |
||||
} |
||||
|
||||
IRCBase& super; |
||||
|
||||
std::string hostname; |
||||
std::string port; |
||||
std::string realname; |
||||
std::string ident; |
||||
std::string password; |
||||
std::string v3SASL; |
||||
std::string nickname; |
||||
|
||||
struct { |
||||
bool selfSigned{ false }; |
||||
bool CNMismactch{ false }; |
||||
bool expired{ false }; |
||||
} sslExcept; |
||||
|
||||
bool useSSL{ false }; |
||||
asio::io_context ioctx; |
||||
asio::ssl::context sslctx; |
||||
tcp::resolver resolver; |
||||
tcp::resolver::results_type endpoints; |
||||
std::string readbuf; |
||||
|
||||
std::optional<tcp::socket> sock; |
||||
std::optional< asio::ssl::stream<asio::ip::tcp::socket> > sslsock; |
||||
|
||||
std::vector<IRCError> sslExceptions; // List of SSL verification errors that were ignored.
|
||||
|
||||
bool isConnected{ false }; // When the socket itself is successfully connected.
|
||||
bool isOnline{ false }; // When the client itself is successfully registered.
|
||||
bool saslInProgress{ false }; // Used during server registration
|
||||
IRCError lastError{ IRCError::NoError }; |
||||
|
||||
std::chrono::seconds keepaliveFreq{ 0 }; |
||||
asio::steady_timer keepaliveTimer; |
||||
|
||||
std::vector<std::shared_ptr<DCC>> dcclist; |
||||
|
||||
std::unordered_map<std::string,std::string> isupport; |
||||
std::vector<std::string> registeredV3support; |
||||
std::vector<std::string> serverV3support; |
||||
|
||||
std::vector<std::shared_ptr<IRCMember>> allMembers; |
||||
std::vector<std::shared_ptr<IRCChannel>> channels; |
||||
|
||||
std::string validPrivilegeModes; // ohv etc. Ordered by most significant first.
|
||||
std::string validPrivilegeSymbols; // @%+ etc. Ordered by most significant first.
|
||||
|
||||
IRCMessage* currentMessage{ nullptr }; |
||||
|
||||
std::vector<std::string> ircv3enabled; |
||||
|
||||
/*
|
||||
* Various helper functions for the IRC client implementation. |
||||
*/ |
||||
char channelModeGroup(char m) const; |
||||
bool isChannelSymbol(char c); |
||||
bool isMemberPrivilegeSymbol(char m) const; |
||||
char memberPrivilegeSymbolToMode(char s) const; |
||||
void setDefaults(); |
||||
void startKeepaliveTimer(); |
||||
void stopKeepaliveTimer(); |
||||
void keepaliveTimeout(const asio::error_code& ec); |
||||
void disconnectHandler(); |
||||
void read(const asio::error_code& ec, std::size_t /*size*/); |
||||
void connected(const asio::error_code& /*ec*/); |
||||
void write(const std::string& command, const std::vector<std::string>& args, const std::string& msg); |
||||
void writeNoMsg(const std::string& command, const std::vector<std::string>& args); |
||||
void write(const std::string& command, const std::string& msg); |
||||
void ctcp(const std::string& messageType, const std::string& target, const std::string& command, const std::string& message = ""); |
||||
void addMemberToChannel(const IRCPrefix& prefix, const std::string& channel); |
||||
void delMemberFromChannel(const IRCPrefix& prefix, const std::string& channel); |
||||
IRCError verify_X509(X509* cert); |
||||
void parseChannelModeMessage(std::shared_ptr<IRCChannel> chan, const std::string& modes, const std::vector<std::string>& args) const; |
||||
void parseIncoming(const std::string& line); |
||||
|
||||
/*
|
||||
* Message handlers for the various messages we receive from the IRC server. |
||||
*/ |
||||
void handleNumeric(const IRCMessage& ircmessage); |
||||
void cmdNick(const IRCMessage& ircmessage); |
||||
void cmdMode(const IRCMessage& ircmessage); |
||||
void cmdQuit(const IRCMessage& ircmessage); |
||||
void cmdJoin(const IRCMessage& ircmessage); |
||||
void cmdPart(const IRCMessage& ircmessage); |
||||
void cmdTopic(const IRCMessage& ircmessage); |
||||
void cmdInvite(const IRCMessage& ircmessage); |
||||
void cmdKick(const IRCMessage& ircmessage); |
||||
void cmdPrivmsg(const IRCMessage& ircmessage); |
||||
void cmdNotice(const IRCMessage& ircmessage); |
||||
void cmdKill(const IRCMessage& ircmessage); |
||||
void cmdPing(const IRCMessage& ircmessage); |
||||
void cmdPong(const IRCMessage& ircmessage); |
||||
void cmdError(const IRCMessage& ircmessage); |
||||
void cmdWallops(const IRCMessage& ircmessage); |
||||
void cmdV3Cap(const IRCMessage& ircmessage); |
||||
void cmdV3Away(const IRCMessage& ircmessage); |
||||
void cmdV3Account(const IRCMessage& ircmessage); |
||||
void cmdV3Authenticate(const IRCMessage& ircmessage); |
||||
void handleUnhandled(const IRCMessage& ircmessage); |
||||
}; |
||||
|
||||
#endif // IRCBASEPRIV_H
|
@ -0,0 +1,114 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "IRCMessage.h" |
||||
#include <algorithm> |
||||
#include <iterator> |
||||
|
||||
IRCMessage::IRCMessage(const IRCPrefix& defaultSender, const std::string& message) |
||||
: m_sender(defaultSender) |
||||
{ |
||||
/*
|
||||
* :sender command arg :message |
||||
*/ |
||||
|
||||
if (message.empty()) |
||||
return; |
||||
|
||||
enum class S { |
||||
IRCv3Tags, |
||||
Prefix, |
||||
Command, |
||||
Argument, |
||||
Message |
||||
} parseState = S::IRCv3Tags; |
||||
|
||||
auto it = message.begin() + 1; |
||||
|
||||
if (message[0] != '@') { |
||||
parseState = S::Prefix; |
||||
} |
||||
|
||||
if (message[0] != ':' && message[0] != '@') { |
||||
// Set up parser for the format: "cmd arg :msg"
|
||||
it = message.begin(); |
||||
parseState = S::Command; |
||||
} |
||||
|
||||
/*
|
||||
* Tokenize the message. Three formats may occur: |
||||
* :server.addr cmd arg :msg |
||||
* @IRCv3Tag;tag2;tag3 :server.addr cmd arg :msg |
||||
* cmd arg :msg |
||||
*/ |
||||
while (it != message.end()) { |
||||
auto next = std::find(it, message.end(), ' '); |
||||
|
||||
switch (parseState) { |
||||
case S::IRCv3Tags: |
||||
{ |
||||
std::string tagstr; |
||||
std::copy(it, next, std::back_inserter(tagstr)); |
||||
parseIRCv3Tags(tagstr); |
||||
parseState = S::Prefix; |
||||
break; |
||||
} |
||||
|
||||
case S::Prefix: |
||||
/* This condition happens if parseState started on IRCv3Tags. */ |
||||
if (*it == ':') |
||||
++it; |
||||
|
||||
m_sender = IRCPrefix(std::string(it, next)); |
||||
parseState = S::Command; |
||||
break; |
||||
|
||||
case S::Command: |
||||
m_command = std::string(it, next); |
||||
parseState = S::Argument; |
||||
break; |
||||
|
||||
case S::Argument: |
||||
if (*it == ':') { |
||||
++it; |
||||
parseState = S::Message; |
||||
// note: fallthrough to "Message"
|
||||
} |
||||
else { |
||||
m_args.emplace_back(it, next); |
||||
break; |
||||
} |
||||
[[fallthrough]]; |
||||
|
||||
case S::Message: |
||||
next = message.end(); |
||||
m_message = std::string(it, next); |
||||
break; |
||||
} |
||||
|
||||
it = (next == message.end()) ? next : next + 1; |
||||
} |
||||
} |
||||
|
||||
const std::string& IRCMessage::operator[](int idx) const |
||||
{ |
||||
static const std::string empty; |
||||
if (idx < 0 || idx >= m_args.size()) |
||||
return empty; |
||||
else |
||||
return m_args[idx]; |
||||
} |
||||
|
||||
void IRCMessage::parseIRCv3Tags(const std::string& tagstr) |
||||
{ |
||||
auto it = tagstr.begin(); |
||||
while (it != tagstr.end()) { |
||||
auto next = std::find(it, tagstr.end(), ';'); |
||||
m_tags.emplace_back(it, next); |
||||
it = (next == tagstr.end()) ? next : next + 1; |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#ifndef IRCMESSAGE_H |
||||
#define IRCMESSAGE_H |
||||
|
||||
#include "IRCPrefix.h" |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
#include <chrono> |
||||
|
||||
class IRCMessage |
||||
{ |
||||
public: |
||||
IRCMessage(const IRCPrefix& defaultSender, const std::string& message); |
||||
|
||||
const std::vector<std::string>& getTags() const { return m_tags; } |
||||
const IRCPrefix& getSender() const { return m_sender; } |
||||
const std::string& getCommand() const { return m_command; } |
||||
const std::vector<std::string>& getArgs() const { return m_args; } |
||||
const std::string& getMessage() const { return m_message; } |
||||
const std::string& getBatchId() const { return m_batchId; } |
||||
const std::chrono::system_clock::time_point& getTimestamp() const { return m_timestamp; } |
||||
|
||||
bool isNumeric() const { return !m_command.empty() && m_command[0] >= '0' && m_command[0] <= '9'; } |
||||
|
||||
bool operator==(const char* commandName) const { return m_command == commandName; } |
||||
const std::string& operator[](int idx) const; |
||||
|
||||
private: |
||||
using SystemClock = std::chrono::system_clock; |
||||
|
||||
std::vector<std::string> m_tags; //!< IRCv3 tags
|
||||
IRCPrefix m_sender; //!< Sender prefix
|
||||
std::string m_command; //!< Command name
|
||||
std::vector<std::string> m_args; //!< Arguments to command
|
||||
std::string m_message; //!< Message to command
|
||||
|
||||
std::string m_batchId; //!< Set if this is part of a batch
|
||||
SystemClock::time_point m_timestamp; //!< When message occurred.
|
||||
|
||||
void parseIRCv3Tags(const std::string& tagstr); |
||||
}; |
||||
|
||||
#endif // IRCMESSAGE_H
|
@ -0,0 +1,27 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::addMemberToChannel(const IRCPrefix& prefix, const std::string& channel) |
||||
{ |
||||
auto member = super.getMember(prefix.toString()); |
||||
if (!member) { |
||||
member = std::make_shared<IRCMember>(prefix); |
||||
allMembers.emplace_back(member); |
||||
} |
||||
|
||||
auto chanentry = super.getChannel(channel); |
||||
if (!chanentry) { |
||||
chanentry = std::make_shared<IRCChannel>(channel, super); |
||||
channels.emplace_back(chanentry); |
||||
} |
||||
|
||||
member->addChannel(chanentry); |
||||
chanentry->addMember(member); |
||||
} |
@ -0,0 +1,30 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBasePriv.h" |
||||
|
||||
char IRCBasePriv::channelModeGroup(char m) const |
||||
{ |
||||
char group[] = {'A', 'B', 'C', 'D', 'M', 0}; |
||||
int gi = 0; // group index
|
||||
|
||||
const std::string& cm = isupport.at("CHANMODES"); |
||||
for (char c : cm) { |
||||
if (c == ',') { |
||||
++gi; |
||||
continue; |
||||
} |
||||
if (c == m) |
||||
return group[gi]; |
||||
} |
||||
|
||||
++gi; // No match yet... Test for channel-member modes
|
||||
if (validPrivilegeModes.find(m) == std::string::npos) |
||||
++gi; // Not found, advance to the last "group" (numeric zero, failure).
|
||||
|
||||
return group[gi]; |
||||
} |
@ -0,0 +1,51 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Commands.h" |
||||
|
||||
void IRCBasePriv::connected(const asio::error_code& /*ec*/) |
||||
{ |
||||
using namespace Command::IRC; |
||||
|
||||
if (useSSL) { |
||||
asio::async_read_until(sslsock.value(), asio::dynamic_buffer(readbuf), "\r\n", |
||||
[this](const asio::error_code& e, std::size_t size){ read(e, size); }); |
||||
} |
||||
else { |
||||
asio::async_read_until(sock.value(), asio::dynamic_buffer(readbuf), "\r\n", |
||||
[this](const asio::error_code& e, std::size_t size){ read(e, size); }); |
||||
} |
||||
|
||||
isConnected = true; |
||||
|
||||
registeredV3support.clear(); |
||||
writeNoMsg(Command::IRCv3::CAP, { Command::IRCv3::LS }); |
||||
|
||||
if (!password.empty()) |
||||
write(Command::IRC::PASS, password); |
||||
|
||||
write(Command::IRC::NICK, nickname); |
||||
write(Command::IRC::USER, {ident, "0", "*"}, realname); |
||||
|
||||
if (useSSL) { |
||||
/* TODO test if these two below must be set _before_ ::connect happens, on Windows platform. */ |
||||
sslsock->next_layer().set_option(asio::socket_base::reuse_address(true)); |
||||
sslsock->next_layer().set_option(asio::socket_base::keep_alive(true)); |
||||
} |
||||
else { |
||||
/* TODO test if these two below must be set _before_ ::connect happens, on Windows platform. */ |
||||
sock->set_option(asio::socket_base::reuse_address(true)); |
||||
sock->set_option(asio::socket_base::keep_alive(true)); |
||||
} |
||||
|
||||
if (sslExceptions.empty()) |
||||
super.onConnected(); |
||||
else |
||||
super.onConnectedWithSSLExceptions(sslExceptions); |
||||
} |
@ -0,0 +1,24 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Utilities.h" |
||||
|
||||
void IRCBasePriv::ctcp(const std::string& messageType, const std::string& target, const std::string& command, const std::string& message) |
||||
{ |
||||
if (!isConnected) |
||||
return; |
||||
|
||||
std::string ctcpdata; |
||||
ctcpdata.push_back(CTCPflag); |
||||
ctcpdata.append(command); |
||||
if (!message.empty()) |
||||
ctcpdata.append(" " + message); |
||||
ctcpdata.push_back(CTCPflag); |
||||
write(messageType, { target }, ctcpdata); |
||||
} |
@ -0,0 +1,63 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Utilities.h" |
||||
|
||||
void IRCBasePriv::delMemberFromChannel(const IRCPrefix& prefix, const std::string& channel) |
||||
{ |
||||
auto member = super.getMember(prefix.nickname()); |
||||
auto chanentry = super.getChannel(channel); |
||||
|
||||
if (!member || !chanentry) |
||||
return; |
||||
|
||||
member->delChannel(chanentry); |
||||
|
||||
/* Delete only if not us. */ |
||||
if (member->channels().empty() && prefix.nickname() != nickname) { |
||||
chanentry->delMember(member); |
||||
auto it = std::find_if(allMembers.begin(), allMembers.end(), |
||||
[&prefix](auto mem) { |
||||
return prefix == mem->prefix(); |
||||
}); |
||||
|
||||
if (it != allMembers.end()) |
||||
allMembers.erase(it); |
||||
} |
||||
|
||||
/* If us, delete everything we know about this channel. */ |
||||
else if (prefix.nickname() == nickname) { |
||||
/* We also need to remove this channel from every member in that channel. */ |
||||
for (const auto& mptr : chanentry->members()) { |
||||
const auto& m = mptr.member(); |
||||
IRCPrefix p = m->prefix(); |
||||
if (p.nickname() != nickname) { |
||||
m->delChannel(chanentry); |
||||
if (m->isDangling()) { |
||||
auto amit = std::find_if(allMembers.begin(), allMembers.end(), [&p](const auto& ptr){ |
||||
return ptr->prefix() == p; |
||||
}); |
||||
if (amit != allMembers.end()) |
||||
allMembers.erase(amit); |
||||
} |
||||
} |
||||
} |
||||
|
||||
chanentry->delMember(member); |
||||
|
||||
/* Erase this channel */ |
||||
auto it = std::find_if(channels.begin(), channels.end(), |
||||
[&channel](auto cptr) { |
||||
return strEquals(channel, cptr->name()); |
||||
}); |
||||
|
||||
if (it != channels.end()) |
||||
channels.erase(it); |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::disconnectHandler() |
||||
{ |
||||
// Just to ensure we're actually closed... might throw something and we really do not care.
|
||||
if (useSSL) |
||||
try { sslsock->lowest_layer().close(); } catch(...) {} |
||||
else |
||||
try { sock->close(); } catch(...) {} |
||||
|
||||
readbuf.clear(); |
||||
channels.clear(); |
||||
allMembers.clear(); |
||||
setDefaults(); |
||||
super.onDisconnected(); |
||||
} |
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBasePriv.h" |
||||
|
||||
bool IRCBasePriv::isChannelSymbol(char c) |
||||
{ |
||||
const std::string& ct = isupport.at("CHANTYPES"); |
||||
return ct.find(c) != std::string::npos; |
||||
} |
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBasePriv.h" |
||||
|
||||
bool IRCBasePriv::isMemberPrivilegeSymbol(char m) const |
||||
{ |
||||
bool ret = validPrivilegeSymbols.find(m) != std::string::npos; |
||||
return ret; |
||||
} |
@ -0,0 +1,21 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBasePriv.h" |
||||
#include "../Commands.h" |
||||
#include <fmt/format.h> |
||||
|
||||
void IRCBasePriv::keepaliveTimeout(const asio::error_code& ec) |
||||
{ |
||||
using namespace Command::IRC; |
||||
|
||||
if (ec != asio::error::operation_aborted && isOnline) { |
||||
auto ts = std::chrono::steady_clock::now().time_since_epoch().count(); |
||||
write(Command::IRC::PING, fmt::format("KeepAlive {}", ts)); |
||||
startKeepaliveTimer(); |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBasePriv.h" |
||||
|
||||
char IRCBasePriv::memberPrivilegeSymbolToMode(char s) const |
||||
{ |
||||
auto pos = validPrivilegeSymbols.find(s); |
||||
if (pos == std::string::npos) |
||||
return '\0'; |
||||
else |
||||
return validPrivilegeModes[pos]; |
||||
} |
@ -0,0 +1,56 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::parseChannelModeMessage(std::shared_ptr<IRCChannel> chan, const std::string& modes, const std::vector<std::string>& args) const |
||||
{ |
||||
char sign; // + or -
|
||||
int argidx = 0; |
||||
for (char m : modes) { |
||||
if (m == '+' || m == '-') { |
||||
sign = m; |
||||
continue; |
||||
} |
||||
char group = channelModeGroup(m); |
||||
if (group == 'A') |
||||
++argidx; |
||||
|
||||
else if (group == 'B') { |
||||
if (sign == '+') |
||||
chan->setMode(m, args[argidx]); |
||||
else |
||||
chan->delMode(m); |
||||
++argidx; |
||||
} |
||||
|
||||
else if (group == 'C') { |
||||
if (sign == '+') { |
||||
chan->setMode(m, args[argidx]); |
||||
++argidx; |
||||
} |
||||
else |
||||
chan->delMode(m); |
||||
} |
||||
|
||||
else if (group == 'D') { |
||||
if (sign == '+') |
||||
chan->setMode(m, ""); |
||||
else |
||||
chan->delMode(m); |
||||
} |
||||
|
||||
else if (group == 'M') { |
||||
auto member = chan->getMember(args[argidx]); |
||||
++argidx; |
||||
if (sign == '+') |
||||
member->get().addMode(m); |
||||
else |
||||
member->get().delMode(m); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,143 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBase.h" |
||||
#include "../IRCBasePriv.h" |
||||
#include "../Commands.h" |
||||
#include <fmt/format.h> |
||||
|
||||
#include <iostream> // Needs better logger, spdlog :) |
||||
|
||||
void IRCBasePriv::parseIncoming(const std::string& line) |
||||
{ |
||||
using namespace Command::IRC; |
||||
|
||||
/*
|
||||
* Do NOT EVER do an "early return" from this function. |
||||
* Make sure it always reaches the end! |
||||
* No throwing from any of the message handler functions either. |
||||
*/ |
||||
|
||||
IRCMessage ircmessage(IRCPrefix(hostname), line); |
||||
currentMessage = &ircmessage; |
||||
|
||||
if constexpr (DumpReadData) { |
||||
std::cout << "[RCV] "; |
||||
|
||||
std::cout << fmt::format("tagc={} ", ircmessage.getTags().size()); |
||||
int c = 0; |
||||
for (const auto& tag : ircmessage.getTags()) { |
||||
std::cout << fmt::format("tag({})=\"{}\" ", c, tag); |
||||
++c; |
||||
} |
||||
|
||||
std::cout << fmt::format(R"(from="{}" cmd="{}" msg="{}" argc={} )", ircmessage.getSender().composite(), ircmessage.getCommand(), ircmessage.getMessage(), ircmessage.getArgs().size()); |
||||
|
||||
c = 0; |
||||
for (const auto& arg : ircmessage.getArgs()) { |
||||
std::cout << fmt::format("arg({})=\"{}\" ", c, arg); |
||||
++c; |
||||
} |
||||
std::cout << std::endl; |
||||
} |
||||
|
||||
|
||||
using namespace Command::IRC; |
||||
using namespace Numeric; |
||||
|
||||
/*
|
||||
* Numeric reply |
||||
*/ |
||||
if (ircmessage.isNumeric()) { |
||||
handleNumeric(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == NICK) { |
||||
cmdNick(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == MODE) { |
||||
cmdMode(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == QUIT) { |
||||
cmdQuit(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == JOIN) { |
||||
cmdJoin(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == PART) { |
||||
cmdPart(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == TOPIC) { |
||||
cmdTopic(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == INVITE) { |
||||
cmdInvite(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == KICK) { |
||||
cmdKick(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == PRIVMSG) { |
||||
cmdPrivmsg(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == NOTICE) { |
||||
cmdNotice(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == KILL) { |
||||
cmdKill(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == PING) { |
||||
cmdPing(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == PONG) { |
||||
cmdPong(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == ERROR_) { |
||||
cmdError(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == WALLOPS) { |
||||
cmdWallops(ircmessage); |
||||
} |
||||
|
||||
else if (ircmessage == Command::IRCv3::CAP && !isOnline) { |
||||
cmdV3Cap(ircmessage); |
||||
} |
||||
|
||||
/* IRCv3 capability "away-notify" */ |
||||
else if (ircmessage == Command::IRC::AWAY) { |
||||
cmdV3Away(ircmessage); |
||||
} |
||||
|
||||
/* IRCv3 capability "account-notify" */ |
||||
else if (ircmessage == Command::Extension::ACCOUNT) { |
||||
cmdV3Account(ircmessage); |
||||
} |
||||
|
||||
/* IRCv3 capability "sasl" */ |
||||
else if (ircmessage == Command::Extension::AUTHENTICATE) { |
||||
cmdV3Authenticate(ircmessage); |
||||
} |
||||
|
||||
else { |
||||
handleUnhandled(ircmessage); |
||||
} |
||||
|
||||
currentMessage = nullptr; |
||||
} |
@ -0,0 +1,34 @@ |
||||
/*
|
||||
* IdealIRC Core - Internet Relay Chat API |
||||
* Copyright (C) 2021 Tom-Andre Barstad. |
||||
* This software is licensed under the Software Attribution License. |
||||
* See LICENSE for more information. |
||||
*/ |
||||
|
||||
#include "../IRCBasePriv.h" |
||||
|
||||
void IRCBasePriv::read(const asio::error_code& ec, std::size_t /*size*/) |
||||
{ |
||||
lastError = SystemErrorToIRCError(ec); |
||||
if (lastError != IRCError::NoError) { |
||||
disconnectHandler(); |
||||
return; |
||||
} |
||||
|
||||
auto crIt = std::find(readbuf.begin(), readbuf.end(), '\r'); |
||||
|
||||
if (crIt != readbuf.end()) { |
||||
std::string line(readbuf.begin(), crIt); |
||||
readbuf.erase(readbuf.begin(), crIt + 2); |
||||
parseIncoming(line); |
||||
} |
||||
|
||||
if (useSSL) { |
||||
asio::async_read_until(sslsock.value(), asio::dynamic_buffer(readbuf), "\r\n", |
||||
[this](const asio::error_code& e, std::size_t size){ read(e, size); }); |
||||
} |
||||
else { |
||||
asio::async_read_until(sock.value(), asio::dynamic_buffer(readbuf), "\r\n", |
||||
[this](const asio::error_code& e, std::size_t size){ read(e, size); }); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue