Edit: 7/3/2026
Cải thiện CDX API Query: đổi wildcard
Nếu paste script xong mà bị 503, 429, timeout error status code thì đợi 1 lúc rồi chạy lại
Haivl.com
Bước 1:
Truy cập Wayback Machine (https://web.archive.org)
Bước 2:
Mở Dev Console bằng Ctrl+Shift+I
Bước 3:
Paste vào Console và Enter
Bước 4:
Đợi tầm 5-10s tùy vào tốc độ mạng hoặc có bị rate-limited hay ko
Bước 5:
Xem xong thì chỉnh
thành
để xem nốt ảnh còn lại
Cab.vn
Như bước 1-3 của Haivl.com nhưng ko Enter vội mà chỉnh dòng API, SITE_ID, TITLE thành
Video hướng dẫn:
drive.google.com
Extra:
Những Youtube links lấy từ các post được archived của Cab.vn CABVN (https://cab.vn/c/*)
Các youtube links vẫn còn
Các youtube links đã ko còn nhưng vẫn xem được trên Wayback Machine
Các youtube links đã ko còn và ko xem được trên Wayback Machine, chỉ có tiêu đề
Cải thiện CDX API Query: đổi wildcard
* thành &matchType=prefix(ít bị rate-limit và nhanh hơn nhiều) + xóa &collapse=timestamp:100 vì không có tác dụng gìNếu paste script xong mà bị 503, 429, timeout error status code thì đợi 1 lúc rồi chạy lại
Haivl.com
Bước 1:
Truy cập Wayback Machine (https://web.archive.org)
Bước 2:
Mở Dev Console bằng Ctrl+Shift+I
Bước 3:
Paste vào Console và Enter
JavaScript:
(async () => {
// ===== CHANGE ONLY THESE =====
const CONFIG = {
API: 'https://web.archive.org/cdx/search/cdx?url=s.haivl.com/data/photos&matchType=prefix&output=json&fl=mimetype,timestamp,original&collapse=digest&limit=999999&filter=mimetype:image/.*',
SITE_ID: 'haivl_photos',
TITLE: 'Haivl Image Browser',
DEFAULT_IF_MODE: true,
DEFAULT_SORT: 'oldest', // newest | oldest
DEFAULT_DEDUPE: 'latest', // latest | oldest | all
DEFAULT_FILTER: 'all', // all | new
MAX_CONCURRENT_LOADS: 3,
// ===== retry settings =====
PREVIEW_RETRY_COUNT: 4,
PREVIEW_RETRY_BASE_DELAY_MS: 1500,
LIGHTBOX_RETRY_COUNT: 4,
LIGHTBOX_RETRY_BASE_DELAY_MS: 1500
};
// ===== INTERNAL KEYS =====
const KEY = (name) => `wb_${CONFIG.SITE_ID}_${name}`;
const STORAGE = {
opened: KEY('opened'),
sort: KEY('sort'),
dedupe: KEY('dedupe'),
ifMode: KEY('ifMode'),
filter: KEY('filter')
};
// ===== STATE =====
const opened = new Set(JSON.parse(localStorage.getItem(STORAGE.opened) || '[]'));
let sortMode = localStorage.getItem(STORAGE.sort) || CONFIG.DEFAULT_SORT;
let dedupeMode = localStorage.getItem(STORAGE.dedupe) || CONFIG.DEFAULT_DEDUPE;
let useIfMode = (localStorage.getItem(STORAGE.ifMode) ?? String(CONFIG.DEFAULT_IF_MODE)) === 'true';
let filterMode = localStorage.getItem(STORAGE.filter) || CONFIG.DEFAULT_FILTER;
let observer = null;
let loadingCount = 0;
// ===== HELPERS =====
const save = {
opened: () => localStorage.setItem(STORAGE.opened, JSON.stringify([...opened])),
sort: () => localStorage.setItem(STORAGE.sort, sortMode),
dedupe: () => localStorage.setItem(STORAGE.dedupe, dedupeMode),
ifMode: () => localStorage.setItem(STORAGE.ifMode, String(useIfMode)),
filter: () => localStorage.setItem(STORAGE.filter, filterMode),
};
const makeArchivedUrl = (timestamp, original) =>
`https://web.archive.org/web/${timestamp}/${original}`;
const makeOpenUrl = (timestamp, original) =>
useIfMode
? `https://web.archive.org/web/${timestamp}if_/${original}`
: `https://web.archive.org/web/${timestamp}/${original}`;
const formatTimestamp = (ts) =>
/^\d{14}$/.test(ts)
? `${ts.slice(0,4)}-${ts.slice(4,6)}-${ts.slice(6,8)} ${ts.slice(8,10)}:${ts.slice(10,12)}:${ts.slice(12,14)}`
: ts;
const isOpened = (identityUrl) => opened.has(identityUrl);
const labels = {
dedupe() {
if (dedupeMode === 'latest') return 'Dedup: latest per original';
if (dedupeMode === 'oldest') return 'Dedup: oldest per original';
return 'Dedup: keep all snapshots';
},
sort() {
return sortMode === 'newest' ? 'Sort: newest first' : 'Sort: oldest first';
},
filter() {
return filterMode === 'new' ? 'Filter: unopened only' : 'Filter: all';
},
ifMode() {
return useIfMode ? 'Open mode: if_' : 'Open mode: normal';
}
};
function getRetryDelay(attempt, baseMs) {
return baseMs * Math.pow(2, Math.max(0, attempt - 1));
}
function isElementStillConnected(el) {
return !!(el && el.isConnected);
}
// ===== FETCH DATA =====
const res = await fetch(CONFIG.API);
const data = await res.json();
const allRows = data
.slice(1)
.filter(r => Array.isArray(r) && r.length >= 3)
.map(([mimetype, timestamp, original]) => ({
mimetype,
timestamp,
original,
identityUrl: makeArchivedUrl(timestamp, original)
}));
function buildRows() {
let rows;
if (dedupeMode === 'all') {
const seen = new Set();
rows = [];
for (const row of allRows) {
if (!seen.has(row.identityUrl)) {
seen.add(row.identityUrl);
rows.push(row);
}
}
} else {
const map = new Map();
for (const row of allRows) {
const existing = map.get(row.original);
if (!existing) {
map.set(row.original, row);
continue;
}
if (dedupeMode === 'latest' && row.timestamp > existing.timestamp) map.set(row.original, row);
if (dedupeMode === 'oldest' && row.timestamp < existing.timestamp) map.set(row.original, row);
}
rows = [...map.values()];
}
rows.sort((a, b) =>
sortMode === 'newest'
? b.timestamp.localeCompare(a.timestamp)
: a.timestamp.localeCompare(b.timestamp)
);
if (filterMode === 'new') {
rows = rows.filter(row => !isOpened(row.identityUrl));
}
return rows;
}
let visibleRows = buildRows();
function applyCardState(card, openedAlready) {
card.style.background = openedAlready ? '#eaf8ea' : '#161616';
card.style.border = openedAlready ? '2px solid #48b36b' : '2px solid #2f2f2f';
card.style.opacity = openedAlready ? '0.78' : '1';
const badge = card.querySelector('.wb-badge');
if (badge) {
badge.textContent = openedAlready ? 'OPENED' : 'NEW';
badge.style.background = openedAlready ? '#2e8b57' : '#555';
badge.style.color = '#fff';
}
const title = card.querySelector('.wb-title-link');
if (title) {
title.style.color = openedAlready ? '#7fd69b' : '#8ab4ff';
title.style.fontWeight = openedAlready ? '700' : '500';
}
const original = card.querySelector('.wb-original');
if (original) original.style.color = openedAlready ? '#8fdca6' : '#bbb';
const imgWrap = card.querySelector('.wb-img-wrap');
if (imgWrap) {
imgWrap.style.boxShadow = openedAlready
? '0 0 0 3px rgba(72,179,107,.35)'
: 'none';
}
const toggleBtn = card.querySelector('.wb-toggle');
if (toggleBtn) toggleBtn.textContent = openedAlready ? 'Unmark' : 'Mark opened';
}
function updateStats() {
visibleRows = buildRows();
const total = visibleRows.length;
const done = visibleRows.filter(r => isOpened(r.identityUrl)).length;
const left = total - done;
const stats = document.getElementById('wb-stats');
if (stats) {
stats.textContent =
`Shown: ${total} | Opened: ${done} | Left: ${left} | ${labels.sort()} | ${labels.dedupe()} | ${labels.ifMode()} | ${labels.filter()}`;
}
const sortBtn = document.getElementById('wb-sort');
if (sortBtn) sortBtn.textContent = labels.sort();
const dedupeBtn = document.getElementById('wb-dedupe');
if (dedupeBtn) dedupeBtn.textContent = labels.dedupe();
const ifBtn = document.getElementById('wb-ifmode');
if (ifBtn) ifBtn.textContent = labels.ifMode();
const filterBtn = document.getElementById('wb-filter');
if (filterBtn) filterBtn.textContent = labels.filter();
}
function markOpened(identityUrl, card) {
opened.add(identityUrl);
save.opened();
if (card) applyCardState(card, true);
updateStats();
}
function markUnopened(identityUrl, card) {
opened.delete(identityUrl);
save.opened();
if (card) applyCardState(card, false);
updateStats();
}
function schedulePreviewRetry(img) {
const attempt = Number(img.dataset.retryAttempt || '0') + 1;
img.dataset.retryAttempt = String(attempt);
const ph = img.parentElement?.querySelector('.wb-placeholder');
if (attempt > CONFIG.PREVIEW_RETRY_COUNT) {
if (ph) ph.textContent = 'Preview failed or rate-limited';
return;
}
const delay = getRetryDelay(attempt, CONFIG.PREVIEW_RETRY_BASE_DELAY_MS);
if (ph) ph.textContent = `Retrying preview (${attempt}/${CONFIG.PREVIEW_RETRY_COUNT}) in ${Math.ceil(delay / 1000)}s...`;
setTimeout(() => {
if (!isElementStillConnected(img)) return;
img.dataset.loading = 'false';
img.dataset.failed = 'false';
flushPendingVisibleImages();
}, delay);
}
function tryLoadImage(img) {
if (!img || img.dataset.loaded === 'true' || img.dataset.loading === 'true') return;
if (loadingCount >= CONFIG.MAX_CONCURRENT_LOADS) return;
const src = img.dataset.src;
if (!src) return;
img.dataset.loading = 'true';
loadingCount++;
const temp = new Image();
temp.onload = () => {
if (!isElementStillConnected(img)) {
loadingCount--;
return;
}
img.src = src;
img.dataset.loaded = 'true';
img.dataset.loading = 'false';
img.dataset.failed = 'false';
loadingCount--;
const ph = img.parentElement.querySelector('.wb-placeholder');
if (ph) ph.remove();
flushPendingVisibleImages();
};
temp.onerror = () => {
if (!isElementStillConnected(img)) {
loadingCount--;
return;
}
img.dataset.loading = 'false';
img.dataset.failed = 'true';
loadingCount--;
schedulePreviewRetry(img);
flushPendingVisibleImages();
};
temp.src = src;
}
function flushPendingVisibleImages() {
const imgs = [...document.querySelectorAll('img[data-src]:not([data-loaded="true"]):not([data-loading="true"])')];
for (const img of imgs) {
if (loadingCount >= CONFIG.MAX_CONCURRENT_LOADS) break;
const rect = img.getBoundingClientRect();
const nearViewport = rect.top < window.innerHeight + 1200 && rect.bottom > -1200;
if (nearViewport) tryLoadImage(img);
}
}
function setupObserver() {
if (observer) observer.disconnect();
observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
tryLoadImage(entry.target);
}
}, {
root: null,
rootMargin: '1200px 0px',
threshold: 0.01
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
window.addEventListener('scroll', flushPendingVisibleImages, { passive: true });
setTimeout(flushPendingVisibleImages, 100);
}
function loadLightboxImageWithRetry(imgEl, loadingEl, url, onDone) {
let attempt = 0;
function run() {
attempt++;
if (!isElementStillConnected(imgEl)) return;
const temp = new Image();
temp.onload = () => {
if (!isElementStillConnected(imgEl)) return;
imgEl.src = url;
imgEl.style.display = 'block';
if (loadingEl && loadingEl.isConnected) loadingEl.remove();
onDone();
};
temp.onerror = () => {
if (!isElementStillConnected(imgEl)) return;
if (attempt > CONFIG.LIGHTBOX_RETRY_COUNT) {
if (loadingEl && loadingEl.isConnected) {
loadingEl.textContent = 'Failed to load image preview';
}
return;
}
const delay = getRetryDelay(attempt, CONFIG.LIGHTBOX_RETRY_BASE_DELAY_MS);
if (loadingEl && loadingEl.isConnected) {
loadingEl.textContent = `Retrying image (${attempt}/${CONFIG.LIGHTBOX_RETRY_COUNT}) in ${Math.ceil(delay / 1000)}s...`;
}
setTimeout(run, delay);
};
temp.src = url;
}
run();
}
function openLightbox(openUrl, identityUrl, card) {
markOpened(identityUrl, card);
document.getElementById('wb-lightbox')?.remove();
const overlay = document.createElement('div');
overlay.id = 'wb-lightbox';
overlay.style.cssText = `
position: fixed; inset: 0; background: rgba(0,0,0,0.92);
z-index: 100000; display: flex; flex-direction: column;
`;
overlay.innerHTML = `
<div class="wb-lb-toolbar" style="
display:flex; align-items:center; gap:8px; padding:10px;
background: rgba(20,20,20,0.95); border-bottom: 1px solid #333;
color:#eee; font: 13px Arial, sans-serif;">
<button id="wb-lb-close">Close</button>
<button id="wb-lb-open-tab">Open in new tab</button>
<button id="wb-lb-reset">Reset view</button>
<div style="margin-left:8px; opacity:.85; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
Wheel = move image, Shift + wheel = horizontal move, click image = zoom toggle, drag = pan, Esc = close
</div>
</div>
<div id="wb-lb-stage" style="
position: relative; flex: 1; overflow: hidden; cursor: grab;
display: flex; align-items: flex-start; justify-content: center;">
<div id="wb-lb-loading" style="
position:absolute; top:24px; color:#bbb; font:16px Arial,sans-serif;">
Loading image...
</div>
<img id="wb-lb-img" style="
max-width:none; max-height:none; transform-origin: top center;
user-select:none; -webkit-user-drag:none; display:none; cursor:zoom-in;">
</div>
`;
document.body.appendChild(overlay);
const stage = overlay.querySelector('#wb-lb-stage');
const img = overlay.querySelector('#wb-lb-img');
const loading = overlay.querySelector('#wb-lb-loading');
let scale = 1;
let x = 0;
let y = 0;
let dragging = false;
let movedDuringDrag = false;
let startX = 0;
let startY = 0;
function applyTransform() {
img.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
img.style.cursor = scale === 1 ? 'zoom-in' : 'zoom-out';
}
function resetView() {
scale = 1;
x = 0;
y = 0;
applyTransform();
}
function toggleZoomAtPoint(clientX, clientY) {
const rect = stage.getBoundingClientRect();
const cx = clientX - rect.left - rect.width / 2;
const cy = clientY - rect.top;
if (scale === 1) {
const newScale = 2;
const ratio = newScale / scale;
scale = newScale;
x = x * ratio + cx * (1 - ratio);
y = y * ratio + cy * (1 - ratio);
} else {
scale = 1;
x = 0;
y = 0;
}
applyTransform();
}
function closeLightbox() {
overlay.remove();
document.removeEventListener('keydown', onKeyDown);
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
window.removeEventListener('blur', onWindowBlur);
if (filterMode === 'new') {
render();
}
}
function onKeyDown(e) {
if (e.key === 'Escape') closeLightbox();
}
function onWindowBlur() {
dragging = false;
movedDuringDrag = false;
stage.style.cursor = 'grab';
}
document.addEventListener('keydown', onKeyDown);
window.addEventListener('blur', onWindowBlur);
overlay.querySelector('#wb-lb-close').onclick = closeLightbox;
overlay.querySelector('#wb-lb-open-tab').onclick = () => window.open(openUrl, '_blank');
overlay.querySelector('#wb-lb-reset').onclick = resetView;
overlay.addEventListener('click', (e) => {
if (e.target !== img && !e.target.closest('.wb-lb-toolbar')) {
closeLightbox();
}
});
stage.addEventListener('wheel', (e) => {
e.preventDefault();
const panSpeed = Math.max(100, 260 / Math.max(scale, 0.5));
if (e.shiftKey) {
x -= e.deltaY * (panSpeed / 100);
} else {
y -= e.deltaY * (panSpeed / 100);
}
applyTransform();
}, { passive: false });
stage.addEventListener('mousedown', (e) => {
if (e.button !== 0 || e.target !== img) return;
dragging = true;
movedDuringDrag = false;
startX = e.clientX - x;
startY = e.clientY - y;
stage.style.cursor = 'grabbing';
});
function onMouseMove(e) {
if (!dragging) return;
const newX = e.clientX - startX;
const newY = e.clientY - startY;
if (Math.abs(newX - x) > 3 || Math.abs(newY - y) > 3) movedDuringDrag = true;
x = newX;
y = newY;
applyTransform();
}
function onMouseUp(e) {
if (e.button === 0) {
dragging = false;
stage.style.cursor = 'grab';
}
}
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
img.addEventListener('click', (e) => {
e.stopPropagation();
if (movedDuringDrag) {
movedDuringDrag = false;
return;
}
toggleZoomAtPoint(e.clientX, e.clientY);
});
loadLightboxImageWithRetry(img, loading, openUrl, resetView);
}
function render() {
visibleRows = buildRows();
document.documentElement.innerHTML = `
<head>
<title>${CONFIG.TITLE}</title>
<meta charset="utf-8">
<style>
* { box-sizing: border-box; }
body { margin: 0; background: #0f0f0f; color: #eee; font-family: Arial, sans-serif; }
.wb-topbar { position: sticky; top: 0; z-index: 9999; background: #111; border-bottom: 1px solid #2b2b2b; padding: 12px; }
.wb-title { font-size: 20px; font-weight: 700; margin-bottom: 8px; }
.wb-stats { font-size: 13px; color: #ccc; margin-bottom: 10px; line-height: 1.4; }
.wb-controls { display: flex; flex-wrap: wrap; gap: 8px; }
.wb-controls button { background: #222; color: #eee; border: 1px solid #444; border-radius: 8px; padding: 8px 12px; cursor: pointer; }
.wb-controls button:hover { background: #2c2c2c; }
.wb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(520px, 1fr)); gap: 14px; padding: 14px; }
.wb-card { background: #161616; border: 2px solid #2f2f2f; border-radius: 12px; padding: 12px; transition: .15s ease; }
.wb-meta-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 10px; }
.wb-badge { display: inline-block; min-width: 66px; text-align: center; padding: 4px 8px; border-radius: 999px; background: #555; font-size: 12px; font-weight: 700; }
.wb-meta-text { color: #ddd; font-size: 13px; }
.wb-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.wb-actions button, .wb-actions a { background: #222; color: #eee; border: 1px solid #444; border-radius: 8px; padding: 8px 12px; text-decoration: none; font-size: 13px; cursor: pointer; }
.wb-title-link { display: block; margin-bottom: 8px; color: #8ab4ff; text-decoration: none; word-break: break-all; font-size: 14px; }
.wb-original { color: #bbb; font-size: 13px; margin-bottom: 10px; word-break: break-all; }
.wb-img-wrap { width: 100%; min-height: 420px; display: flex; align-items: center; justify-content: center; background: #0b0b0b; border-radius: 10px; overflow: hidden; position: relative; cursor: zoom-in; }
.wb-img-wrap img { display: block; width: 100%; max-height: 78vh; object-fit: contain; background: #0b0b0b; cursor: zoom-in; }
.wb-img-wrap:hover { outline: 2px solid #4d78cc; }
.wb-placeholder { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #999; font-size: 16px; text-align: center; padding: 20px; }
.wb-empty { padding: 40px 16px; text-align: center; color: #bbb; font-size: 18px; }
</style>
</head>
<body>
<div class="wb-topbar">
<div class="wb-title">${CONFIG.TITLE}</div>
<div id="wb-stats" class="wb-stats"></div>
<div class="wb-controls">
<button id="wb-filter"></button>
<button id="wb-sort"></button>
<button id="wb-dedupe"></button>
<button id="wb-ifmode"></button>
<button id="wb-copy-new">Copy unopened</button>
<button id="wb-open-next">Open next unopened</button>
<button id="wb-reset">Reset progress</button>
<button id="wb-reload">Reload UI</button>
</div>
</div>
<div id="wb-grid" class="wb-grid"></div>
</body>
`;
const grid = document.getElementById('wb-grid');
if (!visibleRows.length) {
const empty = document.createElement('div');
empty.className = 'wb-empty';
empty.textContent = 'No items to show.';
grid.appendChild(empty);
} else {
visibleRows.forEach((item, idx) => {
const openUrl = makeOpenUrl(item.timestamp, item.original);
const openedAlready = isOpened(item.identityUrl);
const card = document.createElement('div');
card.className = 'wb-card';
card.innerHTML = `
<div class="wb-meta-row">
<span class="wb-badge"></span>
<span class="wb-meta-text">${idx + 1}. ${item.mimetype} | ${formatTimestamp(item.timestamp)}</span>
</div>
<div class="wb-actions">
<button class="wb-open">Open</button>
<button class="wb-toggle">Mark opened</button>
<a href="${openUrl}" target="_blank" class="wb-direct">Open link</a>
</div>
<a href="${openUrl}" target="_blank" class="wb-title-link">${openUrl}</a>
<div class="wb-original">${item.original}</div>
<div class="wb-img-wrap" title="Click image for large preview">
<div class="wb-placeholder">Waiting to load when near viewport...</div>
<img loading="lazy" data-src="${openUrl}" data-retry-attempt="0" data-failed="false">
</div>
`;
const img = card.querySelector('img');
const imgWrap = card.querySelector('.wb-img-wrap');
const openAndMark = () => {
window.open(openUrl, '_blank');
markOpened(item.identityUrl, card);
};
const previewAndMark = () => {
openLightbox(openUrl, item.identityUrl, card);
};
card.querySelector('.wb-open').onclick = openAndMark;
card.querySelector('.wb-title-link').onclick = () => {
setTimeout(() => {
markOpened(item.identityUrl, card);
if (filterMode === 'new') render();
}, 50);
};
card.querySelector('.wb-direct').onclick = () => {
setTimeout(() => {
markOpened(item.identityUrl, card);
if (filterMode === 'new') render();
}, 50);
};
imgWrap.onclick = (e) => {
e.preventDefault();
previewAndMark();
};
img.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
previewAndMark();
};
card.querySelector('.wb-toggle').onclick = () => {
if (isOpened(item.identityUrl)) markUnopened(item.identityUrl, card);
else markOpened(item.identityUrl, card);
if (filterMode === 'new') render();
};
applyCardState(card, openedAlready);
grid.appendChild(card);
});
}
document.getElementById('wb-filter').onclick = () => {
filterMode = filterMode === 'all' ? 'new' : 'all';
save.filter();
render();
};
document.getElementById('wb-sort').onclick = () => {
sortMode = sortMode === 'newest' ? 'oldest' : 'newest';
save.sort();
render();
};
document.getElementById('wb-dedupe').onclick = () => {
dedupeMode = dedupeMode === 'latest' ? 'oldest' : dedupeMode === 'oldest' ? 'all' : 'latest';
save.dedupe();
render();
};
document.getElementById('wb-ifmode').onclick = () => {
useIfMode = !useIfMode;
save.ifMode();
render();
};
document.getElementById('wb-copy-new').onclick = async () => {
const text = buildRows()
.filter(item => !isOpened(item.identityUrl))
.map(item => makeOpenUrl(item.timestamp, item.original))
.join('\n');
if (!text) {
alert('No unopened links to copy');
return;
}
try {
await navigator.clipboard.writeText(text);
alert(`Copied ${text.split('\n').length} unopened links`);
} catch (e) {
console.error(e);
alert('Clipboard failed — see console');
prompt('Copy manually:', text);
}
};
document.getElementById('wb-open-next').onclick = () => {
const next = buildRows().find(item => !isOpened(item.identityUrl));
if (!next) return alert('No unopened links left');
const openUrl = makeOpenUrl(next.timestamp, next.original);
window.open(openUrl, '_blank');
opened.add(next.identityUrl);
save.opened();
updateStats();
const cards = document.querySelectorAll('.wb-card');
for (const card of cards) {
const link = card.querySelector('.wb-title-link');
if (link && link.href === openUrl) {
applyCardState(card, true);
break;
}
}
};
document.getElementById('wb-reset').onclick = () => {
if (!confirm('Reset all saved progress?')) return;
localStorage.removeItem(STORAGE.opened);
opened.clear();
render();
};
document.getElementById('wb-reload').onclick = () => render();
updateStats();
setupObserver();
}
render();
})();
Bước 4:
Đợi tầm 5-10s tùy vào tốc độ mạng hoặc có bị rate-limited hay ko
Bước 5:
Xem xong thì chỉnh
Mã:
s.haivl.com
Mã:
s2.haivl.com
Cab.vn
Như bước 1-3 của Haivl.com nhưng ko Enter vội mà chỉnh dòng API, SITE_ID, TITLE thành
Mã:
API: 'https://web.archive.org/cdx/search/cdx?url=i.cab.vn&matchType=prefix&output=json&fl=mimetype,timestamp,original&collapse=digest&limit=999999&filter=mimetype:image/.*&filter=!original:.*\\/avatars\\/.*&filter=!original:.*robots\\.txt.*&filter=!original:.*\\/static\\/.*',
SITE_ID: 'cabvn_photos',
TITLE: 'Cabvn Image Browser',
Video hướng dẫn:
Voz.vn cab.vn haivl.com.mp4
Những Youtube links lấy từ các post được archived của Cab.vn CABVN (https://cab.vn/c/*)
Các youtube links vẫn còn
Các youtube links đã ko còn nhưng vẫn xem được trên Wayback Machine
Các youtube links đã ko còn và ko xem được trên Wayback Machine, chỉ có tiêu đề
Sửa lần cuối:


