UNPKG

@defikitdotnet/x-ai-combat

Version:

XCombatAI - Social Media Engagement Template for the Agent Framework

555 lines (548 loc) 20 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.XAIAdapter = exports.sqliteTables = void 0; const logger_1 = require("../utils/logger"); /** * SQL schema for x-ai-combat tables */ exports.sqliteTables = ` PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; -- Users table for storing Twitter users CREATE TABLE IF NOT EXISTS "users" ( "id" TEXT PRIMARY KEY, "twitterId" TEXT NOT NULL, "username" TEXT NOT NULL, "linkedAt" TEXT NOT NULL, "updatedAt" TEXT, "agentId" TEXT REFERENCES accounts(id) ON DELETE CASCADE ); -- Create index for faster queries CREATE INDEX IF NOT EXISTS "idx_users_twitterId" ON "users" ("twitterId"); CREATE INDEX IF NOT EXISTS "idx_users_agentId" ON "users" ("agentId"); -- Posts table for storing Twitter posts with stats CREATE TABLE IF NOT EXISTS "posts" ( "id" TEXT PRIMARY KEY, "tweetId" TEXT NOT NULL, "twitterUserId" TEXT NOT NULL, "userId" TEXT, "content" TEXT NOT NULL, "raw" TEXT NOT NULL, "stats" TEXT DEFAULT '{"likes":0,"retweets":0,"quotes":0,"replies":0,"total":0,"famePoints":0}', "lastInteractionAt" TEXT, "createdAt" TEXT NOT NULL, "updatedAt" TEXT, "agentId" TEXT REFERENCES accounts(id) ON DELETE CASCADE ); -- Create indexes for faster queries CREATE INDEX IF NOT EXISTS "idx_posts_tweetId" ON "posts" ("tweetId"); CREATE INDEX IF NOT EXISTS "idx_posts_twitterUserId" ON "posts" ("twitterUserId"); CREATE INDEX IF NOT EXISTS "idx_posts_userId" ON "posts" ("userId"); CREATE INDEX IF NOT EXISTS "idx_posts_agentId" ON "posts" ("agentId"); -- Replied tweets table for tracking which tweets have been replied to CREATE TABLE IF NOT EXISTS "replied_tweets" ( "tweetId" TEXT PRIMARY KEY, "repliedAt" TEXT NOT NULL, "agentId" TEXT REFERENCES accounts(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS "idx_replied_tweets_agentId" ON "replied_tweets" ("agentId"); COMMIT; `; /** * XAIAdapter - Consolidated adapter for x-ai-combat module * Implements database operations for User, Post, and Interaction models */ class XAIAdapter { /** * Initialize XAIAdapter with SQLite database */ constructor(databaseAdapter) { this.databaseAdapter = databaseAdapter; } /** * Initialize the database schema for x-ai-combat tables */ init() { try { this.databaseAdapter.db.exec(exports.sqliteTables); (0, logger_1.log)("X-AI-Combat database tables initialized successfully"); } catch (err) { (0, logger_1.error)("Failed to initialize x-ai-combat database tables:", err); throw err; } } /** * Get the underlying database instance */ getDatabase() { return this.databaseAdapter; } /** * Get the database adapter - required for ModuleAdapterInterface */ getDatabaseAdapter() { return this.databaseAdapter; } // ====== Tweet Reply Tracking Methods ====== /** * Mark a tweet as having been replied to */ async markInteractionAsReplied(tweetId, agentId) { try { const sql = ` INSERT OR REPLACE INTO replied_tweets (tweetId, repliedAt, agentId) VALUES (?, ?, ?) `; const stmt = this.databaseAdapter.db.prepare(sql); stmt.run(tweetId, new Date().toISOString(), agentId || null); return true; } catch (error) { console.error("Error marking tweet as replied:", error); return false; } } /** * Get all tweets that have been replied to */ async getRepliedInteractions(agentId) { try { let sql = "SELECT * FROM replied_tweets"; let params = []; if (agentId) { sql += " WHERE agentId = ?"; params.push(agentId); } const stmt = this.databaseAdapter.db.prepare(sql); const rows = stmt.all(...params); return rows.map(row => ({ tweetId: row.tweetId, repliedAt: new Date(row.repliedAt), agentId: row.agentId })); } catch (error) { console.error("Error getting replied tweets:", error); return []; } } /** * Check if a tweet has been replied to */ async hasTweetBeenReplied(tweetId) { try { const sql = "SELECT COUNT(*) as count FROM replied_tweets WHERE tweetId = ?"; const stmt = this.databaseAdapter.db.prepare(sql); const result = stmt.get(tweetId); return result.count > 0; } catch (error) { console.error("Error checking if tweet has been replied to:", error); return false; } } // ====== User Methods ====== /** * Find user by ID */ async findUserById(id) { try { const sql = "SELECT * FROM users WHERE id = ?"; const stmt = this.databaseAdapter.db.prepare(sql); const user = stmt.get(id); if (!user) return null; // Convert date strings to Date objects user.linkedAt = new Date(user.linkedAt); if (user.updatedAt) { user.updatedAt = new Date(user.updatedAt); } return user; } catch (error) { console.error("Error finding user by ID:", error); return null; } } /** * Find user by Twitter ID */ async findUserByTwitterId(twitterId, agentId) { try { let sql, params; if (agentId) { sql = "SELECT * FROM users WHERE twitterId = ? AND agentId = ?"; params = [twitterId, agentId]; } else { sql = "SELECT * FROM users WHERE twitterId = ?"; params = [twitterId]; } const stmt = this.databaseAdapter.db.prepare(sql); const user = stmt.get(...params); if (!user) return null; // Convert date strings to Date objects user.linkedAt = new Date(user.linkedAt); if (user.updatedAt) { user.updatedAt = new Date(user.updatedAt); } return user; } catch (error) { console.error("Error finding user by Twitter ID:", error); return null; } } /** * Create or update a user */ async createUser(user) { try { const sql = ` INSERT OR REPLACE INTO users (id, twitterId, username, linkedAt, updatedAt, agentId) VALUES (?, ?, ?, ?, ?, ?) `; const stmt = this.databaseAdapter.db.prepare(sql); stmt.run(user.id, user.twitterId, user.username, user.linkedAt.toISOString(), user.updatedAt ? user.updatedAt.toISOString() : null, user.agentId || null); // After creating a user, update any posts or interactions associated with this twitter ID this.linkPostsToUser(user.twitterId, user.id); return true; } catch (error) { console.error("Error creating user:", error); return false; } } /** * Link existing posts to a user when they connect their account */ async linkPostsToUser(twitterId, userId) { try { const sql = `UPDATE posts SET userId = ? WHERE twitterUserId = ? AND (userId IS NULL OR userId = '')`; const stmt = this.databaseAdapter.db.prepare(sql); stmt.run(userId, twitterId); (0, logger_1.log)(`Linked posts with Twitter ID ${twitterId} to user ${userId}`); } catch (err) { (0, logger_1.error)(`Error linking posts to user:`, err); } } /** * Update an existing user */ async updateUser(user) { try { const sql = ` UPDATE users SET twitterId = ?, username = ?, linkedAt = ?, updatedAt = ? WHERE id = ? `; const stmt = this.databaseAdapter.db.prepare(sql); stmt.run(user.twitterId, user.username, user.linkedAt.toISOString(), user.updatedAt ? user.updatedAt.toISOString() : null, user.id); return true; } catch (error) { console.error("Error updating user:", error); return false; } } /** * Get all users */ async getAllUsers(agentId) { try { let sql = "SELECT * FROM users"; let params = []; if (agentId) { sql += " WHERE agentId = ?"; params.push(agentId); } const stmt = this.databaseAdapter.db.prepare(sql); const rows = stmt.all(...params); return rows.map(row => { return { id: row.id, twitterId: row.twitterId, username: row.username, linkedAt: new Date(row.linkedAt), updatedAt: row.updatedAt ? new Date(row.updatedAt) : undefined, agentId: row.agentId }; }); } catch (error) { console.error("Error getting all users:", error); return []; } } // ====== Post Methods ====== /** * Find post by ID */ async findPostById(id) { try { const sql = "SELECT * FROM posts WHERE id = ?"; const stmt = this.databaseAdapter.db.prepare(sql); const post = stmt.get(id); if (!post) return null; // Convert date strings to Date objects post.createdAt = new Date(post.createdAt); if (post.updatedAt) { post.updatedAt = new Date(post.updatedAt); } if (post.lastInteractionAt) { post.lastInteractionAt = new Date(post.lastInteractionAt); } // Parse stats JSON if (typeof post.stats === 'string') { post.stats = JSON.parse(post.stats); } return post; } catch (error) { console.error("Error finding post by ID:", error); return null; } } /** * Find post by Tweet ID */ async findPostByTweetId(tweetId) { try { const sql = "SELECT * FROM posts WHERE tweetId = ?"; const stmt = this.databaseAdapter.db.prepare(sql); const post = stmt.get(tweetId); if (!post) return null; // Convert date strings to Date objects post.createdAt = new Date(post.createdAt); if (post.updatedAt) { post.updatedAt = new Date(post.updatedAt); } if (post.lastInteractionAt) { post.lastInteractionAt = new Date(post.lastInteractionAt); } // Parse stats JSON if (typeof post.stats === 'string') { post.stats = JSON.parse(post.stats); } return post; } catch (error) { console.error("Error finding post by Tweet ID:", error); return null; } } /** * Create or update a post */ async createPost(post) { try { // Ensure stats is converted to a string if it's an object const statsStr = typeof post.stats === 'object' ? JSON.stringify(post.stats) : post.stats; const sql = ` INSERT OR REPLACE INTO posts ( id, tweetId, twitterUserId, userId, content, raw, stats, lastInteractionAt, createdAt, updatedAt, agentId ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const stmt = this.databaseAdapter.db.prepare(sql); stmt.run(post.id, post.tweetId, post.twitterUserId, post.userId || null, post.content, post.raw, statsStr, post.lastInteractionAt ? post.lastInteractionAt.toISOString() : null, post.createdAt.toISOString(), post.updatedAt ? post.updatedAt.toISOString() : null, post.agentId || null); return true; } catch (error) { console.error("Error creating post:", error); return false; } } /** * Get posts by user ID with pagination */ async getPostsByUserId(userId, page = 1, limit = 10, agentId) { try { const offset = (page - 1) * limit; // Add optional agentId filter const agentFilter = agentId ? 'AND agentId = ?' : ''; const params = agentId ? [userId, agentId, limit, offset] : [userId, limit, offset]; const sql = ` SELECT * FROM posts WHERE userId = ? ${agentFilter} ORDER BY createdAt DESC LIMIT ? OFFSET ? `; const stmt = this.databaseAdapter.db.prepare(sql); const rows = stmt.all(...params); // Count total posts const countSql = `SELECT COUNT(*) as count FROM posts WHERE userId = ? ${agentFilter}`; const countStmt = this.databaseAdapter.db.prepare(countSql); const countParams = agentId ? [userId, agentId] : [userId]; const countResult = countStmt.get(...countParams); const posts = rows.map(row => this.parsePostRow(row)); return { posts, total: countResult.count }; } catch (error) { console.error("Error getting posts by user ID:", error); return { posts: [], total: 0 }; } } /** * Get all posts from the database */ async getAllPosts(agentId) { try { let sql = "SELECT * FROM posts"; let params = []; if (agentId) { sql += " WHERE agentId = ?"; params.push(agentId); } sql += " ORDER BY createdAt DESC"; const stmt = this.databaseAdapter.db.prepare(sql); const rows = stmt.all(...params); return rows.map(row => this.parsePostRow(row)); } catch (error) { console.error("Error getting all posts:", error); return []; } } /** * Get posts by Twitter user ID */ async getPostsByTwitterUserId(twitterUserId, agentId) { try { let sql = "SELECT * FROM posts WHERE twitterUserId = ?"; let params = [twitterUserId]; if (agentId) { sql += " AND agentId = ?"; params.push(agentId); } sql += " ORDER BY createdAt DESC"; const stmt = this.databaseAdapter.db.prepare(sql); const rows = stmt.all(...params); return rows.map(row => this.parsePostRow(row)); } catch (error) { console.error("Error getting posts by Twitter user ID:", error); return []; } } /** * Get leaderboard data with user fame points and stats */ async getLeaderboard(period = 'all', limit = 10, agentId) { try { // Get the SQL condition for the date period const periodLabel = this.getPeriodLabel(period); // Add the agentId condition to the SQL query if provided let agentCondition = ''; let params = []; if (agentId) { agentCondition = 'AND p.agentId = ? '; params.push(agentId); } // Calculate stats for all users with Twitter interactions const sql = ` WITH user_stats AS ( SELECT p.userId, u.username, u.twitterId, SUM(json_extract(p.stats, '$.famePoints')) as famePoints, SUM(json_extract(p.stats, '$.likes')) as likes, SUM(json_extract(p.stats, '$.retweets')) as retweets, SUM(json_extract(p.stats, '$.quotes')) as quotes, SUM(json_extract(p.stats, '$.replies')) as replies, MAX(p.lastInteractionAt) as lastActive FROM posts p LEFT JOIN users u ON p.userId = u.id WHERE p.userId IS NOT NULL ${periodLabel ? `AND ${periodLabel}` : ''} ${agentCondition} GROUP BY p.userId ) SELECT userId, username, twitterId, famePoints, likes, retweets, quotes, replies, lastActive FROM user_stats ORDER BY famePoints DESC LIMIT ? `; const stmt = this.databaseAdapter.db.prepare(sql); params.push(limit); const rows = stmt.all(...params); // Count total users in the leaderboard (without limit) const countSql = ` SELECT COUNT(DISTINCT p.userId) as total FROM posts p WHERE p.userId IS NOT NULL ${periodLabel ? `AND ${periodLabel}` : ''} ${agentCondition} `; const countStmt = this.databaseAdapter.db.prepare(countSql); const countResult = countStmt.get(...params.slice(0, -1)); return { users: rows.map(row => ({ userId: row.userId, username: row.username, twitterId: row.twitterId, famePoints: row.famePoints || 0, stats: { likes: row.likes || 0, retweets: row.retweets || 0, quotes: row.quotes || 0, replies: row.replies || 0, total: (row.likes || 0) + (row.retweets || 0) + (row.quotes || 0) + (row.replies || 0) }, lastActive: row.lastActive ? new Date(row.lastActive) : undefined })), totalUsers: countResult.total }; } catch (error) { console.error("Error getting leaderboard:", error); return { users: [], totalUsers: 0 }; } } /** * Get period SQL condition for leaderboard */ getPeriodLabel(period) { switch (period) { case 'current_day': return "DATE(p.lastInteractionAt) = DATE('now')"; case 'current_week': return "strftime('%Y-%W', p.lastInteractionAt) = strftime('%Y-%W', 'now')"; case 'current_month': return "strftime('%Y-%m', p.lastInteractionAt) = strftime('%Y-%m', 'now')"; case 'all': default: return ''; // No additional time constraint for all time } } parsePostRow(row) { const post = { id: row.id, tweetId: row.tweetId, twitterUserId: row.twitterUserId, userId: row.userId, content: row.content, raw: row.raw, createdAt: new Date(row.createdAt), updatedAt: row.updatedAt ? new Date(row.updatedAt) : undefined, lastInteractionAt: row.lastInteractionAt ? new Date(row.lastInteractionAt) : undefined, agentId: row.agentId, stats: typeof row.stats === 'string' ? JSON.parse(row.stats) : row.stats }; return post; } } exports.XAIAdapter = XAIAdapter; //# sourceMappingURL=XAIAdapter.js.map