
Retrace: Find the Origin of Any Image
document.documentElement.classList.toggle('dark', mode === 'dark');
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
') : svg(''); }; const setTheme = (mode, remember) => { document.documentElement.classList.toggle('dark', mode === 'dark'); document.documentElement.style.colorScheme = mode; if (remember) { try { localStorage.setItem('theme', mode); } catch { } } showTheme(); }; showTheme(); themeBtn.onclick = () => setTheme(document.documentElement.classList.contains('dark') ? 'light' : 'dark', true); media.addEventListener('change', (e) => { let chosen = null; try { chosen = localStorage.getItem('theme'); } catch { } if (chosen !== 'light' && chosen !== 'dark') setTheme(e.matches ? 'dark' : 'light', false); });
const langSel = document.getElementById('lang');
const LANG = document.documentElement.lang || 'en';
document.getElementById('lang-code').textContent = LANG.toUpperCase();
const LANG_NAMES = { en: 'English', ru: 'Русский', es: 'Español', de: 'Deutsch',
pt: 'Português', fr: 'Français' };
langSel.onclick = (ev) => {
ev.stopPropagation();
const open = document.getElementById('lang-menu');
for (const x of document.querySelectorAll('.corner .menu')) x.remove();
if (open) return;
const m = document.createElement('div');
m.className = 'menu lang-menu';
m.id = 'lang-menu';
m.setAttribute('role', 'menu');
m.innerHTML = Object.entries(LANG_NAMES).map(([code, name]) =>
${code === LANG ? '' : ''}${name}).join('');
langSel.parentElement.appendChild(m);
const c = langSel.parentElement.getBoundingClientRect();
m.style.right = ${Math.max(0, c.right - langSel.getBoundingClientRect().right)}px;
m.onclick = (e) => {
const x = e.target.closest('[data-lang]');
if (x && x.dataset.lang !== LANG) switchLang(x.dataset.lang); else m.remove();
};
};
document.addEventListener('click', (e) => {
const lm = document.getElementById('lang-menu');
if (lm && !lm.contains(e.target) && !langSel.contains(e.target)) lm.remove();
});
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape') return;
for (const x of document.querySelectorAll('.corner .menu')) x.remove();
});
const switchLang = (to) => {
const rest = location.pathname.replace(/^/(ru|es|de|pt|fr)(?=/|$)/, '') || '/';
location.assign((to === 'en' ? '' : '/' + to) + rest
- (to === 'en' ? '?lang=en' : '') + location.hash); }; if (new URLSearchParams(location.search).has('lang')) { const q = new URLSearchParams(location.search); q.delete('lang'); history.replaceState({}, '', location.pathname
- (q.size ? '?' + q : '') + location.hash); }
const nForm = LANG === 'ru'
? (n) => { const a = Math.abs(n) % 100; const b = a % 10;
return a > 10 && a 1 && b (Math.abs(n) (Math.abs(n) === 1 ? 0 : 1);
const tp = (key, n, params) => {
const forms = T[key];
if (!Array.isArray(forms)) return t(key, { n, ...params });
let s = forms[Math.min(nForm(n), forms.length - 1)] ?? '';
for (const [k, v] of Object.entries({ n, ...params })) {
s = s.split({${k}}).join(String(v));
}
return s;
};
if (HAS_DICT) { if (!document.getElementById('doc').firstChild) document.title = t('page_title'); const tagline = document.getElementById('tagline'); if (tagline) tagline.textContent = t('tagline'); for (const el of [themeBtn, langSel]) { const key = el === langSel ? 'lang_btn' : 'theme_btn'; el.title = t(key); el.setAttribute('aria-label', t(key)); } document.getElementById('go').textContent = t('check_btn'); document.getElementById('drop').innerHTML = `${t('drop_zone')}${t('drop_hint')}
`; document.getElementById('shoot-t').textContent = t('btn_shoot'); document.getElementById('pick-t').textContent = t('btn_pick'); document.getElementById('f-guide').textContent = t('footer_guide'); document.getElementById('f-pricing').textContent = t('footer_pricing'); document.getElementById('f-tube').textContent = t('footer_tube'); document.getElementById('f-echo').textContent = t('footer_echo'); document.getElementById('f-about').textContent = t('footer_about'); document.getElementById('f-privacy').textContent = t('footer_privacy'); document.getElementById('f-terms').textContent = t('footer_terms'); document.getElementById('f-contact').textContent = t('footer_contact'); document.getElementById('f-copy').textContent = t('footer_copyright', { year: new Date().getFullYear() }); }
const openToAnchor = () => { const id = location.hash.slice(1); if (!id) return; const el = document.getElementById(id); for (let p = el; p; p = p.parentElement) { if (p.tagName === 'DETAILS') p.open = true; } if (el) el.scrollIntoView(); }; addEventListener('hashchange', openToAnchor); const esc = (s) => String(s ?? '').replace(/[<>&"]/g, (c) => ({ '': '>', '&': '&', '"': '"' }[c])); const host = (u) => { try { return new URL(u).host.replace(/^www./, ''); } catch { return u; } }; const pageLink = (u) => { try { const p = new URL(String(u)); return /^https?:$/.test(p.protocol) && ((p.pathname && p.pathname !== '/') || p.search) ? String(u) : ''; } catch { return ''; } }; const skipText = (o) => { const k = (o || {}).skip_key; return k && T['skip_' + k] ? t('skip_' + k, o.skip_args || {}) : String((o || {}).skipped || ''); }; const basisText = (bs) => { if (!bs || bs.n_sources === undefined) return String((bs || {}).note || ''); let s = bs.kind === 'hash' ? t('basis_note_hash', { n: bs.copies, sources: bs.n_sources === 1 ? t('basis_src_one') : t('basis_src_n', { n: bs.n_sources }) }) : bs.kind === 'words' ? t('basis_note_words', { named: (bs.named_by || []) .map((k) => T['named_' + k] || k).join(', ') }) : t('basis_note_frame'); if (bs.judged_same_shot) { s += ' ' + t('basis_tail_judged', { n: bs.judged_same_shot }); } const missing = (bs.missing || []).join(', '); if (bs.verify_ran === false) { s += ' ' + (missing ? t('basis_tail_not_searched_missing', { names: missing }) : t('basis_tail_not_searched')); } else if (missing) { s += ' ' + t('basis_tail_partial', { names: missing })
- ' ' + ((bs.searched_by || []).length ? t('basis_tail_searched_by', { had: bs.searched_by.join(', ') }) : t('basis_tail_no_second'))
- ' ' + t('basis_tail_words_note');
}
return s;
};
const panoUrl = (lat, lon) =>
https://www.google.com/maps/place/${lat},${lon}/@${lat},${lon},17z/data=!5m1!1e5;
const outward = (root) => { const fix = (a) => { const h = a.getAttribute('href') || ''; if (!/^https?:/i.test(h) && !/^/api//.test(h)) return; a.target = '_blank'; if (!/noopener/.test(a.rel || '')) { a.rel = ((a.rel ? a.rel + ' ' : '') + 'noopener noreferrer').trim(); } }; if (root.matches && root.matches('a[href]')) fix(root); for (const a of (root.querySelectorAll ? root.querySelectorAll('a[href]') : [])) fix(a); }; new MutationObserver((recs) => { for (const rec of recs) { for (const n of rec.addedNodes) if (n.nodeType === 1) outward(n); } }).observe(document.body, { childList: true, subtree: true });
const wordyName = (n) => { const parts = String(n || '').replace(/.[a-z0-9]{2,5}$/i, '') .split(/[^0-9A-Za-zА-Яа-яЁё]+/) .map((w) => ({ raw: w, core: w.replace(/\d+/g, '') })) .filter(({ raw, core }) => core.length >= 3 && raw.length core.toLowerCase())).size >= 2; }; const fileLine = (row) => (row && row.file ? `↳ ${t('file_label')} ${esc(row.file)}
`
- (row.name && wordyName(row.name) ? `${t('file_name_note', { name: esc(row.name) })}
` : '') : '');
function engines(f, queryEngine) { const set = new Set(); for (const s of f.sources || []) { const x = String(s).toLowerCase(); if (x.startsWith('web_text_search')) { set.add(queryEngine || 'vlm'); continue; } if (x.includes('names_repeated') || x.includes('names_standing')) { set.add('yandex'); continue; } if (x.startsWith('google_lens')) { set.add('google'); continue; } const e = ['yandex', 'google', 'vlm', 'commons', 'registry'] .find((p) => x.startsWith(p)); set.add(e || s); } return [...set].sort(); }
const SLD2 = new Set(['co', 'com', 'net', 'org', 'ac', 'gov', 'edu', 'or', 'ne']);
const regDomain = (d) => {
const parts = String(d || '').toLowerCase().replace(/^.+|.+$/g, '')
.split('.').filter(Boolean);
if (parts.length {
const x = k.trim().toLowerCase();
return !TRACK_KEYS.has(x) && !TRACK_PREFIX.some((p) => x.startsWith(p));
};
const sameLangPage = (u) => {
try {
const x = new URL(u);
const segs = x.pathname.split('/').filter(Boolean);
if (segs.length > 1 && LANG_SEG.test(segs[0].toLowerCase())) segs.shift();
const q = x.search.replace(/^?/, '').split('&')
.filter((pair) => pair && keepParam(pair.split('=')[0])).join('&');
return ${regDomain(x.hostname)}/${segs.join('/')}${q ? '?' + q : ''};
} catch { return String(u); }
};
const dropLangMirrors = (urls) => {
const best = new Map();
for (const u of urls || []) {
const k = sameLangPage(u);
const cur = best.get(k);
if (cur === undefined) { best.set(k, u); continue; }
const plain = (x) => {
try {
const h = new URL(x);
const seg = h.pathname.split('/').filter(Boolean)[0] || '';
return (LANG_SEG.test(seg.toLowerCase()) ? 1 : 0)
- (LANG_SEG.test(h.hostname.split('.')[0].toLowerCase()) ? 1 : 0);
} catch { return 9; }
};
if (plain(u) thumbOfSite(rank ? (sites || [])[rank - 1] : null);
const thumbOfSite = (s) => {
const u = s && (s.thumb_url || s.image_url);
if (!u) return '';
const img = ``;
return s.image_url
?
${img}: img; };
const CAPTION_FIELD = 'handles_written_under_the_found_image'; const CAPTION_NAMES = 'names_written_under_the_found_image_on_several_domains'; const captioned = (c) => (c.fields || []).some( (f) => f === CAPTION_FIELD || f === CAPTION_NAMES); const byEvidence = (c) => [ (c.anchor_urls || []).length ? 0 : 1, captioned(c) ? 0 : 1, -(c.n_domains || 0), -((c.support_urls || []).length), ]; const sortedCands = (idn) => [...((idn || {}).candidates || [])].sort((a, b) => { const x = byEvidence(a); const y = byEvidence(b); for (let i = 0; i { if (h.only_source_kind) return h.only_source_kind; const s = String(h.only_source || '').toLowerCase(); const one = (x) => (/ocr|visible_text|watermark/.test(x) ? 'text' : /file_name|filename/.test(x) ? 'filenames' : /sites|pages|commons|registry|serp|lens|handles|publisher/.test(x) ? 'pages' : /vlm|ai_summary|ai_overview/.test(x) ? 'model' : 'tags'); const order = ['text', 'filenames', 'pages', 'tags', 'model']; return s.split(/[,;+/]| and /).map(one) .sort((a, b) => order.indexOf(a) - order.indexOf(b))[0] || 'tags'; };
const CODE_LINE = { tags: 'lost_tags', filenames: 'lost_file_words' };
const codeKey = (h) => {
const k = h.claim_key || (h.added_by === 'code' ? CODE_LINE[kindOf(h)] : '');
return k && T['code_' + k] ? k : '';
};
const codeItems = (h) => (Array.isArray(h.items) ? h.items
: (String(h.claim || '').match(/«([^»]+)»/g) || []).map((x) => x.replace(/[«»]/g, '')));
const claimOf = (h) => {
const k = codeKey(h);
return k ? t('code_' + k, { items: codeItems(h)
.map((x) => t('quote_item', { x: esc(x) })).join(', ') }) : esc(h.claim);
};
const verifyOf = (h) => {
const k = codeKey(h);
return k ? t('code_' + k + '_verify') : esc(h.how_to_verify || '');
};
const reasonText = (fam, text, key) => {
const name = key ? ${fam}_${key.k} : '';
if (!name || !T[name]) return esc(text || '');
return t(name, Object.fromEntries(Object.entries(key.a || {})
.map(([k, v]) => [k, esc(v)])));
};
const reasonList = (fam, texts, keys) => (texts || [])
.map((x, i) => reasonText(fam, x, (keys || [])[i])).join('; ');
const anchorD = (x) => ((x.points || {}).tier === 'refuted' && x.d ((ph || {}).numbers || []).map((p) => ${esc(p.number)}${ p.carrier ? (${esc(p.carrier)}) : ''}).join(', ');
let dByPage = null;
const standsOn = (r, c) => {
if (!dByPage) {
dByPage = new Map();
for (const x of r.verify || []) {
if (!x.page || typeof x.d !== 'number') continue;
const k = sameLangPage(x.page);
dByPage.set(k, Math.min(anchorD(x), dByPage.get(k) ?? 99));
}
}
const ds = [...(c.anchor_urls || []), ...(c.support_urls || [])]
.map((u) => dByPage.get(sameLangPage(u)))
.filter((d) => typeof d === 'number')
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。