Spaces:
Sleeping
Sleeping
| // ==UserScript== | |
| // @name Gemini Business 2API Helper (JSON Format + Expiration) | |
| // @namespace https://linux.do/u/f-droid | |
| // @version 1.6 | |
| // @icon https://cdn.inviter.co/community/b5c3dc29-b7e3-49f9-a18d-819398ba4fe6.png | |
| // @description 提取配置,支持 JSON 格式,过期时间逻辑为:Cookie过期时间戳 - 12小时。 | |
| // @author Gemini Business | |
| // @match https://business.gemini.google/* | |
| // @grant GM_setClipboard | |
| // @grant GM_addStyle | |
| // @grant GM_cookie | |
| // @connect business.gemini.google | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const getFavicon = () => { | |
| const link = document.querySelector("link[rel*='icon']") || document.querySelector("link[rel='shortcut icon']"); | |
| return link ? link.href : 'https://cdn.inviter.co/community/b5c3dc29-b7e3-49f9-a18d-819398ba4fe6.png'; | |
| }; | |
| GM_addStyle(` | |
| :root { | |
| --gb-primary: #1a73e8; | |
| --gb-primary-hover: #1557b0; | |
| --gb-success: #1e8e3e; | |
| --gb-success-hover: #137333; | |
| --gb-surface: #ffffff; | |
| --gb-bg: #f8f9fa; | |
| --gb-text-main: #202124; | |
| --gb-text-sub: #5f6368; | |
| --gb-border: #dadce0; | |
| --gb-shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15); | |
| --gb-shadow-md: 0 4px 8px 3px rgba(60,64,67,0.15), 0 1px 3px rgba(60,64,67,0.3); | |
| --gb-shadow-lg: 0 8px 24px rgba(60,64,67,0.2); | |
| --gb-font: 'Google Sans', 'Roboto', Arial, sans-serif; | |
| --gb-mono: 'Roboto Mono', 'Menlo', monospace; | |
| --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| #gb-float-ball { | |
| position: fixed; | |
| bottom: 32px; | |
| right: 32px; | |
| width: 60px; | |
| height: 60px; | |
| background: white; | |
| border-radius: 50%; | |
| box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); | |
| cursor: pointer; | |
| z-index: 9998; | |
| border: 1px solid var(--gb-border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: var(--transition); | |
| transform: scale(1); | |
| } | |
| #gb-float-ball:hover { | |
| transform: scale(1.1) rotate(10deg); | |
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); | |
| } | |
| #gb-float-ball img { | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 8px; | |
| object-fit: contain; | |
| pointer-events: none; | |
| } | |
| #gb-float-ball::after { | |
| content: 'JSON'; | |
| position: absolute; | |
| bottom: -4px; | |
| right: -4px; | |
| font-size: 8px; | |
| font-weight: bold; | |
| background: var(--gb-success); | |
| color: white; | |
| padding: 2px 6px; | |
| border-radius: 8px; | |
| transform: rotate(-15deg); | |
| } | |
| #gb-overlay { | |
| position: fixed; inset: 0; | |
| background: rgba(32, 33, 36, 0.6); | |
| backdrop-filter: blur(3px); | |
| z-index: 9999; | |
| display: flex; align-items: center; justify-content: center; | |
| opacity: 0; pointer-events: none; | |
| transition: opacity 0.25s ease; | |
| } | |
| #gb-overlay.active { opacity: 1; pointer-events: auto; } | |
| #gb-panel { | |
| width: 550px; max-width: 90vw; | |
| background: var(--gb-surface); | |
| border-radius: 24px; | |
| box-shadow: var(--gb-shadow-lg); | |
| overflow: hidden; | |
| display: flex; flex-direction: column; | |
| transform: scale(0.92) translateY(20px); | |
| transition: transform 0.3s cubic-bezier(0.2, 0.0, 0, 1); | |
| font-family: var(--gb-font); | |
| } | |
| #gb-overlay.active #gb-panel { transform: scale(1) translateY(0); } | |
| .gb-header { | |
| background: linear-gradient(135deg, #4285f4 0%, #34a853 100%); | |
| padding: 24px 32px; | |
| color: white; | |
| } | |
| .gb-title { font-size: 22px; font-weight: 500; margin: 0; letter-spacing: 0.5px; } | |
| .gb-subtitle { font-size: 13px; opacity: 0.9; margin-top: 6px; font-weight: 400; } | |
| .gb-body { padding: 24px 32px 16px; background: var(--gb-surface); } | |
| .gb-label { | |
| font-size: 14px; color: var(--gb-text-sub); | |
| margin-bottom: 12px; font-weight: 500; | |
| display: flex; justify-content: space-between; align-items: center; | |
| } | |
| .gb-textarea-wrapper { | |
| position: relative; | |
| border: 1px solid var(--gb-border); | |
| border-radius: 12px; | |
| background: var(--gb-bg); | |
| transition: border-color 0.2s, background 0.2s; | |
| } | |
| .gb-textarea-wrapper.editing { | |
| background: white; | |
| border-color: var(--gb-primary); | |
| box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2); | |
| } | |
| .gb-textarea { | |
| width: 100%; height: 220px; | |
| border: none; background: transparent; | |
| padding: 16px; | |
| font-family: var(--gb-mono); | |
| font-size: 13px; line-height: 1.6; | |
| color: var(--gb-text-main); | |
| resize: none; outline: none; | |
| box-sizing: border-box; | |
| white-space: pre; | |
| } | |
| .gb-status { | |
| margin-top: 12px; font-size: 13px; | |
| display: flex; align-items: center; gap: 8px; | |
| color: var(--gb-text-sub); | |
| height: 20px; | |
| } | |
| .gb-dot { width: 8px; height: 8px; border-radius: 50%; background: #ccc; transition: background 0.3s; } | |
| .gb-dot.success { background: var(--gb-success); } | |
| .gb-dot.error { background: #ea4335; } | |
| .gb-footer { | |
| padding: 16px 32px 24px; | |
| display: flex; justify-content: flex-end; gap: 12px; | |
| border-top: 1px solid #f1f3f4; | |
| background: var(--gb-surface); | |
| } | |
| .gb-btn { | |
| border: none; outline: none; | |
| padding: 0 24px; height: 40px; | |
| border-radius: 20px; | |
| font-family: var(--gb-font); | |
| font-size: 14px; font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| display: flex; align-items: center; justify-content: center; | |
| } | |
| .gb-btn-text { | |
| background: transparent; color: var(--gb-text-sub); | |
| } | |
| .gb-btn-text:hover { background: rgba(0,0,0,0.05); color: var(--gb-text-main); } | |
| .gb-btn-primary { | |
| background: var(--gb-primary); color: white; | |
| box-shadow: var(--gb-shadow-sm); | |
| } | |
| .gb-btn-primary:hover { | |
| background: var(--gb-primary-hover); | |
| box-shadow: var(--gb-shadow-md); | |
| } | |
| .gb-btn-primary:active { transform: scale(0.98); } | |
| .gb-btn-success { | |
| background: var(--gb-success); color: white; | |
| } | |
| .gb-btn-success:hover { background: var(--gb-success-hover); } | |
| .gb-hidden { display: none !important; } | |
| `); | |
| // Helper: 格式化 Unix 时间戳 (秒) | |
| const formatTimestamp = (ts) => { | |
| if (!ts) return "Session (Browser Close)"; | |
| // ts 是秒,需要转毫秒 | |
| const date = new Date(ts * 1000); | |
| const y = date.getFullYear(); | |
| const m = String(date.getMonth() + 1).padStart(2, '0'); | |
| const d = String(date.getDate()).padStart(2, '0'); | |
| const h = String(date.getHours()).padStart(2, '0'); | |
| const min = String(date.getMinutes()).padStart(2, '0'); | |
| const s = String(date.getSeconds()).padStart(2, '0'); | |
| return `${y}-${m}-${d} ${h}:${min}:${s}`; | |
| }; | |
| const floatBall = document.createElement('div'); | |
| floatBall.id = 'gb-float-ball'; | |
| floatBall.title = "提取配置"; | |
| const ballIcon = document.createElement('img'); | |
| ballIcon.src = getFavicon(); | |
| floatBall.appendChild(ballIcon); | |
| document.body.appendChild(floatBall); | |
| const overlay = document.createElement('div'); | |
| overlay.id = 'gb-overlay'; | |
| const panel = document.createElement('div'); | |
| panel.id = 'gb-panel'; | |
| overlay.appendChild(panel); | |
| document.body.appendChild(overlay); | |
| const header = document.createElement('div'); | |
| header.className = 'gb-header'; | |
| const title = document.createElement('h2'); | |
| title.className = 'gb-title'; | |
| title.textContent = 'Gemini Business 2API Helper'; | |
| const subtitle = document.createElement('div'); | |
| subtitle.className = 'gb-subtitle'; | |
| subtitle.textContent = '配置提取与管理'; | |
| header.appendChild(title); | |
| header.appendChild(subtitle); | |
| panel.appendChild(header); | |
| const body = document.createElement('div'); | |
| body.className = 'gb-body'; | |
| const label = document.createElement('div'); | |
| label.className = 'gb-label'; | |
| label.textContent = '提取的配置 (JSON):'; | |
| body.appendChild(label); | |
| const textWrapper = document.createElement('div'); | |
| textWrapper.className = 'gb-textarea-wrapper'; | |
| const textarea = document.createElement('textarea'); | |
| textarea.className = 'gb-textarea'; | |
| textarea.readOnly = true; | |
| textarea.spellcheck = false; | |
| textWrapper.appendChild(textarea); | |
| body.appendChild(textWrapper); | |
| const statusDiv = document.createElement('div'); | |
| statusDiv.className = 'gb-status'; | |
| const statusDot = document.createElement('div'); | |
| statusDot.className = 'gb-dot'; | |
| const statusText = document.createElement('span'); | |
| statusText.textContent = '等待操作...'; | |
| statusDiv.appendChild(statusDot); | |
| statusDiv.appendChild(statusText); | |
| body.appendChild(statusDiv); | |
| panel.appendChild(body); | |
| const footer = document.createElement('div'); | |
| footer.className = 'gb-footer'; | |
| const btnClose = document.createElement('button'); | |
| btnClose.className = 'gb-btn gb-btn-text'; | |
| btnClose.textContent = '关闭'; | |
| const btnEdit = document.createElement('button'); | |
| btnEdit.className = 'gb-btn gb-btn-text'; | |
| btnEdit.textContent = '编辑'; | |
| const btnSave = document.createElement('button'); | |
| btnSave.className = 'gb-btn gb-btn-success gb-hidden'; | |
| btnSave.textContent = '保存修改'; | |
| const btnCopy = document.createElement('button'); | |
| btnCopy.className = 'gb-btn gb-btn-primary'; | |
| btnCopy.textContent = '复制配置'; | |
| footer.appendChild(btnClose); | |
| footer.appendChild(btnEdit); | |
| footer.appendChild(btnSave); | |
| footer.appendChild(btnCopy); | |
| panel.appendChild(footer); | |
| let isEditing = false; | |
| let extractedData = ""; | |
| const setStatus = (type, msg) => { | |
| statusDot.className = 'gb-dot ' + (type === 'success' ? 'success' : type === 'error' ? 'error' : ''); | |
| statusText.textContent = msg; | |
| }; | |
| const toggleEditMode = () => { | |
| isEditing = !isEditing; | |
| textarea.readOnly = !isEditing; | |
| if (isEditing) { | |
| textWrapper.classList.add('editing'); | |
| btnEdit.classList.add('gb-hidden'); | |
| btnCopy.classList.add('gb-hidden'); | |
| btnSave.classList.remove('gb-hidden'); | |
| setStatus('normal', '正在编辑...'); | |
| textarea.focus(); | |
| } else { | |
| textWrapper.classList.remove('editing'); | |
| btnEdit.classList.remove('gb-hidden'); | |
| btnCopy.classList.remove('gb-hidden'); | |
| btnSave.classList.add('gb-hidden'); | |
| } | |
| }; | |
| const saveContent = () => { | |
| extractedData = textarea.value; | |
| toggleEditMode(); | |
| setStatus('success', '修改已保存'); | |
| }; | |
| const openPanel = () => { | |
| overlay.classList.add('active'); | |
| textarea.value = "正在读取环境数据..."; | |
| textarea.style.color = "#9aa0a6"; | |
| btnCopy.disabled = true; | |
| btnEdit.style.display = 'none'; | |
| setStatus('normal', '分析中...'); | |
| const pathParts = window.location.pathname.split('/'); | |
| const cidIndex = pathParts.indexOf('cid'); | |
| const config_id = (cidIndex !== -1 && pathParts.length > cidIndex + 1) ? pathParts[cidIndex + 1] : null; | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const csesidx = urlParams.get('csesidx'); | |
| GM_cookie('list', {}, (cookies, error) => { | |
| if (error) { | |
| textarea.value = "错误:无法读取 Cookie。\n请检查 Tampermonkey 权限。"; | |
| textarea.style.color = "#ea4335"; | |
| setStatus('error', '读取失败'); | |
| return; | |
| } | |
| const host_c_oses_raw = (cookies.find(c => c.name === '__Host-C_OSES' && c.domain === window.location.hostname) || {}).value; | |
| const host_c_oses_formatted = host_c_oses_raw ? `"${host_c_oses_raw}"` : "null"; | |
| // 获取 SES Cookie 对象 | |
| const sesCookieObj = cookies.find(c => c.name === '__Secure-C_SES') || {}; | |
| const secure_c_ses = sesCookieObj.value || null; | |
| // 修正过期时间逻辑:Cookie 过期时间戳 - 12小时 (12 * 60 * 60 = 43200秒) | |
| let expireTime = "Session (Unknown)"; | |
| if (sesCookieObj.expirationDate) { | |
| // expirationDate 是秒 | |
| const adjustedTimestamp = sesCookieObj.expirationDate - 43200; | |
| expireTime = formatTimestamp(adjustedTimestamp); | |
| } else { | |
| // 如果没有过期时间(浏览器会话结束),显示 Session | |
| expireTime = "Session (Browser Close)"; | |
| } | |
| if (!config_id || !csesidx || !secure_c_ses) { | |
| textarea.value = "⚠️ 数据不完整。\n请确保您在 Gemini Business 聊天界面,且 URL 包含 /cid/ 和 ?csesidx="; | |
| textarea.style.color = "#f9ab00"; | |
| setStatus('error', '数据缺失'); | |
| return; | |
| } | |
| extractedData = `"csesidx": "${csesidx}", | |
| "config_id": "${config_id}", | |
| "secure_c_ses": "${secure_c_ses}", | |
| "host_c_oses": ${host_c_oses_formatted}, | |
| "expires_at": "${expireTime}"`; | |
| textarea.value = extractedData; | |
| textarea.style.color = "var(--gb-text-main)"; | |
| btnCopy.disabled = false; | |
| btnEdit.style.display = 'block'; | |
| setStatus('success', '提取成功'); | |
| }); | |
| }; | |
| const closePanel = () => { | |
| overlay.classList.remove('active'); | |
| if (isEditing) { | |
| textarea.value = extractedData; | |
| toggleEditMode(); | |
| } | |
| }; | |
| const copyToClipboard = () => { | |
| if (!textarea.value) return; | |
| GM_setClipboard(textarea.value); | |
| const originalText = btnCopy.textContent; | |
| btnCopy.textContent = "已复制"; | |
| btnCopy.classList.remove('gb-btn-primary'); | |
| btnCopy.classList.add('gb-btn-success'); | |
| setTimeout(() => { | |
| btnCopy.textContent = originalText; | |
| btnCopy.classList.remove('gb-btn-success'); | |
| btnCopy.classList.add('gb-btn-primary'); | |
| closePanel(); | |
| }, 1200); | |
| }; | |
| floatBall.addEventListener('click', openPanel); | |
| btnClose.addEventListener('click', closePanel); | |
| btnCopy.addEventListener('click', copyToClipboard); | |
| btnEdit.addEventListener('click', toggleEditMode); | |
| btnSave.addEventListener('click', saveContent); | |
| overlay.addEventListener('click', (e) => { | |
| if (e.target === overlay) closePanel(); | |
| }); | |
| })(); |