Script d'aperçu des liens sur Yakihonne

Gerfon
npub1r6thdg3lhpzsfwkv9xszc42907pk66r4hg0y4yzaggzqyut2kjzqgerufd
hex
252b7ed04e621cc8ad4f5f80940e2aa4718571d2864a91d5544b5e357a9c4aeenevent
nevent1qqsz22m76p8xy8xg4484lqy5pc42guv9w8fgvj53642ykh3402wy4msprpmhxue69uhhyetvv9ujuem4d36kwatvw5hx6mm9qgspa9mk5glms3gyhtxzngpv24zhlqmddp6m58j2jpw5ypqzw94tfpqnkuad9naddr
naddr1qq2k566ntue4qc2v2prhj6jtdfp8xs28wf28yqgcwaehxw309aex2mrp0yhxwatvw4nh2mr49ekk7egzyq0fwa4z87uy2p96es56qtz4g4lcxmtgwkapuj5st4pqgqn3d26ggqcyqqq823c6357gxKind-30023 (Article)
Par défaut la génération des aperçus des liens ne fonctionne pas sur Yakihonne.com. #yakihonne #nostrfr
Pour résoudre ce problème, j'ai trouvé l'astuce suivante :
- Installer l'extension Tampermonkey
- Dans les paramètres de l'extension autoriser les scripts utilisateur

- Dans Tampermonkey créer le script suivant :
// ==UserScript==
// @name Yakihonne - Aperçus URL carte compacte
// @namespace local.yakihonne.preview
// @version 2.2
// @description Affiche une carte compacte avec image complète à gauche et titre centré à droite
// @match https://yakihonne.com/*
// @grant GM_xmlhttpRequest
// @connect *
// ==/UserScript==
(function () {
'use strict';
const processed = new Set();
const style = document.createElement('style');
style.textContent = `
.yh-preview-card {
display: flex;
align-items: stretch;
width: 100%;
max-width: 100%;
height: 96px;
margin: 8px 0 10px 0;
border: 1px solid #ddd;
border-radius: 14px;
overflow: hidden;
background: #f7f3f1;
text-decoration: none;
cursor: pointer;
box-sizing: border-box;
}
.yh-preview-card:hover {
background: #f0ecea;
}
.yh-preview-image {
width: 220px;
min-width: 220px;
height: 100%;
overflow: hidden;
background: #f7f3f1;
display: flex;
align-items: center;
justify-content: center;
}
.yh-preview-image img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
background: #f7f3f1;
}
.yh-preview-title {
flex: 1;
min-width: 0;
padding: 10px 14px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: #111;
font-size: 16px;
font-weight: 700;
line-height: 1.25;
}
.yh-preview-title span {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
@media (max-width: 700px) {
.yh-preview-image {
width: 140px;
min-width: 140px;
}
.yh-preview-title {
font-size: 14px;
padding: 8px;
}
}
`;
document.head.appendChild(style);
function escapeHtml(text) {
return String(text || '').replace(/[&<>"']/g, c => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[c]));
}
function meta(doc, selector) {
return doc.querySelector(selector)?.getAttribute('content') || '';
}
function absoluteUrl(value, base) {
try {
return new URL(value, base).href;
} catch {
return '';
}
}
function fetchPreview(url) {
return new Promise(resolve => {
GM_xmlhttpRequest({
method: 'GET',
url,
timeout: 15000,
onload: res => {
try {
const doc = new DOMParser().parseFromString(
res.responseText,
'text/html'
);
const title =
meta(doc, 'meta[property="og:title"]') ||
meta(doc, 'meta[name="twitter:title"]') ||
doc.querySelector('title')?.textContent ||
url;
const image =
meta(doc, 'meta[property="og:image"]') ||
meta(doc, 'meta[name="twitter:image"]') ||
'';
resolve({
url,
title: title.trim(),
image: absoluteUrl(image, url)
});
} catch {
resolve(null);
}
},
onerror: () => resolve(null),
ontimeout: () => resolve(null)
});
});
}
function createCard(data) {
if (!data.image) return null;
const card = document.createElement('a');
card.className = 'yh-preview-card';
card.href = data.url;
card.target = '_blank';
card.rel = 'noopener noreferrer';
card.innerHTML = `
<div class="yh-preview-image">
<img src="${escapeHtml(data.image)}" loading="lazy">
</div>
<div class="yh-preview-title">
<span>${escapeHtml(data.title)}</span>
</div>
`;
card.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
window.open(data.url, '_blank', 'noopener,noreferrer');
}, true);
return card;
}
async function scanLinks() {
const links = document.querySelectorAll('a[href^="http"]');
for (const link of links) {
const url = link.href;
if (!url) continue;
if (url.includes('yakihonne.com')) continue;
if (url.startsWith('nostr:')) continue;
if (link.closest('.yh-preview-card')) continue;
const key = url + '|' + link.textContent;
if (processed.has(key)) continue;
processed.add(key);
const preview = await fetchPreview(url);
if (!preview) continue;
const card = createCard(preview);
if (!card) continue;
if (
!link.nextElementSibling ||
!link.nextElementSibling.classList.contains('yh-preview-card')
) {
link.insertAdjacentElement('afterend', card);
}
}
}
const observer = new MutationObserver(() => {
clearTimeout(window.__yhPreviewTimer);
window.__yhPreviewTimer = setTimeout(() => {
scanLinks();
}, 500);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(scanLinks, 1500);
setInterval(scanLinks, 3000);
})();
Normalement vous devriez obtenir le résultat suivant :

原始 JSON
{
"kind": 30023,
"id": "252b7ed04e621cc8ad4f5f80940e2aa4718571d2864a91d5544b5e357a9c4aee",
"pubkey": "1e9776a23fb84504bacc29a02c55457f836d6875ba1e4a905d420402716ab484",
"created_at": 1780140383,
"tags": [
[
"client",
"Yakihonne",
"31990:20986fb83e775d96d188ca5c9df10ce6d613e0eb7e5768a0f0b12b37cdac21b3:1700732875747"
],
[
"published_at",
"1780140306"
],
[
"d",
"jkS_3PaLPGyjKjBsAGrTr"
],
[
"image",
"https://image.nostr.build/ffca2401b910554614a62e99121ca6fc92ab243bc5a93ca68920534847bf0c2a.jpg"
],
[
"title",
"Script d'aperçu des liens sur Yakihonne"
],
[
"summary",
""
],
[
"t",
"yakihonne"
],
[
"t",
"nostrfr"
]
],
"content": "Par défaut la génération des aperçus des liens ne fonctionne pas sur [Yakihonne.com](https://yakihonne.com). #yakihonne #nostrfr\n\nPour résoudre ce problème, j'ai trouvé l'astuce suivante :\n\n1. Installer l'extension [Tampermonkey](https://www.tampermonkey.net/)\n2. Dans les paramètres de l'extension autoriser les scripts utilisateur\n\n3. Dans Tampermonkey créer le script suivant :\n```\n// ==UserScript==\n// @name Yakihonne - Aperçus URL carte compacte\n// @namespace local.yakihonne.preview\n// @version 2.2\n// @description Affiche une carte compacte avec image complète à gauche et titre centré à droite\n// @match https://yakihonne.com/*\n// @grant GM_xmlhttpRequest\n// @connect *\n// ==/UserScript==\n\n(function () {\n 'use strict';\n\n const processed = new Set();\n\n const style = document.createElement('style');\n style.textContent = `\n .yh-preview-card {\n display: flex;\n align-items: stretch;\n width: 100%;\n max-width: 100%;\n height: 96px;\n margin: 8px 0 10px 0;\n border: 1px solid #ddd;\n border-radius: 14px;\n overflow: hidden;\n background: #f7f3f1;\n text-decoration: none;\n cursor: pointer;\n box-sizing: border-box;\n }\n\n .yh-preview-card:hover {\n background: #f0ecea;\n }\n\n .yh-preview-image {\n width: 220px;\n min-width: 220px;\n height: 100%;\n overflow: hidden;\n background: #f7f3f1;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .yh-preview-image img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n display: block;\n background: #f7f3f1;\n }\n\n .yh-preview-title {\n flex: 1;\n min-width: 0;\n padding: 10px 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n color: #111;\n font-size: 16px;\n font-weight: 700;\n line-height: 1.25;\n }\n\n .yh-preview-title span {\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n\n @media (max-width: 700px) {\n .yh-preview-image {\n width: 140px;\n min-width: 140px;\n }\n\n .yh-preview-title {\n font-size: 14px;\n padding: 8px;\n }\n }\n `;\n document.head.appendChild(style);\n\n function escapeHtml(text) {\n return String(text || '').replace(/[\u0026\u003c\u003e\"']/g, c =\u003e ({\n '\u0026': '\u0026amp;',\n '\u003c': '\u0026lt;',\n '\u003e': '\u0026gt;',\n '\"': '\u0026quot;',\n \"'\": '\u0026#039;'\n }[c]));\n }\n\n function meta(doc, selector) {\n return doc.querySelector(selector)?.getAttribute('content') || '';\n }\n\n function absoluteUrl(value, base) {\n try {\n return new URL(value, base).href;\n } catch {\n return '';\n }\n }\n\n function fetchPreview(url) {\n return new Promise(resolve =\u003e {\n GM_xmlhttpRequest({\n method: 'GET',\n url,\n timeout: 15000,\n\n onload: res =\u003e {\n try {\n const doc = new DOMParser().parseFromString(\n res.responseText,\n 'text/html'\n );\n\n const title =\n meta(doc, 'meta[property=\"og:title\"]') ||\n meta(doc, 'meta[name=\"twitter:title\"]') ||\n doc.querySelector('title')?.textContent ||\n url;\n\n const image =\n meta(doc, 'meta[property=\"og:image\"]') ||\n meta(doc, 'meta[name=\"twitter:image\"]') ||\n '';\n\n resolve({\n url,\n title: title.trim(),\n image: absoluteUrl(image, url)\n });\n } catch {\n resolve(null);\n }\n },\n\n onerror: () =\u003e resolve(null),\n ontimeout: () =\u003e resolve(null)\n });\n });\n }\n\n function createCard(data) {\n if (!data.image) return null;\n\n const card = document.createElement('a');\n\n card.className = 'yh-preview-card';\n card.href = data.url;\n card.target = '_blank';\n card.rel = 'noopener noreferrer';\n\n card.innerHTML = `\n \u003cdiv class=\"yh-preview-image\"\u003e\n \u003cimg src=\"${escapeHtml(data.image)}\" loading=\"lazy\"\u003e\n \u003c/div\u003e\n \u003cdiv class=\"yh-preview-title\"\u003e\n \u003cspan\u003e${escapeHtml(data.title)}\u003c/span\u003e\n \u003c/div\u003e\n `;\n\n card.addEventListener('click', function (e) {\n e.preventDefault();\n e.stopPropagation();\n window.open(data.url, '_blank', 'noopener,noreferrer');\n }, true);\n\n return card;\n }\n\n async function scanLinks() {\n const links = document.querySelectorAll('a[href^=\"http\"]');\n\n for (const link of links) {\n const url = link.href;\n\n if (!url) continue;\n if (url.includes('yakihonne.com')) continue;\n if (url.startsWith('nostr:')) continue;\n if (link.closest('.yh-preview-card')) continue;\n\n const key = url + '|' + link.textContent;\n\n if (processed.has(key)) continue;\n processed.add(key);\n\n const preview = await fetchPreview(url);\n\n if (!preview) continue;\n\n const card = createCard(preview);\n\n if (!card) continue;\n\n if (\n !link.nextElementSibling ||\n !link.nextElementSibling.classList.contains('yh-preview-card')\n ) {\n link.insertAdjacentElement('afterend', card);\n }\n }\n }\n\n const observer = new MutationObserver(() =\u003e {\n clearTimeout(window.__yhPreviewTimer);\n\n window.__yhPreviewTimer = setTimeout(() =\u003e {\n scanLinks();\n }, 500);\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true\n });\n\n setTimeout(scanLinks, 1500);\n setInterval(scanLinks, 3000);\n})();\n```\nNormalement vous devriez obtenir le résultat suivant :\n\n",
"sig": "871346bf57951bffd4c0e2f329c6f7b0a6c49c9c9eb1d574530d1f3f80c4eee73217ae6e77a7b207793cdd3b9a64f983cee181148d9fb8cca03d2f11f0087973"
}