From 3b169793c3211aed174f8ec2a0d71d2ed450e3e8 Mon Sep 17 00:00:00 2001 From: wipedlifepotato <60944239+wipedlifepotato@users.noreply.github.com> Date: Mon, 24 Aug 2026 16:22:33 +0300 Subject: [PATCH] torrent-rpc-client-page --- contrib/webconsole/torrent-rpc.js | 67 ++++ contrib/webconsole/torrent_client.html | 280 +++++++++++++ daemon/HTTPServer.cpp | 519 +++---------------------- libi2pd_client/ClientContext.h | 1 + libi2pd_client/TorrentsRPC.cpp | 12 + libi2pd_client/TorrentsRPC.h | 4 + 6 files changed, 420 insertions(+), 463 deletions(-) create mode 100644 contrib/webconsole/torrent-rpc.js create mode 100644 contrib/webconsole/torrent_client.html diff --git a/contrib/webconsole/torrent-rpc.js b/contrib/webconsole/torrent-rpc.js new file mode 100644 index 00000000..23bb109d --- /dev/null +++ b/contrib/webconsole/torrent-rpc.js @@ -0,0 +1,67 @@ +class TorrentClient { + constructor(host = '127.0.0.1', port = 9191, path = 'mytorrents') { + this.host = host; + this.port = port; + this.path = path; + } + + setConnection(host, port, path) { + this.host = host; + this.port = port; + this.path = path; + } + + getEndpoint() { + return `http://${this.host}:${this.port}${this.path}`; + } + + getStatusString(status) { + switch (status) { + case 4: return 'DOWNLOADING'; + case 6: return 'SEEDING'; + case 0: return 'STOPPED'; + case 2: return 'CHECKING'; + default: return 'QUEUED'; + } + } + + formatBytes(bytesPerSec) { + if (!bytesPerSec) return '0 B/s'; + if (bytesPerSec > 1024 * 1024) { + return (bytesPerSec / (1024 * 1024)).toFixed(2) + ' MB/s'; + } + return (bytesPerSec / 1024).toFixed(2) + ' KB/s'; + } + + async addTorrent(b64metainfo) { + const res = await fetch(this.getEndpoint(), { + method: "POST", + body: JSON.stringify({ 'method': 'torrent-add', 'arguments': { 'metainfo': b64metainfo } }), + headers: { 'Content-Type': 'application/json' } + }); + return res.json(); + } + + async removeTorrent(id) { + const res = await fetch(this.getEndpoint(), { + method: "POST", + body: JSON.stringify({ 'method': 'torrent-remove', 'arguments': { 'ids': [id], 'delete-local-data': true } }), + headers: { 'Content-Type': 'application/json' } + }); + return res.json(); + } + + async getTorrents() { + const res = await fetch(this.getEndpoint(), { + method: "POST", + body: JSON.stringify({ + 'method': 'torrent-get', + 'arguments': { "fields": ["id", "name", "status", "rateDownload", "rateUpload", "totalSize", "percentDone"] } + }), + headers: { 'Content-Type': 'application/json' } + }); + return res.json(); + } +} + +//window.tjs = new TorrentClient(); diff --git a/contrib/webconsole/torrent_client.html b/contrib/webconsole/torrent_client.html new file mode 100644 index 00000000..d3792fc9 --- /dev/null +++ b/contrib/webconsole/torrent_client.html @@ -0,0 +1,280 @@ + + +
+
+
+

TORRENT_DAEMON_INTERFACE

+
CONNECTING...
+
+ +
+ Drop torrent payload here or browse file + +
+ +
+ + + + + + + + + + + + + + + + +
IDNameStatusProgressDownload RateActions
No active transfers
+
+
+
+ + diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index dd3cc941..2f1f09f6 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -92,8 +92,7 @@ namespace http { const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; - const char HTTP_PAGE_JSTORRENTCLIENT[] = "js_torr_client"; - + const char OPENJS_TORRENT_CLIENT_PAGE[] = "torrent_client_open"; static std::string ConvertTime (uint64_t time) { struct tm caltime; @@ -1196,11 +1195,14 @@ namespace http { s << "
\r\n" << tr("Torrents") << ":
\r\n
\r\n"; for (auto& it: torrentsTunnels) { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << " | Open Torrent Client for this Tunnel"; s << "
\r\n"<< std::endl; + } s << "
\r\n
\r\n"; } @@ -1395,469 +1397,59 @@ namespace http { ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); + else if (page == OPENJS_TORRENT_CLIENT_PAGE) + { + // todo: to an another method with return; + std::string tunnelname = params["tunnelname"]; + auto findTunnelEndpoint = [](const std::string& tunnelname) -> std::pair { + for (auto& [port, server] : i2p::client::context.GetTorrentsRPCServers()) + { + if (!server) continue; + for (const auto& [rpcPath, weakTunnel] : server->GetTunnels()) + { + if (auto tunnel = weakTunnel.lock()) + { + LogPrint(eLogDebug, "HTTP SERVER DFASDFASDF tunnel", tunnel->GetName()); + if (tunnel->GetName() == tunnelname) + { + + uint16_t rpcPort = server->GetAcceptor().local_endpoint().port(); + return std::pair(rpcPort, rpcPath); + } + } + } + } + return std::pair(0, ""); + }; + auto [rpcPort, rpcPath] = findTunnelEndpoint(tunnelname); + if (rpcPort && rpcPath.length()) + { + std::string tjs_file = i2p::fs::DataDirPath("webconsole/torrent-rpc.js"); + std::string tclient_file = i2p::fs::DataDirPath("webconsole/torrent_client.html"); + if (i2p::fs::Exists(tjs_file)) { + std::ifstream f(tjs_file, std::ifstream::binary); + std::stringstream tjsss; + tjsss << f.rdbuf(); + s << "\r\n"; + s << "\r\n"; + } + else { + s << "

Not found $datadir/webconsole/torrent-rpc.js

\r\n"; + } + if (i2p::fs::Exists(tclient_file)) { + std::ifstream f(tclient_file, std::ifstream::binary); + s << f.rdbuf() << "\r\n"; + } + else { + s << "

Not found $datadir/webconsole/torrent_client.html

\r\n"; + } + } else { + s << "

Not found rpc tunnel " << tunnelname << "

\r\n"; + } + } else if (page == HTTP_PAGE_LEASESETS) ShowLeasesSets(s); - else if (page == HTTP_PAGE_JSTORRENTCLIENT) - { - // Prepare a code. For add torrent. Content-Security-Policy also need - s << R"TJS( - - - - - - - Torrent Client Daemon - - - - -
-
-

TORRENT_DAEMON_INTERFACE

-
CONNECTING...
-
- -
-
- - -
-
- - -
-
- - -
- -
- -
- Drop torrent payload here or browse file - -
- -
- - - - - - - - - - - - - - - - -
IDNameStatusProgressDownload RateActions
No active transfers
-
-
- - - - - - )TJS"; - // TODO: or withous JS though torrent class. Example of usage the library -> https://github.com/wipedlifepotato/NeonI2P_JS/blob/master/Example/torrent_client.html - } - else { + else { res.code = 400; ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO return; @@ -1891,6 +1483,7 @@ namespace http { i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); + else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h index ca2253f9..a61dda38 100644 --- a/libi2pd_client/ClientContext.h +++ b/libi2pd_client/ClientContext.h @@ -189,6 +189,7 @@ namespace client const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; } const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; } const decltype(m_TorrentsTunnels)& GetTorrentsTunnels () const { return m_TorrentsTunnels; } + const decltype(m_TorrentsRPCServers)& GetTorrentsRPCServers () const { return m_TorrentsRPCServers; } std::shared_ptr GetHttpProxy () const { std::lock_guard l(m_ProxyMutex); diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index ca139fe5..80bbc79d 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -511,6 +511,18 @@ namespace torrents m_Tunnels.emplace (rpcPath, tunnel); } + std::pair TorrentsRPCServer::GetTunnelEndpoint (std::shared_ptr tunnel) const + { + for (const auto& [path, weakTunnel] : m_Tunnels) + { + if (auto t = weakTunnel.lock(); t == tunnel) + { + return { m_Acceptor.local_endpoint(), path }; + } + } + return {}; + } + std::shared_ptr TorrentsRPCServer::GetTunnel (std::string_view path) const { auto it = m_Tunnels.find (path); diff --git a/libi2pd_client/TorrentsRPC.h b/libi2pd_client/TorrentsRPC.h index 3215ae35..592eeeb1 100644 --- a/libi2pd_client/TorrentsRPC.h +++ b/libi2pd_client/TorrentsRPC.h @@ -59,6 +59,10 @@ namespace torrents void AddTunnel (std::string_view path, std::shared_ptr tunnel); std::shared_ptr GetTunnel (std::string_view path) const; + std::pair GetTunnelEndpoint (std::shared_ptr tunnel) const; + + const auto& GetTunnels() const { return m_Tunnels; } + const auto& GetAcceptor() const { return m_Acceptor; } private: void Accept ();