rename cookie_grabber -> tmwd_cdp_bridge (CDP bridge extension)
This commit is contained in:
@@ -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; // 异步响应
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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});
|
|
||||||
@@ -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": [
|
|
||||||
"<all_urls>"
|
|
||||||
],
|
|
||||||
"content_scripts": [
|
|
||||||
{
|
|
||||||
"matches": [
|
|
||||||
"<all_urls>"
|
|
||||||
],
|
|
||||||
"js": [
|
|
||||||
"content.js"
|
|
||||||
],
|
|
||||||
"run_at": "document_idle"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>HttpOnly Cookie Grabber</title>
|
|
||||||
<script src="popup.js" defer></script>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; padding: 10px; }
|
|
||||||
ul { padding-left: 20px; }
|
|
||||||
li { margin: 5px 0; }
|
|
||||||
pre { background-color: #f8f8f8; padding: 10px; border: 1px solid #ccc; max-height: 150px; overflow-y: auto; }
|
|
||||||
button { margin-top: 10px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Get All Cookies</h1>
|
|
||||||
<ul id="cookiesDisplay">No available cookies</ul>
|
|
||||||
<button id="refresh">Refresh and Copy Cookies</button>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -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 = '<li>No available cookies</li>';
|
|
||||||
} 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
43
assets/tmwd_cdp_bridge/background.js
Normal file
43
assets/tmwd_cdp_bridge/background.js
Normal file
@@ -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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
31
assets/tmwd_cdp_bridge/content.js
Normal file
31
assets/tmwd_cdp_bridge/content.js
Normal file
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
28
assets/tmwd_cdp_bridge/manifest.json
Normal file
28
assets/tmwd_cdp_bridge/manifest.json
Normal file
@@ -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": ["<all_urls>"],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["<all_urls>"],
|
||||||
|
"js": ["content.js"],
|
||||||
|
"run_at": "document_idle",
|
||||||
|
"all_frames": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_title": "Cookie Grabber"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
assets/tmwd_cdp_bridge/popup.html
Normal file
19
assets/tmwd_cdp_bridge/popup.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body{width:420px;max-height:500px;margin:0;padding:8px;font:12px monospace;background:#1e1e1e;color:#d4d4d4;overflow-y:auto}
|
||||||
|
h3{margin:4px 0;color:#569cd6}
|
||||||
|
button{background:#264f78;color:#fff;border:none;padding:4px 12px;cursor:pointer;border-radius:3px;margin-bottom:6px}
|
||||||
|
button:hover{background:#37699e}
|
||||||
|
pre{white-space:pre-wrap;word-break:break-all;margin:0;padding:6px;background:#252526;border-radius:3px;max-height:420px;overflow-y:auto}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>🍪 Cookies</h3>
|
||||||
|
<button id="refresh">刷新</button>
|
||||||
|
<pre id="out">点击刷新获取 cookies...</pre>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
assets/tmwd_cdp_bridge/popup.js
Normal file
24
assets/tmwd_cdp_bridge/popup.js
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
@@ -42,9 +42,39 @@ fetch('PDF_URL').then(r=>r.blob()).then(b=>{
|
|||||||
- 已修复:移除TM脚本内轮询,改由Python侧`get_session_dict()`前后对比检测新标签
|
- 已修复:移除TM脚本内轮询,改由Python侧`get_session_dict()`前后对比检测新标签
|
||||||
- 同理:TM脚本中任何后台逻辑都应避免依赖setTimeout轮询
|
- 同理:TM脚本中任何后台逻辑都应避免依赖setTimeout轮询
|
||||||
|
|
||||||
## Cookie提取(含HttpOnly)
|
## Cookie+CDP桥(tmwd_cdp_bridge扩展)
|
||||||
前提:需先安装`assets/cookie_grabber/`扩展
|
前提:需先安装`assets/tmwd_cdp_bridge/`扩展(含debugger权限)
|
||||||
机制:注入`id="__ljqcg__"`的div→扩展检测后自动将完整cookie写回该元素textContent(含HttpOnly)## 验证码/页面视觉截图
|
触发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时最干净,无需截屏)
|
- 优先:JS `canvas.toDataURL()` 直接拿base64(验证码是canvas/img时最干净,无需截屏)
|
||||||
- 备选:`window.open(location.href,'_blank')` 前台开新标签→win32截图→完后close
|
- 备选:`window.open(location.href,'_blank')` 前台开新标签→win32截图→完后close
|
||||||
- GM_openInTab在web_execute_js不可用(非油猴上下文)
|
- GM_openInTab在web_execute_js不可用(非油猴上下文)
|
||||||
|
|||||||
Reference in New Issue
Block a user