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:
pax 2026-04-04 21:11:01 -05:00
parent 70d9f12460
commit e8f72c6fe6
3 changed files with 61 additions and 1 deletions

View File

@ -9,6 +9,7 @@ from dataclasses import dataclass, field
import httpx
from ..config import USER_AGENT, DEFAULT_PAGE_SIZE
from ..cache import log_connection
log = logging.getLogger("booru")
@ -53,9 +54,14 @@ class BooruClient(ABC):
headers={"User-Agent": USER_AGENT},
follow_redirects=True,
timeout=20.0,
event_hooks={"request": [self._log_request]},
)
return self._client
@staticmethod
async def _log_request(request: httpx.Request) -> None:
log_connection(str(request.url))
async def close(self) -> None:
if self._client and not self._client.is_closed:
await self._client.aclose()

View File

@ -4,13 +4,32 @@ from __future__ import annotations
import hashlib
import zipfile
from collections import OrderedDict
from datetime import datetime
from pathlib import Path
from urllib.parse import urlparse
import httpx
from PIL import Image
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:
return hashlib.sha256(url.encode()).hexdigest()[:16]
@ -110,7 +129,6 @@ async def download_image(
local.unlink() # Remove corrupt cache entry
# Extract referer from URL domain (needed for Gelbooru CDN etc.)
from urllib.parse import urlparse
parsed = urlparse(url)
# Map CDN hostnames back to the main site
referer_host = parsed.netloc
@ -120,6 +138,8 @@ async def download_image(
referer_host = "danbooru.donmai.us"
referer = f"{parsed.scheme}://{referer_host}/"
log_connection(url)
own_client = client is None
if own_client:
client = httpx.AsyncClient(

View File

@ -53,6 +53,7 @@ class SettingsDialog(QDialog):
self._tabs.addTab(self._build_blacklist_tab(), "Blacklist")
self._tabs.addTab(self._build_paths_tab(), "Paths")
self._tabs.addTab(self._build_theme_tab(), "Theme")
self._tabs.addTab(self._build_network_tab(), "Network")
# Bottom buttons
btns = QHBoxLayout()
@ -338,6 +339,39 @@ class SettingsDialog(QDialog):
layout.addStretch()
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:
from PySide6.QtGui import QDesktopServices
from PySide6.QtCore import QUrl