// ==UserScript== // @name xLoader // @namespace https://github.com/immerzu/xLoader // @version 1.0.15 // @description Fügt auf X.com/Twitter unter jedem Tweet einen Download-Button hinzu und lädt dessen Medien (Bilder, Videos, GIFs) über den nativen "Speichern unter"-Dialog herunter. Die Medien-URLs werden im Hintergrund vorgeladen, sodass der Dialog unmittelbar nach dem Klick erscheint. Der Speichern-Dialog-Modus und der Cache sind über das Tampermonkey-Menü konfigurierbar. // @author xLoader // @license MIT // @homepageURL https://github.com/immerzu/xLoader // @supportURL https://github.com/immerzu/xLoader/issues // @updateURL https://raw.githubusercontent.com/immerzu/xLoader/main/xLoader.user.js // @downloadURL https://raw.githubusercontent.com/immerzu/xLoader/main/xLoader.user.js // @match https://x.com/* // @match https://twitter.com/* // @grant GM_download // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @connect x.com // @connect twitter.com // @connect pbs.twimg.com // @connect video.twimg.com // @connect abs.twimg.com // @noframes // @run-at document-end // ==/UserScript== /* * xLoader v1.0.15 (Artikel-Card-Bilder) * --------------------------------------------------------------------------- * v1.0.15: * - Fix: Bilder von Artikel-Cards (verlinkte Artikel) wurden nicht geladen — * sie liegen auf pbs.twimg.com/card_img/... (nicht pbs.twimg.com/media/) * und wurden gefiltert; außerdem fehlte der Medien-Indikator, sodass bei * solchen Tweets gar kein Download-Button erschien. card_img wird jetzt * als Medium erkannt und heruntergeladen (beste Auflösung via srcset). * --------------------------------------------------------------------------- * v1.0.14 (mehrere Medien pro Tweet) * --------------------------------------------------------------------------- * v1.0.14: * - Fix: Bei Tweets mit eingebetteten Medien (Quoted-/Link-Cards auf * Detailseiten) wurde der falsche Tweet adressiert: getTweetId nahm den * ersten Zeitstempel (äußerer Tweet), obwohl die sichtbaren Poster zum * eingebetteten Tweet gehörten. getTweetId/getHandle orientieren sich * jetzt am Medien-Element (findMediaElement) und dessen /status/-Link. * - Fix: Es wurden nicht mehr ALLE sichtbaren Medien eines Tweets geladen — * nur noch die, die die X-API liefert. X.com kürzt Medien in Konversations- * Antworten (v. a. Videos verlinkter Tweets). Jetzt werden die API-Medien * um die im DOM sichtbaren Poster/Standbilder ergänzt (mergeDomItems, mit * Dedup: gleiche Media-ID oder URL-Basis) — auch bei Cache-Treffern. * - Neu: Echte Video-MP4s für Poster, die die API nicht liefert: Das Poster * wird angetippt (X.com startet die Wiedergabe), die Voll-MP4 wird aus dem * Performance-Buffer gelesen (video.twimg.com/amplify_video//vid/avc1/ * 0/0//.mp4) und das Video sofort stummgeschaltet/pausiert. * Schlägt das fehl, bleibt das Poster als Bild-Notnagel. * --------------------------------------------------------------------------- * v1.0.12 (Fehlerbehebungen) * --------------------------------------------------------------------------- * v1.0.12: * - Fix: normalizeMediaUrl akzeptierte wieder ALLE URLs — der isMediaUrl- * Filter ging bei der m3u8-Bereinigung in v1.0.9 verloren. Der DOM- * Fallback hätte dadurch auch Avatare/Icons/Emojis/externe Bilder als * Medien heruntergeladen. * - Fix: GM_download ohne timeout — ein hängender Download (Server ohne * Antwort) deaktivierte den Button dauerhaft ("Lädt Medien …"). Jetzt * greift nach CONFIG.requestTimeoutMs (60 s) der ontimeout-Pfad. * - Fix: Live-Token-Retry wurde auch bei HTTP 429 (Rate-Limit) ausgelöst — * der Token ist dort nicht das Problem, der Retry verschärfte das Limit * nur. Token-Retry jetzt nur bei Auth-/Netzwerkfehlern. * - Fix: Tweets im Prefetch-Retry-Cooldown wurden bei erneutem Erscheinen * im DOM sofort neu gestartet (Doppel-Request). schedulePrefetch wartet * jetzt auf den geplanten Retry. * - Verbessert: Medien-Recheck läuft bis zu 3× (vorher genau 1×) — Tweets, * deren Medien erst später laden (Lazy-Loading), bekommen nun doch einen * Download-Button. * - Fix: GIF-Tweets lieferten im DOM-Fallback (API nicht erreichbar) nichts * ("Keine Medien gefunden") — das Poster (tweet_video_thumb) wurde nicht * erkannt. Aus dem Poster wird jetzt die echte MP4-URL abgeleitet * (pbs.twimg.com/tweet_video_thumb/ -> video.twimg.com/tweet_video/.mp4, * live verifiziert) — GIFs sind auf X.com MP4-Streams. * --------------------------------------------------------------------------- * v1.0.11 (Bugfix: "NaN%" im Fortschritt) * --------------------------------------------------------------------------- * v1.0.11: * - Fix: Bei Downloads ohne brauchbare Größenangabe zeigte das Fortschritts- * Badge "NaN%" — Tampermonkey liefert in onprogress teils fehlende oder * NaN-Werte für done/total (z. B. unbekannte Gesamtgröße). Fehlende/nicht- * endliche Werte werden jetzt ignoriert (Badge bleibt unverändert), nur * gültige Prozente werden angezeigt. * - Badge jetzt nur noch bei Videodateien ab 5 MB (CONFIG.progressBadgeMinBytes): * Bei Bildern und kurzen Videos erscheint keine Fortschrittsanzeige mehr. * --------------------------------------------------------------------------- * v1.0.10 (Bugfix Fortschrittsanzeige) * --------------------------------------------------------------------------- * v1.0.10: * - Fix: Fortschrittsanzeige funktionierte nicht — Tampermonkey liefert im * GM_download-onprogress nur { done, total } (Bytes), kein percent. Der * Prozentwert wird jetzt daraus berechnet (progress.percent wird weiterhin * unterstützt, falls ein Manager ihn liefert). * - Neu: Sichtbares Fortschritts-Badge über dem Button (vorher lief die * Anzeige nur über btn.title = Hover-Tooltip und war praktisch nicht * wahrnehmbar). Das Badge zeigt "42%" und verschwindet nach dem Download * automatisch. * --------------------------------------------------------------------------- * v1.0.9 (Optimierungen) * --------------------------------------------------------------------------- * v1.0.9: * - Bugfix: x-guest-token-Header hing fälschlich an der ct0-Länge (32) statt * am Guest-Token selbst — der Header wird jetzt immer mitgesendet, wenn ein * Guest-Token existiert. * - Bugfix: Kaputter Live-Token-Fallback (erfundene main.{Timestamp}.js-URL, * garantiert 404) entfernt; der Token wird zuerst direkt im HTML der * Startseite gesucht, erst dann in den echten Script-URLs. Zusätzlich * @connect abs.twimg.com ergänzt (X.com-Scripts liegen dort und wurden von * GM_xmlhttpRequest bisher blockiert). * - Bugfix: Prefetch-Fehler (401/429/Timeout/leere Antwort) blockierten den * Tweet dauerhaft (prefetchSeen blieb gesetzt). Jetzt max. 2 automatische * Retries mit 10-s-Cooldown, danach Aufgeben bis zum nächsten Klick. * - Bugfix: m3u8→mp4-Umbenennung im DOM-Fallback entfernt (erzeugte * potenziell kaputte Downloads — Playlisten lassen sich nicht einfach * umbenennen). * - Neu: Fortschrittsanzeige beim Download (GM_download onprogress) im * Button-Titel ("Lade i/n … 42%"). * - Neu: Prefetch nur für sichtbare Tweets (IntersectionObserver, * rootMargin 200px) — deutlich weniger API-Calls beim schnellen Scrollen. * - Fehlermeldungen differenziert: 429 (Rate-Limit) und 401/403 werden * explizit angezeigt statt "Keine Medien gefunden". * - Konsistenz: Log-Präfix [xLoader], localStorage-Keys, data-Attribute und * CSS-Klassen auf xLoader umgestellt (alter Live-Token-Key wird beim * ersten Lesen migriert). * - Metadaten: @homepageURL/@supportURL/@updateURL/@downloadURL auf GitHub * (immerzu/xLoader) ergänzt. * --------------------------------------------------------------------------- * v1.0.8: * - Live-Token-Retry wird nicht mehr durch einen gecachten (möglicherweise * veralteten) Live-Token blockiert: Bei API-Fehlern (401/403, z. B. nach * Token-Rotation durch X.com) wird jetzt IMMER versucht, einen frischen * Bearer-Token zu holen. Vorher brachen Video-/GIF-Downloads nach einer * Token-Rotation bis zum Ablauf der 1-h-TTL des Caches („Keine Medien * gefunden“). * - Prefetch-Cache-Verhalten korrigiert: Nach einem Download (Cache wird * invalidiert) wird der Tweet jetzt wieder im Hintergrund vorgeladen, wenn * er erneut im DOM erscheint (prefetchSeen-Eintrag wird geleert). Zudem * blockieren abgelaufene Cache-Einträge (TTL 5 min) keinen neuen Prefetch * mehr (getCachedItems statt mediaCache.has). * --------------------------------------------------------------------------- * v1.0.7 (Optionale Verbesserungen) * --------------------------------------------------------------------------- * v1.0.7: * - Tampermonkey-Menü (GM_registerMenuCommand): * * "Speichern unter-Dialog" umschalten: nur erstes Medium (Default) oder * alle Medien (persistiert in localStorage) * * "Medien-Cache leeren" (Prefetch-Cache + Queue zurücksetzen) * - Tweet-ID-Extraktion robuster: bevorzugt den Zeitstempel-Link (garantiert * die Haupt-Tweet-ID), dann Links ohne /photo|/video|/analytics-Suffix. * Behebt Randfälle bei Quoted-Tweets und Analytics-Links. * - saveAs-Logik: Bei Modus "nur erster" bekommt nur das erste Medium den * "Speichern unter"-Dialog, weitere Medien gehen direkt in den Download- * Ordner (deutlich weniger Klicks bei Mehrfachmedien). * --------------------------------------------------------------------------- * v1.0.6: Projekt-/Skriptname "xLoader" (zuvor Downloadhilfe); @author und * @namespace auf die Veröffentlichungs-Identität gesetzt. Keine * Funktionsänderungen gegenüber v1.0.5. * --------------------------------------------------------------------------- * v1.0.5 (Layout-Fix: Button "verrutscht"): * v1.0.5: Der Download-Button wird nicht mehr als eigenständiges Element mit * fester Größe gebaut, sondern durch Klonen eines vorhandenen X.com- * Aktionsleisten-Buttons (bewährter Ansatz aus X Media Downloader). Damit * werden Größe, Höhe, Abstände und responsives Verhalten der Leiste exakt * übernommen (vorher: fester 36px-Button, der bei manchen Tweet-Fenstern * über die Leiste hinausragte). Nur das Icon (SVG) und der Zähler werden * ersetzt bzw. ausgeblendet. * --------------------------------------------------------------------------- * v1.0.4: Keine Funktionsänderungen — nur Metadaten für die Veröffentlichung: * - @license MIT ergänzt (Greasy Fork zeigt sonst "Alle Rechte vorbehalten") * - @description überarbeitet * - @author / @namespace sind Platzhalter: vor dem Einfügen bei Greasy Fork * durch eigene Werte ersetzen, z. B. * @author * @namespace https://greasyfork.org/users/ * (Die User-ID steht in der URL deines Greasy-Fork-Profils.) * Injektion: Ein Download-Button (Pfeil-nach-unten) wird in die * Aktionsleiste (div[role="group"]) jedes Tweets eingefügt, * sobald der Tweet ein Medien-Indiz trägt (Bild, Video- * Poster,