diff --git a/config/accounts/accounts.json b/config/accounts/accounts.json index 13a44ac..7b8a15a 100644 --- a/config/accounts/accounts.json +++ b/config/accounts/accounts.json @@ -2,6 +2,7 @@ "accounts": [ { "name": "MarketingTeam", + "contentType":"productivity", "role": "marketing", "credentials": { "apiKey": "MARKETING_X_API_KEY", @@ -9,18 +10,17 @@ "accessToken": "MARKETING_ACCESS_TOKEN", "accessSecret": "MARKETING_ACCESS_SECRET" }, - "guardrails": { "preventDuplicates": true, "minIntervalMinutes": 30, - "dailyLimit": 5, + "dailyLimit": 10, "blockWords": [ "spam", "illegal", "hate" ], - "lastActionTimestamp": "2025-12-15T07:34:35.102Z", - "actionsToday": 4 + "lastActionTimestamp": "2026-03-22T14:45:42.112Z", + "actionsToday": 1 }, "defaults": { "maxPostsPerDay": 1, @@ -30,19 +30,20 @@ { "id": "mkt-post-1", "type": "post", - "content": "Big news! Our new feature drops this Friday. Stay tuned!", + "useAI": true, "schedule": "0 9 * * *" }, { "id": "mkt-post-2", "type": "post", - "content": "Pro tip: Use our product to save time.", + "useAI": true, "schedule": "0 13 * * *" } ] }, { "name": "CommunityManager", + "contentType":"growth", "role": "community_manager", "credentials": { "apiKey": "COMMUNITY_X_API_KEY", @@ -53,7 +54,7 @@ "guardrails": { "preventDuplicates": true, "minIntervalMinutes": 30, - "dailyLimit": 5, + "dailyLimit": 10, "blockWords": [ "spam", "illegal", @@ -82,6 +83,7 @@ }, { "name": "FounderAccount", + "contentType":"developer", "role": "founder", "credentials": { "apiKey": "FOUNDER_X_API_KEY", @@ -97,7 +99,9 @@ "scam", "illegal", "hate" - ] + ], + "lastActionTimestamp": "2026-03-22T14:45:42.142Z", + "actionsToday": 1 }, "defaults": { "maxPostsPerDay": 5 @@ -106,7 +110,7 @@ { "id": "founder-post-1", "type": "post", - "content": "Leadership means building a team that can execute consistently. Proud of ours.", + "useAI": true, "schedule": "0 8 * * *" }, { diff --git a/src/ai/generator.ts b/src/ai/generator.ts new file mode 100644 index 0000000..99fce00 --- /dev/null +++ b/src/ai/generator.ts @@ -0,0 +1,32 @@ +const developer = [ + "Debugging is where real learning happens.", + "You don’t need more tutorials, you need more projects.", + "Build first, optimize later.", + "Your code improves with every mistake you fix.", +]; + +const productivity = [ + "Focus beats motivation every time.", + "Deep work creates real progress.", + "Consistency > intensity.", +]; + +const growth = [ + "Start before you feel ready.", + "Every expert was once a beginner.", + "Progress compounds over time.", +]; + +function randomFrom(arr: string[]) { + return arr[Math.floor(Math.random() * arr.length)]; +} + +export function generateTweet(type?: string): string { + if (type === "developer") return randomFrom(developer); + if (type === "productivity") return randomFrom(productivity); + if (type === "growth") return randomFrom(growth); + + // fallback + const categories = [developer, productivity, growth]; + return randomFrom(categories[Math.floor(Math.random() * categories.length)]); +} \ No newline at end of file diff --git a/src/core/autonomous.ts b/src/core/autonomous.ts new file mode 100644 index 0000000..78c8e83 --- /dev/null +++ b/src/core/autonomous.ts @@ -0,0 +1,29 @@ +import { loadAccountsConfig } from "../utils/configLoader"; +import { executePost } from "../executors/postExecutor"; + +function getRandomDelay(minMinutes: number, maxMinutes: number) { + const min = minMinutes * 60 * 1000; + const max = maxMinutes * 60 * 1000; + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +export async function startAutonomousMode() { + const data = await loadAccountsConfig(); + + console.log("[AUTO] Autonomous mode started..."); + + while (true) { + for (const account of data.accounts) { + const postAction = account.actions.find(a => a.type === "post"); + + if (postAction) { + await executePost(account, postAction); + } + } + + const delay = getRandomDelay(1, 2); //change it to initial i.e 30,90 + console.log(`[AUTO] Next cycle in ${Math.round(delay / 60000)} mins`); + + await new Promise(res => setTimeout(res, delay)); + } +} \ No newline at end of file diff --git a/src/core/scheduler.ts b/src/core/scheduler.ts index 9a06866..9086e12 100644 --- a/src/core/scheduler.ts +++ b/src/core/scheduler.ts @@ -60,7 +60,7 @@ async function isDuplicate(account: AccountConfig, action: Action): Promise l.account === account.name && l.action === "post"); - const content = action.content?.trim(); + const content = action.contentType?.trim(); if (!content) return false; for (let i = recent.length - 1; i >= Math.max(0, recent.length - 10); i--) { const entry = recent[i]; diff --git a/src/executors/postExecutor.ts b/src/executors/postExecutor.ts index 004af9e..731e975 100644 --- a/src/executors/postExecutor.ts +++ b/src/executors/postExecutor.ts @@ -3,6 +3,7 @@ import { getTwitterClient } from "../services/twitterClient"; import { Action, AccountConfig } from "../types"; import { checkGuardrails } from "../utils/guardrails"; import { saveAccountGuardrails } from "../utils/configLoader"; // <-- NEW +import { generateTweet } from "../ai/generator"; /** * Executes scheduled/automated post actions @@ -10,8 +11,10 @@ import { saveAccountGuardrails } from "../utils/configLoader"; // <-- NEW */ export async function executePost(account: AccountConfig, action: Action): Promise { const client = getTwitterClient(account.credentials); - const content = (action.content || "").trim(); - +const content = action.useAI + ? generateTweet((account as any).contentType) + : action.contentType?.trim() || ""; // Ensure content is a string and trim whitespace + // BASIC VALIDATIONS if (!content) { await appendLog({ diff --git a/src/executors/replyExecutor.ts b/src/executors/replyExecutor.ts index a4bcc90..abb4997 100644 --- a/src/executors/replyExecutor.ts +++ b/src/executors/replyExecutor.ts @@ -5,7 +5,7 @@ import { AccountConfig, Action } from "../types"; export async function executeReply(account: AccountConfig, action: Action): Promise { const client = getTwitterClient(account.credentials); const target = action.targetId; - const content = (action.content || "").trim(); + const content = (action.contentType || "").trim(); if (!target) { await appendLog({ diff --git a/src/index.ts b/src/index.ts index 4a0d608..1c04c08 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,14 +5,21 @@ import dotenv from "dotenv"; import { startScheduler } from "./core/scheduler"; +import { startAutonomousMode } from "./core/autonomous"; dotenv.config(); async function main() { - console.log("Starting XBOT scheduler..."); - await startScheduler(); - // Keep process alive for scheduler - process.stdin.resume(); + const mode = process.env.MODE || "scheduler"; + console.log(`Starting XBOT in ${mode} mode...`); + + if (mode === "auto") { + await startAutonomousMode(); + } else { + await startScheduler(); + // Keep process alive for scheduler + process.stdin.resume(); + } } main().catch((err) => { diff --git a/src/types.ts b/src/types.ts index 5531b45..406eb62 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,9 +3,10 @@ export type ActionType = "post" | "like" | "reply" | "retweet"; export interface Action { id: string; type: ActionType; - content?: string; + contentType?: string; targetId?: string; schedule: string; + useAI?: boolean; } // Guardrail configuration interface