File: //proc/thread-self/root/tmp/bewertemich_viral_patch.mjs
import { readFileSync, writeFileSync } from 'node:fs';
const updateFile = (file, updater) => {
const before = readFileSync(file, 'utf8');
const after = updater(before);
if (after === before) throw new Error(`No changes applied to ${file}`);
writeFileSync(file, after, 'utf8');
};
updateFile('web/scripts/generate-seo-pages.mjs', (source) => {
const start = source.indexOf('const renderViralSharePanel = (page, canonical) => {');
const end = source.indexOf('\nconst renderRelatedLinks = (page, relatedMap) => {', start);
if (start < 0 || end < 0) throw new Error('renderViralSharePanel block not found');
const block = `const renderViralSharePanel = (page, canonical) => {
if (!page.shareText) return '';
const shareTitle = page.shareTitle || page.title || 'bewertemich.de';
const shareText = page.shareText;
const shareUrl = page.shareUrl ? `${siteBase}${page.shareUrl}` : canonical;
const pageSlug = page.slug;
const withShareParams = (target) => {
const url = new URL(shareUrl);
url.searchParams.set('utm_source', 'viral_share');
url.searchParams.set('utm_medium', target);
url.searchParams.set('utm_campaign', pageSlug);
return url.toString();
};
const shareUrls = {
whatsapp: withShareParams('whatsapp'),
telegram: withShareParams('telegram'),
native: withShareParams('native'),
copy: withShareParams('copy')
};
const encodedWhatsAppText = encodeURIComponent(`${shareText} ${shareUrls.whatsapp}`);
const encodedTelegramUrl = encodeURIComponent(shareUrls.telegram);
const encodedTelegramText = encodeURIComponent(shareText);
return `
<section class="card viral-share-panel" aria-labelledby="viral-share-${escapeAttr(pageSlug)}">
<div>
<span class="eyebrow">Teilen bringt neue Stimmen</span>
<h2 id="viral-share-${escapeAttr(pageSlug)}">Challenge weitergeben</h2>
<p>${escapeHtml(shareText)}</p>
</div>
<div class="share-actions">
<a class="btn btn-primary" href="https://wa.me/?text=${encodedWhatsAppText}" target="_blank" rel="noreferrer" data-viral-share-target="whatsapp" data-share-page="${escapeAttr(pageSlug)}">WhatsApp teilen</a>
<a class="btn btn-secondary dark" href="https://t.me/share/url?url=${encodedTelegramUrl}&text=${encodedTelegramText}" target="_blank" rel="noreferrer" data-viral-share-target="telegram" data-share-page="${escapeAttr(pageSlug)}">Telegram teilen</a>
<button class="btn btn-secondary dark" type="button" data-viral-share-target="native" data-share-page="${escapeAttr(pageSlug)}">System-Share</button>
<button class="btn btn-secondary dark" type="button" data-viral-share-target="copy" data-share-page="${escapeAttr(pageSlug)}">Link kopieren</button>
</div>
<p class="share-hint" data-copy-feedback="${escapeAttr(pageSlug)}">Teile die Challenge mit jemandem, der schnell 5 Fotos fair mitbewerten kann.</p>
</section>
<script>
(function () {
const shareTitle = ${JSON.stringify(shareTitle)};
const shareText = ${JSON.stringify(shareText)};
const shareUrl = ${JSON.stringify(shareUrl)};
const shareUrls = ${JSON.stringify(shareUrls)};
const pageSlug = ${JSON.stringify(pageSlug)};
const pathWithSearch = () => window.location.pathname + window.location.search;
const post = (target) => {
if (typeof window.fetch !== 'function') return;
const targetShareUrl = shareUrls[target] || shareUrl;
fetch('/api/public/events', {
method: 'POST',
headers: { 'content-type': 'application/json' },
credentials: 'same-origin',
keepalive: true,
body: JSON.stringify({
event: 'seo_viral_share_click',
source: 'seo_static',
surface: pageSlug,
entryPoint: target,
path: pathWithSearch(),
referrer: document.referrer || '',
meta: { target, shareUrl: targetShareUrl, campaign: pageSlug, utmSource: 'viral_share' }
})
}).catch(function () {});
};
for (const el of document.querySelectorAll('[data-viral-share-target][data-share-page="' + pageSlug + '"]')) {
el.addEventListener('click', function () {
const target = el.getAttribute('data-viral-share-target') || 'unknown';
const targetShareUrl = shareUrls[target] || shareUrl;
post(target);
if (target === 'native' && navigator.share) {
navigator.share({ title: shareTitle, text: shareText, url: targetShareUrl }).catch(function () {});
}
if (target === 'copy') {
navigator.clipboard && navigator.clipboard.writeText(targetShareUrl).then(function () {
const feedback = document.querySelector('[data-copy-feedback="' + pageSlug + '"]');
if (feedback) feedback.textContent = 'Link kopiert. Jetzt direkt an 2-3 Kontakte senden.';
}).catch(function () {});
}
});
}
})();
</script>`;
};`;
let next = source.slice(0, start) + block + source.slice(end);
next = next.replaceAll('path: window.location.pathname,', 'path: window.location.pathname + window.location.search,');
return next;
});
updateFile('api/src/jobs/seoGrowthDigest.js', (source) => {
let next = source;
next = next.replace(
'const buildDigestHtml = ({ siteBase, windowLabel, funnelRows, totals, leaderboardRows, contextRows, reciprocityRows, decisionRows, shareTargetRows, pwaShortcutRows, actionFlags }) => {',
'const buildDigestHtml = ({ siteBase, windowLabel, funnelRows, totals, leaderboardRows, contextRows, reciprocityRows, decisionRows, shareTargetRows, pwaShortcutRows, viralInboundRows, actionFlags }) => {'
);
const pwaSectionEnd = `
\t\t\t<div style="margin-top:18px;border:1px solid #e5e7eb;border-radius:16px;padding:16px;background:#fff;">
\t\t\t\t<div style="font-size:14px;color:#4b5563;font-weight:800;">Share-Ziele der Challenges</div>`;
const viralSection = `
\t\t\t<div style="margin-top:18px;border:1px solid #e5e7eb;border-radius:16px;padding:16px;background:#fff;">
\t\t\t\t<div style="font-size:14px;color:#4b5563;font-weight:800;">Viral-Share-Folgebesuche</div>
\t\t\t\t<p style="margin:6px 0 10px;color:#64748b;font-size:13px;">Misst Besuche, die über Challenge-/Spiel-Shares mit <code>utm_source=viral_share</code> zurückkommen.</p>
\t\t\t\t<table style="width:100%;border-collapse:collapse;margin-top:10px;font-size:13px;">
\t\t\t\t\t<tbody>
\t\t\t\t\t\t${viralInboundRows.map((row) => `
\t\t\t\t\t\t\t<tr>
\t\t\t\t\t\t\t\t<td style="padding:8px;border-top:1px solid #f1f5f9;font-weight:700;">${escapeHtml(row.entry_name)}</td>
\t\t\t\t\t\t\t\t<td style="padding:8px;border-top:1px solid #f1f5f9;text-align:right;">${formatNumber(row.total)} Events</td>
\t\t\t\t\t\t\t\t<td style="padding:8px;border-top:1px solid #f1f5f9;text-align:right;color:#64748b;">${escapeHtml(row.path || row.referrer || '')}</td>
\t\t\t\t\t\t\t</tr>
\t\t\t\t\t\t`).join('') || '<tr><td style="padding:8px;color:#6b7280;">Noch keine Viral-Share-Folgebesuche.</td></tr>'}
\t\t\t\t\t</tbody>
\t\t\t\t</table>
\t\t\t</div>
`;
if (!next.includes(pwaSectionEnd)) throw new Error('Digest insertion point not found');
next = next.replace(pwaSectionEnd, viralSection + pwaSectionEnd);
next = next.replace(
'const [eventRows, landingRows, leaderboardRows, contextRows, reciprocityRows, decisionRawRows, shareTargetRawRows, pwaShortcutRows] = await Promise.all([',
'const [eventRows, landingRows, leaderboardRows, contextRows, reciprocityRows, decisionRawRows, shareTargetRawRows, pwaShortcutRows, viralInboundRows] = await Promise.all(['
);
const pwaQuery = `\t\tquery(
\t\t\tdb,
\t\t\t\`SELECT COALESCE(NULLIF(entry_point, ''), 'direkt') AS entry_name, path, COUNT(*) AS total
\t\t\t FROM analytics_events
\t\t\t WHERE created_at >= (UTC_TIMESTAMP() - INTERVAL ? DAY)
\t\t\t AND (path LIKE '%utm_source=pwa_shortcut%' OR referrer LIKE '%pwa_shortcut%')
\t\t\t GROUP BY entry_name, path
\t\t\t ORDER BY total DESC
\t\t\t LIMIT 10\`,
\t\t\t[DAYS]
\t\t)
\t]);`;
const pwaAndViralQueries = `\t\tquery(
\t\t\tdb,
\t\t\t\`SELECT COALESCE(NULLIF(entry_point, ''), 'direkt') AS entry_name, path, COUNT(*) AS total
\t\t\t FROM analytics_events
\t\t\t WHERE created_at >= (UTC_TIMESTAMP() - INTERVAL ? DAY)
\t\t\t AND (path LIKE '%utm_source=pwa_shortcut%' OR referrer LIKE '%pwa_shortcut%')
\t\t\t GROUP BY entry_name, path
\t\t\t ORDER BY total DESC
\t\t\t LIMIT 10\`,
\t\t\t[DAYS]
\t\t),
\t\tquery(
\t\t\tdb,
\t\t\t\`SELECT COALESCE(NULLIF(entry_point, ''), 'direkt') AS entry_name, path, referrer, COUNT(*) AS total
\t\t\t FROM analytics_events
\t\t\t WHERE created_at >= (UTC_TIMESTAMP() - INTERVAL ? DAY)
\t\t\t AND (path LIKE '%utm_source=viral_share%' OR referrer LIKE '%utm_source=viral_share%')
\t\t\t GROUP BY entry_name, path, referrer
\t\t\t ORDER BY total DESC
\t\t\t LIMIT 10\`,
\t\t\t[DAYS]
\t\t)
\t]);`;
if (!next.includes(pwaQuery)) throw new Error('PWA query block not found');
next = next.replace(pwaQuery, pwaAndViralQueries);
next = next.replace(
'\t\tshareTargetRows,\n\t\tpwaShortcutRows,\n\t\tactionFlags: buildActionFlags(totals, normalizedLeaderboardRows, decisionRows)',
'\t\tshareTargetRows,\n\t\tpwaShortcutRows,\n\t\tviralInboundRows,\n\t\tactionFlags: buildActionFlags(totals, normalizedLeaderboardRows, decisionRows)'
);
return next;
});
updateFile('scripts/growth-smoke-check.mjs', (source) => source
.replace(
`{ path: '/profilbild-challenge/', contains: ['Profilbild Challenge', '5 Fotos mitbewerten', '/mitbewerten/', 'seo_viral_share_click', 'data-viral-share-target="whatsapp"'] }`,
`{ path: '/profilbild-challenge/', contains: ['Profilbild Challenge', '5 Fotos mitbewerten', '/mitbewerten/', 'seo_viral_share_click', 'data-viral-share-target="whatsapp"', 'utm_source=viral_share', 'utm_campaign=profilbild-challenge'] }`
)
.replace(
`{ path: '/foto-bewerten-spiel/', contains: ['Foto bewerten Spiel', '/mitbewerten/', 'seo_viral_share_click', 'data-viral-share-target="telegram"'] }`,
`{ path: '/foto-bewerten-spiel/', contains: ['Foto bewerten Spiel', '/mitbewerten/', 'seo_viral_share_click', 'data-viral-share-target="telegram"', 'utm_source=viral_share', 'utm_campaign=foto-bewerten-spiel'] }`
)
);