Files
Alcea 2755aacd2f Add files via upload
Show unmatched Akkoma entries
2025-09-14 11:46:14 +02:00

299 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Alcea ↔ Akkoma Match Viewer</title>
<style>
body { font-family: 'Segoe UI', Tahoma, sans-serif; padding: 20px; max-width: 1200px; margin: 0 auto; background-color: #f8f9fa; color: #333; }
h1 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
.stats { font-weight: bold; margin: 20px 0; padding: 15px; background: #e8f4f8; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.stat-item { display: inline-block; margin: 0 15px; cursor: pointer; padding: 8px 12px; border-radius: 4px; transition: background-color 0.2s; }
.stat-item:hover { background-color: #d1e7f5; }
.stat-count { color: #2980b9; font-weight: bold; font-size: 1.2em; }
.match { background: white; padding: 16px; margin: 16px 0; border-left: 5px solid #2ecc71; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
.url { font-family: monospace; font-size: 0.9em; color: #7f8c8d; margin: 8px 0; }
.preview { font-style: italic; color: #555; margin: 10px 0; padding: 10px; background-color: #f9f9f9; border-left: 3px solid #bdc3c7; }
pre { background: #f5f5f5; padding: 12px; border: 1px solid #ddd; overflow-x: auto; border-radius: 4px; font-size: 0.95em; }
.match-type { display: inline-block; padding: 4px 10px; border-radius: 4px; color: white; font-size: 0.8em; margin-left: 10px; background: #7f8c8d; }
.match-type.raw { background: #27ae60; }
.match-type.normalized { background: #2ecc71; }
.match-type.noBreaks { background: #3498db; }
.match-type.loose { background: #9b59b6; }
.match-type.finalNormalized { background: #e67e22; }
.filter-active { background-color: #d1e7f5; box-shadow: 0 0 0 2px #3498db inset; }
.no-matches, .loading, .error { text-align: center; padding: 30px; color: #7f8c8d; }
.error { color: #c0392b; background: #fadbd8; border-radius: 5px; margin: 20px 0; }
.refresh-button { background-color: #3498db; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-left: 10px; font-size: 0.9em; }
.refresh-button:hover { background-color: #2980b9; }
.preview-link { cursor: pointer; color: #2980b9; text-decoration: underline; margin-left: 10px; display: inline-block; font-size: 0.9em; }
</style>
</head>
<body>
<h1>Compare Alcea with Akkoma</h1>
<div class="stats" id="stats">Loading data...</div>
<div id="matches"></div>
<script>
let allData = {
alceaEntries: [],
akkomaEntries: [],
matches: [],
unmatchedAkkoma: []
};
const urlParams = new URLSearchParams(window.location.search);
const filterType = urlParams.get('filter');
const filterValue = urlParams.get('value');
function getCacheBustingTimestamp() {
return new Date().getTime();
}
function normalizeLineBreaks(str) {
return str.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
}
function normalizeText(str) {
return normalizeLineBreaks(str).normalize('NFC').trim();
}
function removeLineBreaks(str) {
return normalizeText(str).replace(/\n/g, '');
}
function normalizeTextCompletely(str) {
return str.replace(/\r\n|\r/g, '\n').replace(/\s+/g, ' ').trim().normalize('NFC');
}
async function fetchJson(url) {
const cacheBuster = getCacheBustingTimestamp();
const urlWithCacheBusting = url.includes('?')
? `${url}&_=${cacheBuster}`
: `${url}?_=${cacheBuster}`;
const res = await fetch(urlWithCacheBusting);
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
return await res.json();
}
function extractAlceaEntries(alceaData) {
const entries = [];
if (Array.isArray(alceaData)) {
alceaData.forEach(item => {
if (typeof item === 'object' && item !== null) {
Object.values(item).forEach(entry => {
if (entry && typeof entry.value === 'string' && entry.value.includes('#acws')) {
entries.push(entry);
}
});
}
});
} else if (typeof alceaData === 'object' && alceaData !== null) {
Object.values(alceaData).forEach(entry => {
if (entry && typeof entry.value === 'string' && entry.value.includes('#acws')) {
entries.push(entry);
}
});
}
return entries;
}
function filterMatches() {
if (filterType === 'unmatchedAkkoma') return allData.unmatchedAkkoma;
if (!filterType || !filterValue) return allData.matches;
switch (filterType) {
case 'alcea':
return allData.matches.filter(match => match.value.includes(filterValue));
case 'akkoma':
return allData.matches.filter(match => match.preview.includes(filterValue) || match.url === filterValue);
case 'type':
return allData.matches.filter(match => match.type === filterValue);
default:
return allData.matches;
}
}
function updateStats() {
const stats = document.getElementById('stats');
const filtered = filterMatches();
stats.innerHTML = `
<span class="stat-item" id="alcea-stat">
Alcea entries with #acws: <span class="stat-count">${allData.alceaEntries.length}</span>
</span> |
<span class="stat-item" id="akkoma-stat">
Akkoma entries: <span class="stat-count">${allData.akkomaEntries.length}</span>
</span> |
<span class="stat-item" id="matches-stat">
Matches found: <span class="stat-count">${allData.matches.length}</span>
</span> |
<span class="stat-item" id="unmatched-akkoma-stat">
Unmatched Akkoma entries: <span class="stat-count">${allData.unmatchedAkkoma.length}</span>
</span>
${filterType ? `<br><div style="margin-top:10px;font-weight:normal;">Active filter: ${filterType} = ${filterValue} | <a href="${window.location.pathname}">Clear filter</a></div>` : ''}
<br>
<button class="refresh-button" id="refresh-data">Refresh Data</button>
`;
document.getElementById('alcea-stat').onclick = () => location.href = '?filter=alcea&value=all';
document.getElementById('akkoma-stat').onclick = () => location.href = '?filter=akkoma&value=all';
document.getElementById('matches-stat').onclick = () => location.href = '?filter=matches&value=all';
document.getElementById('unmatched-akkoma-stat').onclick = () => location.href = '?filter=unmatchedAkkoma&value=1';
document.getElementById('refresh-data').onclick = loadData;
if (filterType) {
const el = document.getElementById(`${filterType}-stat`);
if (el) el.classList.add('filter-active');
}
}
function displayMatches() {
const matchesDiv = document.getElementById('matches');
const filtered = filterMatches();
if (filtered.length === 0) {
matchesDiv.innerHTML = '<div class="no-matches">No matches found with the current filter.</div>';
return;
}
//tlpreview
matchesDiv.innerHTML = '';
filtered.forEach(entry => {
const div = document.createElement('div');
div.className = 'match';
if (entry.type) {
// Build preview link URL using first 40 chars of value (with linebreaks replaced)
const rawValue = entry.value;
const sliced = rawValue
.slice(0, 40)
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\n/g, '\r\n');
let previewTextParam = '';
try {
previewTextParam = encodeURIComponent(sliced);
} catch (e) {
console.warn('Encoding failed for preview link text:', sliced, e);
previewTextParam = '';
}
const previewUrl = `https://alceawis.de/other/extra/scripts/fakesocialmedia/commentload.html?text=${previewTextParam}&number=8000`;
div.innerHTML = `
<div><strong>Match type:</strong> ${entry.type}
<span class="match-type ${entry.type}">${entry.type}</span></div>
<div class="url"><strong>Akkoma URL:</strong> <a href="${entry.url}" target="_blank">${entry.url}</a></div>
<div class="preview"><strong>Preview:</strong> ${entry.preview}</div>
<div><strong>Alcea value snippet:</strong>
<pre>${entry.value.slice(0, 500)}${entry.value.length > 500 ? '...' : ''}</pre>
<a class="preview-link" href="${previewUrl}" target="_blank">View in preview viewer ↗</a>
</div>
`;
} else {
div.innerHTML = `
<div class="url"><strong>Akkoma URL:</strong> <a href="${entry.url}" target="_blank">${entry.url}</a></div>
<div class="preview"><strong>Preview:</strong> ${entry.preview}</div>
<div><em>No matching Alcea entry found.</em></div>
`;
}
matchesDiv.appendChild(div);
});
}
async function loadData() {
const stats = document.getElementById('stats');
const matchesDiv = document.getElementById('matches');
stats.textContent = 'Loading data...';
matchesDiv.innerHTML = '<div class="loading">Loading data...</div>';
try {
const [alceaJson, akkomaJson] = await Promise.all([
fetchJson('/other/extra/scripts/fakesocialmedia/data_alcea.json'),
fetchJson('/other/extra/scripts/fakesocialmedia/0ld/data_akkoma.json')
]);
allData.alceaEntries = extractAlceaEntries(alceaJson);
allData.akkomaEntries = Array.isArray(akkomaJson) ? akkomaJson : Object.values(akkomaJson);
allData.matches = [];
const matchedAkkomaUrls = new Set();
for (const alcea of allData.alceaEntries) {
const value = alcea.value;
const rawValue = value;
const normValue = normalizeText(value);
const noBreaksValue = removeLineBreaks(value);
const normValueCompletely = normalizeTextCompletely(value);
let matched = false;
for (const akkoma of allData.akkomaEntries) {
if (!akkoma.preview) continue;
const rawPreview = akkoma.preview;
const normPreview = normalizeText(rawPreview);
const noBreaksPreview = removeLineBreaks(rawPreview);
const normPreviewCompletely = normalizeTextCompletely(rawPreview);
const url = akkoma.url;
if (rawValue.startsWith(rawPreview)) {
allData.matches.push({ type: 'raw', preview: rawPreview, url, value });
matchedAkkomaUrls.add(url);
matched = true;
break;
}
if (normValue.startsWith(normPreview)) {
allData.matches.push({ type: 'normalized', preview: rawPreview, url, value });
matchedAkkomaUrls.add(url);
matched = true;
break;
}
if (noBreaksValue.startsWith(noBreaksPreview)) {
allData.matches.push({ type: 'noBreaks', preview: rawPreview, url, value });
matchedAkkomaUrls.add(url);
matched = true;
break;
}
if (normValue.indexOf(normPreview) >= 0 && normValue.indexOf(normPreview) <= 30) {
allData.matches.push({ type: 'loose', preview: rawPreview, url, value });
matchedAkkomaUrls.add(url);
matched = true;
break;
}
if (normValueCompletely.indexOf(normPreviewCompletely) >= 0 && normValueCompletely.indexOf(normPreviewCompletely) <= 30) {
allData.matches.push({ type: 'finalNormalized', preview: rawPreview, url, value });
matchedAkkomaUrls.add(url);
matched = true;
break;
}
}
}
allData.unmatchedAkkoma = allData.akkomaEntries.filter(ak => !matchedAkkomaUrls.has(ak.url));
updateStats();
displayMatches();
} catch (err) {
stats.textContent = '❌ Error loading or processing data';
stats.className = 'error';
matchesDiv.innerHTML = `
<div class="error">
<p><strong>Error Details:</strong></p>
<p>${err.message}</p>
<p>Check the browser console for more info.</p>
</div>
`;
console.error(err);
}
}
loadData();
</script>
</body>
</html>