Discord RPC: echter Fund - activity_type falsch typisiert + Cover-Bild statt Logo
Der entscheidende verbleibende Bug: 'activity_type' wurde als rohes int gesetzt,
pypresence.Presence.update() ruft aber intern .value darauf auf und wirft
AttributeError - jedes Update mit echten Mediendaten schlug dadurch fehl (nur die
generische Idle-Presence ohne activity_type kam durch). Fix: ActivityType-Enum
verwenden (LISTENING/WATCHING statt Default PLAYING), zusammen mit 'instance: False'
nach Vorbild einer frueheren, funktionierenden Electron-Implementierung. Per
Discord-Screenshot verifiziert: RPC erscheint jetzt korrekt.
Zusaetzlich:
- large_image nutzt jetzt die echte Video-/Cover-Thumbnail-URL (Discord akzeptiert
externe Bild-URLs, nicht nur hochgeladene Asset-Keys) statt des statischen Logos,
mit Hochskalierung fuer die kleinen YouTube-Music-Player-Bar-Thumbnails.
- media_probe.py: Thumbnail-Erkennung nutzt jetzt getAttribute('src') statt .src -
bei einem noch nicht geladenen <img> (leeres src-Attribut) loeste .src faelschlich
auf die aktuelle Seiten-URL auf statt null zu liefern.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+32
-1
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -16,6 +17,11 @@ from PySide6.QtCore import QThread
|
|||||||
|
|
||||||
from .config import APP_NAME
|
from .config import APP_NAME
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pypresence.types import ActivityType
|
||||||
|
except ImportError: # pypresence fehlt -> Discord-Feature bleibt einfach aus
|
||||||
|
ActivityType = None
|
||||||
|
|
||||||
# Asset-Keys, die (optional) unter discord.com/developers/applications -> Rich
|
# Asset-Keys, die (optional) unter discord.com/developers/applications -> Rich
|
||||||
# Presence -> Art Assets hochgeladen werden koennen. Fehlen sie, zeigt Discord
|
# Presence -> Art Assets hochgeladen werden koennen. Fehlen sie, zeigt Discord
|
||||||
# einfach kein Bild an - es gibt keinen Fehler.
|
# einfach kein Bild an - es gibt keinen Fehler.
|
||||||
@@ -37,6 +43,17 @@ def _truncate(text: str | None, limit: int = 128, fallback: str = "") -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
_THUMBNAIL_SIZE_RE = re.compile(r"=w\d+-h\d+(-[a-z0-9-]*)?$", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def _upsize_thumbnail(url: str | None) -> str | None:
|
||||||
|
"""YouTube-Music-Player-Bar-Thumbnails kommen sehr klein (z.B. '=w60-h60-l90-rj').
|
||||||
|
Fuer eine scharfe Darstellung in Discord die Groesse im URL-Suffix hochsetzen."""
|
||||||
|
if not url:
|
||||||
|
return url
|
||||||
|
return _THUMBNAIL_SIZE_RE.sub("=w544-h544-l90-rj", url)
|
||||||
|
|
||||||
|
|
||||||
def build_presence_payload(info: dict[str, Any], session_start: int) -> dict[str, Any]:
|
def build_presence_payload(info: dict[str, Any], session_start: int) -> dict[str, Any]:
|
||||||
"""Baut das update()-Payload fuer pypresence aus den vom Browser-Tab gelieferten
|
"""Baut das update()-Payload fuer pypresence aus den vom Browser-Tab gelieferten
|
||||||
Medien-Informationen."""
|
Medien-Informationen."""
|
||||||
@@ -46,10 +63,24 @@ def build_presence_payload(info: dict[str, Any], session_start: int) -> dict[str
|
|||||||
default_state = "YouTube Music" if is_music else "YouTube"
|
default_state = "YouTube Music" if is_music else "YouTube"
|
||||||
state = _truncate(info.get("subtitle"), fallback=default_state)
|
state = _truncate(info.get("subtitle"), fallback=default_state)
|
||||||
|
|
||||||
|
thumbnail = info.get("thumbnail")
|
||||||
|
if isinstance(thumbnail, str) and thumbnail.startswith("http"):
|
||||||
|
# Discord akzeptiert fuer large_image auch direkte externe Bild-URLs (nicht nur
|
||||||
|
# vorab hochgeladene Asset-Keys) - so zeigt Discord das echte Video-/Cover-Bild
|
||||||
|
# statt eines statischen Logos.
|
||||||
|
large_image = _upsize_thumbnail(thumbnail) if is_music else thumbnail
|
||||||
|
else:
|
||||||
|
large_image = ASSET_MUSIC if is_music else ASSET_YOUTUBE
|
||||||
|
|
||||||
payload: dict[str, Any] = {
|
payload: dict[str, Any] = {
|
||||||
|
# LISTENING = Hoert, WATCHING = Schaut - statt des Default-Typs PLAYING
|
||||||
|
# (Spielt). Muss ein ActivityType-Enum-Member sein, kein rohes int - pypresence
|
||||||
|
# ruft intern .value darauf auf.
|
||||||
|
"activity_type": ActivityType.LISTENING if is_music else ActivityType.WATCHING,
|
||||||
|
"instance": False,
|
||||||
"details": title,
|
"details": title,
|
||||||
"state": state,
|
"state": state,
|
||||||
"large_image": ASSET_MUSIC if is_music else ASSET_YOUTUBE,
|
"large_image": large_image,
|
||||||
"large_text": "YouTube Music" if is_music else "YouTube",
|
"large_text": "YouTube Music" if is_music else "YouTube",
|
||||||
"small_image": ASSET_PLAY if playing else ASSET_PAUSE,
|
"small_image": ASSET_PLAY if playing else ASSET_PAUSE,
|
||||||
"small_text": "Spielt" if playing else "Pausiert",
|
"small_text": "Spielt" if playing else "Pausiert",
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ MEDIA_PROBE_JS = r"""
|
|||||||
var el = document.querySelector(sel);
|
var el = document.querySelector(sel);
|
||||||
return el ? el.textContent.trim() : null;
|
return el ? el.textContent.trim() : null;
|
||||||
}
|
}
|
||||||
|
// Wichtig: getAttribute('src') statt .src - bei einem noch nicht (nach-)geladenen
|
||||||
|
// <img> (leeres/fehlendes src-Attribut, z.B. waehrend Lazy-Loading) loest die .src
|
||||||
|
// Property faelschlich auf die aktuelle Seiten-URL auf statt null/"" zu liefern.
|
||||||
|
function imgSrc(sel) {
|
||||||
|
var el = document.querySelector(sel);
|
||||||
|
var raw = el ? el.getAttribute('src') : null;
|
||||||
|
return (raw && /^https?:\/\//i.test(raw)) ? raw : null;
|
||||||
|
}
|
||||||
var video = document.querySelector('video');
|
var video = document.querySelector('video');
|
||||||
var isMusic = location.hostname.indexOf('music.youtube.com') !== -1;
|
var isMusic = location.hostname.indexOf('music.youtube.com') !== -1;
|
||||||
var title = null, subtitle = null, thumbnail = null;
|
var title = null, subtitle = null, thumbnail = null;
|
||||||
@@ -14,8 +22,7 @@ MEDIA_PROBE_JS = r"""
|
|||||||
if (isMusic) {
|
if (isMusic) {
|
||||||
title = txt('.title.ytmusic-player-bar') || txt('ytmusic-player-bar .title');
|
title = txt('.title.ytmusic-player-bar') || txt('ytmusic-player-bar .title');
|
||||||
subtitle = txt('.byline.ytmusic-player-bar') || txt('ytmusic-player-bar .byline');
|
subtitle = txt('.byline.ytmusic-player-bar') || txt('ytmusic-player-bar .byline');
|
||||||
var imgM = document.querySelector('ytmusic-player-bar img, .image.ytmusic-player-bar img');
|
thumbnail = imgSrc('ytmusic-player-bar img, .image.ytmusic-player-bar img');
|
||||||
thumbnail = imgM ? imgM.src : null;
|
|
||||||
} else {
|
} else {
|
||||||
var t = document.title.replace(/ - YouTube$/, '');
|
var t = document.title.replace(/ - YouTube$/, '');
|
||||||
title = t || null;
|
title = t || null;
|
||||||
|
|||||||
Reference in New Issue
Block a user