Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions config/accounts/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
"accounts": [
{
"name": "MarketingTeam",
"contentType":"productivity",
"role": "marketing",
"credentials": {
"apiKey": "MARKETING_X_API_KEY",
"apiSecret": "MARKETING_X_API_SECRET",
"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,
Expand All @@ -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",
Expand All @@ -53,7 +54,7 @@
"guardrails": {
"preventDuplicates": true,
"minIntervalMinutes": 30,
"dailyLimit": 5,
"dailyLimit": 10,
"blockWords": [
"spam",
"illegal",
Expand Down Expand Up @@ -82,6 +83,7 @@
},
{
"name": "FounderAccount",
"contentType":"developer",
"role": "founder",
"credentials": {
"apiKey": "FOUNDER_X_API_KEY",
Expand All @@ -97,7 +99,9 @@
"scam",
"illegal",
"hate"
]
],
"lastActionTimestamp": "2026-03-22T14:45:42.142Z",
"actionsToday": 1
},
"defaults": {
"maxPostsPerDay": 5
Expand All @@ -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 * * *"
},
{
Expand Down
32 changes: 32 additions & 0 deletions src/ai/generator.ts
Original file line number Diff line number Diff line change
@@ -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)]);
}
29 changes: 29 additions & 0 deletions src/core/autonomous.ts
Original file line number Diff line number Diff line change
@@ -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));
}
}
2 changes: 1 addition & 1 deletion src/core/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async function isDuplicate(account: AccountConfig, action: Action): Promise<bool
if (action.type !== "post") return false;
const logs = await readLogs(50);
const recent = logs.filter((l) => 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];
Expand Down
7 changes: 5 additions & 2 deletions src/executors/postExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ 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
* Applies guardrails and logs everything
*/
export async function executePost(account: AccountConfig, action: Action): Promise<void> {
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({
Expand Down
2 changes: 1 addition & 1 deletion src/executors/replyExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AccountConfig, Action } from "../types";
export async function executeReply(account: AccountConfig, action: Action): Promise<void> {
const client = getTwitterClient(account.credentials);
const target = action.targetId;
const content = (action.content || "").trim();
const content = (action.contentType || "").trim();

if (!target) {
await appendLog({
Expand Down
15 changes: 11 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down