social-agent-cli 2.3.2 → 3.0.0
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 +123 -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.0",
|
|
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,210 @@
|
|
|
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}"` : "";
|
|
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
|
+
this.cmd(`--headed open "${this.homeUrl}"`, 30000);
|
|
138
66
|
|
|
139
|
-
console.log(`\n[${this.platform}]
|
|
67
|
+
console.log(`\n[${this.platform}] Tarayıcı açıldı: ${this.homeUrl}`);
|
|
140
68
|
const { createInterface } = await import("node:readline");
|
|
141
69
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
142
70
|
await new Promise<void>((resolve) => {
|
|
143
|
-
rl.question("Hesabın açıksa Enter'a bas
|
|
144
|
-
rl.close();
|
|
145
|
-
resolve();
|
|
146
|
-
});
|
|
71
|
+
rl.question("Hesabın açıksa Enter'a bas: ", () => { rl.close(); resolve(); });
|
|
147
72
|
});
|
|
148
|
-
|
|
149
73
|
console.log(`[${this.platform}] Kayıt tamamlandı.`);
|
|
150
|
-
|
|
151
|
-
await this.close();
|
|
74
|
+
process.exit(0);
|
|
152
75
|
}
|
|
153
76
|
|
|
154
77
|
/**
|
|
155
78
|
* Sayfayı öğren - AI ile selector map oluştur
|
|
156
79
|
*/
|
|
157
80
|
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);
|
|
81
|
+
await this.setupProfile();
|
|
161
82
|
|
|
83
|
+
// Sayfaya git
|
|
84
|
+
this.cmd(`open "${this.homeUrl}"`, 30000);
|
|
85
|
+
this.cmd("wait 3000");
|
|
86
|
+
|
|
87
|
+
// Screenshot al
|
|
162
88
|
const screenshotPath = join(PATHS.screenshots, `${this.platform}_learn_${actionName}.png`);
|
|
163
|
-
|
|
89
|
+
this.cmd(`screenshot "${screenshotPath}"`);
|
|
164
90
|
|
|
165
|
-
// ARIA
|
|
166
|
-
const
|
|
91
|
+
// ARIA snapshot al (agent-browser'ın en güçlü özelliği)
|
|
92
|
+
const snapshot = this.cmd("snapshot", 10000);
|
|
167
93
|
|
|
168
94
|
console.log(`[${this.platform}] Screenshot: ${screenshotPath}`);
|
|
169
|
-
console.log(`[${this.platform}]
|
|
95
|
+
console.log(`[${this.platform}] Snapshot: ${snapshot.length} bytes`);
|
|
170
96
|
console.log(`[${this.platform}] Claude analiz ediyor...`);
|
|
171
97
|
|
|
172
|
-
const map = await generateMap(this.platform, screenshotPath,
|
|
98
|
+
const map = await generateMap(this.platform, screenshotPath, snapshot, taskDescription, actionName);
|
|
173
99
|
|
|
174
100
|
console.log(`[${this.platform}] "${actionName}" map oluşturuldu (${map.steps.length} adım)`);
|
|
175
|
-
|
|
101
|
+
process.exit(0);
|
|
176
102
|
return map;
|
|
177
103
|
}
|
|
178
104
|
|
|
179
105
|
/**
|
|
180
|
-
*
|
|
106
|
+
* Sayfaya git
|
|
181
107
|
*/
|
|
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
|
-
}
|
|
108
|
+
navigate(url: string): void {
|
|
109
|
+
this.cmd(`open "${url}"`, 30000);
|
|
110
|
+
this.cmd("wait 2000");
|
|
265
111
|
}
|
|
266
112
|
|
|
267
113
|
/**
|
|
268
|
-
* Tek bir step
|
|
269
|
-
*/
|
|
270
|
-
/**
|
|
271
|
-
* Parametre map'i - post objesinden + ekstra params'dan placeholder'ları doldurur
|
|
114
|
+
* Tek bir step çalıştır
|
|
272
115
|
*/
|
|
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[];
|
|
116
|
+
executeStep(step: any, content: string, imagePath?: string): void {
|
|
117
|
+
const value = (step.value || "").replace("{{CONTENT}}", content).replace("{{IMAGE}}", imagePath || "");
|
|
287
118
|
|
|
288
119
|
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);
|
|
120
|
+
case "goto":
|
|
121
|
+
this.navigate(step.url || this.homeUrl);
|
|
293
122
|
break;
|
|
294
|
-
}
|
|
295
123
|
|
|
296
124
|
case "wait":
|
|
297
|
-
|
|
125
|
+
this.cmd(`wait ${step.waitMs || 1000}`);
|
|
298
126
|
break;
|
|
299
127
|
|
|
300
128
|
case "click": {
|
|
129
|
+
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
301
130
|
let clicked = false;
|
|
302
|
-
for (const sel of
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
await page.click(sel);
|
|
306
|
-
clicked = true;
|
|
307
|
-
break;
|
|
308
|
-
} catch { /* next */ }
|
|
131
|
+
for (const sel of sels) {
|
|
132
|
+
if (sel.includes("{{")) continue;
|
|
133
|
+
try { this.cmd(`click "${sel}"`); clicked = true; break; } catch {}
|
|
309
134
|
}
|
|
310
|
-
if (!clicked) throw new Error(`Click
|
|
311
|
-
if (step.waitMs)
|
|
135
|
+
if (!clicked) throw new Error(`Click: ${sels[0]}`);
|
|
136
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
312
137
|
break;
|
|
313
138
|
}
|
|
314
139
|
|
|
315
140
|
case "type": {
|
|
316
|
-
const
|
|
317
|
-
|
|
141
|
+
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
318
142
|
let typed = false;
|
|
319
|
-
for (const sel of
|
|
143
|
+
for (const sel of sels) {
|
|
144
|
+
if (sel.includes("{{")) continue;
|
|
320
145
|
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
|
-
}
|
|
146
|
+
this.cmd(`fill "${sel}" "${value.replace(/"/g, '\\"')}"`);
|
|
333
147
|
typed = true;
|
|
334
148
|
break;
|
|
335
|
-
} catch {
|
|
149
|
+
} catch {
|
|
150
|
+
try {
|
|
151
|
+
this.cmd(`click "${sel}"`);
|
|
152
|
+
this.cmd(`keyboard inserttext "${value.replace(/"/g, '\\"')}"`);
|
|
153
|
+
typed = true;
|
|
154
|
+
break;
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
336
157
|
}
|
|
337
|
-
if (!typed) throw new Error(`Type
|
|
338
|
-
if (step.waitMs)
|
|
158
|
+
if (!typed) throw new Error(`Type: ${sels[0]}`);
|
|
159
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
339
160
|
break;
|
|
340
161
|
}
|
|
341
162
|
|
|
342
163
|
case "keypress":
|
|
343
|
-
|
|
344
|
-
if (step.waitMs)
|
|
164
|
+
this.cmd(`press ${step.key || "Enter"}`);
|
|
165
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
345
166
|
break;
|
|
346
167
|
|
|
347
|
-
case "
|
|
348
|
-
const
|
|
349
|
-
|
|
168
|
+
case "upload": {
|
|
169
|
+
const filePath = value || imagePath || "";
|
|
170
|
+
if (!filePath || filePath.includes("{{")) break;
|
|
171
|
+
|
|
172
|
+
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
173
|
+
let uploaded = false;
|
|
174
|
+
for (const sel of sels) {
|
|
175
|
+
try {
|
|
176
|
+
this.cmd(`upload "${sel}" "${filePath}"`);
|
|
177
|
+
uploaded = true;
|
|
178
|
+
break;
|
|
179
|
+
} catch {}
|
|
180
|
+
}
|
|
181
|
+
if (!uploaded) throw new Error(`Upload: ${filePath}`);
|
|
182
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
350
183
|
break;
|
|
351
184
|
}
|
|
352
185
|
}
|
|
353
186
|
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Snapshot al (heal için)
|
|
190
|
+
*/
|
|
191
|
+
getSnapshot(): string {
|
|
192
|
+
try { return this.cmd("snapshot", 10000); } catch { return ""; }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Screenshot al
|
|
197
|
+
*/
|
|
198
|
+
takeScreenshot(path: string): void {
|
|
199
|
+
try { this.cmd(`screenshot "${path}"`); } catch {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async close(): Promise<void> {
|
|
203
|
+
// agent-browser daemon otomatik yönetir
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async launch(): Promise<any> {
|
|
207
|
+
await this.setupProfile();
|
|
208
|
+
return null; // agent-browser'da page objesi yok, cmd ile çalışıyor
|
|
209
|
+
}
|
|
354
210
|
}
|