diff --git a/assets/cookie_grabber/background.js b/assets/cookie_grabber/background.js deleted file mode 100644 index cdff0aa..0000000 --- a/assets/cookie_grabber/background.js +++ /dev/null @@ -1,24 +0,0 @@ -// background.js - 保留原有事件 + 新增消息监听 -chrome.runtime.onInstalled.addListener(() => { - console.log('Cookie Grabber installed'); -}); - -// content script 请求cookie时的处理 -chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { - if (msg.type === 'getCookies' && sender.tab) { - const url = sender.tab.url; - // 普通cookie + partitioned cookie 双查合并 - chrome.cookies.getAll({url}, cookies => { - console.log('[CookieGrabber] normal cookies:', cookies.map(c => c.name)); - chrome.cookies.getAll({url, partitionKey: {topLevelSite: url.match(/^https?:\/\/[^/]+/)[0]}}, pCookies => { - console.log('[CookieGrabber] partitioned cookies:', pCookies.map(c => c.name)); - const map = {}; - cookies.forEach(c => map[c.name] = c.value); - pCookies.forEach(c => map[c.name] = c.value); - const str = Object.entries(map).map(([k,v]) => k + '=' + v).join('; '); - sendResponse({cookies: str}); - }); - }); - return true; // 异步响应 - } -}); \ No newline at end of file diff --git a/assets/cookie_grabber/content.js b/assets/cookie_grabber/content.js deleted file mode 100644 index 64ad502..0000000 --- a/assets/cookie_grabber/content.js +++ /dev/null @@ -1,18 +0,0 @@ -// content.js - MutationObserver 监听触发元素 -const TRIGGER_ID = '__ljqcg__'; - -const obs = new MutationObserver(muts => { - for (const m of muts) { - for (const node of m.addedNodes) { - if (node.nodeType === 1 && node.id === TRIGGER_ID) { - chrome.runtime.sendMessage({type: 'getCookies'}, res => { - if (res && res.cookies) node.textContent = res.cookies; - else node.textContent = '__cg_error__'; - }); - return; - } - } - } -}); - -obs.observe(document.documentElement, {childList: true, subtree: true}); diff --git a/assets/cookie_grabber/manifest.json b/assets/cookie_grabber/manifest.json deleted file mode 100644 index 1e41399..0000000 --- a/assets/cookie_grabber/manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "manifest_version": 3, - "name": "Cookie Grabber", - "version": "1.0", - "description": "获取所有 cookies", - "permissions": [ - "cookies", - "storage", - "tabs", - "activeTab" - ], - "background": { - "service_worker": "background.js" - }, - "action": { - "default_popup": "popup.html" - }, - "host_permissions": [ - "" - ], - "content_scripts": [ - { - "matches": [ - "" - ], - "js": [ - "content.js" - ], - "run_at": "document_idle" - } - ] -} \ No newline at end of file diff --git a/assets/cookie_grabber/popup.html b/assets/cookie_grabber/popup.html deleted file mode 100644 index b25f8a4..0000000 --- a/assets/cookie_grabber/popup.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - HttpOnly Cookie Grabber - - - - -

Get All Cookies

- - - - \ No newline at end of file diff --git a/assets/cookie_grabber/popup.js b/assets/cookie_grabber/popup.js deleted file mode 100644 index 3979435..0000000 --- a/assets/cookie_grabber/popup.js +++ /dev/null @@ -1,56 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // 当刷新按钮被点击时,获取当前页面的 cookies - document.getElementById('refresh').addEventListener('click', fetchCookies); - // 加载时显示当前页面的 cookies - fetchCookies(); -}); - -// 从 cookies 存储中获取当前页面的 cookies -function fetchCookies() { - // 获取当前活动的标签页 - chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { - const activeTab = tabs[0]; // 获取活动标签页 - const currentUrl = activeTab.url; // 当前页面的 URL - console.log("当前活动的 URL:", currentUrl); - - // 双查: 普通 + partitioned - const origin = currentUrl.match(/^https?:\/\/[^\/]+/)[0]; - chrome.cookies.getAll({ url: currentUrl }, (cookies) => { - chrome.cookies.getAll({ url: currentUrl, partitionKey: { topLevelSite: origin } }, (pCookies) => { - const map = {}; - cookies.forEach(c => map[c.name] = c.value); - pCookies.forEach(c => map[c.name] = c.value); - const allCookies = Object.entries(map); - - const cookiesDisplay = document.getElementById('cookiesDisplay'); - cookiesDisplay.innerHTML = ''; - - if (allCookies.length === 0) { - cookiesDisplay.innerHTML = '
  • No available cookies
  • '; - } else { - let cookiesString = ''; - allCookies.forEach(([name, value]) => { - const li = document.createElement('li'); - li.textContent = `${name}: ${value}`; - cookiesDisplay.appendChild(li); - cookiesString += `${name}=${value}; `; - }); - console.log('cookies:', allCookies.length); - copyCookiesToClipboard(cookiesString.trim()); - } - }); - }); - }); -} - -// 将 cookies 复制到剪贴板 -function copyCookiesToClipboard(cookiesString) { - // 使用 Clipboard API 复制到剪贴板 - navigator.clipboard.writeText(cookiesString) - .then(() => { - console.log("Cookies copied to clipboard:", cookiesString); - }) - .catch(err => { - console.error("Unable to copy to clipboard:", err); - }); -} \ No newline at end of file diff --git a/assets/tmwd_cdp_bridge/background.js b/assets/tmwd_cdp_bridge/background.js new file mode 100644 index 0000000..a2f150e --- /dev/null +++ b/assets/tmwd_cdp_bridge/background.js @@ -0,0 +1,43 @@ +// background.js - Cookie + CDP Bridge +chrome.runtime.onInstalled.addListener(() => console.log('CDP Bridge installed')); + +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + if (msg.action === 'getCookies') { + handleGetCookies(msg, sender).then(sendResponse); + return true; + } + if (msg.action === 'cdp') { + handleCDP(msg, sender).then(sendResponse); + return true; + } +}); + +async function handleGetCookies(msg, sender) { + try { + const url = msg.url || sender.tab?.url; + const origin = url.match(/^https?:\/\/[^\/]+/)[0]; + const all = await chrome.cookies.getAll({ url }); + const part = await chrome.cookies.getAll({ url, partitionKey: { topLevelSite: origin } }).catch(() => []); + const merged = [...all]; + for (const c of part) { + if (!merged.some(x => x.name === c.name && x.domain === c.domain)) merged.push(c); + } + return { ok: true, data: merged }; + } catch (e) { + return { ok: false, error: e.message }; + } +} + +async function handleCDP(msg, sender) { + const tabId = msg.tabId || sender.tab?.id; + if (!tabId) return { ok: false, error: 'no tabId' }; + try { + await chrome.debugger.attach({ tabId }, '1.3'); + const result = await chrome.debugger.sendCommand({ tabId }, msg.method, msg.params || {}); + await chrome.debugger.detach({ tabId }); + return { ok: true, data: result }; + } catch (e) { + try { await chrome.debugger.detach({ tabId }); } catch (_) {} + return { ok: false, error: e.message }; + } +} \ No newline at end of file diff --git a/assets/tmwd_cdp_bridge/content.js b/assets/tmwd_cdp_bridge/content.js new file mode 100644 index 0000000..722d87e --- /dev/null +++ b/assets/tmwd_cdp_bridge/content.js @@ -0,0 +1,31 @@ +// content.js - DOM trigger bridge +const TID = '__ljq_ctrl'; + +new MutationObserver(muts => { + for (const m of muts) for (const n of m.addedNodes) { + if (n.id === TID || (n.querySelector && n.querySelector('#' + TID))) { + const el = n.id === TID ? n : n.querySelector('#' + TID); + handle(el); + } + } +}).observe(document.documentElement, { childList: true, subtree: true }); + +async function handle(el) { + try { + const cmd = el.dataset.cmd || 'cookies'; + let resp; + if (cmd === 'cookies') { + resp = await chrome.runtime.sendMessage({ action: 'getCookies', url: location.href }); + } else if (cmd === 'cdp') { + const method = el.dataset.method; + const params = el.dataset.params ? JSON.parse(el.dataset.params) : {}; + const tabId = el.dataset.tabid ? parseInt(el.dataset.tabid) : undefined; + resp = await chrome.runtime.sendMessage({ action: 'cdp', method, params, tabId }); + } else { + resp = { ok: false, error: 'unknown cmd: ' + cmd }; + } + el.textContent = JSON.stringify(resp); + } catch (e) { + el.textContent = JSON.stringify({ ok: false, error: e.message }); + } +} \ No newline at end of file diff --git a/assets/tmwd_cdp_bridge/manifest.json b/assets/tmwd_cdp_bridge/manifest.json new file mode 100644 index 0000000..efaa6ef --- /dev/null +++ b/assets/tmwd_cdp_bridge/manifest.json @@ -0,0 +1,28 @@ +{ + "manifest_version": 3, + "name": "Cookie Grabber", + "version": "2.0", + "description": "Cookie viewer + CDP bridge", + "permissions": [ + "cookies", + "tabs", + "activeTab", + "debugger" + ], + "host_permissions": [""], + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"], + "run_at": "document_idle", + "all_frames": true + } + ], + "action": { + "default_popup": "popup.html", + "default_title": "Cookie Grabber" + } +} \ No newline at end of file diff --git a/assets/tmwd_cdp_bridge/popup.html b/assets/tmwd_cdp_bridge/popup.html new file mode 100644 index 0000000..d704ace --- /dev/null +++ b/assets/tmwd_cdp_bridge/popup.html @@ -0,0 +1,19 @@ + + + + + + + +

    🍪 Cookies

    + +
    点击刷新获取 cookies...
    + + + \ No newline at end of file diff --git a/assets/tmwd_cdp_bridge/popup.js b/assets/tmwd_cdp_bridge/popup.js new file mode 100644 index 0000000..00c8839 --- /dev/null +++ b/assets/tmwd_cdp_bridge/popup.js @@ -0,0 +1,24 @@ +document.addEventListener('DOMContentLoaded', () => { + const out = document.getElementById('out'); + const btn = document.getElementById('refresh'); + btn.addEventListener('click', fetchCookies); + fetchCookies(); +}); + +async function fetchCookies() { + const out = document.getElementById('out'); + try { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (!tab?.url) { out.textContent = 'No active tab'; return; } + const resp = await chrome.runtime.sendMessage({ action: 'getCookies', url: tab.url }); + if (!resp?.ok) { out.textContent = 'Error: ' + (resp?.error || 'unknown'); return; } + if (!resp.data.length) { out.textContent = '(no cookies)'; return; } + // 展示带标记 + out.textContent = resp.data.map(c => + `${c.name}=${c.value}` + (c.httpOnly ? ' [H]' : '') + (c.secure ? ' [S]' : '') + (c.partitionKey ? ' [P]' : '') + ).join('\n'); + // 自动复制 name=value; 格式到剪贴板 + const str = resp.data.map(c => `${c.name}=${c.value}`).join('; '); + await navigator.clipboard.writeText(str); + } catch (e) { out.textContent = 'Error: ' + e.message; } +} \ No newline at end of file diff --git a/memory/tmwebdriver_sop.md b/memory/tmwebdriver_sop.md index f03e209..508486c 100644 --- a/memory/tmwebdriver_sop.md +++ b/memory/tmwebdriver_sop.md @@ -42,9 +42,39 @@ fetch('PDF_URL').then(r=>r.blob()).then(b=>{ - 已修复:移除TM脚本内轮询,改由Python侧`get_session_dict()`前后对比检测新标签 - 同理:TM脚本中任何后台逻辑都应避免依赖setTimeout轮询 -## Cookie提取(含HttpOnly) -前提:需先安装`assets/cookie_grabber/`扩展 -机制:注入`id="__ljqcg__"`的div→扩展检测后自动将完整cookie写回该元素textContent(含HttpOnly)## 验证码/页面视觉截图 +## Cookie+CDP桥(tmwd_cdp_bridge扩展) +前提:需先安装`assets/tmwd_cdp_bridge/`扩展(含debugger权限) +触发ID:`__ljq_ctrl` +### Cookie提取(含HttpOnly) +注入`id="__ljq_ctrl"`的div(无需data-cmd,默认cookies)→扩展写回JSON到textContent +```js +const d=document.createElement('div');d.id='__ljq_ctrl'; +document.body.appendChild(d); +await new Promise(r=>setTimeout(r,300)); +return d.textContent; // {ok:true, data:[...]} +``` +### CDP命令(任意Chrome DevTools Protocol) +```js +const d=document.createElement('div');d.id='__ljq_ctrl'; +d.dataset.cmd='cdp'; d.dataset.method='Network.getCookies'; +d.dataset.params=JSON.stringify({urls:[location.href]}); +document.body.appendChild(d); +await new Promise(r=>setTimeout(r,500)); +return d.textContent; // {ok:true, data:{...}} +``` +- 可用任意CDP方法(Network/DOM/Page/Runtime等),参数通过data-params传JSON +- 每次调用会attach→sendCommand→detach debugger,页面顶部会短暂显示调试提示 + +## 登录凭证autofill获取 +检测:simphtml.py已内置autofill检测,`web_scan`输出的input会带`data-autofilled="true"`属性,value显示为`⚠️受保护-读tmwebdriver_sop的autofill章节提取`(非真实值) +问题:`:-webkit-autofill`可探测autofill状态,但`input.value`为空(Chrome安全保护,需物理点击释放) +突破:PostMessage点击输入框触发释放 +前置:枚举Chrome主窗口标题匹配web_scan当前页标题,不匹配则切换标签页(避免点到后台tab) +流程:JS检查`:-webkit-autofill`→获取`getBoundingClientRect()*devicePixelRatio`→PostMessage发`WM_LBUTTONDOWN/UP`到`Chrome_RenderWidgetHostHWND`子窗口→读`value` +坑:多个RenderWidgetHostHWND共存(NexonLauncher等非浏览器Chrome应用也有),必须EnumWindows按父窗口标题匹配目标页再取其子RenderWidget +平台:Windows用PostMessage;macOS用CGEvent(未测试) + +## 验证码/页面视觉截图 - 优先:JS `canvas.toDataURL()` 直接拿base64(验证码是canvas/img时最干净,无需截屏) - 备选:`window.open(location.href,'_blank')` 前台开新标签→win32截图→完后close - GM_openInTab在web_execute_js不可用(非油猴上下文)