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脚本中任何后台逻辑都应避免依赖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不可用(非油猴上下文)
|
||||
|
||||
Reference in New Issue
Block a user