Add Network tab to settings — shows all connected hosts
Logs every outgoing connection (API requests and image downloads) with timestamps. Network tab in Settings shows all hosts contacted this session with request counts. No telemetry, just transparency.
This commit is contained in:
parent
70d9f12460
commit
e8f72c6fe6
@ -9,6 +9,7 @@ from dataclasses import dataclass, field
|
|||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ..config import USER_AGENT, DEFAULT_PAGE_SIZE
|
from ..config import USER_AGENT, DEFAULT_PAGE_SIZE
|
||||||
|
from ..cache import log_connection
|
||||||
|
|
||||||
log = logging.getLogger("booru")
|
log = logging.getLogger("booru")
|
||||||
|
|
||||||
@ -53,9 +54,14 @@ class BooruClient(ABC):
|
|||||||
headers={"User-Agent": USER_AGENT},
|
headers={"User-Agent": USER_AGENT},
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
timeout=20.0,
|
timeout=20.0,
|
||||||
|
event_hooks={"request": [self._log_request]},
|
||||||
)
|
)
|
||||||
return self._client
|
return self._client
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _log_request(request: httpx.Request) -> None:
|
||||||
|
log_connection(str(request.url))
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
if self._client and not self._client.is_closed:
|
if self._client and not self._client.is_closed:
|
||||||
await self._client.aclose()
|
await self._client.aclose()
|
||||||
|
|||||||
@ -4,13 +4,32 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from collections import OrderedDict
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .config import cache_dir, thumbnails_dir, USER_AGENT
|
from .config import cache_dir, thumbnails_dir, USER_AGENT
|
||||||
|
|
||||||
|
# Track all outgoing connections: {host: [timestamp, ...]}
|
||||||
|
_connection_log: OrderedDict[str, list[str]] = OrderedDict()
|
||||||
|
|
||||||
|
|
||||||
|
def log_connection(url: str) -> None:
|
||||||
|
host = urlparse(url).netloc
|
||||||
|
if host not in _connection_log:
|
||||||
|
_connection_log[host] = []
|
||||||
|
_connection_log[host].append(datetime.now().strftime("%H:%M:%S"))
|
||||||
|
# Keep last 50 entries per host
|
||||||
|
_connection_log[host] = _connection_log[host][-50:]
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection_log() -> dict[str, list[str]]:
|
||||||
|
return dict(_connection_log)
|
||||||
|
|
||||||
|
|
||||||
def _url_hash(url: str) -> str:
|
def _url_hash(url: str) -> str:
|
||||||
return hashlib.sha256(url.encode()).hexdigest()[:16]
|
return hashlib.sha256(url.encode()).hexdigest()[:16]
|
||||||
@ -110,7 +129,6 @@ async def download_image(
|
|||||||
local.unlink() # Remove corrupt cache entry
|
local.unlink() # Remove corrupt cache entry
|
||||||
|
|
||||||
# Extract referer from URL domain (needed for Gelbooru CDN etc.)
|
# Extract referer from URL domain (needed for Gelbooru CDN etc.)
|
||||||
from urllib.parse import urlparse
|
|
||||||
parsed = urlparse(url)
|
parsed = urlparse(url)
|
||||||
# Map CDN hostnames back to the main site
|
# Map CDN hostnames back to the main site
|
||||||
referer_host = parsed.netloc
|
referer_host = parsed.netloc
|
||||||
@ -120,6 +138,8 @@ async def download_image(
|
|||||||
referer_host = "danbooru.donmai.us"
|
referer_host = "danbooru.donmai.us"
|
||||||
referer = f"{parsed.scheme}://{referer_host}/"
|
referer = f"{parsed.scheme}://{referer_host}/"
|
||||||
|
|
||||||
|
log_connection(url)
|
||||||
|
|
||||||
own_client = client is None
|
own_client = client is None
|
||||||
if own_client:
|
if own_client:
|
||||||
client = httpx.AsyncClient(
|
client = httpx.AsyncClient(
|
||||||
|
|||||||
@ -53,6 +53,7 @@ class SettingsDialog(QDialog):
|
|||||||
self._tabs.addTab(self._build_blacklist_tab(), "Blacklist")
|
self._tabs.addTab(self._build_blacklist_tab(), "Blacklist")
|
||||||
self._tabs.addTab(self._build_paths_tab(), "Paths")
|
self._tabs.addTab(self._build_paths_tab(), "Paths")
|
||||||
self._tabs.addTab(self._build_theme_tab(), "Theme")
|
self._tabs.addTab(self._build_theme_tab(), "Theme")
|
||||||
|
self._tabs.addTab(self._build_network_tab(), "Network")
|
||||||
|
|
||||||
# Bottom buttons
|
# Bottom buttons
|
||||||
btns = QHBoxLayout()
|
btns = QHBoxLayout()
|
||||||
@ -338,6 +339,39 @@ class SettingsDialog(QDialog):
|
|||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
return w
|
return w
|
||||||
|
|
||||||
|
# -- Network tab --
|
||||||
|
|
||||||
|
def _build_network_tab(self) -> QWidget:
|
||||||
|
from ..core.cache import get_connection_log
|
||||||
|
w = QWidget()
|
||||||
|
layout = QVBoxLayout(w)
|
||||||
|
|
||||||
|
layout.addWidget(QLabel(
|
||||||
|
"All hosts contacted this session. booru-viewer only connects\n"
|
||||||
|
"to the booru sites you configure — no telemetry or analytics."
|
||||||
|
))
|
||||||
|
|
||||||
|
self._net_list = QListWidget()
|
||||||
|
self._net_list.setAlternatingRowColors(True)
|
||||||
|
layout.addWidget(self._net_list)
|
||||||
|
|
||||||
|
refresh_btn = QPushButton("Refresh")
|
||||||
|
refresh_btn.clicked.connect(self._refresh_network)
|
||||||
|
layout.addWidget(refresh_btn)
|
||||||
|
|
||||||
|
self._refresh_network()
|
||||||
|
return w
|
||||||
|
|
||||||
|
def _refresh_network(self) -> None:
|
||||||
|
from ..core.cache import get_connection_log
|
||||||
|
self._net_list.clear()
|
||||||
|
log = get_connection_log()
|
||||||
|
if not log:
|
||||||
|
self._net_list.addItem("No connections made yet")
|
||||||
|
return
|
||||||
|
for host, times in log.items():
|
||||||
|
self._net_list.addItem(f"{host} ({len(times)} requests, last: {times[-1]})")
|
||||||
|
|
||||||
def _edit_custom_css(self) -> None:
|
def _edit_custom_css(self) -> None:
|
||||||
from PySide6.QtGui import QDesktopServices
|
from PySide6.QtGui import QDesktopServices
|
||||||
from PySide6.QtCore import QUrl
|
from PySide6.QtCore import QUrl
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user