social-agent-cli 2.3.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/runner.ts +13 -116
- package/bin/postinstall.js +13 -8
- package/core/screenshot.ts +13 -28
- package/install.ts +2 -1
- package/package.json +1 -2
- package/platforms/browser/driver.ts +125 -267
package/ai/runner.ts
CHANGED
|
@@ -37,19 +37,16 @@ export async function runCommand(platform: string, command: string): Promise<voi
|
|
|
37
37
|
if (repeatCount > 1) console.log(` [${r + 1}/${repeatCount}]`);
|
|
38
38
|
|
|
39
39
|
const params = { ...step.parameters };
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
images: (params["{{IMAGE}}"] || params["IMAGE"]) ? [params["{{IMAGE}}"] || params["IMAGE"]] : undefined,
|
|
43
|
-
platform,
|
|
44
|
-
};
|
|
40
|
+
const content = params["{{CONTENT}}"] || params["CONTENT"] || "";
|
|
41
|
+
const image = params["{{IMAGE}}"] || params["IMAGE"] || "";
|
|
45
42
|
|
|
46
43
|
const driver = new BrowserDriver(platform, url);
|
|
47
|
-
|
|
44
|
+
await driver.launch();
|
|
48
45
|
|
|
49
46
|
try {
|
|
47
|
+
// Ana sayfaya git (ilk step goto değilse)
|
|
50
48
|
if (map.steps[0]?.action !== "goto") {
|
|
51
|
-
|
|
52
|
-
await page.waitForTimeout(3000);
|
|
49
|
+
driver.navigate(url);
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
let steps = [...map.steps];
|
|
@@ -57,19 +54,10 @@ export async function runCommand(platform: string, command: string): Promise<voi
|
|
|
57
54
|
|
|
58
55
|
for (let idx = 0; idx < steps.length; idx++) {
|
|
59
56
|
const s = steps[idx];
|
|
60
|
-
const rp = (t: string) => replacePlaceholders(t, post, params);
|
|
61
57
|
|
|
62
58
|
try {
|
|
63
59
|
console.log(` ${idx + 1}. ${s.description}`);
|
|
64
|
-
|
|
65
|
-
if (s.action === "goto") {
|
|
66
|
-
let gotoUrl = rp(s.url || url);
|
|
67
|
-
if (gotoUrl.includes("{{")) gotoUrl = url;
|
|
68
|
-
await page.goto(gotoUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
69
|
-
} else {
|
|
70
|
-
await executeStep(page, s, post, params);
|
|
71
|
-
}
|
|
72
|
-
if (s.waitMs) await page.waitForTimeout(s.waitMs);
|
|
60
|
+
driver.executeStep(s, content, image);
|
|
73
61
|
} catch (err: any) {
|
|
74
62
|
if (healCount >= 2) {
|
|
75
63
|
console.log(` ✗ ${err.message} (atlanıyor)`);
|
|
@@ -80,12 +68,12 @@ export async function runCommand(platform: string, command: string): Promise<voi
|
|
|
80
68
|
console.log(` → Düzeltiliyor...`);
|
|
81
69
|
|
|
82
70
|
const ss = join(PATHS.screenshots, `${platform}_heal.png`);
|
|
83
|
-
|
|
84
|
-
const
|
|
71
|
+
driver.takeScreenshot(ss);
|
|
72
|
+
const snap = driver.getSnapshot();
|
|
85
73
|
|
|
86
74
|
const fixed = await healAndContinue(
|
|
87
|
-
platform, idx, steps, ss,
|
|
88
|
-
err.message,
|
|
75
|
+
platform, idx, steps, ss, snap,
|
|
76
|
+
err.message, content || step.description, "medium"
|
|
89
77
|
);
|
|
90
78
|
steps = [...steps.slice(0, idx), ...fixed];
|
|
91
79
|
idx--;
|
|
@@ -93,17 +81,16 @@ export async function runCommand(platform: string, command: string): Promise<voi
|
|
|
93
81
|
}
|
|
94
82
|
|
|
95
83
|
console.log(` ✓ Tamamlandı`);
|
|
96
|
-
saveToHistory({ platform, content:
|
|
84
|
+
saveToHistory({ platform, content: content || step.description, action: mapAction.includes("post") ? "post" : mapAction, success: true });
|
|
97
85
|
} catch (err: any) {
|
|
98
86
|
console.log(` ✗ ${err.message}`);
|
|
99
|
-
saveToHistory({ platform, content:
|
|
87
|
+
saveToHistory({ platform, content: content || step.description, action: mapAction, success: false, error: err.message });
|
|
100
88
|
} finally {
|
|
101
89
|
await driver.close();
|
|
102
90
|
}
|
|
103
91
|
|
|
104
92
|
if (r < repeatCount - 1) {
|
|
105
|
-
|
|
106
|
-
await new Promise((res) => setTimeout(res, delay));
|
|
93
|
+
await new Promise((res) => setTimeout(res, 2000 + Math.random() * 3000));
|
|
107
94
|
}
|
|
108
95
|
}
|
|
109
96
|
console.log("");
|
|
@@ -112,93 +99,3 @@ export async function runCommand(platform: string, command: string): Promise<voi
|
|
|
112
99
|
console.log("[runner] Tamamlandı.");
|
|
113
100
|
process.exit(0);
|
|
114
101
|
}
|
|
115
|
-
|
|
116
|
-
function replacePlaceholders(text: string, post: Post, params: Record<string, string>): string {
|
|
117
|
-
if (!text) return text;
|
|
118
|
-
let r = text.replace("{{CONTENT}}", post.content || "");
|
|
119
|
-
for (const [k, v] of Object.entries(params)) {
|
|
120
|
-
r = r.replace(k.startsWith("{{") ? k : `{{${k}}}`, v);
|
|
121
|
-
}
|
|
122
|
-
return r;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function executeStep(page: any, step: any, post: Post, params: Record<string, string>): Promise<void> {
|
|
126
|
-
const rp = (t: string) => replacePlaceholders(t, post, params);
|
|
127
|
-
const selectors = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean).map((s: string) => rp(s));
|
|
128
|
-
|
|
129
|
-
switch (step.action) {
|
|
130
|
-
case "wait":
|
|
131
|
-
await page.waitForTimeout(step.waitMs || 1000);
|
|
132
|
-
break;
|
|
133
|
-
|
|
134
|
-
case "click": {
|
|
135
|
-
for (const sel of selectors) {
|
|
136
|
-
if (sel.includes("{{")) continue;
|
|
137
|
-
try {
|
|
138
|
-
await page.waitForSelector(sel, { timeout: 5000 });
|
|
139
|
-
await page.click(sel);
|
|
140
|
-
return;
|
|
141
|
-
} catch {}
|
|
142
|
-
}
|
|
143
|
-
throw new Error(`Click: ${selectors[0]}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
case "type": {
|
|
147
|
-
const text = rp(step.value || "");
|
|
148
|
-
for (const sel of selectors) {
|
|
149
|
-
if (sel.includes("{{")) continue;
|
|
150
|
-
try {
|
|
151
|
-
await page.waitForSelector(sel, { timeout: 5000 });
|
|
152
|
-
const ce = await page.$eval(sel, (el: Element) =>
|
|
153
|
-
el.getAttribute("contenteditable") === "true" || el.getAttribute("role") === "textbox"
|
|
154
|
-
).catch(() => false);
|
|
155
|
-
if (ce) {
|
|
156
|
-
await page.click(sel);
|
|
157
|
-
await page.evaluate(([s, t]: string[]) => {
|
|
158
|
-
const el = document.querySelector(s);
|
|
159
|
-
if (el) { (el as HTMLElement).focus(); document.execCommand("insertText", false, t); }
|
|
160
|
-
}, [sel, text]);
|
|
161
|
-
} else {
|
|
162
|
-
await page.fill(sel, text);
|
|
163
|
-
}
|
|
164
|
-
return;
|
|
165
|
-
} catch {}
|
|
166
|
-
}
|
|
167
|
-
throw new Error(`Type: ${selectors[0]}`);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
case "keypress":
|
|
171
|
-
await page.keyboard.press(step.key || "Enter");
|
|
172
|
-
break;
|
|
173
|
-
|
|
174
|
-
case "upload": {
|
|
175
|
-
const filePath = rp(step.value || "{{IMAGE}}");
|
|
176
|
-
if (!filePath || filePath.includes("{{")) break;
|
|
177
|
-
|
|
178
|
-
// 1. input[type=file] direkt (file picker açmaz)
|
|
179
|
-
try {
|
|
180
|
-
const inputs = await page.locator('input[type="file"]').all();
|
|
181
|
-
for (const inp of inputs) {
|
|
182
|
-
try { await inp.setInputFiles(filePath); return; } catch {}
|
|
183
|
-
}
|
|
184
|
-
} catch {}
|
|
185
|
-
|
|
186
|
-
// 2. fileChooser event listener ile butona tıkla
|
|
187
|
-
for (const sel of selectors) {
|
|
188
|
-
let done = false;
|
|
189
|
-
const handler = async (fc: any) => { await fc.setFiles(filePath); done = true; };
|
|
190
|
-
page.on("filechooser", handler);
|
|
191
|
-
try {
|
|
192
|
-
await page.click(sel);
|
|
193
|
-
// fileChooser event'inin tetiklenmesini bekle
|
|
194
|
-
for (let w = 0; w < 20 && !done; w++) await page.waitForTimeout(250);
|
|
195
|
-
page.removeListener("filechooser", handler);
|
|
196
|
-
if (done) return;
|
|
197
|
-
} catch {
|
|
198
|
-
page.removeListener("filechooser", handler);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
throw new Error(`Upload: ${filePath}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
package/bin/postinstall.js
CHANGED
|
@@ -2,18 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
import { execSync } from "node:child_process";
|
|
4
4
|
|
|
5
|
+
// agent-browser kurulumu
|
|
5
6
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
console.log(" ✓ Playwright Chromium yüklendi");
|
|
7
|
+
execSync("agent-browser --version", { stdio: "pipe" });
|
|
8
|
+
console.log(" ✓ agent-browser zaten kurulu");
|
|
9
9
|
} catch {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
try {
|
|
11
|
+
console.log(" agent-browser kuruluyor...");
|
|
12
|
+
execSync("npm install -g agent-browser", { stdio: "pipe" });
|
|
13
|
+
execSync("agent-browser install", { stdio: "pipe" });
|
|
14
|
+
console.log(" ✓ agent-browser kuruldu");
|
|
15
|
+
} catch {
|
|
16
|
+
console.log(" ⚠ agent-browser otomatik kurulamadı.");
|
|
17
|
+
console.log(" Manuel kur: npm install -g agent-browser && agent-browser install");
|
|
18
|
+
}
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
console.log(`
|
|
15
22
|
✓ social-agent kuruldu!
|
|
16
|
-
|
|
17
|
-
Başlamak için:
|
|
18
|
-
social-agent setup
|
|
23
|
+
Başlamak için: social-agent setup
|
|
19
24
|
`);
|
package/core/screenshot.ts
CHANGED
|
@@ -1,37 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* HTML dosyasını JPEG'
|
|
4
|
+
* HTML dosyasını JPEG/PNG'ye çevirir
|
|
5
5
|
* Kullanım: npx tsx core/screenshot.ts /tmp/input.html /tmp/output.jpeg 1200 675
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
const [, , htmlPath, outputPath, widthStr = "1200", heightStr = "675"] = process.argv;
|
|
7
|
+
const [, , htmlPath, outputPath, widthStr = "1200", heightStr = "675"] = process.argv;
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const width = parseInt(widthStr);
|
|
16
|
-
const height = parseInt(heightStr);
|
|
17
|
-
|
|
18
|
-
const browser = await chromium.launch({ headless: true });
|
|
19
|
-
const page = await browser.newPage({ viewport: { width, height } });
|
|
20
|
-
await page.goto(`file://${htmlPath}`);
|
|
21
|
-
await page.waitForTimeout(500);
|
|
22
|
-
|
|
23
|
-
const isJpeg = outputPath.endsWith(".jpeg") || outputPath.endsWith(".jpg");
|
|
24
|
-
await page.screenshot({
|
|
25
|
-
path: outputPath,
|
|
26
|
-
type: isJpeg ? "jpeg" : "png",
|
|
27
|
-
quality: isJpeg ? 100 : undefined,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
await browser.close();
|
|
31
|
-
console.log(outputPath);
|
|
9
|
+
if (!htmlPath || !outputPath) {
|
|
10
|
+
console.error("Kullanım: npx tsx core/screenshot.ts <html-path> <output-path> [width] [height]");
|
|
11
|
+
process.exit(1);
|
|
32
12
|
}
|
|
33
13
|
|
|
34
|
-
|
|
14
|
+
try {
|
|
15
|
+
execSync(`agent-browser --viewport ${widthStr}x${heightStr} open "file://${htmlPath}"`, { stdio: "pipe", timeout: 15000 });
|
|
16
|
+
execSync(`agent-browser wait 500`, { stdio: "pipe" });
|
|
17
|
+
execSync(`agent-browser screenshot "${outputPath}"`, { stdio: "pipe", timeout: 10000 });
|
|
18
|
+
console.log(outputPath);
|
|
19
|
+
} catch (err: any) {
|
|
35
20
|
console.error(err.message);
|
|
36
21
|
process.exit(1);
|
|
37
|
-
}
|
|
22
|
+
}
|
package/install.ts
CHANGED
|
@@ -148,7 +148,8 @@ Gönderme komutları (HER ZAMAN bu komutları kullan, asla cd veya npx tsx kulla
|
|
|
148
148
|
if (!existsSync(join(PKG_DIR, "node_modules"))) {
|
|
149
149
|
execSync("npm install", { cwd: PKG_DIR, stdio: "pipe" });
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
// agent-browser Chrome kurulumu
|
|
152
|
+
try { execSync("agent-browser install", { stdio: "pipe", timeout: 120000 }); } catch {}
|
|
152
153
|
|
|
153
154
|
// Claude Code permissions - social-agent komutları otomatik onay
|
|
154
155
|
const claudeSettingsPath = join(homedir(), ".claude", "settings.json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "social-agent-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "AI-powered social media agent - free APIs + browser automation with self-healing selectors",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"tsconfig.json"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"playwright": "^1.50.0",
|
|
28
27
|
"tsx": "^4.19.0"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
@@ -1,354 +1,212 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { execSync, spawn, type ChildProcess } from "node:child_process";
|
|
5
3
|
import { PATHS } from "../../core/config.js";
|
|
6
|
-
import { loadMap, generateMap
|
|
7
|
-
import { pickChromeProfile, getSavedProfile
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
import { loadMap, generateMap } from "../../ai/mapper.js";
|
|
5
|
+
import { pickChromeProfile, getSavedProfile } from "../../core/profiles.js";
|
|
6
|
+
import type { SelectorMap } from "../../core/types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Browser Driver - agent-browser (Rust) ile browser otomasyonu
|
|
10
|
+
* Playwright yerine native CDP, çok daha hızlı ve güvenilir
|
|
11
|
+
*/
|
|
14
12
|
export class BrowserDriver {
|
|
15
|
-
private
|
|
16
|
-
private context: BrowserContext | null = null;
|
|
17
|
-
private page: Page | null = null;
|
|
18
|
-
private chromeProcess: ChildProcess | null = null;
|
|
19
|
-
private weStartedChrome = false;
|
|
13
|
+
private profilePath: string = "";
|
|
20
14
|
|
|
21
15
|
constructor(private platform: string, private homeUrl: string) {}
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*/
|
|
26
|
-
private async tryConnectCDP(): Promise<boolean> {
|
|
17
|
+
private cmd(command: string, timeout = 15000): string {
|
|
18
|
+
const profileFlag = this.profilePath ? `--profile "${this.profilePath}" --headed` : "--auto-connect";
|
|
27
19
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} catch {}
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Chrome'u debug port ile başlat
|
|
43
|
-
* Chrome kendi default data-dir'iyle CDP kabul etmiyor,
|
|
44
|
-
* bu yüzden profil dosyalarını ayrı bir dizine kopyalayıp oradan açıyoruz.
|
|
45
|
-
*/
|
|
46
|
-
private async startChrome(profileDirName: string): Promise<void> {
|
|
47
|
-
const srcDataDir = join(homedir(), "Library", "Application Support", "Google", "Chrome");
|
|
48
|
-
const workDir = join(PATHS.profiles, "chrome-cdp");
|
|
49
|
-
const workProfileDir = join(workDir, profileDirName);
|
|
50
|
-
|
|
51
|
-
// Çalışma dizinini oluştur
|
|
52
|
-
const { mkdirSync, copyFileSync, existsSync: exists } = await import("node:fs");
|
|
53
|
-
mkdirSync(workProfileDir, { recursive: true });
|
|
54
|
-
|
|
55
|
-
// Cookie ve login verilerini kopyala
|
|
56
|
-
const filesToCopy = [
|
|
57
|
-
"Cookies", "Cookies-journal",
|
|
58
|
-
"Login Data", "Login Data-journal",
|
|
59
|
-
"Web Data", "Web Data-journal",
|
|
60
|
-
"Preferences", "Secure Preferences",
|
|
61
|
-
];
|
|
62
|
-
const srcProfileDir = join(srcDataDir, profileDirName);
|
|
63
|
-
for (const f of filesToCopy) {
|
|
64
|
-
const src = join(srcProfileDir, f);
|
|
65
|
-
if (exists(src)) {
|
|
66
|
-
try { copyFileSync(src, join(workProfileDir, f)); } catch {}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// Local State ana dizinde
|
|
70
|
-
const localState = join(srcDataDir, "Local State");
|
|
71
|
-
if (exists(localState)) {
|
|
72
|
-
try { copyFileSync(localState, join(workDir, "Local State")); } catch {}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
this.chromeProcess = spawn(CHROME_PATH, [
|
|
76
|
-
`--remote-debugging-port=${CDP_PORT}`,
|
|
77
|
-
`--user-data-dir=${workDir}`,
|
|
78
|
-
`--profile-directory=${profileDirName}`,
|
|
79
|
-
"--no-first-run",
|
|
80
|
-
"--no-default-browser-check",
|
|
81
|
-
], {
|
|
82
|
-
detached: true,
|
|
83
|
-
stdio: "ignore",
|
|
84
|
-
});
|
|
85
|
-
this.chromeProcess.unref();
|
|
86
|
-
this.weStartedChrome = true;
|
|
87
|
-
|
|
88
|
-
for (let i = 0; i < 30; i++) {
|
|
89
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
90
|
-
if (await this.tryConnectCDP()) return;
|
|
20
|
+
return execSync(`agent-browser ${profileFlag} ${command}`, {
|
|
21
|
+
encoding: "utf-8",
|
|
22
|
+
timeout,
|
|
23
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
24
|
+
}).trim();
|
|
25
|
+
} catch (err: any) {
|
|
26
|
+
const stderr = err.stderr?.toString() || "";
|
|
27
|
+
const stdout = err.stdout?.toString() || "";
|
|
28
|
+
throw new Error(stderr || stdout || err.message);
|
|
91
29
|
}
|
|
92
|
-
throw new Error("Chrome başlatıldı ama CDP bağlantısı kurulamadı");
|
|
93
30
|
}
|
|
94
31
|
|
|
95
32
|
/**
|
|
96
|
-
*
|
|
33
|
+
* Profil seç ve hazırla
|
|
97
34
|
*/
|
|
98
|
-
async
|
|
35
|
+
private async setupProfile(askProfile = false): Promise<void> {
|
|
99
36
|
const profile = askProfile
|
|
100
37
|
? await pickChromeProfile(this.platform)
|
|
101
38
|
: getSavedProfile(this.platform);
|
|
102
39
|
|
|
103
40
|
if (!profile) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`[${this.platform}] profil seçilmemiş. Önce: npx tsx cli.ts login ${this.platform}`
|
|
106
|
-
);
|
|
41
|
+
throw new Error(`[${this.platform}] profil seçilmemiş. Önce: social-agent login ${this.platform}`);
|
|
107
42
|
}
|
|
108
43
|
|
|
109
|
-
//
|
|
110
|
-
|
|
44
|
+
// agent-browser profil dizini
|
|
45
|
+
this.profilePath = join(PATHS.profiles, `ab-${this.platform}`);
|
|
111
46
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
await this.startChrome(profile.dirName);
|
|
116
|
-
} else {
|
|
117
|
-
console.log(`[${this.platform}] Açık Chrome'a bağlanıldı.`);
|
|
118
|
-
}
|
|
47
|
+
// İlk kullanımda Chrome cookie'lerini kopyala
|
|
48
|
+
const { existsSync, mkdirSync, copyFileSync } = await import("node:fs");
|
|
49
|
+
mkdirSync(this.profilePath, { recursive: true });
|
|
119
50
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Sadece sekmeyi kapat, Chrome'u kapatma
|
|
127
|
-
if (this.page) {
|
|
128
|
-
try { await this.page.close(); } catch {}
|
|
51
|
+
const cookieFiles = ["Cookies", "Cookies-journal", "Login Data", "Login Data-journal", "Preferences"];
|
|
52
|
+
for (const f of cookieFiles) {
|
|
53
|
+
const src = join(profile.fullPath, f);
|
|
54
|
+
if (existsSync(src)) {
|
|
55
|
+
try { copyFileSync(src, join(this.profilePath, f)); } catch {}
|
|
56
|
+
}
|
|
129
57
|
}
|
|
130
58
|
}
|
|
131
59
|
|
|
132
60
|
/**
|
|
133
|
-
* Login - profil seç,
|
|
61
|
+
* Login - profil seç, tarayıcı aç
|
|
134
62
|
*/
|
|
135
63
|
async login(): Promise<void> {
|
|
136
|
-
|
|
137
|
-
|
|
64
|
+
await this.setupProfile(true);
|
|
65
|
+
// Login için --profile ile --headed aç
|
|
66
|
+
const profileFlag = `--profile "${this.profilePath}" --headed`;
|
|
67
|
+
execSync(`agent-browser ${profileFlag} open "${this.homeUrl}"`, { stdio: "pipe", timeout: 30000 });
|
|
138
68
|
|
|
139
|
-
console.log(`\n[${this.platform}]
|
|
69
|
+
console.log(`\n[${this.platform}] Tarayıcı açıldı: ${this.homeUrl}`);
|
|
140
70
|
const { createInterface } = await import("node:readline");
|
|
141
71
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
142
72
|
await new Promise<void>((resolve) => {
|
|
143
|
-
rl.question("Hesabın açıksa Enter'a bas
|
|
144
|
-
rl.close();
|
|
145
|
-
resolve();
|
|
146
|
-
});
|
|
73
|
+
rl.question("Hesabın açıksa Enter'a bas: ", () => { rl.close(); resolve(); });
|
|
147
74
|
});
|
|
148
|
-
|
|
149
75
|
console.log(`[${this.platform}] Kayıt tamamlandı.`);
|
|
150
|
-
|
|
151
|
-
await this.close();
|
|
76
|
+
process.exit(0);
|
|
152
77
|
}
|
|
153
78
|
|
|
154
79
|
/**
|
|
155
80
|
* Sayfayı öğren - AI ile selector map oluştur
|
|
156
81
|
*/
|
|
157
82
|
async learn(taskDescription = "yeni bir metin postu at", actionName = "post"): Promise<SelectorMap> {
|
|
158
|
-
|
|
159
|
-
await page.goto(this.homeUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
160
|
-
await page.waitForTimeout(3000);
|
|
83
|
+
await this.setupProfile();
|
|
161
84
|
|
|
85
|
+
// Sayfaya git
|
|
86
|
+
this.cmd(`open "${this.homeUrl}"`, 30000);
|
|
87
|
+
this.cmd("wait 3000");
|
|
88
|
+
|
|
89
|
+
// Screenshot al
|
|
162
90
|
const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn_${actionName}.png`);
|
|
163
|
-
|
|
91
|
+
this.cmd(`screenshot "${screenshotPath}"`);
|
|
164
92
|
|
|
165
|
-
// ARIA
|
|
166
|
-
const
|
|
93
|
+
// ARIA snapshot al (agent-browser'ın en güçlü özelliği)
|
|
94
|
+
const snapshot = this.cmd("snapshot", 10000);
|
|
167
95
|
|
|
168
96
|
console.log(`[${this.platform}] Screenshot: ${screenshotPath}`);
|
|
169
|
-
console.log(`[${this.platform}]
|
|
97
|
+
console.log(`[${this.platform}] Snapshot: ${snapshot.length} bytes`);
|
|
170
98
|
console.log(`[${this.platform}] Claude analiz ediyor...`);
|
|
171
99
|
|
|
172
|
-
const map = await generateMap(this.platform, screenshotPath,
|
|
100
|
+
const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
|
|
173
101
|
|
|
174
102
|
console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
|
|
175
|
-
|
|
103
|
+
process.exit(0);
|
|
176
104
|
return map;
|
|
177
105
|
}
|
|
178
106
|
|
|
179
107
|
/**
|
|
180
|
-
*
|
|
108
|
+
* Sayfaya git
|
|
181
109
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.log(`[${this.platform}] Map yok, öğrenme başlatılıyor...`);
|
|
186
|
-
map = await this.learn();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const page = await this.launch();
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
await page.goto(this.homeUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
193
|
-
await page.waitForTimeout(2000);
|
|
194
|
-
|
|
195
|
-
let steps = [...map.steps];
|
|
196
|
-
let i = 0;
|
|
197
|
-
|
|
198
|
-
while (i < steps.length) {
|
|
199
|
-
const step = steps[i];
|
|
200
|
-
try {
|
|
201
|
-
console.log(`[${this.platform}] Step ${i + 1}/${steps.length}: ${step.description}`);
|
|
202
|
-
await this.executeStep(page, step, post);
|
|
203
|
-
i++;
|
|
204
|
-
} catch (err: any) {
|
|
205
|
-
console.log(`[${this.platform}] Step #${i + 1} başarısız: ${err.message}`);
|
|
206
|
-
|
|
207
|
-
// Screenshot + DOM al, AI'a gönder
|
|
208
|
-
const healScreenshot = join(PATHS.screenshots, `${this.platform}_heal.png`);
|
|
209
|
-
await page.screenshot({ path: healScreenshot });
|
|
210
|
-
|
|
211
|
-
const domSnap = await page.evaluate(`(function() {
|
|
212
|
-
var SKIP = {"script":1,"style":1,"noscript":1,"link":1,"meta":1};
|
|
213
|
-
function ser(el, d, b) {
|
|
214
|
-
if (d > 15 || b.c <= 0) return "";
|
|
215
|
-
var t = el.tagName.toLowerCase();
|
|
216
|
-
if (SKIP[t]) return "";
|
|
217
|
-
if (t === "svg") return "<svg/>";
|
|
218
|
-
var st = window.getComputedStyle(el);
|
|
219
|
-
if (st.display === "none" || st.visibility === "hidden") return "";
|
|
220
|
-
var at = [];
|
|
221
|
-
for (var i = 0; i < el.attributes.length; i++) {
|
|
222
|
-
var a = el.attributes[i];
|
|
223
|
-
if (a.name === "style") continue;
|
|
224
|
-
at.push(a.name + '="' + (a.value.length > 150 ? a.value.substring(0,150) : a.value).replace(/"/g,"'") + '"');
|
|
225
|
-
}
|
|
226
|
-
var as = at.length ? " " + at.join(" ") : "";
|
|
227
|
-
var dt = "";
|
|
228
|
-
for (var j = 0; j < el.childNodes.length; j++) {
|
|
229
|
-
if (el.childNodes[j].nodeType === 3) { var tx = (el.childNodes[j].textContent||"").trim(); if (tx) dt += tx.substring(0,100); }
|
|
230
|
-
}
|
|
231
|
-
var ch = "";
|
|
232
|
-
for (var k = 0; k < el.children.length; k++) ch += ser(el.children[k], d+1, b);
|
|
233
|
-
if (!as && !dt && !ch) return "";
|
|
234
|
-
var r = "<"+t+as+">"+dt+ch+"</"+t+">";
|
|
235
|
-
b.c -= r.length;
|
|
236
|
-
return r;
|
|
237
|
-
}
|
|
238
|
-
return ser(document.body, 0, {c:500000});
|
|
239
|
-
})()`);
|
|
240
|
-
|
|
241
|
-
// AI kaldığı yerden devam etsin
|
|
242
|
-
const newSteps = await healAndContinue(
|
|
243
|
-
this.platform,
|
|
244
|
-
i,
|
|
245
|
-
steps,
|
|
246
|
-
healScreenshot,
|
|
247
|
-
domSnap as string,
|
|
248
|
-
err.message,
|
|
249
|
-
post.content
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
// Kalan adımları yeni planla değiştir
|
|
253
|
-
steps = [...steps.slice(0, i), ...newSteps];
|
|
254
|
-
// i'yi artırma - yeni ilk adımı deneyecek
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
await page.waitForTimeout(2000);
|
|
259
|
-
await this.close();
|
|
260
|
-
return { success: true, platform: this.platform };
|
|
261
|
-
} catch (err: any) {
|
|
262
|
-
await this.close();
|
|
263
|
-
return { success: false, platform: this.platform, error: err.message };
|
|
264
|
-
}
|
|
110
|
+
navigate(url: string): void {
|
|
111
|
+
this.cmd(`open "${url}"`, 30000);
|
|
112
|
+
this.cmd("wait 2000");
|
|
265
113
|
}
|
|
266
114
|
|
|
267
115
|
/**
|
|
268
|
-
* Tek bir step
|
|
269
|
-
*/
|
|
270
|
-
/**
|
|
271
|
-
* Parametre map'i - post objesinden + ekstra params'dan placeholder'ları doldurur
|
|
116
|
+
* Tek bir step çalıştır
|
|
272
117
|
*/
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
result = result.replace("{{CONTENT}}", post.content || "");
|
|
276
|
-
result = result.replace("{{IMAGE}}", post.images?.[0] || "");
|
|
277
|
-
if (params) {
|
|
278
|
-
for (const [key, val] of Object.entries(params)) {
|
|
279
|
-
result = result.replace(`{{${key}}}`, val);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return result;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private async executeStep(page: Page, step: SelectorStep, post: Post, params?: Record<string, string>): Promise<void> {
|
|
286
|
-
const selectors = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean) as string[];
|
|
118
|
+
executeStep(step: any, content: string, imagePath?: string): void {
|
|
119
|
+
const value = (step.value || "").replace("{{CONTENT}}", content).replace("{{IMAGE}}", imagePath || "");
|
|
287
120
|
|
|
288
121
|
switch (step.action) {
|
|
289
|
-
case "goto":
|
|
290
|
-
|
|
291
|
-
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
292
|
-
if (step.waitMs) await page.waitForTimeout(step.waitMs);
|
|
122
|
+
case "goto":
|
|
123
|
+
this.navigate(step.url || this.homeUrl);
|
|
293
124
|
break;
|
|
294
|
-
}
|
|
295
125
|
|
|
296
126
|
case "wait":
|
|
297
|
-
|
|
127
|
+
this.cmd(`wait ${step.waitMs || 1000}`);
|
|
298
128
|
break;
|
|
299
129
|
|
|
300
130
|
case "click": {
|
|
131
|
+
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
301
132
|
let clicked = false;
|
|
302
|
-
for (const sel of
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
await page.click(sel);
|
|
306
|
-
clicked = true;
|
|
307
|
-
break;
|
|
308
|
-
} catch { /* next */ }
|
|
133
|
+
for (const sel of sels) {
|
|
134
|
+
if (sel.includes("{{")) continue;
|
|
135
|
+
try { this.cmd(`click "${sel}"`); clicked = true; break; } catch {}
|
|
309
136
|
}
|
|
310
|
-
if (!clicked) throw new Error(`Click
|
|
311
|
-
if (step.waitMs)
|
|
137
|
+
if (!clicked) throw new Error(`Click: ${sels[0]}`);
|
|
138
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
312
139
|
break;
|
|
313
140
|
}
|
|
314
141
|
|
|
315
142
|
case "type": {
|
|
316
|
-
const
|
|
317
|
-
|
|
143
|
+
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
318
144
|
let typed = false;
|
|
319
|
-
for (const sel of
|
|
145
|
+
for (const sel of sels) {
|
|
146
|
+
if (sel.includes("{{")) continue;
|
|
320
147
|
try {
|
|
321
|
-
|
|
322
|
-
const isContentEditable = await page.$eval(sel, (el: Element) =>
|
|
323
|
-
el.getAttribute("contenteditable") === "true" ||
|
|
324
|
-
el.getAttribute("role") === "textbox"
|
|
325
|
-
).catch(() => false);
|
|
326
|
-
|
|
327
|
-
if (isContentEditable) {
|
|
328
|
-
await page.click(sel);
|
|
329
|
-
await page.keyboard.type(text, { delay: 30 });
|
|
330
|
-
} else {
|
|
331
|
-
await page.fill(sel, text);
|
|
332
|
-
}
|
|
148
|
+
this.cmd(`fill "${sel}" "${value.replace(/"/g, '\\"')}"`);
|
|
333
149
|
typed = true;
|
|
334
150
|
break;
|
|
335
|
-
} catch {
|
|
151
|
+
} catch {
|
|
152
|
+
try {
|
|
153
|
+
this.cmd(`click "${sel}"`);
|
|
154
|
+
this.cmd(`keyboard inserttext "${value.replace(/"/g, '\\"')}"`);
|
|
155
|
+
typed = true;
|
|
156
|
+
break;
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
336
159
|
}
|
|
337
|
-
if (!typed) throw new Error(`Type
|
|
338
|
-
if (step.waitMs)
|
|
160
|
+
if (!typed) throw new Error(`Type: ${sels[0]}`);
|
|
161
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
339
162
|
break;
|
|
340
163
|
}
|
|
341
164
|
|
|
342
165
|
case "keypress":
|
|
343
|
-
|
|
344
|
-
if (step.waitMs)
|
|
166
|
+
this.cmd(`press ${step.key || "Enter"}`);
|
|
167
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
345
168
|
break;
|
|
346
169
|
|
|
347
|
-
case "
|
|
348
|
-
const
|
|
349
|
-
|
|
170
|
+
case "upload": {
|
|
171
|
+
const filePath = value || imagePath || "";
|
|
172
|
+
if (!filePath || filePath.includes("{{")) break;
|
|
173
|
+
|
|
174
|
+
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
175
|
+
let uploaded = false;
|
|
176
|
+
for (const sel of sels) {
|
|
177
|
+
try {
|
|
178
|
+
this.cmd(`upload "${sel}" "${filePath}"`);
|
|
179
|
+
uploaded = true;
|
|
180
|
+
break;
|
|
181
|
+
} catch {}
|
|
182
|
+
}
|
|
183
|
+
if (!uploaded) throw new Error(`Upload: ${filePath}`);
|
|
184
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
350
185
|
break;
|
|
351
186
|
}
|
|
352
187
|
}
|
|
353
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Snapshot al (heal için)
|
|
192
|
+
*/
|
|
193
|
+
getSnapshot(): string {
|
|
194
|
+
try { return this.cmd("snapshot", 10000); } catch { return ""; }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Screenshot al
|
|
199
|
+
*/
|
|
200
|
+
takeScreenshot(path: string): void {
|
|
201
|
+
try { this.cmd(`screenshot "${path}"`); } catch {}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async close(): Promise<void> {
|
|
205
|
+
// agent-browser daemon otomatik yönetir
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async launch(): Promise<any> {
|
|
209
|
+
await this.setupProfile();
|
|
210
|
+
return null; // agent-browser'da page objesi yok, cmd ile çalışıyor
|
|
211
|
+
}
|
|
354
212
|
}
|