WebAPI: Add a way to download .torrent file using search plugin

* Simplify nova2dl script
* Use search engine name instead of site URL (like nova2 does)
* Add a way to download torrent using search plugin

PR #20824.
This commit is contained in:
Vladimir Golovnev 2024-05-15 08:47:40 +03:00 committed by GitHub
commit 4d8713ce11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 89 additions and 50 deletions

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -36,10 +36,10 @@
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "searchpluginmanager.h" #include "searchpluginmanager.h"
SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager) SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager)
: QObject {manager} : QObject(manager)
, m_manager {manager} , m_manager {manager}
, m_downloadProcess {new QProcess {this}} , m_downloadProcess {new QProcess(this)}
{ {
m_downloadProcess->setEnvironment(QProcess::systemEnvironment()); m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished) connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
@ -48,7 +48,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri
{ {
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG, Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
(SearchPluginManager::engineLocation() / Path(u"nova2dl.py"_s)).toString(), (SearchPluginManager::engineLocation() / Path(u"nova2dl.py"_s)).toString(),
siteUrl, pluginName,
url url
}; };
// Launch search // Launch search

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -41,7 +41,7 @@ class SearchDownloadHandler : public QObject
friend class SearchPluginManager; friend class SearchPluginManager;
SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager); SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager);
signals: signals:
void downloadFinished(const QString &path); void downloadFinished(const QString &path);

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -61,13 +61,13 @@ namespace
} }
SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager) SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager)
: QObject {manager} : QObject(manager)
, m_pattern {pattern} , m_pattern {pattern}
, m_category {category} , m_category {category}
, m_usedPlugins {usedPlugins} , m_usedPlugins {usedPlugins}
, m_manager {manager} , m_manager {manager}
, m_searchProcess {new QProcess {this}} , m_searchProcess {new QProcess(this)}
, m_searchTimeout {new QTimer {this}} , m_searchTimeout {new QTimer(this)}
{ {
// Load environment variables (proxy) // Load environment variables (proxy)
m_searchProcess->setEnvironment(QProcess::systemEnvironment()); m_searchProcess->setEnvironment(QProcess::systemEnvironment());
@ -177,7 +177,8 @@ bool SearchHandler::parseSearchResult(const QStringView line, SearchResult &sear
const QList<QStringView> parts = line.split(u'|'); const QList<QStringView> parts = line.split(u'|');
const int nbFields = parts.size(); const int nbFields = parts.size();
if (nbFields <= PL_ENGINE_URL) return false; // Anything after ENGINE_URL is optional if (nbFields <= PL_ENGINE_URL)
return false; // Anything after ENGINE_URL is optional
searchResult = SearchResult(); searchResult = SearchResult();
searchResult.fileUrl = parts.at(PL_DL_LINK).trimmed().toString(); // download URL searchResult.fileUrl = parts.at(PL_DL_LINK).trimmed().toString(); // download URL
@ -194,7 +195,8 @@ bool SearchHandler::parseSearchResult(const QStringView line, SearchResult &sear
if (!ok || (searchResult.nbLeechers < 0)) if (!ok || (searchResult.nbLeechers < 0))
searchResult.nbLeechers = -1; searchResult.nbLeechers = -1;
searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed().toString(); // Search site URL searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed().toString(); // Search engine site URL
searchResult.engineName = m_manager->pluginNameBySiteURL(searchResult.siteUrl); // Search engine name
if (nbFields > PL_DESC_LINK) if (nbFields > PL_DESC_LINK)
searchResult.descrLink = parts.at(PL_DESC_LINK).trimmed().toString(); // Description Link searchResult.descrLink = parts.at(PL_DESC_LINK).trimmed().toString(); // Description Link

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -46,6 +46,7 @@ struct SearchResult
qlonglong fileSize = 0; qlonglong fileSize = 0;
qlonglong nbSeeders = 0; qlonglong nbSeeders = 0;
qlonglong nbLeechers = 0; qlonglong nbLeechers = 0;
QString engineName;
QString siteUrl; QString siteUrl;
QString descrLink; QString descrLink;
QDateTime pubDate; QDateTime pubDate;

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -181,6 +181,17 @@ PluginInfo *SearchPluginManager::pluginInfo(const QString &name) const
return m_plugins.value(name); return m_plugins.value(name);
} }
QString SearchPluginManager::pluginNameBySiteURL(const QString &siteURL) const
{
for (const PluginInfo *plugin : asConst(m_plugins))
{
if (plugin->url == siteURL)
return plugin->name;
}
return {};
}
void SearchPluginManager::enablePlugin(const QString &name, const bool enabled) void SearchPluginManager::enablePlugin(const QString &name, const bool enabled)
{ {
PluginInfo *plugin = m_plugins.value(name, nullptr); PluginInfo *plugin = m_plugins.value(name, nullptr);
@ -338,9 +349,9 @@ void SearchPluginManager::checkForUpdates()
, this, &SearchPluginManager::versionInfoDownloadFinished); , this, &SearchPluginManager::versionInfoDownloadFinished);
} }
SearchDownloadHandler *SearchPluginManager::downloadTorrent(const QString &siteUrl, const QString &url) SearchDownloadHandler *SearchPluginManager::downloadTorrent(const QString &pluginName, const QString &url)
{ {
return new SearchDownloadHandler {siteUrl, url, this}; return new SearchDownloadHandler(pluginName, url, this);
} }
SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins) SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins)

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -75,6 +75,7 @@ public:
QStringList supportedCategories() const; QStringList supportedCategories() const;
QStringList getPluginCategories(const QString &pluginName) const; QStringList getPluginCategories(const QString &pluginName) const;
PluginInfo *pluginInfo(const QString &name) const; PluginInfo *pluginInfo(const QString &name) const;
QString pluginNameBySiteURL(const QString &siteURL) const;
void enablePlugin(const QString &name, bool enabled = true); void enablePlugin(const QString &name, bool enabled = true);
void updatePlugin(const QString &name); void updatePlugin(const QString &name);
@ -84,7 +85,7 @@ public:
void checkForUpdates(); void checkForUpdates();
SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins); SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
SearchDownloadHandler *downloadTorrent(const QString &siteUrl, const QString &url); SearchDownloadHandler *downloadTorrent(const QString &pluginName, const QString &url);
static PluginVersion getPluginVersion(const Path &filePath); static PluginVersion getPluginVersion(const Path &filePath);
static QString categoryFullName(const QString &categoryName); static QString categoryFullName(const QString &categoryName);

View file

@ -70,7 +70,8 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, tr("Size", "i.e: file size")); m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, tr("Size", "i.e: file size"));
m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources")); m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources"));
m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources")); m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources"));
m_searchListModel->setHeaderData(SearchSortModel::ENGINE_URL, Qt::Horizontal, tr("Search engine")); m_searchListModel->setHeaderData(SearchSortModel::ENGINE_NAME, Qt::Horizontal, tr("Engine"));
m_searchListModel->setHeaderData(SearchSortModel::ENGINE_URL, Qt::Horizontal, tr("Engine URL"));
m_searchListModel->setHeaderData(SearchSortModel::PUB_DATE, Qt::Horizontal, tr("Published On")); m_searchListModel->setHeaderData(SearchSortModel::PUB_DATE, Qt::Horizontal, tr("Published On"));
// Set columns text alignment // Set columns text alignment
m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
@ -269,8 +270,8 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
{ {
const QString torrentUrl = m_proxyModel->data( const QString torrentUrl = m_proxyModel->data(
m_proxyModel->index(rowIndex.row(), SearchSortModel::DL_LINK)).toString(); m_proxyModel->index(rowIndex.row(), SearchSortModel::DL_LINK)).toString();
const QString siteUrl = m_proxyModel->data( const QString engineName = m_proxyModel->data(
m_proxyModel->index(rowIndex.row(), SearchSortModel::ENGINE_URL)).toString(); m_proxyModel->index(rowIndex.row(), SearchSortModel::ENGINE_NAME)).toString();
if (torrentUrl.startsWith(u"magnet:", Qt::CaseInsensitive)) if (torrentUrl.startsWith(u"magnet:", Qt::CaseInsensitive))
{ {
@ -278,7 +279,7 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
} }
else else
{ {
SearchDownloadHandler *downloadHandler = m_searchHandler->manager()->downloadTorrent(siteUrl, torrentUrl); SearchDownloadHandler *downloadHandler = m_searchHandler->manager()->downloadTorrent(engineName, torrentUrl);
connect(downloadHandler, &SearchDownloadHandler::downloadFinished connect(downloadHandler, &SearchDownloadHandler::downloadFinished
, this, [this, option](const QString &source) { addTorrentToSession(source, option); }); , this, [this, option](const QString &source) { addTorrentToSession(source, option); });
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater); connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
@ -529,6 +530,7 @@ void SearchJobWidget::appendSearchResults(const QVector<SearchResult> &results)
setModelData(SearchSortModel::NAME, result.fileName, result.fileName); setModelData(SearchSortModel::NAME, result.fileName, result.fileName);
setModelData(SearchSortModel::DL_LINK, result.fileUrl, result.fileUrl); setModelData(SearchSortModel::DL_LINK, result.fileUrl, result.fileUrl);
setModelData(SearchSortModel::ENGINE_NAME, result.engineName, result.engineName);
setModelData(SearchSortModel::ENGINE_URL, result.siteUrl, result.siteUrl); setModelData(SearchSortModel::ENGINE_URL, result.siteUrl, result.siteUrl);
setModelData(SearchSortModel::DESC_LINK, result.descrLink, result.descrLink); setModelData(SearchSortModel::DESC_LINK, result.descrLink, result.descrLink);
setModelData(SearchSortModel::SIZE, Utils::Misc::friendlyUnit(result.fileSize), result.fileSize, (Qt::AlignRight | Qt::AlignVCenter)); setModelData(SearchSortModel::SIZE, Utils::Misc::friendlyUnit(result.fileSize), result.fileSize, (Qt::AlignRight | Qt::AlignVCenter));

View file

@ -44,6 +44,7 @@ public:
SIZE, SIZE,
SEEDS, SEEDS,
LEECHES, LEECHES,
ENGINE_NAME,
ENGINE_URL, ENGINE_URL,
PUB_DATE, PUB_DATE,
DL_LINK, DL_LINK,

View file

@ -1,7 +1,9 @@
#VERSION: 1.23 #VERSION: 1.24
# Author: # Author:
# Christophe DUMEZ (chris@qbittorrent.org) # Christophe DUMEZ (chris@qbittorrent.org)
# Contributors:
# Vladimir Golovnev (glassez@yandex.ru)
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met: # modification, are permitted provided that the following conditions are met:
@ -27,9 +29,7 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
import glob
import importlib import importlib
import os
import pathlib import pathlib
import sys import sys
@ -40,34 +40,24 @@ if current_path not in sys.path:
from helpers import download_file from helpers import download_file
supported_engines = dict()
engines = glob.glob(os.path.join(os.path.dirname(__file__), 'engines', '*.py'))
for engine in engines:
e = engine.split(os.sep)[-1][:-3]
if len(e.strip()) == 0:
continue
if e.startswith('_'):
continue
try:
module = importlib.import_module("engines." + e)
engine_class = getattr(module, e)
globals()[e] = engine_class
engine_url = getattr(engine_class, 'url')
supported_engines[engine_url] = e
except Exception:
pass
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 3: if len(sys.argv) < 3:
raise SystemExit('./nova2dl.py engine_url download_parameter') raise SystemExit('./nova2dl.py engine_name download_parameter')
engine_url = sys.argv[1].strip()
engine_name = sys.argv[1].strip()
download_param = sys.argv[2].strip() download_param = sys.argv[2].strip()
if engine_url not in supported_engines.keys():
raise SystemExit('./nova2dl.py: this engine_url was not recognized') try:
engine = globals()[supported_engines[engine_url]]() module = importlib.import_module("engines." + engine_name)
engine_class = getattr(module, engine_name)
engine = engine_class()
except Exception as e:
print(repr(e))
raise SystemExit('./nova2dl.py: this engine_name was not recognized')
if hasattr(engine, 'download_torrent'): if hasattr(engine, 'download_torrent'):
engine.download_torrent(download_param) engine.download_torrent(download_param)
else: else:
print(download_file(download_param)) print(download_file(download_param))
sys.exit(0) sys.exit(0)

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com> * Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -36,8 +37,11 @@
#include <QList> #include <QList>
#include <QSharedPointer> #include <QSharedPointer>
#include "base/addtorrentmanager.h"
#include "base/global.h" #include "base/global.h"
#include "base/interfaces/iapplication.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/search/searchdownloadhandler.h"
#include "base/search/searchhandler.h" #include "base/search/searchhandler.h"
#include "base/utils/datetime.h" #include "base/utils/datetime.h"
#include "base/utils/foreignapps.h" #include "base/utils/foreignapps.h"
@ -213,6 +217,29 @@ void SearchController::deleteAction()
m_searchHandlers.erase(iter); m_searchHandlers.erase(iter);
} }
void SearchController::downloadTorrentAction()
{
requireParams({u"torrentUrl"_s, u"pluginName"_s});
const QString torrentUrl = params()[u"torrentUrl"_s];
const QString pluginName = params()[u"pluginName"_s];
if (torrentUrl.startsWith(u"magnet:", Qt::CaseInsensitive))
{
app()->addTorrentManager()->addTorrent(torrentUrl);
}
else
{
SearchDownloadHandler *downloadHandler = SearchPluginManager::instance()->downloadTorrent(pluginName, torrentUrl);
connect(downloadHandler, &SearchDownloadHandler::downloadFinished
, this, [this, downloadHandler](const QString &source)
{
app()->addTorrentManager()->addTorrent(source);
downloadHandler->deleteLater();
});
}
}
void SearchController::pluginsAction() void SearchController::pluginsAction()
{ {
const QStringList allPlugins = SearchPluginManager::instance()->allPlugins(); const QStringList allPlugins = SearchPluginManager::instance()->allPlugins();
@ -300,6 +327,7 @@ int SearchController::generateSearchId() const
* - "fileSize" * - "fileSize"
* - "nbSeeders" * - "nbSeeders"
* - "nbLeechers" * - "nbLeechers"
* - "engineName"
* - "siteUrl" * - "siteUrl"
* - "descrLink" * - "descrLink"
* - "pubDate" * - "pubDate"
@ -316,6 +344,7 @@ QJsonObject SearchController::getResults(const QList<SearchResult> &searchResult
{u"fileSize"_s, searchResult.fileSize}, {u"fileSize"_s, searchResult.fileSize},
{u"nbSeeders"_s, searchResult.nbSeeders}, {u"nbSeeders"_s, searchResult.nbSeeders},
{u"nbLeechers"_s, searchResult.nbLeechers}, {u"nbLeechers"_s, searchResult.nbLeechers},
{u"engineName"_s, searchResult.engineName},
{u"siteUrl"_s, searchResult.siteUrl}, {u"siteUrl"_s, searchResult.siteUrl},
{u"descrLink"_s, searchResult.descrLink}, {u"descrLink"_s, searchResult.descrLink},
{u"pubDate"_s, Utils::DateTime::toSecsSinceEpoch(searchResult.pubDate)} {u"pubDate"_s, Utils::DateTime::toSecsSinceEpoch(searchResult.pubDate)}

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com> * Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -56,6 +57,7 @@ private slots:
void statusAction(); void statusAction();
void resultsAction(); void resultsAction();
void deleteAction(); void deleteAction();
void downloadTorrentAction();
void pluginsAction(); void pluginsAction();
void installPluginAction(); void installPluginAction();
void uninstallPluginAction(); void uninstallPluginAction();