social-light 0.0.1

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.
@@ -0,0 +1,260 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import Database from 'better-sqlite3';
5
+ import { getConfig } from './config.mjs';
6
+
7
+ /**
8
+ * Check if database tables are initialized
9
+ * @param {Object} db - Database connection
10
+ * @returns {boolean} True if tables are initialized
11
+ */
12
+ const checkTablesInitialized = (db) => {
13
+ try {
14
+ const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='posts';").get();
15
+ return Boolean(tableExists);
16
+ } catch (error) {
17
+ console.error('Error checking database tables:', error);
18
+ return false;
19
+ }
20
+ };
21
+
22
+ /**
23
+ * Get database connection
24
+ * @returns {Object} Database connection
25
+ * @example
26
+ * const db = getDb();
27
+ * const posts = db.prepare('SELECT * FROM posts').all();
28
+ */
29
+ export const getDb = () => {
30
+ const config = getConfig();
31
+
32
+ // Resolve path with home directory if needed
33
+ const dbPath = config.dbPath.replace(/^~/, os.homedir());
34
+
35
+ // Ensure directory exists
36
+ fs.ensureDirSync(path.dirname(dbPath));
37
+
38
+ // Connect to database
39
+ const db = new Database(dbPath);
40
+
41
+ // Check if tables exist and initialize if needed
42
+ if (!checkTablesInitialized(db)) {
43
+ // Create tables directly to avoid circular dependency
44
+ try {
45
+ db.exec(`
46
+ CREATE TABLE IF NOT EXISTS posts (
47
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ title TEXT,
49
+ content TEXT NOT NULL,
50
+ platforms TEXT,
51
+ publish_date TEXT,
52
+ published INTEGER DEFAULT 0,
53
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
54
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS logs (
58
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
59
+ action TEXT NOT NULL,
60
+ details TEXT,
61
+ timestamp TEXT DEFAULT CURRENT_TIMESTAMP
62
+ );
63
+ `);
64
+ } catch (error) {
65
+ console.error('Error initializing database tables:', error);
66
+ }
67
+ }
68
+
69
+ return db;
70
+ };
71
+
72
+ /**
73
+ * Initialize database schema
74
+ * @param {Object} existingDb - Optional existing database connection
75
+ * @returns {boolean} True if successful
76
+ * @example
77
+ * const success = initializeDb();
78
+ */
79
+ export const initializeDb = (existingDb = null) => {
80
+ // Use provided database connection or get a new one
81
+ const db = existingDb || getDb();
82
+
83
+ try {
84
+ // Create posts table if it doesn't exist
85
+ db.exec(`
86
+ CREATE TABLE IF NOT EXISTS posts (
87
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
88
+ title TEXT,
89
+ content TEXT NOT NULL,
90
+ platforms TEXT,
91
+ publish_date TEXT,
92
+ published INTEGER DEFAULT 0,
93
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
94
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
95
+ );
96
+
97
+ CREATE TABLE IF NOT EXISTS logs (
98
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
99
+ action TEXT NOT NULL,
100
+ details TEXT,
101
+ timestamp TEXT DEFAULT CURRENT_TIMESTAMP
102
+ );
103
+ `);
104
+
105
+ return true;
106
+ } catch (error) {
107
+ console.error('Error initializing database:', error);
108
+ return false;
109
+ }
110
+ };
111
+
112
+ /**
113
+ * Get all posts with optional filters
114
+ * @param {Object} filters - Query filters
115
+ * @param {boolean} filters.published - Filter by published status
116
+ * @returns {Array} Array of post objects
117
+ * @example
118
+ * const unpublishedPosts = getPosts({ published: false });
119
+ */
120
+ export const getPosts = (filters = {}) => {
121
+ const db = getDb();
122
+
123
+ let query = 'SELECT * FROM posts';
124
+ const params = [];
125
+
126
+ // Apply filters
127
+ if (filters.published !== undefined) {
128
+ query += ' WHERE published = ?';
129
+ params.push(Number(filters.published));
130
+ }
131
+
132
+ // Add ordering
133
+ query += ' ORDER BY publish_date ASC';
134
+
135
+ return db.prepare(query).all(...params);
136
+ };
137
+
138
+ /**
139
+ * Get a single post by ID
140
+ * @param {number} id - Post ID
141
+ * @returns {Object|null} Post object or null if not found
142
+ * @example
143
+ * const post = getPostById(1);
144
+ */
145
+ export const getPostById = (id) => {
146
+ const db = getDb();
147
+ return db.prepare('SELECT * FROM posts WHERE id = ?').get(id);
148
+ };
149
+
150
+ /**
151
+ * Create a new post
152
+ * @param {Object} post - Post object
153
+ * @returns {number} ID of the created post
154
+ * @example
155
+ * const postId = createPost({
156
+ * title: 'My first post',
157
+ * content: 'Hello world!',
158
+ * platforms: 'Twitter,Bluesky',
159
+ * publish_date: '2023-01-01'
160
+ * });
161
+ */
162
+ export const createPost = (post) => {
163
+ const db = getDb();
164
+
165
+ const { title, content, platforms, publish_date } = post;
166
+
167
+ const result = db.prepare(`
168
+ INSERT INTO posts (title, content, platforms, publish_date)
169
+ VALUES (?, ?, ?, ?)
170
+ `).run(title, content, platforms, publish_date);
171
+
172
+ return result.lastInsertRowid;
173
+ };
174
+
175
+ /**
176
+ * Update an existing post
177
+ * @param {number} id - Post ID
178
+ * @param {Object} updates - Fields to update
179
+ * @returns {boolean} True if successful
180
+ * @example
181
+ * const success = updatePost(1, {
182
+ * title: 'Updated title',
183
+ * content: 'Updated content'
184
+ * });
185
+ */
186
+ export const updatePost = (id, updates) => {
187
+ const db = getDb();
188
+
189
+ // Build update query dynamically
190
+ const fields = Object.keys(updates).filter(field =>
191
+ ['title', 'content', 'platforms', 'publish_date', 'published'].includes(field)
192
+ );
193
+
194
+ if (fields.length === 0) return false;
195
+
196
+ const setClause = fields.map(field => `${field} = ?`).join(', ');
197
+ const params = fields.map(field => updates[field]);
198
+
199
+ // Add updated_at
200
+ const query = `
201
+ UPDATE posts
202
+ SET ${setClause}, updated_at = CURRENT_TIMESTAMP
203
+ WHERE id = ?
204
+ `;
205
+
206
+ params.push(id);
207
+
208
+ const result = db.prepare(query).run(...params);
209
+ return result.changes > 0;
210
+ };
211
+
212
+ /**
213
+ * Mark posts as published
214
+ * @param {Array|number} ids - Post ID or array of post IDs
215
+ * @returns {number} Number of posts updated
216
+ * @example
217
+ * const count = markAsPublished([1, 2, 3]);
218
+ */
219
+ export const markAsPublished = (ids) => {
220
+ const db = getDb();
221
+
222
+ if (!Array.isArray(ids)) {
223
+ ids = [ids];
224
+ }
225
+
226
+ let totalChanges = 0;
227
+
228
+ // Use a transaction for bulk updates
229
+ const updateStmt = db.prepare(`
230
+ UPDATE posts
231
+ SET published = 1, updated_at = CURRENT_TIMESTAMP
232
+ WHERE id = ?
233
+ `);
234
+
235
+ const transaction = db.transaction((postIds) => {
236
+ for (const id of postIds) {
237
+ const result = updateStmt.run(id);
238
+ totalChanges += result.changes;
239
+ }
240
+ return totalChanges;
241
+ });
242
+
243
+ return transaction(ids);
244
+ };
245
+
246
+ /**
247
+ * Log an action to the database
248
+ * @param {string} action - Action name
249
+ * @param {Object} details - Additional details (will be JSON.stringified)
250
+ * @example
251
+ * logAction('post_created', { postId: 1, platforms: ['Twitter'] });
252
+ */
253
+ export const logAction = (action, details = {}) => {
254
+ const db = getDb();
255
+
256
+ db.prepare(`
257
+ INSERT INTO logs (action, details)
258
+ VALUES (?, ?)
259
+ `).run(action, JSON.stringify(details));
260
+ };
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { getConfig, updateConfig } from "./config.mjs";
4
+ import fs from "fs-extra";
5
+ import path from "path";
6
+ import os from "os";
7
+
8
+ // This is a utility script to properly set the platformConfigs in config.json
9
+ console.log("Fixing configuration file...");
10
+
11
+ // Get the current config
12
+ const config = getConfig();
13
+ console.log("Current config:", config);
14
+
15
+ // Create platformConfigs if it doesn't exist
16
+ if (!config.platformConfigs) {
17
+ config.platformConfigs = {};
18
+ }
19
+
20
+ // Get environment variables for Bluesky
21
+ const blueskyHandle = process.env.BLUESKY_HANDLE;
22
+ const blueskyPassword = process.env.BLUESKY_APP_PASSWORD;
23
+ const pdsHost = process.env.PDSHOST || "https://bsky.social";
24
+
25
+ // Check if we have Bluesky credentials in environment variables
26
+ if (blueskyHandle && blueskyPassword) {
27
+ console.log("Found Bluesky credentials in environment variables");
28
+
29
+ // Add Bluesky configuration to platformConfigs
30
+ config.platformConfigs.bluesky = {
31
+ handle: blueskyHandle,
32
+ password: blueskyPassword,
33
+ pdshost: pdsHost,
34
+ };
35
+
36
+ // Ensure Bluesky is in defaultPlatforms
37
+ if (
38
+ !config.defaultPlatforms ||
39
+ !config.defaultPlatforms.includes("bluesky")
40
+ ) {
41
+ config.defaultPlatforms = ["bluesky"];
42
+ }
43
+ } else {
44
+ console.log("No Bluesky credentials found in environment variables");
45
+
46
+ // Prompt user for credentials if in interactive mode
47
+ if (process.stdout.isTTY) {
48
+ console.log(
49
+ "Please run the following command to set up Bluesky credentials:"
50
+ );
51
+ console.log("social-light init");
52
+ }
53
+ }
54
+
55
+ // Save the updated config
56
+ const configPath = path.join(os.homedir(), ".social-light", "config.json");
57
+ fs.writeJsonSync(configPath, config, { spaces: 2 });
58
+
59
+ console.log("Updated config:", config);
60
+ console.log("Configuration file updated successfully!");
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Base Social Platform API Interface
3
+ * All platform-specific API implementations should extend this class
4
+ */
5
+ export class SocialPlatform {
6
+ /**
7
+ * Constructor for the base social platform
8
+ * @param {Object} config - Platform-specific configuration
9
+ */
10
+ constructor(config = {}) {
11
+ this.config = config;
12
+ this.name = "base";
13
+ this.authenticated = false;
14
+ }
15
+
16
+ /**
17
+ * Check if platform is properly configured
18
+ * @returns {boolean} True if platform is configured
19
+ */
20
+ isConfigured() {
21
+ return false;
22
+ }
23
+
24
+ /**
25
+ * Authenticate with the platform API
26
+ * @returns {Promise<boolean>} True if authentication successful
27
+ */
28
+ async authenticate() {
29
+ this.authenticated = false;
30
+ return false;
31
+ }
32
+
33
+ /**
34
+ * Post content to the platform
35
+ * @param {Object} post - Post content and metadata
36
+ * @param {string} post.text - Text content of the post
37
+ * @param {string} post.title - Title/caption of the post (if applicable)
38
+ * @param {Array<string>} post.mediaUrls - Array of media URLs to attach
39
+ * @param {Object} post.options - Platform-specific options
40
+ * @returns {Promise<Object>} Response data including post ID or errors
41
+ */
42
+ async post(post) {
43
+ throw new Error("Method not implemented");
44
+ }
45
+
46
+ /**
47
+ * Get status of a post
48
+ * @param {string} postId - ID of the post to check
49
+ * @returns {Promise<Object>} Status information
50
+ */
51
+ async getPostStatus(postId) {
52
+ throw new Error("Method not implemented");
53
+ }
54
+
55
+ /**
56
+ * Delete a post from the platform
57
+ * @param {string} postId - ID of the post to delete
58
+ * @returns {Promise<boolean>} True if deletion successful
59
+ */
60
+ async deletePost(postId) {
61
+ throw new Error("Method not implemented");
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Placeholder for unsupported platforms
67
+ */
68
+ class UnsupportedPlatform extends SocialPlatform {
69
+ /**
70
+ * Constructor for unsupported platform
71
+ * @param {string} platformName - Name of the unsupported platform
72
+ * @param {Object} config - Configuration
73
+ */
74
+ constructor(platformName, config = {}) {
75
+ super(config);
76
+ this.name = platformName.toLowerCase();
77
+ this.authenticated = false;
78
+ }
79
+
80
+ /**
81
+ * Check if platform is properly configured
82
+ * @returns {boolean} Always returns false
83
+ */
84
+ isConfigured() {
85
+ return false;
86
+ }
87
+
88
+ /**
89
+ * Authentication attempt (always fails)
90
+ * @returns {Promise<boolean>} Always returns false
91
+ */
92
+ async authenticate() {
93
+ console.warn(`Platform '${this.name}' is not currently supported.`);
94
+ return false;
95
+ }
96
+
97
+ /**
98
+ * Post attempt (always fails)
99
+ * @returns {Promise<Object>} Error response
100
+ */
101
+ async post() {
102
+ throw new Error(`Platform '${this.name}' is not currently supported.`);
103
+ }
104
+
105
+ /**
106
+ * Get status attempt (always fails)
107
+ * @returns {Promise<Object>} Error response
108
+ */
109
+ async getPostStatus() {
110
+ throw new Error(`Platform '${this.name}' is not currently supported.`);
111
+ }
112
+
113
+ /**
114
+ * Delete attempt (always fails)
115
+ * @returns {Promise<boolean>} Always returns false
116
+ */
117
+ async deletePost() {
118
+ throw new Error(`Platform '${this.name}' is not currently supported.`);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Factory for creating platform instances
124
+ */
125
+ export const PlatformFactory = {
126
+ /**
127
+ * Create a new platform instance
128
+ * @param {string} platform - Platform name (only 'bluesky' is supported)
129
+ * @param {Object} config - Platform-specific configuration
130
+ * @returns {SocialPlatform} Platform instance
131
+ */
132
+ create(platform, config = {}) {
133
+ switch (platform.toLowerCase()) {
134
+ case "bluesky":
135
+ return import("./bluesky.mjs").then(
136
+ (module) => new module.BlueskyPlatform(config)
137
+ );
138
+ default:
139
+ throw new Error(`Unsupported platform: ${platform}`);
140
+ }
141
+ },
142
+ };