social-agent-cli 4.0.0 → 4.1.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 +15 -9
- package/cli.ts +4 -6
- package/core/upload-helper.ts +34 -0
- package/maps/linkedin.json +11 -44
- package/package.json +2 -1
- package/platforms/browser/driver.ts +47 -8
package/ai/runner.ts
CHANGED
|
@@ -15,16 +15,22 @@ export async function runCommand(platform: string, command: string): Promise<voi
|
|
|
15
15
|
const url = BROWSER_PLATFORMS[platform];
|
|
16
16
|
if (!url) { console.log(`"${platform}" desteklenmiyor.`); process.exit(1); }
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// --content "..." ve --image ... parse et
|
|
19
|
+
let content = "";
|
|
20
|
+
let image = "";
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.replace(/^
|
|
27
|
-
|
|
22
|
+
const contentMatch = command.match(/--content\s+([\s\S]+?)(?=\s+--|\s*$)/);
|
|
23
|
+
const imageMatch = command.match(/--image\s+(\S+)/);
|
|
24
|
+
|
|
25
|
+
if (contentMatch) {
|
|
26
|
+
content = contentMatch[1].replace(/^"|"$/g, "").trim();
|
|
27
|
+
} else {
|
|
28
|
+
// Flag yoksa tüm komutu content olarak al
|
|
29
|
+
content = command.replace(/--image\s+\S+/g, "").trim();
|
|
30
|
+
}
|
|
31
|
+
if (imageMatch) {
|
|
32
|
+
image = imageMatch[1];
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
// Map seç
|
|
30
36
|
const map = loadMap(platform, "post");
|
package/cli.ts
CHANGED
|
@@ -189,13 +189,11 @@ switch (command) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
case "run": {
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
const runCmd =
|
|
195
|
-
? filteredArgs.slice(1).join(" ")
|
|
196
|
-
: filteredArgs.join(" ");
|
|
192
|
+
const runPlatform = BROWSER_PLATFORMS[args[0]] ? args[0] : "x";
|
|
193
|
+
const runArgs = BROWSER_PLATFORMS[args[0]] ? args.slice(1) : args;
|
|
194
|
+
const runCmd = runArgs.join(" ");
|
|
197
195
|
if (!runCmd) {
|
|
198
|
-
console.log('Kullanım: social-agent run <platform> "
|
|
196
|
+
console.log('Kullanım: social-agent run <platform> --content "mesaj" [--image /path/to/img]');
|
|
199
197
|
break;
|
|
200
198
|
}
|
|
201
199
|
await runCommand(runPlatform, runCmd);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkedIn upload helper - Playwright fileChooser ile dosya yükler
|
|
3
|
+
* agent-browser CDP ile çalışmıyor çünkü LinkedIn native file dialog kullanıyor
|
|
4
|
+
*
|
|
5
|
+
* Kullanım: npx tsx core/upload-helper.ts <cdp-port> <button-selector> <file-path>
|
|
6
|
+
*/
|
|
7
|
+
import { chromium } from "playwright";
|
|
8
|
+
|
|
9
|
+
const [, , port, selector, filePath] = process.argv;
|
|
10
|
+
|
|
11
|
+
if (!port || !selector || !filePath) {
|
|
12
|
+
console.error("Kullanım: npx tsx core/upload-helper.ts <port> <selector> <file-path>");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function upload() {
|
|
17
|
+
const browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
|
|
18
|
+
const context = browser.contexts()[0];
|
|
19
|
+
const pages = context.pages();
|
|
20
|
+
const page = pages[pages.length - 1]; // son aktif sayfa
|
|
21
|
+
|
|
22
|
+
const [fileChooser] = await Promise.all([
|
|
23
|
+
page.waitForEvent("filechooser", { timeout: 10000 }),
|
|
24
|
+
page.click(selector),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
await fileChooser.setFiles(filePath);
|
|
28
|
+
console.log("OK");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
upload().catch((e) => {
|
|
32
|
+
console.error(e.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
package/maps/linkedin.json
CHANGED
|
@@ -2,68 +2,35 @@
|
|
|
2
2
|
"post": {
|
|
3
3
|
"platform": "linkedin",
|
|
4
4
|
"action": "post",
|
|
5
|
-
"description": "LinkedIn
|
|
6
|
-
"version":
|
|
7
|
-
"lastUpdated": "2026-03-14T12:00:00.000Z",
|
|
5
|
+
"description": "LinkedIn post paylaşma",
|
|
6
|
+
"version": 3,
|
|
8
7
|
"parameters": [
|
|
9
8
|
"{{CONTENT}}"
|
|
10
9
|
],
|
|
11
10
|
"steps": [
|
|
12
11
|
{
|
|
13
12
|
"action": "click",
|
|
14
|
-
"description": "Gönderi
|
|
13
|
+
"description": "\"Gönderi başlatın\" butonuna tıkla",
|
|
15
14
|
"selector": "button.share-box-feed-entry__trigger",
|
|
16
|
-
"fallbackSelectors": [
|
|
17
|
-
|
|
18
|
-
"[aria-label*='Gönderi başlat']",
|
|
19
|
-
"button[aria-label*='post']"
|
|
20
|
-
]
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"action": "wait",
|
|
24
|
-
"description": "Post oluşturma modalının açılmasını bekle",
|
|
25
|
-
"waitMs": 1500
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"action": "click",
|
|
29
|
-
"description": "Metin alanına tıkla",
|
|
30
|
-
"selector": "[role='textbox'][aria-label*='Text editor']",
|
|
31
|
-
"fallbackSelectors": [
|
|
32
|
-
"[role='textbox'][contenteditable='true']",
|
|
33
|
-
".ql-editor[contenteditable='true']",
|
|
34
|
-
"[aria-label*='Metin düzenleyici']"
|
|
35
|
-
]
|
|
15
|
+
"fallbackSelectors": [],
|
|
16
|
+
"waitMs": 2000
|
|
36
17
|
},
|
|
37
18
|
{
|
|
38
19
|
"action": "type",
|
|
39
20
|
"description": "Post metnini yaz",
|
|
40
|
-
"selector": "[role
|
|
21
|
+
"selector": "[role=\"textbox\"]",
|
|
41
22
|
"value": "{{CONTENT}}",
|
|
42
23
|
"fallbackSelectors": [
|
|
43
|
-
"[
|
|
44
|
-
|
|
45
|
-
]
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"action": "wait",
|
|
49
|
-
"description": "Metin girişi sonrası bekleme",
|
|
24
|
+
"[contenteditable=\"true\"]"
|
|
25
|
+
],
|
|
50
26
|
"waitMs": 1000
|
|
51
27
|
},
|
|
52
28
|
{
|
|
53
29
|
"action": "click",
|
|
54
|
-
"description": "
|
|
30
|
+
"description": "\"Gönderi\" gönder butonuna tıkla",
|
|
55
31
|
"selector": "button.share-actions__primary-action",
|
|
56
|
-
"fallbackSelectors": [
|
|
57
|
-
|
|
58
|
-
"[aria-label='Gönder']",
|
|
59
|
-
"button[aria-label*='Post']",
|
|
60
|
-
"button[aria-label*='Paylaş']"
|
|
61
|
-
]
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
"action": "wait",
|
|
65
|
-
"description": "Post gönderimini bekle",
|
|
66
|
-
"waitMs": 2000
|
|
32
|
+
"fallbackSelectors": [],
|
|
33
|
+
"waitMs": 3000
|
|
67
34
|
}
|
|
68
35
|
]
|
|
69
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "social-agent-cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.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,6 +24,7 @@
|
|
|
24
24
|
"tsconfig.json"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"playwright": "^1.58.2",
|
|
27
28
|
"tsx": "^4.19.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
@@ -146,16 +146,31 @@ export class BrowserDriver {
|
|
|
146
146
|
|
|
147
147
|
case "click": {
|
|
148
148
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
149
|
+
// 1. CSS selector ile dene
|
|
149
150
|
for (const sel of sels) {
|
|
150
151
|
if (sel.includes("{{")) continue;
|
|
151
152
|
try { this.cmd(`click "${sel}"`); if (step.waitMs) this.cmd(`wait ${step.waitMs}`); return; } catch {}
|
|
152
153
|
}
|
|
153
|
-
|
|
154
|
+
// 2. JS eval fallback - description'dan buton text'i çıkar
|
|
155
|
+
const desc = step.description || "";
|
|
156
|
+
// Tırnak içindeki text'leri bul: "Gönderi", 'Post', vb.
|
|
157
|
+
const textMatches = desc.match(/["'""\u201C\u201D]([^"'""\u201C\u201D]+)["'""\u201C\u201D]/g) || [];
|
|
158
|
+
const texts = textMatches.map(m => m.replace(/["'""\u201C\u201D]/g, "").trim()).filter(Boolean);
|
|
159
|
+
|
|
160
|
+
for (const text of texts) {
|
|
161
|
+
try {
|
|
162
|
+
this.cmd(`eval "document.querySelectorAll('button,a,[role=button]').forEach(b => { if(b.textContent.trim()==='${text}' || b.textContent.trim().startsWith('${text}')) b.click() })"`);
|
|
163
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
164
|
+
return;
|
|
165
|
+
} catch {}
|
|
166
|
+
}
|
|
167
|
+
throw new Error(`Click: ${sels[0] || desc}`);
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
case "type": {
|
|
157
171
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
158
|
-
const escaped = val.replace(/"/g, '\\"');
|
|
172
|
+
const escaped = val.replace(/"/g, '\\"').replace(/'/g, "\\'");
|
|
173
|
+
// 1. CSS selector ile dene
|
|
159
174
|
for (const sel of sels) {
|
|
160
175
|
if (sel.includes("{{")) continue;
|
|
161
176
|
try {
|
|
@@ -164,6 +179,12 @@ export class BrowserDriver {
|
|
|
164
179
|
return;
|
|
165
180
|
} catch {}
|
|
166
181
|
}
|
|
182
|
+
// 2. Fallback: sayfadaki ilk textbox/contenteditable'a yaz
|
|
183
|
+
try {
|
|
184
|
+
this.cmd(`eval "var el = document.querySelector('[role=textbox],[contenteditable=true]'); if(el) { el.focus(); document.execCommand('insertText', false, '${escaped}'); }"`);
|
|
185
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
186
|
+
return;
|
|
187
|
+
} catch {}
|
|
167
188
|
throw new Error(`Type: ${sels[0]}`);
|
|
168
189
|
}
|
|
169
190
|
|
|
@@ -175,17 +196,35 @@ export class BrowserDriver {
|
|
|
175
196
|
case "upload": {
|
|
176
197
|
const filePath = val || imagePath || "";
|
|
177
198
|
if (!filePath || filePath.includes("{{")) break;
|
|
178
|
-
|
|
199
|
+
|
|
200
|
+
// 1. agent-browser upload ile dene (X'te çalışır)
|
|
179
201
|
try {
|
|
180
|
-
this.cmd(`upload "input[type='file']" "${filePath}"`,
|
|
202
|
+
this.cmd(`upload "input[type='file']" "${filePath}"`, 5000);
|
|
181
203
|
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
182
204
|
return;
|
|
183
205
|
} catch {}
|
|
184
|
-
|
|
206
|
+
|
|
207
|
+
// 2. Playwright fileChooser ile dene (LinkedIn gibi native dialog kullananlar için)
|
|
185
208
|
const sels = [step.selector, ...(step.fallbackSelectors || [])].filter(Boolean);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
const btnSel = sels.find(s => !s.includes("input[type")) || "button";
|
|
210
|
+
try {
|
|
211
|
+
const { dirname: dn } = await import("node:path");
|
|
212
|
+
const { fileURLToPath: fu } = await import("node:url");
|
|
213
|
+
const pkgDir = process.env.SOCIAL_AGENT_PKG || join(dn(fu(import.meta.url)), "../..");
|
|
214
|
+
execSync(
|
|
215
|
+
`npx tsx "${join(pkgDir, 'core/upload-helper.ts')}" ${CDP_PORT} "${btnSel}" "${filePath}"`,
|
|
216
|
+
{ stdio: "pipe", timeout: 15000 }
|
|
217
|
+
);
|
|
218
|
+
this.cmd("wait 3000");
|
|
219
|
+
// Editör modal'ı varsa kapat
|
|
220
|
+
try {
|
|
221
|
+
this.cmd(`eval "document.querySelectorAll('button').forEach(b => { var t=b.textContent.trim().toLowerCase(); if(t==='ileri' || t==='bitti' || t==='done' || t==='next' || t==='tamam') b.click() })"`);
|
|
222
|
+
this.cmd("wait 1000");
|
|
223
|
+
} catch {}
|
|
224
|
+
if (step.waitMs) this.cmd(`wait ${step.waitMs}`);
|
|
225
|
+
return;
|
|
226
|
+
} catch {}
|
|
227
|
+
|
|
189
228
|
throw new Error(`Upload: ${filePath}`);
|
|
190
229
|
}
|
|
191
230
|
}
|