Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Post articles and image-text content to WeChat Official Account via API or Chrome CDP, with markdown-to-WeChat HTML conversion.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/cdp.ts
1import { execSync, type ChildProcess } from 'node:child_process';2import path from 'node:path';3import process from 'node:process';45import {6CdpConnection,7findChromeExecutable as findChromeExecutableBase,8findExistingChromeDebugPort as findExistingChromeDebugPortBase,9getFreePort as getFreePortBase,10launchChrome as launchChromeBase,11resolveSharedChromeProfileDir,12sleep,13waitForChromeDebugPort,14type PlatformCandidates,15} from 'baoyu-chrome-cdp';1617export { CdpConnection, sleep, waitForChromeDebugPort };1819const CHROME_CANDIDATES_FULL: PlatformCandidates = {20darwin: [21'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',22'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',23'/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',24'/Applications/Chromium.app/Contents/MacOS/Chromium',25'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',26],27win32: [28'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',29'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',30'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',31'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',32],33default: [34'/usr/bin/google-chrome',35'/usr/bin/google-chrome-stable',36'/usr/bin/chromium',37'/usr/bin/chromium-browser',38'/snap/bin/chromium',39'/usr/bin/microsoft-edge',40],41};4243let wslHome: string | null | undefined;44function getWslWindowsHome(): string | null {45if (wslHome !== undefined) return wslHome;46if (!process.env.WSL_DISTRO_NAME) {47wslHome = null;48return null;49}50try {51const raw = execSync('cmd.exe /C "echo %USERPROFILE%"', {52encoding: 'utf-8',53timeout: 5_000,54}).trim().replace(/\r/g, '');55wslHome = execSync(`wslpath -u "${raw}"`, {56encoding: 'utf-8',57timeout: 5_000,58}).trim() || null;59} catch {60wslHome = null;61}62return wslHome;63}6465export async function getFreePort(): Promise<number> {66return await getFreePortBase('WECHAT_BROWSER_DEBUG_PORT');67}6869export function findChromeExecutable(chromePathOverride?: string): string | undefined {70if (chromePathOverride?.trim()) return chromePathOverride.trim();71return findChromeExecutableBase({72candidates: CHROME_CANDIDATES_FULL,73envNames: ['WECHAT_BROWSER_CHROME_PATH'],74});75}7677export function getDefaultProfileDir(): string {78return resolveSharedChromeProfileDir({79envNames: ['BAOYU_CHROME_PROFILE_DIR', 'WECHAT_BROWSER_PROFILE_DIR'],80wslWindowsHome: getWslWindowsHome(),81});82}8384export function getAccountProfileDir(alias: string): string {85const base = getDefaultProfileDir();86return path.join(path.dirname(base), `wechat-${alias}`);87}8889export interface ChromeSession {90cdp: CdpConnection;91sessionId: string;92targetId: string;93}9495export async function tryConnectExisting(port: number): Promise<CdpConnection | null> {96try {97const wsUrl = await waitForChromeDebugPort(port, 5_000, { includeLastError: true });98return await CdpConnection.connect(wsUrl, 5_000);99} catch {100return null;101}102}103104export async function findExistingChromeDebugPort(profileDir = getDefaultProfileDir()): Promise<number | null> {105return await findExistingChromeDebugPortBase({ profileDir });106}107108export async function launchChrome(109url: string,110profileDir?: string,111chromePathOverride?: string,112): Promise<{ cdp: CdpConnection; chrome: ChildProcess }> {113const chromePath = findChromeExecutable(chromePathOverride);114if (!chromePath) throw new Error('Chrome not found. Set WECHAT_BROWSER_CHROME_PATH env var.');115116const profile = profileDir ?? getDefaultProfileDir();117const port = await getFreePort();118console.log(`[cdp] Launching Chrome (profile: ${profile})`);119120const chrome = await launchChromeBase({121chromePath,122profileDir: profile,123port,124url,125extraArgs: ['--disable-blink-features=AutomationControlled', '--start-maximized'],126});127128const wsUrl = await waitForChromeDebugPort(port, 30_000, { includeLastError: true });129const cdp = await CdpConnection.connect(wsUrl, 30_000);130131return { cdp, chrome };132}133134export async function getPageSession(cdp: CdpConnection, urlPattern: string): Promise<ChromeSession> {135const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets');136const pageTarget = targets.targetInfos.find((target) => target.type === 'page' && target.url.includes(urlPattern));137138if (!pageTarget) throw new Error(`Page not found: ${urlPattern}`);139140const { sessionId } = await cdp.send<{ sessionId: string }>('Target.attachToTarget', {141targetId: pageTarget.targetId,142flatten: true,143});144145await cdp.send('Page.enable', {}, { sessionId });146await cdp.send('Runtime.enable', {}, { sessionId });147await cdp.send('DOM.enable', {}, { sessionId });148149return { cdp, sessionId, targetId: pageTarget.targetId };150}151152export async function waitForNewTab(153cdp: CdpConnection,154initialIds: Set<string>,155urlPattern: string,156timeoutMs = 30_000,157): Promise<string> {158const start = Date.now();159while (Date.now() - start < timeoutMs) {160const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets');161const newTab = targets.targetInfos.find((target) => (162target.type === 'page' &&163!initialIds.has(target.targetId) &&164target.url.includes(urlPattern)165));166if (newTab) return newTab.targetId;167await sleep(500);168}169throw new Error(`New tab not found: ${urlPattern}`);170}171172export async function clickElement(session: ChromeSession, selector: string): Promise<void> {173const position = await session.cdp.send<{ result: { value: string } }>('Runtime.evaluate', {174expression: `175(function() {176const el = document.querySelector('${selector}');177if (!el) return 'null';178el.scrollIntoView({ block: 'center' });179const rect = el.getBoundingClientRect();180return JSON.stringify({ x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 });181})()182`,183returnByValue: true,184}, { sessionId: session.sessionId });185186if (position.result.value === 'null') throw new Error(`Element not found: ${selector}`);187const pos = JSON.parse(position.result.value);188189await session.cdp.send('Input.dispatchMouseEvent', {190type: 'mousePressed',191x: pos.x,192y: pos.y,193button: 'left',194clickCount: 1,195}, { sessionId: session.sessionId });196await sleep(50);197await session.cdp.send('Input.dispatchMouseEvent', {198type: 'mouseReleased',199x: pos.x,200y: pos.y,201button: 'left',202clickCount: 1,203}, { sessionId: session.sessionId });204}205206export async function typeText(session: ChromeSession, text: string): Promise<void> {207const lines = text.split('\n');208for (let index = 0; index < lines.length; index += 1) {209const line = lines[index];210if (line.length > 0) {211await session.cdp.send('Input.insertText', { text: line }, { sessionId: session.sessionId });212}213if (index < lines.length - 1) {214await session.cdp.send('Input.dispatchKeyEvent', {215type: 'keyDown',216key: 'Enter',217code: 'Enter',218windowsVirtualKeyCode: 13,219}, { sessionId: session.sessionId });220await session.cdp.send('Input.dispatchKeyEvent', {221type: 'keyUp',222key: 'Enter',223code: 'Enter',224windowsVirtualKeyCode: 13,225}, { sessionId: session.sessionId });226}227await sleep(30);228}229}230231export async function pasteFromClipboard(session: ChromeSession): Promise<void> {232const modifiers = process.platform === 'darwin' ? 4 : 2;233await session.cdp.send('Input.dispatchKeyEvent', {234type: 'keyDown',235key: 'v',236code: 'KeyV',237modifiers,238windowsVirtualKeyCode: 86,239}, { sessionId: session.sessionId });240await session.cdp.send('Input.dispatchKeyEvent', {241type: 'keyUp',242key: 'v',243code: 'KeyV',244modifiers,245windowsVirtualKeyCode: 86,246}, { sessionId: session.sessionId });247}248249export async function evaluate<T = unknown>(session: ChromeSession, expression: string): Promise<T> {250const result = await session.cdp.send<{ result: { value: T } }>('Runtime.evaluate', {251expression,252returnByValue: true,253}, { sessionId: session.sessionId });254return result.result.value;255}256