// ==UserScript== // @name netzradar-linker // @source https://gitlab.ard.de/ida/netzradar-linker // @description extrahiert Metadaten (Headline, Hostnamen, URL) des gegenwärtigen Dokuments, generiert Kurz-URL, und kopiert Metadaten und Kurz-URL in die Zwischenablage. // @version 5 // @grant GM_setClipboard // @grant GM.xmlHttpRequest // @match *://*/* // ==/UserScript== (function () { "use strict"; /* ===================== Einstellungen ===================== */ /* Status-Meldungen */ /** Zeige diesen Text im Button an */ const STATUS_START_TEXT = "Link kürzen"; const STATUS_START_COLOR = "#e52600"; /** Zeige diesen Text im Button an, wenn kopiert wurde */ const STATUS_COPIED_TEXT = "Link in 📋 🥳"; const STATUS_COPIED_COLOR = "black"; /** Zeige diesen Text im Button an, wenn Yourls-Abruf nicht klappt */ const STATUS_YOURLS_ERROR_TEXT = "❌ Yourls-Fehler ❌"; /** Setze STATUS_COPIED zurück auf STATUS_START zurück (nach Sekunden) */ const STATUS_RESET_AFTER = 2; /* Ausgabe */ /** Kopiere diese Werte in dieser Form in die Zwischenablage */ const OUTPUT_TEMPLATE = "{{domain}}: {{headline}}\n{{url}}"; /* Headline */ /** Nutze diese Tags (in dieser Reihenfolge), um eine Headline zu finden */ const HEADLINE_TAGS = ["h1", "title"]; /** Nutze diesen Text, wenn keine Headline gefunden wird */ const HEADLINE_FALLBACK = "<Titel bitte manuell eintragen>"; /* Yourls-API */ /** URL unter der die Yourls-API erreichbar */ const YOURLS_API_URL = new URL('http://x.swr.de/a/yourls-api.php'); /** * @typedef {Object} YourlsApiParams * @property {string} username Benutzername * @property {string} password Passwort * @property {'shorturl'} action durchzuführende Aktion * @property {'json'} format Rückgabe-Format */ /** * Parameter für Yourls-API. Details können unter http://x.swr.de/a/ eingesehen werden. * @type {YourlsApiParams} */ const YOURLS_API_PARAMS = { username: "User", password: "Password", action: "shorturl", format: "json", }; /* ===================== Funktionen ===================== */ /* Extraktoren */ /** * Erhalte den Domainname (ohne WWW) von einer URL. * @param {URL} targetURL URL deren Domainname ausgegeben werden soll * @returns {string} Domainname (ohne WWW) */ const extractDomainName = (targetURL) => targetURL.hostname.toLowerCase().startsWith("www.") ? targetURL.hostname.slice(4) : targetURL.hostname; /** * Extrahiere die Headline aus einem Dokument. * @param {Document} document Dokument aus dem die Headline ausgelesen werden soll * @param {Array.<string>} headlineTags Suche in diesen Tags in dieser Reihenfolge nach der Headline * @returns {string | undefined} Headline (wenn gefunden), sonst undefined */ const extractHeadline = (document, headlineTags) => headlineTags .map((tag) => document.querySelector(tag)?.innerText) .filter((mayBeTitle) => typeof mayBeTitle === "string")[0] ?.trim(); /* API-Calls */ /** * Erhalte eine Kurz-URL von `targetURL` von der Yourls-API. * @param {URL} apiURL URL der Yourls-API * @param {YourlsApiParams} apiParams Paramater der der Yourls-API * @param {URL} targetURL URL die gekürzt werden soll * @returns {Promise<string>} Die gekürzte URL * @throws {Error} Es konnte keine Kurz-URL abgeholt werden */ const fetchShortURL = async (apiURL, apiParams, targetURL) => { // GET-URL bauen für Yourls-API const params = { ...apiParams, url: targetURL.toString() }; Object.entries(params).forEach(([name, value]) => apiURL.searchParams.append(name, value) ); // Request durchführen const request = await GM.xmlHttpRequest({ method: "GET", url: apiURL.href }); const data = JSON.parse(request.response) return data.shorturl; }; /* Button */ /** * Füge Interaktions-Button ins Dokument ein. * @param {Document} document Dokument dem der Button hinzufgefügt werden soll * @param {(event: MouseEvent, button: HTMLButtonElement) => Promise<void>} handleClick Löse diese Funktion bei Klick aus * @returns {HTMLButtonElement} Das erstellte HTML-Button-Element */ const addButton = (document, handleClick) => { const button = document.createElement("button"); button.setAttribute("id", "myshortenLink"); button.addEventListener("click", (e) => handleClick(e, button), false); button.innerHTML = STATUS_START_TEXT; // Button stylen button.style.bottom = "20px"; button.style.right = "50px"; button.style.height = "50px"; button.style.width = "200px"; button.style.position = "fixed"; button.style.color = "white"; button.style.fontWeight = "700"; button.style.fontFamily = "sans-serif"; button.style.fontSize = "14px"; button.style.padding = "3px"; button.style.borderStyle = "none"; button.style.borderRadius = "25px"; button.style.zIndex = 99999999; button.style.cursor = "pointer"; button.style.backgroundColor = STATUS_START_COLOR; button.style.transition = "background-color 0.7s ease"; // Button dem Dokument hinzufügen document.body.appendChild(button); return button; }; /** wurde der Button gedrückt? */ let buttonIsPressed = false; /** * Manage den Anzeige-Text und Anzeige-Stil des Buttons * (vgl. für Status: `buttonIsPressed`). * @param {HTMLButtonElement} button Der Knopf */ const toggleButton = (button) => { buttonIsPressed = !buttonIsPressed; // Löse Reflow aus, um sicherzustellen, dass die Transition startet void button.offsetWidth; if (buttonIsPressed) { button.style.backgroundColor = STATUS_COPIED_COLOR; button.innerHTML = STATUS_COPIED_TEXT; return; } button.innerHTML = STATUS_START_TEXT; button.style.backgroundColor = STATUS_START_COLOR; }; /* ===================== Programmablauf ===================== */ /** * Extrahiere Metadaten (Headline, Hostnamen, URL) des aktuellen Dokuments, * generiere Kurz-URL, * und kopiere Metadaten und Kurz-URL in die Zwischenablage. * @param {MouseEvent} event Das auslösende Klick-Event * @param {HTMLButtonElement} button Der gedrückte Knopf */ const handleClick = async (event, button) => { event.preventDefault(); /* Extrahiere Metadaten */ /** URL der Website, die verlinkt werden soll */ const targetURL = new URL(window.location.href); const domainName = extractDomainName(targetURL); // Wenn die extrahierte falsy ist (hier relevant: leerer String oder undefined), // soll HEADLINE_FALLBACK genutzt werden const headline = extractHeadline(document, HEADLINE_TAGS) || HEADLINE_FALLBACK; /* Hole Short-URL und breche ab, falls nicht möglich */ let shortURL = ""; try { shortURL = await fetchShortURL( YOURLS_API_URL, YOURLS_API_PARAMS, targetURL ); } catch (error) { console.error(error); button.innerHTML = STATUS_YOURLS_ERROR_TEXT; throw new Error('Fataler Fehler, konnte short-URL nicht generieren'); } /* Formatiere Ausgabe */ const output = OUTPUT_TEMPLATE.replace("{{domain}}", domainName) .replace("{{headline}}", headline) .replace("{{url}}", shortURL); // Kopiere Ausgabe in den Zwischenspeicher // vgl. https://www.tampermonkey.net/documentation.php?locale=en#api:GM_setClipboard GM_setClipboard(output); /* Informiere Userin, dass kopiert */ toggleButton(button); // Setze Text zurück (falls Userin noch einmal kopieren möchte) let buttonPressedTimeoutID = undefined; // Lösche ggf. existierendes Timeout clearTimeout(buttonPressedTimeoutID); buttonPressedTimeoutID = setTimeout( () => toggleButton(button), STATUS_RESET_AFTER * 1000 ); }; // Button hinzufügen addButton(document, handleClick); })();