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.
- package/.env.example +7 -0
- package/.instructions/checklist.md +45 -0
- package/.instructions/prd.md +182 -0
- package/.instructions/summary.md +122 -0
- package/README.md +196 -0
- package/bin/socialite +7 -0
- package/delete/tiktok.mjs +315 -0
- package/delete/twitter.mjs +258 -0
- package/package.json +51 -0
- package/server.png +0 -0
- package/src/commands/create.mjs +274 -0
- package/src/commands/edit.mjs +198 -0
- package/src/commands/init.mjs +256 -0
- package/src/commands/publish.mjs +192 -0
- package/src/commands/published.mjs +90 -0
- package/src/commands/unpublished.mjs +102 -0
- package/src/index.mjs +107 -0
- package/src/server/client/index.html +41 -0
- package/src/server/client/logo.jpg +0 -0
- package/src/server/client/main.mjs +953 -0
- package/src/server/client/styles.css +535 -0
- package/src/server/index.mjs +315 -0
- package/src/utils/ai.mjs +201 -0
- package/src/utils/config.mjs +127 -0
- package/src/utils/db.mjs +260 -0
- package/src/utils/fix-config.mjs +60 -0
- package/src/utils/social/base.mjs +142 -0
- package/src/utils/social/bluesky.mjs +302 -0
- package/src/utils/social/index.mjs +300 -0
package/src/utils/db.mjs
ADDED
@@ -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
|
+
};
|