Skip to content
Snippets Groups Projects
Commit 939ddba7 authored by Kristoph Sachsenweger's avatar Kristoph Sachsenweger
Browse files

Add new file

parent 53816684
No related branches found
No related tags found
No related merge requests found
// ==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, übersetzt englischsprachigen Titel ins Deutsch, und kopiert Metadaten und Kurz-URL in die Zwischenablage.
// @version 2
// @grant GM_setClipboard
// @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('https://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 bzw. im PWDSafe.
* @type {YourlsApiParams}
*/
const YOURLS_API_PARAMS = {
username: "<User>",
password: "<Password>",
action: "shorturl",
format: "json",
};
/* Google-Translate-API */
/** URL unter der Google-Translate erreichbar ist */
const GOOGLE_TRANSLATE_API_URL =
"https://www.googleapis.com/language/translate/v2";
/**
* Parameter für Google-Translate-API.
* @typedef {Object} GoogleTranslateParams
* @property {string} key Google-API-Key
* @property {string} target Code für die Zielsprache.
* @property {string} source Code für die Ursprungssprache (wird automatisch erkannt, wenn leerer String).
*/
/**
* Google Translate-Parameter.
* @type {GoogleTranslateParams}
*/
const GOOGLE_TRANSLATE_PARAMS = {
key: "YOUR_GOOGLE_API_KEY",
target: "de",
source: "",
};
/*
=====================
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 fetch(apiURL);
if (!request.ok) throw new Error(request);
const data = await request.json();
return data.shorturl;
};
/**
* Übersetze `text` via Google-Translate-API.
* @param {URL} apiURL URL der Google-Translate-API
* @param {GoogleTranslateParams} apiParams - API-Parameter.
* @param {string} text Zu übersetzender Text.
* @returns {Promise<string>} Der übersetzte Text.
* @throws {Error} Request ist fehlgeschlagen.
*/
const fetchTranslation = async (apiURL, apiParams, text) => {
// GET-URL bauen für Google-Translate-API
const params = { ...apiParams, q: text };
Object.entries(params).forEach(([name, value]) =>
apiURL.searchParams.append(name, value)
);
// Request durchführen
const request = await fetch(apiURL);
if (!request.ok) throw new Error(request);
const data = await request.json();
// Übersetzung zurückgeben (wenn nicht vorhanden, den Originaltext)
return data?.data?.translations[0]?.translatedText || text;
};
/* 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,
* übersetze fremdsprachige Headline ins Deutsch
* 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) {
button.innerHTML = STATUS_YOURLS_ERROR_TEXT;
const msg = `Fataler Fehler, konnte short-URL nicht generieren ${JSON.stringify(
error
)}`;
throw new Error(msg);
}
/* Wenn nötig: Versuche Headline zu übersetzen */
let translatedHeadline = headline;
if (headline !== HEADLINE_FALLBACK) {
try {
translatedHeadline = await fetchTranslation(
GOOGLE_TRANSLATE_API_URL,
GOOGLE_TRANSLATE_PARAMS
);
} catch (error) {
console.error(
`Fehler, konnte nicht übersetzen -- fahre ohne Übersetzung fort: ${JSON.stringify(
error
)}`
);
}
}
/* Formatiere Ausgabe */
const output = OUTPUT_TEMPLATE.replace("{{domain}}", domainName)
.replace("{{headline}}", translatedHeadline)
.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);
})();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment