social-agent-cli 1.0.0 → 1.0.2

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 ADDED
@@ -0,0 +1,323 @@
1
+ import { BrowserDriver } from "../platforms/browser/driver.js";
2
+ import { loadMap } from "./mapper.js";
3
+ import { createPlan, type ExecutionPlan, type PlanStep } from "./planner.js";
4
+ import { healAndContinue } from "./mapper.js";
5
+ import { saveToHistory } from "../core/history.js";
6
+ import type { Post, PostResult } from "../core/types.js";
7
+ import { join } from "node:path";
8
+ import { PATHS } from "../core/config.js";
9
+
10
+ const BROWSER_PLATFORMS: Record<string, string> = {
11
+ x: "https://x.com",
12
+ linkedin: "https://www.linkedin.com",
13
+ };
14
+
15
+ /**
16
+ * Doğal dil komutunu planla ve çalıştır.
17
+ */
18
+ export async function runCommand(
19
+ platform: string,
20
+ command: string
21
+ ): Promise<void> {
22
+ const url = BROWSER_PLATFORMS[platform];
23
+ if (!url) {
24
+ console.log(`"${platform}" browser platformu değil.`);
25
+ return;
26
+ }
27
+
28
+ // 1. Plan oluştur
29
+ const plan = await createPlan(platform, command);
30
+
31
+ // 2. Onay al
32
+ console.log("");
33
+ const { createInterface } = await import("node:readline");
34
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
35
+ const answer = await new Promise<string>((resolve) => {
36
+ rl.question("Bu planı çalıştırayım mı? (E/h): ", (a) => { rl.close(); resolve(a.trim()); });
37
+ });
38
+
39
+ if (answer && answer.toLowerCase() !== "e") {
40
+ console.log("İptal edildi.");
41
+ process.exit(0);
42
+ }
43
+
44
+ // 3. Adımları çalıştır
45
+ console.log("\n[runner] Plan çalıştırılıyor...\n");
46
+
47
+ for (const [i, step] of plan.steps.entries()) {
48
+ console.log(`━━━ Adım ${i + 1}/${plan.steps.length}: ${step.description} ━━━`);
49
+
50
+ if (step.type === "learn_new") {
51
+ console.log(`[runner] Yeni aksiyon öğreniliyor: "${step.learnDescription}"`);
52
+ const driver = new BrowserDriver(platform, url);
53
+ await driver.learn(
54
+ step.learnDescription || step.description,
55
+ step.learnActionName || "custom"
56
+ );
57
+ console.log(`[runner] Öğrenildi!\n`);
58
+ }
59
+
60
+ const mapAction = step.type === "use_map"
61
+ ? step.mapAction!
62
+ : step.learnActionName || "custom";
63
+
64
+ const map = loadMap(platform, mapAction);
65
+ if (!map) {
66
+ console.log(`[runner] HATA: "${mapAction}" map'i bulunamadı, atlanıyor.`);
67
+ continue;
68
+ }
69
+
70
+ const repeatCount = step.repeat || 1;
71
+
72
+ for (let r = 0; r < repeatCount; r++) {
73
+ if (repeatCount > 1) console.log(` [${r + 1}/${repeatCount}]`);
74
+
75
+ const post: Post = {
76
+ content: step.parameters["{{CONTENT}}"] || step.parameters["CONTENT"] || "",
77
+ platform,
78
+ };
79
+
80
+ const driver = new BrowserDriver(platform, url);
81
+ const page = await driver.launch();
82
+
83
+ try {
84
+ let steps = [...map.steps];
85
+ let stepIdx = 0;
86
+ let healAttempt = 0;
87
+
88
+ while (stepIdx < steps.length) {
89
+ const mapStep = steps[stepIdx];
90
+
91
+ // Placeholder'ları doldur - boş kalanları akıllıca handle et
92
+ const resolvedParams = resolveParams(step.parameters, r);
93
+
94
+ try {
95
+ if (mapStep.action === "goto") {
96
+ let gotoUrl = mapStep.url || url;
97
+ gotoUrl = replacePlaceholders(gotoUrl, post, resolvedParams);
98
+
99
+ // Eğer hâlâ {{...}} placeholder varsa, bu adımı atla ve ana sayfaya git
100
+ if (gotoUrl.includes("{{")) {
101
+ console.log(` Step ${stepIdx + 1}: ${mapStep.description} (ana sayfa kullanılıyor)`);
102
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
103
+ } else {
104
+ console.log(` Step ${stepIdx + 1}: ${mapStep.description}`);
105
+ await page.goto(gotoUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
106
+ }
107
+ if (mapStep.waitMs) await page.waitForTimeout(mapStep.waitMs);
108
+ } else {
109
+ console.log(` Step ${stepIdx + 1}: ${mapStep.description}`);
110
+ await executeMapStep(page, mapStep, post, resolvedParams);
111
+ }
112
+
113
+ stepIdx++;
114
+ healAttempt = 0; // başarılı, heal sayacını sıfırla
115
+ } catch (err: any) {
116
+ healAttempt++;
117
+ const efforts = ["low", "medium", "medium"];
118
+ const effort = efforts[Math.min(healAttempt - 1, efforts.length - 1)];
119
+
120
+ console.log(` ✗ Hata: ${err.message}`);
121
+ console.log(` [heal] AI devreye giriyor (effort: ${effort}, deneme: ${healAttempt}/3)...`);
122
+
123
+ if (healAttempt > 3) {
124
+ console.log(` [heal] 3 denemede çözülemedi, atlanıyor.`);
125
+ break;
126
+ }
127
+
128
+ // Screenshot + DOM al
129
+ const healScreenshot = join(PATHS.screenshots, `${platform}_heal_${stepIdx}.png`);
130
+ await page.screenshot({ path: healScreenshot });
131
+
132
+ const domSnap = await page.evaluate(`(function() {
133
+ var SKIP = {"script":1,"style":1,"noscript":1,"link":1,"meta":1};
134
+ function ser(el, d, b) {
135
+ if (d > 15 || b.c <= 0) return "";
136
+ var t = el.tagName.toLowerCase();
137
+ if (SKIP[t]) return "";
138
+ if (t === "svg") return "<svg/>";
139
+ var st = window.getComputedStyle(el);
140
+ if (st.display === "none" || st.visibility === "hidden") return "";
141
+ var at = [];
142
+ for (var i = 0; i < el.attributes.length; i++) {
143
+ var a = el.attributes[i];
144
+ if (a.name === "style") continue;
145
+ at.push(a.name + '="' + (a.value.length > 150 ? a.value.substring(0,150) : a.value).replace(/"/g,"'") + '"');
146
+ }
147
+ var as = at.length ? " " + at.join(" ") : "";
148
+ var dt = "";
149
+ for (var j = 0; j < el.childNodes.length; j++) {
150
+ if (el.childNodes[j].nodeType === 3) { var tx = (el.childNodes[j].textContent||"").trim(); if (tx) dt += tx.substring(0,100); }
151
+ }
152
+ var ch = "";
153
+ for (var k = 0; k < el.children.length; k++) ch += ser(el.children[k], d+1, b);
154
+ if (!as && !dt && !ch) return "";
155
+ var r = "<"+t+as+">"+dt+ch+"</"+t+">";
156
+ b.c -= r.length;
157
+ return r;
158
+ }
159
+ return ser(document.body, 0, {c:500000});
160
+ })()`);
161
+
162
+ // AI kaldığı yerden devam etsin (her denemede effort artar)
163
+ const newSteps = await healAndContinue(
164
+ platform,
165
+ stepIdx,
166
+ steps,
167
+ healScreenshot,
168
+ domSnap as string,
169
+ err.message,
170
+ post.content || step.description,
171
+ effort
172
+ );
173
+
174
+ steps = [...steps.slice(0, stepIdx), ...newSteps];
175
+ // stepIdx artırma - yeni planı deneyecek
176
+ }
177
+ }
178
+
179
+ console.log(` ✓ Tamamlandı`);
180
+ saveToHistory({
181
+ platform,
182
+ content: post.content || step.description,
183
+ action: mapAction.includes("post") ? "post" : mapAction,
184
+ success: true,
185
+ });
186
+ } catch (err: any) {
187
+ console.log(` ✗ Hata: ${err.message}`);
188
+ saveToHistory({
189
+ platform,
190
+ content: post.content || step.description,
191
+ action: mapAction.includes("post") ? "post" : mapAction,
192
+ success: false,
193
+ error: err.message,
194
+ });
195
+ } finally {
196
+ await driver.close();
197
+ }
198
+
199
+ // Tekrarlar arası rastgele bekleme
200
+ if (r < repeatCount - 1) {
201
+ const delay = 2000 + Math.random() * 3000;
202
+ console.log(` (${(delay / 1000).toFixed(1)}s bekleniyor...)`);
203
+ await new Promise((resolve) => setTimeout(resolve, delay));
204
+ }
205
+ }
206
+
207
+ console.log("");
208
+ }
209
+
210
+ console.log("[runner] Plan tamamlandı.");
211
+ process.exit(0);
212
+ }
213
+
214
+ /**
215
+ * Parametreleri çözümle - boş olanları repeat index'e göre doldur
216
+ */
217
+ function resolveParams(params: Record<string, string>, repeatIndex: number): Record<string, string> {
218
+ const resolved: Record<string, string> = {};
219
+ for (const [key, val] of Object.entries(params)) {
220
+ resolved[key] = val || "";
221
+ }
222
+ // Repeat index'i ekle (nth-child selector'ları için)
223
+ resolved["{{INDEX}}"] = String(repeatIndex + 1);
224
+ resolved["INDEX"] = String(repeatIndex + 1);
225
+ return resolved;
226
+ }
227
+
228
+ /**
229
+ * Placeholder'ları değiştir
230
+ */
231
+ function replacePlaceholders(text: string, post: Post, params: Record<string, string>): string {
232
+ if (!text) return text;
233
+ let result = text;
234
+ result = result.replace("{{CONTENT}}", post.content || "");
235
+ for (const [key, val] of Object.entries(params)) {
236
+ const placeholder = key.startsWith("{{") ? key : `{{${key}}}`;
237
+ result = result.replace(placeholder, val);
238
+ }
239
+ return result;
240
+ }
241
+
242
+ /**
243
+ * Tek bir map step'ini parametrelerle çalıştır
244
+ */
245
+ async function executeMapStep(
246
+ page: any,
247
+ step: any,
248
+ post: Post,
249
+ params: Record<string, string>
250
+ ): Promise<void> {
251
+ function rp(text: string): string {
252
+ return replacePlaceholders(text, post, params);
253
+ }
254
+
255
+ const selectors = [step.selector, ...(step.fallbackSelectors || [])]
256
+ .filter(Boolean)
257
+ .map((s: string) => rp(s));
258
+
259
+ switch (step.action) {
260
+ case "goto":
261
+ await page.goto(rp(step.url), { waitUntil: "domcontentloaded", timeout: 30000 });
262
+ if (step.waitMs) await page.waitForTimeout(step.waitMs);
263
+ break;
264
+
265
+ case "wait":
266
+ await page.waitForTimeout(step.waitMs || 1000);
267
+ break;
268
+
269
+ case "click": {
270
+ let clicked = false;
271
+
272
+ // Önce normal selector'ları dene
273
+ for (const sel of selectors) {
274
+ // Hâlâ çözümlenmemiş placeholder varsa atla
275
+ if (sel.includes("{{")) continue;
276
+ try {
277
+ await page.waitForSelector(sel, { timeout: 5000 });
278
+ await page.click(sel);
279
+ clicked = true;
280
+ break;
281
+ } catch {}
282
+ }
283
+
284
+ // Hiçbiri çalışmadıysa hata fırlat → heal mekanizması devreye girecek
285
+ // AI ekranı görüp doğru butonu bulacak
286
+ if (!clicked) throw new Error(`Click failed: ${selectors.join(", ")}`);
287
+ if (step.waitMs) await page.waitForTimeout(step.waitMs);
288
+ break;
289
+ }
290
+
291
+ case "type": {
292
+ const text = rp(step.value || "");
293
+ let typed = false;
294
+ for (const sel of selectors) {
295
+ if (sel.includes("{{")) continue;
296
+ try {
297
+ await page.waitForSelector(sel, { timeout: 5000 });
298
+ const isContentEditable = await page.$eval(sel, (el: Element) =>
299
+ el.getAttribute("contenteditable") === "true" ||
300
+ el.getAttribute("role") === "textbox"
301
+ ).catch(() => false);
302
+
303
+ if (isContentEditable) {
304
+ await page.click(sel);
305
+ await page.keyboard.type(text, { delay: 30 });
306
+ } else {
307
+ await page.fill(sel, text);
308
+ }
309
+ typed = true;
310
+ break;
311
+ } catch {}
312
+ }
313
+ if (!typed) throw new Error(`Type failed: ${selectors.join(", ")}`);
314
+ if (step.waitMs) await page.waitForTimeout(step.waitMs);
315
+ break;
316
+ }
317
+
318
+ case "keypress":
319
+ await page.keyboard.press(step.key || "Enter");
320
+ if (step.waitMs) await page.waitForTimeout(step.waitMs);
321
+ break;
322
+ }
323
+ }
package/cli.ts ADDED
@@ -0,0 +1,233 @@
1
+ import { MastodonDriver } from "./platforms/mastodon.js";
2
+ import { BlueskyDriver } from "./platforms/bluesky.js";
3
+ import { BrowserDriver } from "./platforms/browser/driver.js";
4
+ import { loadConfig } from "./core/config.js";
5
+ import { runCommand } from "./ai/runner.js";
6
+ import { printHistory } from "./core/history.js";
7
+ import type { Post, PostResult, PlatformDriver } from "./core/types.js";
8
+
9
+ // ── Platform registry ────────────────────────────────────────
10
+ const BROWSER_PLATFORMS: Record<string, string> = {
11
+ x: "https://x.com",
12
+ linkedin: "https://www.linkedin.com",
13
+ };
14
+
15
+ function getAllDrivers(): PlatformDriver[] {
16
+ const config = loadConfig();
17
+ const drivers: PlatformDriver[] = [];
18
+
19
+ // API-based (ücretsiz)
20
+ if (config.mastodon) drivers.push(new MastodonDriver());
21
+ if (config.bluesky) drivers.push(new BlueskyDriver());
22
+
23
+ // Browser-based (ücretli platformlar)
24
+ for (const [name, url] of Object.entries(BROWSER_PLATFORMS)) {
25
+ drivers.push({
26
+ name,
27
+ type: "browser",
28
+ post: (post: Post) => new BrowserDriver(name, url).post(post),
29
+ });
30
+ }
31
+
32
+ return drivers;
33
+ }
34
+
35
+ // ── Komutlar ─────────────────────────────────────────────────
36
+
37
+ async function cmdLogin(platform: string) {
38
+ const url = BROWSER_PLATFORMS[platform];
39
+ if (!url) {
40
+ console.log(`"${platform}" browser platformu değil. Browser platformları: ${Object.keys(BROWSER_PLATFORMS).join(", ")}`);
41
+ return;
42
+ }
43
+ const driver = new BrowserDriver(platform, url);
44
+ await driver.login();
45
+ }
46
+
47
+ async function cmdLearn(taskDescription: string, platform: string) {
48
+ const url = BROWSER_PLATFORMS[platform];
49
+ if (!url) {
50
+ console.log(`"${platform}" browser platformu değil. Platformlar: ${Object.keys(BROWSER_PLATFORMS).join(", ")}`);
51
+ return;
52
+ }
53
+
54
+ // Görev açıklamasından aksiyon adı türet
55
+ const actionName = taskDescription
56
+ .toLowerCase()
57
+ .replace(/[^a-z0-9çğıöşü\s]/g, "")
58
+ .trim()
59
+ .split(/\s+/)
60
+ .slice(0, 3)
61
+ .join("_") || "custom";
62
+
63
+ const driver = new BrowserDriver(platform, url);
64
+ const map = await driver.learn(taskDescription, actionName);
65
+ console.log(`\n"${actionName}" map (${map.steps.length} adım):`);
66
+ for (const [i, step] of map.steps.entries()) {
67
+ console.log(` ${i + 1}. [${step.action}] ${step.description} → ${step.selector || step.url || ""}`);
68
+ }
69
+ }
70
+
71
+ async function cmdPost(content: string, platforms?: string[], imagePaths?: string[]) {
72
+ const drivers = getAllDrivers();
73
+
74
+ // Platform filtresi
75
+ const activeDrivers = platforms?.length
76
+ ? drivers.filter((d) => platforms.includes(d.name))
77
+ : drivers;
78
+
79
+ if (!activeDrivers.length) {
80
+ console.log("Aktif platform yok. config.json'u kontrol et veya 'login' yap.");
81
+ return;
82
+ }
83
+
84
+ const post: Post = {
85
+ content,
86
+ images: imagePaths,
87
+ platform: "all",
88
+ };
89
+
90
+ console.log(`\nPost gönderiliyor (${activeDrivers.length} platform)...\n`);
91
+
92
+ const results: PostResult[] = [];
93
+ for (const driver of activeDrivers) {
94
+ process.stdout.write(` [${driver.name}] `);
95
+ const result = await driver.post(post);
96
+ results.push(result);
97
+
98
+ if (result.success) {
99
+ console.log(`✓ ${result.url || "gönderildi"}`);
100
+ } else {
101
+ console.log(`✗ ${result.error}`);
102
+ }
103
+ }
104
+
105
+ const successCount = results.filter((r) => r.success).length;
106
+ console.log(`\n${successCount}/${results.length} platform başarılı.`);
107
+ }
108
+
109
+ async function cmdTest(platform: string) {
110
+ console.log(`[${platform}] Test postu gönderiliyor...`);
111
+ const testContent = `Test post - ${new Date().toLocaleString("tr-TR")} 🧪`;
112
+
113
+ const url = BROWSER_PLATFORMS[platform];
114
+ if (url) {
115
+ const driver = new BrowserDriver(platform, url);
116
+ const result = await driver.post({ content: testContent, platform });
117
+ console.log(result.success ? "✓ Başarılı!" : `✗ Hata: ${result.error}`);
118
+ } else {
119
+ const drivers = getAllDrivers();
120
+ const driver = drivers.find((d) => d.name === platform);
121
+ if (!driver) {
122
+ console.log(`"${platform}" platformu bulunamadı.`);
123
+ return;
124
+ }
125
+ const result = await driver.post({ content: testContent, platform });
126
+ console.log(result.success ? `✓ ${result.url}` : `✗ ${result.error}`);
127
+ }
128
+ }
129
+
130
+ function cmdStatus() {
131
+ const config = loadConfig();
132
+ const drivers = getAllDrivers();
133
+
134
+ console.log("\n Platform Durumu\n ───────────────");
135
+ for (const d of drivers) {
136
+ const icon = d.type === "api" ? "🔗" : "🌐";
137
+ console.log(` ${icon} ${d.name} (${d.type})`);
138
+ }
139
+
140
+ if (!drivers.length) {
141
+ console.log(" Hiçbir platform yapılandırılmamış.");
142
+ console.log(" config.json düzenle veya 'login <platform>' çalıştır.");
143
+ }
144
+ console.log();
145
+ }
146
+
147
+ // ── Main ─────────────────────────────────────────────────────
148
+ const [, , command, ...args] = process.argv;
149
+
150
+ switch (command) {
151
+ case "login":
152
+ await cmdLogin(args[0] || "x");
153
+ break;
154
+
155
+ case "learn": {
156
+ // npx tsx cli.ts learn "kişileri takip etmeyi öğren" x
157
+ // npx tsx cli.ts learn x → varsayılan: "post at"
158
+ const lastArg = args[args.length - 1];
159
+ const platform = BROWSER_PLATFORMS[lastArg] ? lastArg : "x";
160
+ const task = args.length > 1
161
+ ? args.slice(0, -1).join(" ")
162
+ : (BROWSER_PLATFORMS[args[0]] ? "yeni bir metin postu at" : args[0] || "yeni bir metin postu at");
163
+ await cmdLearn(task, platform);
164
+ break;
165
+ }
166
+
167
+ case "post": {
168
+ const content = args[0];
169
+ if (!content) {
170
+ console.log('Kullanım: tsx cli.ts post "Post metni" [--platforms x,mastodon] [--image foto.png]');
171
+ break;
172
+ }
173
+ const platformIdx = args.indexOf("--platforms");
174
+ const platforms = platformIdx > -1 ? args[platformIdx + 1]?.split(",") : undefined;
175
+ const imageIdx = args.indexOf("--image");
176
+ const images = imageIdx > -1 ? [args[imageIdx + 1]] : undefined;
177
+ await cmdPost(content, platforms, images);
178
+ break;
179
+ }
180
+
181
+ case "run": {
182
+ // npx tsx cli.ts run x "son 3 tweeti beğen ve takip et"
183
+ // npx tsx cli.ts run linkedin "bağlantı isteği gönder"
184
+ const runPlatform = BROWSER_PLATFORMS[args[0]] ? args[0] : "x";
185
+ const runCmd = BROWSER_PLATFORMS[args[0]]
186
+ ? args.slice(1).join(" ")
187
+ : args.join(" ");
188
+ if (!runCmd) {
189
+ console.log('Kullanım: tsx cli.ts run <platform> "doğal dil komutu"');
190
+ break;
191
+ }
192
+ await runCommand(runPlatform, runCmd);
193
+ break;
194
+ }
195
+
196
+ case "test":
197
+ await cmdTest(args[0] || "mastodon");
198
+ break;
199
+
200
+ case "history":
201
+ printHistory(parseInt(args[0]) || 20);
202
+ break;
203
+
204
+ case "status":
205
+ cmdStatus();
206
+ break;
207
+
208
+ default:
209
+ console.log(`
210
+ social-agent - AI destekli sosyal medya otomasyon aracı
211
+
212
+ Komutlar:
213
+ login <platform> Chrome profil seç ve giriş yap
214
+ learn "<görev açıklaması>" <platform> AI ile herhangi bir aksiyonu öğret
215
+ run <platform> "doğal dil komutu" AI planla + çalıştır (map'leri kombine eder)
216
+ post "metin" Post at
217
+ --platforms x,mastodon Belirli platformlara
218
+ --image foto.png Görsel ekle
219
+ test <platform> Test postu gönder
220
+ status Bağlı platformları göster
221
+
222
+ Örnekler:
223
+ tsx cli.ts login x
224
+ tsx cli.ts learn x → post atma öğren
225
+ tsx cli.ts learn "kişileri takip etmeyi öğren" x → takip akışı
226
+ tsx cli.ts learn "tweet beğenmeyi öğren" x → beğeni akışı
227
+ tsx cli.ts run x "uiuxgorkem'in son 3 tweetini beğen" → AI planla + çalıştır
228
+ tsx cli.ts run x "şu tweeti beğen ve rt yap: https://..." → kombine aksiyon
229
+ tsx cli.ts run linkedin "bağlantı isteği gönder: Ahmet Yılmaz" → eksik map'i öğren + çalıştır
230
+ tsx cli.ts post "Yeni özellik!" --platforms x,mastodon
231
+ tsx cli.ts test mastodon
232
+ `);
233
+ }
package/core/config.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+
5
+ // Data dizini: SOCIAL_AGENT_DATA env var'ı veya ~/.social-agent/
6
+ const DATA_DIR = process.env.SOCIAL_AGENT_DATA || join(homedir(), ".social-agent");
7
+
8
+ export const PATHS = {
9
+ root: DATA_DIR,
10
+ maps: join(DATA_DIR, "maps"),
11
+ profiles: join(DATA_DIR, "profiles"),
12
+ screenshots: join(DATA_DIR, "screenshots"),
13
+ knowledge: join(DATA_DIR, "knowledge"),
14
+ };
15
+
16
+ export interface Config {
17
+ mastodon?: {
18
+ instanceUrl: string;
19
+ accessToken: string;
20
+ };
21
+ bluesky?: {
22
+ identifier: string;
23
+ password: string;
24
+ };
25
+ telegram?: {
26
+ botToken: string;
27
+ chatId: string;
28
+ };
29
+ browser?: {
30
+ chromePath?: string;
31
+ headless?: boolean;
32
+ };
33
+ claude?: {
34
+ model: string;
35
+ };
36
+ }
37
+
38
+ export function loadConfig(): Config {
39
+ const configPath = join(DATA_DIR, "config.json");
40
+ if (!existsSync(configPath)) {
41
+ return {};
42
+ }
43
+ return JSON.parse(readFileSync(configPath, "utf-8"));
44
+ }
@@ -0,0 +1,96 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { PATHS } from "./config.js";
4
+
5
+ const HISTORY_PATH = join(PATHS.root, "history.json");
6
+
7
+ export interface PostRecord {
8
+ id: string;
9
+ timestamp: string;
10
+ platform: string;
11
+ content: string;
12
+ image?: string;
13
+ url?: string;
14
+ success: boolean;
15
+ error?: string;
16
+ action: string; // "post", "like", "retweet", "follow", etc.
17
+ }
18
+
19
+ export function loadHistory(): PostRecord[] {
20
+ if (!existsSync(HISTORY_PATH)) return [];
21
+ try {
22
+ return JSON.parse(readFileSync(HISTORY_PATH, "utf-8"));
23
+ } catch {
24
+ return [];
25
+ }
26
+ }
27
+
28
+ export function saveToHistory(record: Omit<PostRecord, "id" | "timestamp">): PostRecord {
29
+ const history = loadHistory();
30
+ const entry: PostRecord = {
31
+ id: `${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
32
+ timestamp: new Date().toISOString(),
33
+ ...record,
34
+ };
35
+ history.push(entry);
36
+ writeFileSync(HISTORY_PATH, JSON.stringify(history, null, 2));
37
+ return entry;
38
+ }
39
+
40
+ /**
41
+ * Son N postu getir (sadece başarılı olanlar, sadece post aksiyonu)
42
+ */
43
+ export function getRecentPosts(count = 20): PostRecord[] {
44
+ return loadHistory()
45
+ .filter((r) => r.action === "post" && r.success)
46
+ .slice(-count);
47
+ }
48
+
49
+ /**
50
+ * Platform bazlı son postlar
51
+ */
52
+ export function getRecentPostsByPlatform(platform: string, count = 10): PostRecord[] {
53
+ return loadHistory()
54
+ .filter((r) => r.platform === platform && r.action === "post" && r.success)
55
+ .slice(-count);
56
+ }
57
+
58
+ /**
59
+ * Tüm geçmişi insan-okunur formatta döndür (AI'ın üslup analizi için)
60
+ */
61
+ export function getHistoryForAI(count = 15): string {
62
+ const posts = getRecentPosts(count);
63
+ if (!posts.length) return "Henüz post geçmişi yok.";
64
+
65
+ return posts
66
+ .map((p) => `[${p.timestamp.split("T")[0]}] [${p.platform}] ${p.content.substring(0, 200)}`)
67
+ .join("\n");
68
+ }
69
+
70
+ /**
71
+ * Geçmişi konsola yazdır
72
+ */
73
+ export function printHistory(count = 20): void {
74
+ const history = loadHistory().slice(-count);
75
+
76
+ if (!history.length) {
77
+ console.log(" Henüz geçmiş yok.");
78
+ return;
79
+ }
80
+
81
+ console.log(`\n Son ${Math.min(count, history.length)} işlem:\n ─────────────────`);
82
+
83
+ for (const record of history) {
84
+ const date = new Date(record.timestamp).toLocaleString("tr-TR", {
85
+ day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit",
86
+ });
87
+ const icon = record.success ? "✓" : "✗";
88
+ const actionIcon = record.action === "post" ? "📝" : record.action === "like" ? "❤" : "▶";
89
+ const content = record.content ? record.content.substring(0, 60) + (record.content.length > 60 ? "..." : "") : record.action;
90
+
91
+ console.log(` ${icon} ${actionIcon} [${date}] [${record.platform}] ${content}`);
92
+ if (record.url) console.log(` → ${record.url}`);
93
+ if (record.error) console.log(` ✗ ${record.error}`);
94
+ }
95
+ console.log();
96
+ }