Skip to main content
Glama

MediaCrawler MCP Server

by mcp-service
admin.js29.5 kB
// Admin Interface JavaScript // 全局配置 const API_BASE = '/admin/api'; const PLATFORM_LABELS = { bili: '哔哩哔哩', xhs: '小红书', dy: '抖音', ks: '快手', wb: '微博', tieba: '贴吧', zhihu: '知乎' }; let platformNames = {}; let availablePlatformCodes = []; let loginSessionsCache = []; const optimisticLoggedPlatforms = new Set(); // 工具函数 function showMessage(message, type = 'info') { const messageDiv = document.getElementById('message'); if (!messageDiv) { return; } messageDiv.className = `toast status status-${type}`; messageDiv.textContent = message; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } function showError(message) { showMessage(message, 'error'); } function showSuccess(message) { showMessage(message, 'success'); } function showWarning(message) { showMessage(message, 'warning'); } function renderPlatformSessions(sessions = []) { const container = document.getElementById('platform-sessions'); if (!container) { return; } if (!sessions.length) { container.innerHTML = '<div class="platform-empty">暂无登录会话</div>'; return; } const groups = [ { title: '已登录', filter: (session) => session.is_logged_in, statusLabel: '在线', actionLabel: '退出', actionClass: 'platform-action platform-action--danger', actionHandler: (platform) => `logoutPlatform('${platform}')` }, { title: '未登录', filter: (session) => !session.is_logged_in, statusLabel: '离线', actionLabel: '登录', actionClass: 'platform-action platform-action--highlight', actionHandler: (platform) => `quickLogin('${platform}')` } ]; container.innerHTML = groups.map((group) => { const groupSessions = sessions.filter(group.filter); const groupContent = groupSessions.length ? groupSessions.map((session) => { const displayName = session.platform_name || getPlatformDisplayName(session.platform) || session.platform; const lastLogin = session.last_login || (session.is_logged_in ? '最近登录' : '从未登录'); return ` <div class="platform-chip ${session.is_logged_in ? 'is-active' : ''}"> <div class="platform-chip__details"> <span class="platform-chip__name">${displayName}</span> <span class="platform-chip__meta">${lastLogin}</span> </div> <div class="platform-chip__actions"> <span class="platform-chip__status">${group.statusLabel}</span> <button class="${group.actionClass}" onclick="${group.actionHandler(session.platform)}"> ${group.actionLabel} </button> </div> </div> `; }).join('') : '<div class="platform-group__empty">暂无平台</div>'; return ` <section class="platform-group"> <header class="platform-group__header"> <span class="platform-group__title">${group.title}</span> <span class="platform-group__count">${groupSessions.length}</span> </header> <div class="platform-group__list"> ${groupContent} </div> </section> `; }).join(''); } // HTTP 请求封装 async function apiRequest(endpoint, options = {}) { const url = `${API_BASE}${endpoint}`; const defaultOptions = { headers: { 'Content-Type': 'application/json' } }; try { const response = await fetch(url, { ...defaultOptions, ...options }); if (!response.ok) { let detailMessage = `HTTP ${response.status}: ${response.statusText}`; try { const errorBody = await response.json(); if (errorBody && errorBody.detail) { detailMessage = typeof errorBody.detail === 'string' ? errorBody.detail : JSON.stringify(errorBody.detail); } } catch (parseError) { // ignore parse error, fall back to default message } throw new Error(detailMessage); } const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return await response.json(); } return await response.text(); } catch (error) { console.error('API 请求失败:', error); throw error; } } function getPlatformDisplayName(code) { if (!code) { return ''; } return platformNames[code] || PLATFORM_LABELS[code] || code; } function populatePlatformSelectOptions(codes = []) { const select = document.getElementById('platform'); if (!select) { return; } const currentValue = select.value; const options = ['<option value=\"\">请选择平台</option>']; codes.forEach((code) => { options.push(`<option value="${code}">${getPlatformDisplayName(code)}</option>`); }); select.innerHTML = options.join(''); if (currentValue && codes.includes(currentValue)) { select.value = currentValue; } const tip = document.getElementById('platform-tip'); if (tip) { tip.textContent = codes.length ? '请选择需要登录的平台' : '当前没有可登录的平台'; } } async function loadPlatforms() { try { const platforms = await apiRequest('/login/platforms'); availablePlatformCodes = Array.isArray(platforms) ? platforms : []; populatePlatformSelectOptions(availablePlatformCodes); } catch (error) { console.error('加载平台列表失败:', error); const tip = document.getElementById('platform-tip'); if (tip) { tip.textContent = '平台列表加载失败,请稍后重试'; } } } class LoginManager { constructor() { this.currentSession = null; this.currentSessionPlatform = null; this.pollInterval = null; this.pollTimeout = null; this.pollStartTime = null; this.maxPollTime = 5 * 60 * 1000; // 5分钟超时 this.qrCodeDisplayed = false; this.qrCountdownInterval = null; this.qrExpirySeconds = 180; this.autoRefreshTimer = null; } async startLogin(platform, loginType, phone = '', cookie = '') { if (!platform) { showWarning('请先选择需要登录的平台'); return; } this.stopPolling(); this.resetQRCode(); this.setLoading(true); this.currentSession = null; this.currentSessionPlatform = null; try { if (loginType === 'qrcode') { this.toggleRefreshQrButton(true); } else { this.toggleRefreshQrButton(false); } showMessage('正在启动登录流程...', 'info'); const response = await apiRequest('/login/start', { method: 'POST', body: JSON.stringify({ platform, login_type: loginType, phone, cookie }) }); this.currentSession = response.session_id; this.currentSessionPlatform = platform; if (loginType === 'qrcode') { this.startPolling(); this.displayQRCode(response); this.toggleRefreshQrButton(true); } else { await this.checkStatus(); } } catch (error) { showError(`启动登录失败: ${error.message}`); this.toggleRefreshQrButton(false); } finally { this.setLoading(false); } } setLoading(isLoading) { const submitBtn = document.getElementById('login-submit-btn'); if (!submitBtn) { return; } submitBtn.dataset.loading = isLoading ? 'true' : 'false'; if (!submitBtn.dataset.defaultLabel) { submitBtn.dataset.defaultLabel = submitBtn.textContent || '开始登录'; } if (isLoading) { submitBtn.disabled = true; submitBtn.textContent = '处理中...'; submitBtn.classList.remove('btn-primary'); submitBtn.classList.add('btn-secondary'); } else { submitBtn.disabled = false; submitBtn.textContent = submitBtn.dataset.defaultLabel; submitBtn.classList.remove('btn-secondary'); submitBtn.classList.add('btn-primary'); } } setSubmitButtonLoggedInState(platform) { if (!platform) { return; } const platformSelect = document.getElementById('platform'); const submitBtn = document.getElementById('login-submit-btn'); if (!submitBtn) { return; } if (!submitBtn.dataset.defaultLabel) { submitBtn.dataset.defaultLabel = submitBtn.textContent || '开始登录'; } if (platformSelect && platformSelect.value && platformSelect.value !== platform) { return; } submitBtn.disabled = true; submitBtn.textContent = '已登录'; submitBtn.classList.remove('btn-primary'); submitBtn.classList.add('btn-secondary'); } markPlatformSessionLoggedIn(platform) { if (!platform) { return; } const displayName = getPlatformDisplayName(platform) || PLATFORM_LABELS[platform] || platform; platformNames[platform] = displayName; optimisticLoggedPlatforms.add(platform); let hasExisting = false; loginSessionsCache = loginSessionsCache.map((session) => { if (session.platform === platform) { hasExisting = true; return { ...session, platform, platform_name: displayName, is_logged_in: true, last_login: session.last_login || new Date().toLocaleString() }; } return session; }); if (!hasExisting) { loginSessionsCache.push({ platform, platform_name: displayName, is_logged_in: true, last_login: new Date().toLocaleString() }); } renderPlatformSessions(loginSessionsCache); updateLoginButtonState(loginSessionsCache); } toggleRefreshQrButton(visible) { const refreshBtn = document.getElementById('refresh-qr-btn'); if (!refreshBtn) { return; } if (visible) { refreshBtn.classList.remove('d-none'); } else { refreshBtn.classList.add('d-none'); } } resetQRCode() { this.clearQrCountdown(); const qrContainer = document.getElementById('qr-code-container'); const qrWrapper = document.getElementById('qr-image-wrapper'); const countdown = document.getElementById('qr-countdown'); if (qrWrapper) { qrWrapper.innerHTML = ''; } if (countdown) { countdown.textContent = ''; } if (qrContainer) { qrContainer.classList.add('d-none'); } this.qrCodeDisplayed = false; } displayQRCode(response) { if (!response.qr_code_base64) { return; } const qrContainer = document.getElementById('qr-code-container'); if (!qrContainer) { return; } let wrapper = document.getElementById('qr-image-wrapper'); if (!wrapper) { wrapper = document.createElement('div'); wrapper.id = 'qr-image-wrapper'; qrContainer.appendChild(wrapper); } wrapper.innerHTML = ` <div class="text-center mb-3"> <h4>请扫描二维码登录</h4> </div> <div class="text-center"> <img src="data:image/png;base64,${response.qr_code_base64}" class="qr-code-img" alt="登录二维码" style="max-width: 300px; border: 2px solid #ddd; padding: 10px; border-radius: 8px;"> </div> <div class="text-center mt-3"> <p class="status status-info">请使用 ${getPlatformDisplayName(response.platform)} 客户端扫描上方二维码</p> </div> `; qrContainer.classList.remove('d-none'); this.qrCodeDisplayed = true; this.startQrCountdown(response.qrcode_timestamp); } startQrCountdown(qrTimestamp) { const countdown = document.getElementById('qr-countdown'); if (!countdown) { return; } const timestampMs = qrTimestamp ? Number(qrTimestamp) * 1000 : Date.now(); const expireMs = timestampMs + this.qrExpirySeconds * 1000; this.clearQrCountdown(); const updateCountdown = () => { const remaining = Math.floor((expireMs - Date.now()) / 1000); if (remaining <= 0) { countdown.textContent = '二维码已过期,请点击刷新二维码重新获取'; this.clearQrCountdown(); this.toggleRefreshQrButton(true); return; } countdown.textContent = `二维码将在 ${remaining} 秒后过期`; }; updateCountdown(); this.qrCountdownInterval = setInterval(updateCountdown, 1000); } clearQrCountdown() { if (this.qrCountdownInterval) { clearInterval(this.qrCountdownInterval); this.qrCountdownInterval = null; } } startPolling() { this.stopPolling(); this.pollStartTime = Date.now(); this.pollInterval = setInterval(() => { if (this.pollStartTime && Date.now() - this.pollStartTime > this.maxPollTime) { showWarning('登录超时(5分钟),请重新尝试'); this.stopPolling(); this.toggleRefreshQrButton(true); return; } this.checkStatus(); }, 2000); this.pollTimeout = setTimeout(() => { showWarning('登录超时(5分钟),请重新尝试'); this.stopPolling(); this.toggleRefreshQrButton(true); }, this.maxPollTime); } stopPolling() { if (this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; } if (this.pollTimeout) { clearTimeout(this.pollTimeout); this.pollTimeout = null; } this.pollStartTime = null; } async checkStatus() { if (!this.currentSession) { return; } try { const response = await apiRequest(`/login/session/${this.currentSession}`); this.renderStatus(response); if (!this.qrCodeDisplayed && response.qr_code_base64) { this.displayQRCode(response); } if (response.status === 'success') { const platform = this.currentSessionPlatform; showSuccess('登录成功!'); this.stopPolling(); this.resetQRCode(); this.toggleRefreshQrButton(false); this.setSubmitButtonLoggedInState(platform); this.markPlatformSessionLoggedIn(platform); this.currentSession = null; this.currentSessionPlatform = null; await refreshLoginStatus(false); } else if (response.status === 'failed') { showError(response.message || '登录失败,请重试'); this.stopPolling(); this.clearQrCountdown(); this.toggleRefreshQrButton(true); } else if (response.status === 'expired') { showWarning(response.message || '二维码已过期,请重新获取'); this.stopPolling(); this.clearQrCountdown(); this.toggleRefreshQrButton(true); } else if (response.status === 'waiting' || response.status === 'processing') { if (response.qrcode_timestamp) { this.startQrCountdown(response.qrcode_timestamp); } } } catch (error) { console.error('检查登录状态失败:', error); } } renderStatus(response) { const statusElement = document.getElementById('login-status'); if (!statusElement) { return; } statusElement.textContent = response.message || '检查中...'; statusElement.classList.remove('status-info', 'status-success', 'status-error', 'status-warning'); let statusClass = 'status-info'; if (response.status === 'success') { statusClass = 'status-success'; } else if (response.status === 'failed') { statusClass = 'status-error'; } else if (response.status === 'expired') { statusClass = 'status-warning'; } statusElement.classList.add(statusClass); } } async function refreshLoginStatus(silent = false) { if (!silent) { showMessage('正在刷新登录状态...', 'info'); } try { const response = await apiRequest('/login/sessions'); let sessions = Array.isArray(response) ? response : []; sessions.forEach((session) => { if (session.platform && session.platform_name) { platformNames[session.platform] = session.platform_name; } }); sessions = sessions.map((session) => { if (!session.platform) { return session; } if (optimisticLoggedPlatforms.has(session.platform) && !session.is_logged_in) { return { ...session, is_logged_in: true, last_login: session.last_login || new Date().toLocaleString() }; } if (session.is_logged_in) { optimisticLoggedPlatforms.delete(session.platform); } return session; }); loginSessionsCache = sessions; const platformCodes = sessions.map((session) => session.platform).filter(Boolean); if (!availablePlatformCodes.length && platformCodes.length) { availablePlatformCodes = platformCodes; } populatePlatformSelectOptions(availablePlatformCodes.length ? availablePlatformCodes : platformCodes); renderPlatformSessions(loginSessionsCache); updateLoginButtonState(loginSessionsCache); if (!silent) { showSuccess('登录状态已刷新'); } } catch (error) { if (!silent) { showError('刷新失败: ' + error.message); } else { console.error('刷新失败:', error); } } } function updateLoginButtonState(sessions) { const platformSelect = document.getElementById('platform'); const submitBtn = document.getElementById('login-submit-btn'); if (!platformSelect || !submitBtn) { return; } if (!submitBtn.dataset.defaultLabel) { submitBtn.dataset.defaultLabel = submitBtn.textContent || '开始登录'; } if (submitBtn.dataset.loading === 'true') { return; } const selectedPlatform = platformSelect.value; if (!selectedPlatform) { submitBtn.disabled = true; submitBtn.textContent = submitBtn.dataset.defaultLabel; submitBtn.classList.remove('btn-secondary'); submitBtn.classList.add('btn-primary'); return; } const platformSession = sessions.find((session) => session.platform === selectedPlatform); if (platformSession && platformSession.is_logged_in) { submitBtn.disabled = true; submitBtn.textContent = '已登录'; submitBtn.classList.remove('btn-primary'); submitBtn.classList.add('btn-secondary'); } else { submitBtn.disabled = false; submitBtn.textContent = submitBtn.dataset.defaultLabel; submitBtn.classList.remove('btn-secondary'); submitBtn.classList.add('btn-primary'); } } async function logoutPlatform(platform) { if (!platform) { return; } const displayName = getPlatformDisplayName(platform); if (!confirm(`确定要退出 ${displayName} 的登录吗?`)) { return; } try { showMessage('正在退出登录...', 'info'); await apiRequest(`/login/logout/${platform}`, { method: 'POST' }); showSuccess('退出登录成功'); loginManager.resetQRCode(); optimisticLoggedPlatforms.delete(platform); await refreshLoginStatus(false); } catch (error) { showError('退出登录失败: ' + error.message); } } function quickLogin(platform) { const platformSelect = document.getElementById('platform'); if (platformSelect) { platformSelect.value = platform; platformSelect.dispatchEvent(new Event('change')); } const loginTypeSelect = document.getElementById('login_type'); if (loginTypeSelect) { loginTypeSelect.value = 'qrcode'; } toggleLoginFields(); const form = document.getElementById('login-form'); if (form) { form.scrollIntoView({ behavior: 'smooth' }); } } function toggleLoginFields() { const loginTypeSelect = document.getElementById('login_type'); const loginType = loginTypeSelect ? loginTypeSelect.value : 'qrcode'; const phoneField = document.getElementById('phone-field'); const cookieField = document.getElementById('cookie-field'); if (phoneField) { phoneField.classList.toggle('d-none', loginType !== 'phone'); } if (cookieField) { cookieField.classList.toggle('d-none', loginType !== 'cookie'); } if (loginType !== 'qrcode') { loginManager.toggleRefreshQrButton(false); } else if (loginManager.qrCodeDisplayed) { loginManager.toggleRefreshQrButton(true); } } // 配置管理 class ConfigManager { async loadConfig() { try { const config = await apiRequest('/config/current'); this.displayConfig(config); } catch (error) { showError(`加载配置失败: ${error.message}`); } } displayConfig(config) { const configContainer = document.getElementById('config-display'); if (configContainer) { configContainer.innerHTML = `<pre>${JSON.stringify(config, null, 2)}</pre>`; } } async saveConfig(configData) { try { showMessage('正在保存配置...', 'info'); await apiRequest('/config/update', { method: 'POST', body: JSON.stringify(configData) }); showSuccess('配置保存成功'); } catch (error) { showError(`保存配置失败: ${error.message}`); } } } // 状态监控 class StatusMonitor { constructor() { this.monitorInterval = null; } startMonitoring() { this.updateStatus(); this.monitorInterval = setInterval(() => { this.updateStatus(); }, 5000); } stopMonitoring() { if (this.monitorInterval) { clearInterval(this.monitorInterval); this.monitorInterval = null; } } async updateStatus() { try { const status = await apiRequest('/status/summary'); this.displayStatus(status); } catch (error) { console.error('获取状态失败:', error); } } displayStatus(status) { const statusContainer = document.getElementById('status-display'); if (statusContainer) { statusContainer.innerHTML = this.formatStatus(status); } } formatStatus(status) { const serviceStatus = status.service_healthy ? '运行正常' : '服务异常'; const serviceClass = status.service_healthy ? 'status-success' : 'status-error'; const activeConnections = status.active_connections || 0; const pendingTasks = status.pending_tasks ?? '—'; return ` <div class="status-strip"> <div class="status-strip__item"> <span class="status-strip__label">服务状态</span> <span class="status ${serviceClass}">${serviceStatus}</span> </div> <div class="status-strip__item"> <span class="status-strip__label">活跃连接</span> <span class="status-strip__value">${activeConnections}</span> </div> <div class="status-strip__item"> <span class="status-strip__label">排队任务</span> <span class="status-strip__value">${pendingTasks}</span> </div> </div> `; } } // 全局实例 const loginManager = new LoginManager(); const configManager = new ConfigManager(); const statusMonitor = new StatusMonitor(); // 页面初始化 function initLoginPage() { const loginForm = document.getElementById('login-form'); const platformSelect = document.getElementById('platform'); const loginTypeSelect = document.getElementById('login_type'); const submitBtn = document.getElementById('login-submit-btn'); const refreshQrBtn = document.getElementById('refresh-qr-btn'); if (submitBtn && !submitBtn.dataset.defaultLabel) { submitBtn.dataset.defaultLabel = submitBtn.textContent || '开始登录'; } toggleLoginFields(); loadPlatforms().then(() => { refreshLoginStatus(false); }); if (loginForm) { loginForm.addEventListener('submit', async (event) => { event.preventDefault(); const formData = new FormData(loginForm); const platform = formData.get('platform'); const loginType = formData.get('login_type'); const phone = formData.get('phone') || ''; const cookie = formData.get('cookie') || ''; await loginManager.startLogin(platform, loginType, phone, cookie); }); } if (platformSelect) { platformSelect.addEventListener('change', () => { updateLoginButtonState(loginSessionsCache); }); } if (loginTypeSelect) { loginTypeSelect.addEventListener('change', toggleLoginFields); } if (refreshQrBtn) { refreshQrBtn.addEventListener('click', () => { const currentPlatform = document.getElementById('platform')?.value; if (!currentPlatform) { showWarning('请选择需要登录的平台'); return; } const loginType = document.getElementById('login_type')?.value || 'qrcode'; const phone = document.getElementById('phone')?.value || ''; const cookie = document.getElementById('cookie')?.value || ''; loginManager.startLogin(currentPlatform, loginType, phone, cookie); }); } if (!loginManager.autoRefreshTimer) { loginManager.autoRefreshTimer = setInterval(() => { refreshLoginStatus(true); }, 30000); } } function initConfigPage() { configManager.loadConfig(); const configForm = document.getElementById('config-form'); if (configForm) { configForm.addEventListener('submit', async (event) => { event.preventDefault(); const formData = new FormData(configForm); const configData = Object.fromEntries(formData); await configManager.saveConfig(configData); }); } } function initStatusPage() { statusMonitor.startMonitoring(); } document.addEventListener('DOMContentLoaded', () => { if (document.getElementById('login-form')) { initLoginPage(); } if (document.getElementById('config-form')) { initConfigPage(); } if (document.getElementById('status-display')) { initStatusPage(); } }); window.addEventListener('beforeunload', () => { loginManager.stopPolling(); loginManager.clearQrCountdown(); if (loginManager.autoRefreshTimer) { clearInterval(loginManager.autoRefreshTimer); loginManager.autoRefreshTimer = null; } statusMonitor.stopMonitoring(); }); // 暴露给模板使用的全局函数 window.logoutPlatform = logoutPlatform; window.quickLogin = quickLogin; window.toggleLoginFields = toggleLoginFields; window.refreshLoginStatus = refreshLoginStatus;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mcp-service/media-crawler-mcp-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server