Найден корректный robots.txt
Файл управления сканированием сайта роботами
?
Robots.txt указывает поисковым роботам, какие страницы сканировать, а какие — нет. Ошибки в файле могут случайно закрыть важные разделы от индексации.
Robots.txt настроен корректно. Размер файла: 22444 байт. Загружен за: 74мсек.
Проверяемая страница не запрещена в robots.txt.
Показать содержимое robots.txt
# As a condition of accessing this website, you agree to abide by the following
# content signals:
# (a) If a Content-Signal = yes, you may collect content for the corresponding
# use.
# (b) If a Content-Signal = no, you may not collect content for the
# corresponding use.
# (c) If the website operator does not include a Content-Signal for a
# corresponding use, the website operator neither grants nor restricts
# permission via Content-Signal with respect to the corresponding use.
# The content signals and their meanings are:
# search: building a search index and providing search results (e.g., returning
# hyperlinks and short excerpts from your website's contents). Search does not
# include providing AI-generated search summaries.
# ai-input: inputting content into one or more AI models (e.g., retrieval
# augmented generation, grounding, or other real-time taking of content for
# generative AI search answers).
# ai-train: training or fine-tuning AI models.
# use: how AI systems may consume the content (immediate, reference, or full).
# ANY RESTRICTIONS EXPRESSED VIA CONTENT SIGNALS ARE EXPRESS RESERVATIONS OF
# RIGHTS UNDER ARTICLE 4 OF THE EUROPEAN UNION DIRECTIVE 2019/790 ON COPYRIGHT
# AND RELATED RIGHTS IN THE DIGITAL SINGLE MARKET.
# BEGIN Cloudflare Managed content
User-agent: *
Content-Signal: search=yes,ai-train=no,use=reference
Allow: /
User-agent: Amazonbot
Disallow: /
User-agent: Applebot-Extended
Disallow: /
User-agent: Bytespider
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
User-agent: CloudflareBrowserRenderingCrawler
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: GPTBot
Disallow: /
User-agent: meta-externalagent
Disallow: /
# END Cloudflare Managed Content
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>tsinghub.edu.kg 邮箱</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f5f5; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; }
.header h1 { font-size: 24px; }
.header .actions { margin-top: 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.header button { background: rgba(255,255,255,0.2); color: white; border: 1px solid rgba(255,255,255,0.4); padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; }
.header button:hover { background: rgba(255,255,255,0.3); }
.header button:disabled { opacity: 0.5; cursor: not-allowed; }
.spinner { display: none; width: 18px; height: 18px; border: 2px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 0.8s linear infinite; margin-left: 8px; }
.spinner.active { display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.categories { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 15px; }
.category-btn { background: white; border: 1px solid #ddd; padding: 6px 14px; border-radius: 20px; cursor: pointer; font-size: 13px; color: #666; transition: all 0.2s; }
.category-btn:hover { border-color: #667eea; color: #667eea; }
.category-btn.active { background: #667eea; color: white; border-color: #667eea; }
.category-btn .count { background: rgba(0,0,0,0.08); border-radius: 10px; padding: 1px 7px; margin-left: 4px; font-size: 11px; }
.category-btn.active .count { background: rgba(255,255,255,0.25); }
.search-box { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; margin-bottom: 15px; font-size: 14px; }
.search-box:focus { outline: none; border-color: #667eea; }
.email-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.email-count { color: #999; font-size: 13px; }
.toolbar-actions { display: flex; gap: 8px; }
.toolbar-actions button { background: none; border: 1px solid #ddd; padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; color: #666; }
.toolbar-actions button:hover { border-color: #e74c3c; color: #e74c3c; }
.toolbar-actions button:disabled { opacity: 0.3; cursor: not-allowed; }
.email-list { background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.email-item { padding: 12px 20px; border-bottom: 1px solid #eee; cursor: pointer; transition: background 0.2s; display: flex; align-items: center; gap: 12px; }
.email-item:hover { background: #f8f9fa; }
.email-item .cb { flex-shrink: 0; }
.email-item .cb input { cursor: pointer; width: 16px; height: 16px; }
.email-item .info { flex: 1; min-width: 0; }
.email-item .from { font-weight: 600; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.email-item .subject { color: #555; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.email-item .to { color: #bbb; font-size: 11px; margin-top: 2px; }
.email-item .time { color: #999; font-size: 12px; white-space: nowrap; flex-shrink: 0; }
.email-item .del-btn { flex-shrink: 0; background: none; border: none; color: #ccc; cursor: pointer; font-size: 16px; padding: 4px 8px; border-radius: 4px; opacity: 0; transition: all 0.2s; }
.email-item:hover .del-btn { opacity: 1; }
.email-item .del-btn:hover { color: #e74c3c; background: #fef0ef; }
.email-detail { display: none; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-top: 20px; overflow: hidden; }
.email-detail.active { display: block; }
.detail-header { padding: 20px; border-bottom: 1px solid #eee; background: #fafafa; }
.detail-header h3 { margin-bottom: 10px; color: #333; word-break: break-all; }
.detail-header p { color: #666; margin: 4px 0; font-size: 14px; word-break: break-all; }
.detail-actions { padding: 10px 20px; border-bottom: 1px solid #eee; background: #fff; display: flex; gap: 8px; flex-wrap: wrap; }
.detail-actions button { background: #667eea; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; }
.detail-actions button:hover { background: #5a6fd6; }
.detail-actions button.danger { background: #e74c3c; }
.detail-actions button.danger:hover { background: #c0392b; }
.detail-body { padding: 0; }
.detail-body iframe { width: 100%; min-height: 500px; border: none; display: block; }
.detail-raw { display: none; padding: 20px; background: #1e1e1e; color: #d4d4d4; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; white-space: pre-wrap; word-break: break-all; max-height: 600px; overflow-y: auto; }
.detail-raw.active { display: block; }
.back-btn { background: #667eea; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; margin: 15px; font-size: 14px; }
.back-btn:hover { background: #5a6fd6; }
.loading { text-align: center; padding: 40px; color: #999; }
.load-more { text-align: center; padding: 20px; }
.load-more button { background: #667eea; color: white; border: none; padding: 12px 30px; border-radius: 6px; cursor: pointer; font-size: 14px; }
.load-more button:hover { background: #5a6fd6; }
.load-more button:disabled { opacity: 0.5; cursor: not-allowed; }
.toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: #333; color: white; padding: 12px 24px; border-radius: 8px; font-size: 14px; z-index: 9999; opacity: 0; transition: opacity 0.3s; pointer-events: none; }
.toast.show { opacity: 1; }
</style>
</head>
<body>
<div class="header">
<div class="container" style="padding:0">
<h1>tsinghub.edu.kg 邮箱</h1>
<div class="actions">
<button onclick="doRefresh()" id="refreshBtn">刷新</button>
<div class="spinner" id="spinner"></div>
<button onclick="doLoadMore()" id="loadMoreBtn">加载更多</button>
</div>
</div>
</div>
<div class="container">
<input type="text" class="search-box" placeholder="搜索邮件..." oninput="filterEmails(this.value)" id="searchBox">
<div id="categories" class="categories"></div>
<div class="email-toolbar">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap">
<label style="font-size:13px;cursor:pointer;display:flex;align-items:center;gap:4px;user-select:none">
<input type="checkbox" id="selectAllCb" onchange="toggleSelectAllPage()" style="width:16px;height:16px;cursor:pointer">
本页全选
</label>
<label style="font-size:13px;cursor:pointer;display:flex;align-items:center;gap:4px;user-select:none">
<input type="checkbox" id="selectAllEmailsCb" onchange="toggleSelectAllEmails()" style="width:16px;height:16px;cursor:pointer">
全选所有邮件
</label>
<div id="email-count" class="email-count">加载中...</div>
</div>
<div class="toolbar-actions">
<button onclick="deleteSelected()" id="batchDelBtn" disabled>删除选中</button>
</div>
</div>
<div id="email-list" class="email-list"></div>
</div>
<div id="email-detail" class="email-detail"></div>
<div id="toast" class="toast"></div>
<script>
var allEmails = [];
var currentOffset = 0;
var hasMore = false;
var currentEmailId = '';
var loading = false;
var currentCategory = 'all';
var searchQuery = '';
function toast(msg) { var t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(function(){ t.classList.remove('show'); }, 2000); }
function doRefresh() {
if (loading) return;
document.getElementById('spinner').classList.add('active');
document.getElementById('refreshBtn').disabled = true;
currentOffset = 0;
allEmails = [];
loadEmails(false);
}
function doLoadMore() {
if (loading) return;
if (!hasMore) { toast('没有更多邮件了'); return; }
loadEmails(true);
}
async function loadEmails(append) {
if (loading) return;
loading = true;
try {
var url = '/api/emails?limit=30&offset=' + currentOffset;
var res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status);
var data = await res.json();
if (append) { allEmails = allEmails.concat(data.emails); }
else { allEmails = data.emails; }
currentOffset = allEmails.length;
hasMore = data.hasMore;
document.getElementById('email-count').textContent = '共 ' + data.total + ' 封邮件,已加载 ' + allEmails.length + ' 封';
render();
} catch(e) {
document.getElementById('email-list').innerHTML = '<div class="loading">加载失败: ' + escapeHtml(e.message) + '</div>';
} finally {
loading = false;
document.getElementById('spinner').classList.remove('active');
document.getElementById('refreshBtn').disabled = false;
}
}
function escapeHtml(s) {
if (!s) return '';
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
function extractDomain(from) {
if (!from) return 'unknown';
var m = from.match(/@([^>]+)>?$/);
if (m) {
var parts = m[1].toLowerCase().split('.');
if (parts.length >= 2) return parts.slice(-2).join('.');
return m[1].toLowerCase();
}
return 'unknown';
}
function categorizeDomain(domain) {
if (domain.includes('semanticscholar')) return 'Semantic Scholar';
if (domain.includes('openalex')) return 'OpenAlex';
if (domain.includes('tavily')) return 'Tavily';
if (domain.includes('xiaomi') || domain.includes('mimo')) return '其他平台';
if (domain === 'unknown') return '未知';
return domain;
}
function buildCategories() {
var cats = { all: allEmails.length };
for (var i = 0; i < allEmails.length; i++) {
var domain = extractDomain(allEmails[i].from);
var cat = categorizeDomain(domain);
cats[cat] = (cats[cat] || 0) + 1;
}
return cats;
}
function renderCategories() {
var cats = buildCategories();
var container = document.getElementById('categories');
var html = '';
var keys = Object.keys(cats);
var order = ['all', 'Semantic Scholar', 'OpenAlex', 'Tavily'];
keys.sort(function(a,b) {
var ai = order.indexOf(a); if (ai < 0) ai = 999;
var bi = order.indexOf(b); if (bi < 0) bi = 999;
return ai - bi || b.localeCompare(a);
});
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
var active = k === currentCategory ? ' active' : '';
html += '<button class="category-btn' + active + '" onclick="setCategory(\'' + k.replace(/'/g, "\\'") + '\')">' + escapeHtml(k) + ' <span class="count">' + cats[k] + '</span></button>';
}
container.innerHTML = html;
}
function setCategory(cat) {
currentCategory = cat;
renderCategories();
applyFilter();
}
function getFilteredEmails() {
var q = searchQuery.toLowerCase();
var result = [];
for (var i = 0; i < allEmails.length; i++) {
var e = allEmails[i];
var domain = extractDomain(e.from);
var cat = categorizeDomain(domain);
if (currentCategory !== 'all' && cat !== currentCategory) continue;
if (q && (e.from || '').toLowerCase().indexOf(q) < 0 && (e.subject || '').toLowerCase().indexOf(q) < 0 && (e.to || '').toLowerCase().indexOf(q) < 0) continue;
result.push(e);
}
return result;
}
function applyFilter() {
var filtered = getFilteredEmails();
renderEmailList(filtered);
}
function filterEmails(q) {
searchQuery = q;
applyFilter();
}
var selectedIds = {};
function toggleSelect(id) {
if (selectedIds[id]) delete selectedIds[id];
else selectedIds[id] = true;
document.getElementById('batchDelBtn').disabled = Object.keys(selectedIds).length === 0;
updateSelectAllCb();
}
function toggleSelectAllPage() {
var cb = document.getElementById('selectAllCb');
var filtered = getFilteredEmails();
if (cb.checked) {
for (var i = 0; i < filtered.length; i++) {
selectedIds[filtered[i].id] = true;
}
} else {
for (var i = 0; i < filtered.length; i++) {
delete selectedIds[filtered[i].id];
}
}
renderEmailList(filtered);
document.getElementById('batchDelBtn').disabled = Object.keys(selectedIds).length === 0;
}
function toggleSelectAllEmails() {
var cb = document.getElementById('selectAllEmailsCb');
if (cb.checked) {
toast('正在加载所有邮件...');
loadAllEmails(function() {
var all = getFilteredEmails();
for (var i = 0; i < all.length; i++) selectedIds[all[i].id] = true;
document.getElementById('selectAllCb').checked = true;
render();
document.getElementById('batchDelBtn').disabled = false;
toast('已全选 ' + Object.keys(selectedIds).length + ' 封邮件');
});
} else {
selectedIds = {};
document.getElementById('selectAllCb').checked = false;
render();
document.getElementById('batchDelBtn').disabled = true;
}
}
function loadAllEmails(callback) {
if (!hasMore) { callback(); return; }
var origOffset = currentOffset;
var loadNext = function() {
if (!hasMore) { callback(); return; }
var url = '/api/emails?limit=100&offset=' + currentOffset;
fetch(url).then(function(r) { return r.json(); }).then(function(data) {
allEmails = allEmails.concat(data.emails);
currentOffset = allEmails.length;
hasMore = data.hasMore;
document.getElementById('email-count').textContent = '共 ' + data.total + ' 封邮件,已加载 ' + allEmails.length + ' 封';
if (hasMore) { setTimeout(loadNext, 200); }
else { callback(); }
}).catch(function() { callback(); });
};
loadNext();
}
function updateSelectAllCb() {
var cb = document.getElementById('selectAllCb');
if (!cb) return;
var filtered = getFilteredEmails();
if (filtered.length === 0) { cb.checked = false; cb.indeterminate = false; return; }
var allChecked = filtered.every(function(e) { return selectedIds[e.id]; });
var someChecked = filtered.some(function(e) { return selectedIds[e.id]; });
cb.checked = allChecked;
cb.indeterminate = someChecked && !allChecked;
}
async function deleteSingle(id) {
if (!confirm('确定要删除这封邮件吗?')) return;
try {
var res = await fetch('/api/delete/' + id, { method: 'DELETE' });
if (!res.ok) throw new Error('HTTP ' + res.status);
delete selectedIds[id];
toast('已删除');
doRefresh();
} catch(e) {
toast('删除失败: ' + e.message);
}
}
async function deleteSelected() {
var ids = Object.keys(selectedIds);
if (ids.length === 0) return;
if (!confirm('确定要删除选中的 ' + ids.length + ' 封邮件吗?')) return;
try {
var res = await fetch('/api/batch-delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: ids }) });
if (!res.ok) throw new Error('HTTP ' + res.status);
toast('已删除 ' + ids.length + ' 封邮件');
selectedIds = {};
doRefresh();
} catch(e) {
toast('批量删除失败: ' + e.message);
}
}
function render() {
renderCategories();
applyFilter();
}
function renderEmailList(emails) {
var container = document.getElementById('email-list');
if (emails.length === 0) { container.innerHTML = '<div class="loading">暂无邮件</div>'; updateSelectAllCb(); return; }
var html = '';
for (var i = 0; i < emails.length; i++) {
var e = emails[i];
var checked = selectedIds[e.id] ? ' checked' : '';
html += '<div class="email-item">';
html += '<div class="cb"><input type="checkbox"' + checked + ' onclick="event.stopPropagation(); toggleSelect(\'' + e.id + '\')"></div>';
html += '<div class="info" onclick="showDetail(\'' + e.id + '\')">';
html += '<div class="from">' + escapeHtml(e.from) + '</div>';
html += '<div class="subject">' + escapeHtml(decodeMimeWord(e.subject)) + '</div>';
html += '<div class="to">收件: ' + escapeHtml(e.to) + '</div>';
html += '</div>';
html += '<div class="time">' + formatTime(e.time) + '</div>';
html += '<button class="del-btn" onclick="event.stopPropagation(); deleteSingle(\'' + e.id + '\')" title="删除">×</button>';
html += '</div>';
}
if (hasMore) {
html += '<div class="load-more"><button onclick="doLoadMore()">加载更多</button></div>';
}
container.innerHTML = html;
updateSelectAllCb();
}
function decodeMimeWord(str) {
if (!str) return '';
return str.replace(/=?([^?]+)?([BbQq])?([^?]*)?=/g, function(_, charset, encoding, text) {
try {
if (encoding.toUpperCase() === 'B') {
var bytes = atob(text).split('').map(function(c) { return c.charCodeAt(0); });
return new TextDecoder(charset).decode(new Uint8Array(bytes));
} else if (encoding.toUpperCase() === 'Q') {
var decoded = text.replace(/_/g, ' ');
var bytes = []; var i = 0;
while (i < decoded.length) {
if (decoded[i] === '=' && i + 2 < decoded.length) {
bytes.push(parseInt(decoded.substring(i+1, i+3), 16));
i += 3;
} else { bytes.push(decoded.charCodeAt(i)); i++; }
}
return new TextDecoder(charset).decode(new Uint8Array(bytes));
}
} catch(e) {}
return str;
});
}
function formatTime(ts) {
var d = new Date(ts);
var pad = function(n) { return String(n).padStart(2, '0'); };
return d.getFullYear() + '/' + pad(d.getMonth()+1) + '/' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
}
async function showDetail(id) {
try {
var res = await fetch('/api/email/' + id);
if (!res.ok) throw new Error('HTTP ' + res.status);
var email = await res.json();
currentEmailId = id;
var html = '<button class="back-btn" onclick="back()">返回列表</button>';
html += '<div class="detail-header">';
html += '<h3>' + escapeHtml(email.subject) + '</h3>';
html += '<p><strong>发件人:</strong> ' + escapeHtml(email.from) + '</p>';
html += '<p><strong>收件人:</strong> ' + escapeHtml(email.to) + '</p>';
html += '<p><strong>时间:</strong> ' + formatTime(email.time) + '</p>';
html += '</div>';
html += '<div class="detail-actions">';
html += '<button onclick="showRawContent()">查看原始内容</button>';
html += '<button onclick="downloadRaw()">下载 .eml</button>';
html += '<button class="danger" onclick="deleteFromDetail()">删除</button>';
html += '</div>';
var bodyContent = email.body || '<p>无内容</p>';
html += '<div class="detail-body"><iframe id="email-iframe" sandbox="allow-same-origin allow-scripts allow-popups"></iframe></div>';
html += '<div id="raw-content" class="detail-raw"></div>';
document.getElementById('email-detail').innerHTML = html;
document.getElementById('email-detail').classList.add('active');
document.getElementById('email-list').style.display = 'none';
document.getElementById('email-count').style.display = 'none';
document.querySelector('.search-box').style.display = 'none';
document.getElementById('categories').style.display = 'none';
document.querySelector('.email-toolbar').style.display = 'none';
var iframe = document.getElementById('email-iframe');
iframe.srcdoc = bodyContent;
iframe.onload = function() {
try { iframe.style.height = Math.max(500, iframe.contentDocument.documentElement.scrollHeight + 50) + 'px'; } catch(e) {}
};
} catch(e) {
toast('加载邮件失败: ' + e.message);
}
}
async function showRawContent() {
var rawDiv = document.getElementById('raw-content');
if (rawDiv.style.display === 'block') { rawDiv.style.display = 'none'; return; }
rawDiv.style.display = 'block';
rawDiv.textContent = '加载中...';
try {
var res = await fetch('/api/raw/' + currentEmailId);
if (!res.ok) throw new Error('HTTP ' + res.status);
rawDiv.textContent = await res.text();
} catch(e) { rawDiv.textContent = '加载失败: ' + e.message; }
}
function downloadRaw() { window.open('/api/raw/' + currentEmailId, '_blank'); }
async function deleteFromDetail() {
if (!confirm('确定要删除这封邮件吗?')) return;
try {
var res = await fetch('/api/delete/' + currentEmailId, { method: 'DELETE' });
if (!res.ok) throw new Error('HTTP ' + res.status);
toast('已删除');
back();
doRefresh();
} catch(e) { toast('删除失败: ' + e.message); }
}
function back() {
document.getElementById('email-detail').classList.remove('active');
document.getElementById('email-detail').innerHTML = '';
document.getElementById('email-list').style.display = 'block';
document.getElementById('email-count').style.display = 'block';
document.querySelector('.search-box').style.display = 'block';
document.getElementById('categories').style.display = 'flex';
document.querySelector('.email-toolbar').style.display = 'flex';
renderCategories();
}
loadEmails(false);
</script>
</body>
</html>