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
@@ -0,0 +1,302 @@
|
|
1
|
+
import { SocialPlatform } from './base.mjs';
|
2
|
+
import fetch from 'node-fetch';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Bluesky Platform API Implementation
|
6
|
+
* Uses Bluesky's AT Protocol for posting and managing content
|
7
|
+
*/
|
8
|
+
export class BlueskyPlatform extends SocialPlatform {
|
9
|
+
/**
|
10
|
+
* Constructor for Bluesky platform
|
11
|
+
* @param {Object} config - Platform-specific configuration
|
12
|
+
* @param {string} config.handle - Bluesky handle (username)
|
13
|
+
* @param {string} config.password - Bluesky app password
|
14
|
+
* @param {string} config.service - Bluesky service URL (default: https://bsky.social)
|
15
|
+
*/
|
16
|
+
constructor(config = {}) {
|
17
|
+
super(config);
|
18
|
+
this.name = 'bluesky';
|
19
|
+
this.service = config.service || 'https://bsky.social';
|
20
|
+
this.authenticated = false;
|
21
|
+
this.session = null;
|
22
|
+
}
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Check if platform is properly configured
|
26
|
+
* @returns {boolean} True if platform is configured
|
27
|
+
*/
|
28
|
+
isConfigured() {
|
29
|
+
// Check config for credentials
|
30
|
+
const hasConfigCreds = Boolean(
|
31
|
+
this.config.handle &&
|
32
|
+
this.config.password
|
33
|
+
);
|
34
|
+
|
35
|
+
// Also check environment variables
|
36
|
+
const hasEnvCreds = Boolean(
|
37
|
+
process.env.BLUESKY_HANDLE &&
|
38
|
+
process.env.BLUESKY_APP_PASSWORD
|
39
|
+
);
|
40
|
+
|
41
|
+
return hasConfigCreds || hasEnvCreds;
|
42
|
+
}
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Authenticate with the Bluesky API
|
46
|
+
* @returns {Promise<boolean>} True if authentication successful
|
47
|
+
*/
|
48
|
+
async authenticate() {
|
49
|
+
if (!this.isConfigured()) {
|
50
|
+
throw new Error('Bluesky API not properly configured');
|
51
|
+
}
|
52
|
+
|
53
|
+
// Get credentials from config or environment variables
|
54
|
+
const handle = this.config.handle || process.env.BLUESKY_HANDLE;
|
55
|
+
const password = this.config.password || process.env.BLUESKY_APP_PASSWORD;
|
56
|
+
const service = this.config.service || process.env.BLUESKY_SERVICE || this.service;
|
57
|
+
|
58
|
+
try {
|
59
|
+
const response = await fetch(`${service}/xrpc/com.atproto.server.createSession`, {
|
60
|
+
method: 'POST',
|
61
|
+
headers: {
|
62
|
+
'Content-Type': 'application/json'
|
63
|
+
},
|
64
|
+
body: JSON.stringify({
|
65
|
+
identifier: handle,
|
66
|
+
password: password
|
67
|
+
})
|
68
|
+
});
|
69
|
+
|
70
|
+
if (!response.ok) {
|
71
|
+
const error = await response.json();
|
72
|
+
throw new Error(`Bluesky authentication failed: ${JSON.stringify(error)}`);
|
73
|
+
}
|
74
|
+
|
75
|
+
this.session = await response.json();
|
76
|
+
this.authenticated = true;
|
77
|
+
return true;
|
78
|
+
} catch (error) {
|
79
|
+
console.error('Bluesky authentication error:', error);
|
80
|
+
this.authenticated = false;
|
81
|
+
throw error;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Post content to Bluesky
|
87
|
+
* @param {Object} post - Post content and metadata
|
88
|
+
* @param {string} post.text - Text content of the post (required)
|
89
|
+
* @param {Array<string>} post.mediaUrls - URLs of media to attach (optional)
|
90
|
+
* @param {Object} post.options - Bluesky-specific options
|
91
|
+
* @returns {Promise<Object>} Response including post URI and CID
|
92
|
+
*/
|
93
|
+
async post(post) {
|
94
|
+
if (!this.authenticated && !await this.authenticate()) {
|
95
|
+
throw new Error('Bluesky authentication required');
|
96
|
+
}
|
97
|
+
|
98
|
+
if (!post.text) {
|
99
|
+
throw new Error('Post text is required');
|
100
|
+
}
|
101
|
+
|
102
|
+
try {
|
103
|
+
// Create basic post record
|
104
|
+
const record = {
|
105
|
+
$type: 'app.bsky.feed.post',
|
106
|
+
text: post.text,
|
107
|
+
createdAt: new Date().toISOString()
|
108
|
+
};
|
109
|
+
|
110
|
+
// Handle languages if specified
|
111
|
+
if (post.options && post.options.langs) {
|
112
|
+
record.langs = Array.isArray(post.options.langs) ? post.options.langs : [post.options.langs];
|
113
|
+
}
|
114
|
+
|
115
|
+
// Handle media attachments if provided
|
116
|
+
if (post.mediaUrls && post.mediaUrls.length > 0) {
|
117
|
+
const images = await Promise.all(
|
118
|
+
post.mediaUrls.map(url => this._uploadImage(url))
|
119
|
+
);
|
120
|
+
|
121
|
+
if (images.length > 0) {
|
122
|
+
record.embed = {
|
123
|
+
$type: 'app.bsky.embed.images',
|
124
|
+
images: images.map(img => ({
|
125
|
+
alt: post.options?.imageAlt || 'Image',
|
126
|
+
image: img
|
127
|
+
}))
|
128
|
+
};
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
// Handle external link embedding if provided
|
133
|
+
if (post.options && post.options.externalLink) {
|
134
|
+
record.embed = {
|
135
|
+
$type: 'app.bsky.embed.external',
|
136
|
+
external: {
|
137
|
+
uri: post.options.externalLink.uri,
|
138
|
+
title: post.options.externalLink.title || '',
|
139
|
+
description: post.options.externalLink.description || ''
|
140
|
+
}
|
141
|
+
};
|
142
|
+
|
143
|
+
if (post.options.externalLink.thumbnailUrl) {
|
144
|
+
const thumb = await this._uploadImage(post.options.externalLink.thumbnailUrl);
|
145
|
+
record.embed.external.thumb = thumb;
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
// Create the post
|
150
|
+
const response = await fetch(`${this.service}/xrpc/com.atproto.repo.createRecord`, {
|
151
|
+
method: 'POST',
|
152
|
+
headers: {
|
153
|
+
'Authorization': `Bearer ${this.session.accessJwt}`,
|
154
|
+
'Content-Type': 'application/json'
|
155
|
+
},
|
156
|
+
body: JSON.stringify({
|
157
|
+
repo: this.config.handle,
|
158
|
+
collection: 'app.bsky.feed.post',
|
159
|
+
record: record
|
160
|
+
})
|
161
|
+
});
|
162
|
+
|
163
|
+
if (!response.ok) {
|
164
|
+
const error = await response.json();
|
165
|
+
throw new Error(`Bluesky post failed: ${JSON.stringify(error)}`);
|
166
|
+
}
|
167
|
+
|
168
|
+
const result = await response.json();
|
169
|
+
return {
|
170
|
+
id: result.uri.split('/').pop(),
|
171
|
+
uri: result.uri,
|
172
|
+
cid: result.cid
|
173
|
+
};
|
174
|
+
} catch (error) {
|
175
|
+
console.error('Bluesky post error:', error);
|
176
|
+
throw error;
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Get status of a post
|
182
|
+
* @param {string} postUri - URI of the post to check
|
183
|
+
* @returns {Promise<Object>} Post status information
|
184
|
+
*/
|
185
|
+
async getPostStatus(postUri) {
|
186
|
+
if (!this.authenticated && !await this.authenticate()) {
|
187
|
+
throw new Error('Bluesky authentication required');
|
188
|
+
}
|
189
|
+
|
190
|
+
try {
|
191
|
+
// Parse URI to get repo and record ID
|
192
|
+
const parts = postUri.split('/');
|
193
|
+
const repo = parts[2];
|
194
|
+
const recordId = parts.pop();
|
195
|
+
const collection = 'app.bsky.feed.post';
|
196
|
+
|
197
|
+
const response = await fetch(`${this.service}/xrpc/com.atproto.repo.getRecord?repo=${repo}&collection=${collection}&rkey=${recordId}`, {
|
198
|
+
method: 'GET',
|
199
|
+
headers: {
|
200
|
+
'Authorization': `Bearer ${this.session.accessJwt}`
|
201
|
+
}
|
202
|
+
});
|
203
|
+
|
204
|
+
if (!response.ok) {
|
205
|
+
const error = await response.json();
|
206
|
+
throw new Error(`Failed to get post status: ${JSON.stringify(error)}`);
|
207
|
+
}
|
208
|
+
|
209
|
+
const result = await response.json();
|
210
|
+
return {
|
211
|
+
uri: postUri,
|
212
|
+
cid: result.cid,
|
213
|
+
record: result.value
|
214
|
+
};
|
215
|
+
} catch (error) {
|
216
|
+
console.error('Bluesky get status error:', error);
|
217
|
+
throw error;
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
/**
|
222
|
+
* Delete a post from Bluesky
|
223
|
+
* @param {string} postUri - URI of the post to delete
|
224
|
+
* @returns {Promise<boolean>} True if deletion successful
|
225
|
+
*/
|
226
|
+
async deletePost(postUri) {
|
227
|
+
if (!this.authenticated && !await this.authenticate()) {
|
228
|
+
throw new Error('Bluesky authentication required');
|
229
|
+
}
|
230
|
+
|
231
|
+
try {
|
232
|
+
// Parse URI to get repo and record ID
|
233
|
+
const parts = postUri.split('/');
|
234
|
+
const repo = parts[2];
|
235
|
+
const recordId = parts.pop();
|
236
|
+
const collection = 'app.bsky.feed.post';
|
237
|
+
|
238
|
+
const response = await fetch(`${this.service}/xrpc/com.atproto.repo.deleteRecord`, {
|
239
|
+
method: 'POST',
|
240
|
+
headers: {
|
241
|
+
'Authorization': `Bearer ${this.session.accessJwt}`,
|
242
|
+
'Content-Type': 'application/json'
|
243
|
+
},
|
244
|
+
body: JSON.stringify({
|
245
|
+
repo,
|
246
|
+
collection,
|
247
|
+
rkey: recordId
|
248
|
+
})
|
249
|
+
});
|
250
|
+
|
251
|
+
if (!response.ok) {
|
252
|
+
const error = await response.json();
|
253
|
+
throw new Error(`Failed to delete post: ${JSON.stringify(error)}`);
|
254
|
+
}
|
255
|
+
|
256
|
+
return true;
|
257
|
+
} catch (error) {
|
258
|
+
console.error('Bluesky delete error:', error);
|
259
|
+
throw error;
|
260
|
+
}
|
261
|
+
}
|
262
|
+
|
263
|
+
/**
|
264
|
+
* Upload an image to Bluesky
|
265
|
+
* @param {string} imageUrl - URL of the image to upload
|
266
|
+
* @returns {Promise<Object>} Blob reference
|
267
|
+
* @private
|
268
|
+
*/
|
269
|
+
async _uploadImage(imageUrl) {
|
270
|
+
try {
|
271
|
+
// Fetch the image data
|
272
|
+
const imageResponse = await fetch(imageUrl);
|
273
|
+
if (!imageResponse.ok) {
|
274
|
+
throw new Error(`Failed to fetch image: ${imageResponse.statusText}`);
|
275
|
+
}
|
276
|
+
|
277
|
+
const imageBuffer = await imageResponse.buffer();
|
278
|
+
const contentType = imageResponse.headers.get('content-type') || 'image/jpeg';
|
279
|
+
|
280
|
+
// Upload the image blob
|
281
|
+
const uploadResponse = await fetch(`${this.service}/xrpc/com.atproto.repo.uploadBlob`, {
|
282
|
+
method: 'POST',
|
283
|
+
headers: {
|
284
|
+
'Authorization': `Bearer ${this.session.accessJwt}`,
|
285
|
+
'Content-Type': contentType
|
286
|
+
},
|
287
|
+
body: imageBuffer
|
288
|
+
});
|
289
|
+
|
290
|
+
if (!uploadResponse.ok) {
|
291
|
+
const error = await uploadResponse.json();
|
292
|
+
throw new Error(`Failed to upload image: ${JSON.stringify(error)}`);
|
293
|
+
}
|
294
|
+
|
295
|
+
const result = await uploadResponse.json();
|
296
|
+
return result.blob;
|
297
|
+
} catch (error) {
|
298
|
+
console.error('Bluesky image upload error:', error);
|
299
|
+
throw error;
|
300
|
+
}
|
301
|
+
}
|
302
|
+
}
|
@@ -0,0 +1,300 @@
|
|
1
|
+
import { PlatformFactory } from "./base.mjs";
|
2
|
+
import { getConfig, getCredentials } from "../config.mjs";
|
3
|
+
import { logAction } from "../db.mjs";
|
4
|
+
import dotenv from 'dotenv';
|
5
|
+
|
6
|
+
// Load environment variables
|
7
|
+
dotenv.config();
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Social API manager for handling multiple platforms
|
11
|
+
*/
|
12
|
+
export class SocialAPI {
|
13
|
+
/**
|
14
|
+
* Constructor for the Social API manager
|
15
|
+
* @param {Object} options - Configuration options
|
16
|
+
*/
|
17
|
+
constructor(options = {}) {
|
18
|
+
this.platforms = new Map();
|
19
|
+
this.config = getConfig();
|
20
|
+
this.options = options;
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Initialize a specific platform
|
25
|
+
* @param {string} platform - Platform name
|
26
|
+
* @param {Object} config - Platform-specific configuration
|
27
|
+
* @returns {Promise<boolean>} True if initialization successful
|
28
|
+
*/
|
29
|
+
async initPlatform(platform, config = {}) {
|
30
|
+
try {
|
31
|
+
// Get credentials from config.json
|
32
|
+
const platformCredentials = getCredentials(platform);
|
33
|
+
|
34
|
+
// Merge provided config and stored credentials
|
35
|
+
const mergedConfig = {
|
36
|
+
...platformCredentials,
|
37
|
+
...config
|
38
|
+
};
|
39
|
+
|
40
|
+
// Get platform instance from factory
|
41
|
+
const platformInstance = await PlatformFactory.create(platform, mergedConfig);
|
42
|
+
|
43
|
+
// Store the platform instance
|
44
|
+
this.platforms.set(platform.toLowerCase(), platformInstance);
|
45
|
+
|
46
|
+
// Attempt authentication if configured
|
47
|
+
if (platformInstance.isConfigured()) {
|
48
|
+
await platformInstance.authenticate();
|
49
|
+
}
|
50
|
+
|
51
|
+
return true;
|
52
|
+
} catch (error) {
|
53
|
+
console.error(`Failed to initialize ${platform}:`, error);
|
54
|
+
return false;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Get a platform instance by name
|
60
|
+
* @param {string} platform - Platform name
|
61
|
+
* @returns {SocialPlatform|null} Platform instance or null if not found
|
62
|
+
*/
|
63
|
+
getPlatform(platform) {
|
64
|
+
return this.platforms.get(platform.toLowerCase()) || null;
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Check if a platform is initialized and authenticated
|
69
|
+
* @param {string} platform - Platform name
|
70
|
+
* @returns {boolean} True if platform is ready
|
71
|
+
*/
|
72
|
+
isPlatformReady(platform) {
|
73
|
+
const platformInstance = this.getPlatform(platform);
|
74
|
+
return platformInstance && platformInstance.authenticated;
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Post content to multiple platforms
|
79
|
+
* @param {Object} post - Post content and metadata
|
80
|
+
* @param {string} post.text - Text content of the post
|
81
|
+
* @param {string} post.title - Title/caption of the post (optional)
|
82
|
+
* @param {Array<string>} post.mediaUrls - Media URLs to attach (optional)
|
83
|
+
* @param {Array<string>} post.platforms - Platforms to post to
|
84
|
+
* @param {Object} post.options - Platform-specific options
|
85
|
+
* @returns {Promise<Object>} Results for each platform
|
86
|
+
*/
|
87
|
+
async post(post) {
|
88
|
+
if (!post.platforms || post.platforms.length === 0) {
|
89
|
+
throw new Error("No platforms specified for posting");
|
90
|
+
}
|
91
|
+
|
92
|
+
const results = {};
|
93
|
+
const errors = [];
|
94
|
+
|
95
|
+
// Post to each platform
|
96
|
+
for (const platform of post.platforms) {
|
97
|
+
try {
|
98
|
+
const platformLower = platform.toLowerCase();
|
99
|
+
let platformInstance = this.getPlatform(platformLower);
|
100
|
+
|
101
|
+
if (!platformInstance) {
|
102
|
+
await this.initPlatform(platformLower);
|
103
|
+
platformInstance = this.getPlatform(platformLower);
|
104
|
+
}
|
105
|
+
|
106
|
+
if (!platformInstance) {
|
107
|
+
throw new Error(`Platform ${platform} not initialized`);
|
108
|
+
}
|
109
|
+
|
110
|
+
if (!platformInstance.authenticated) {
|
111
|
+
await platformInstance.authenticate();
|
112
|
+
}
|
113
|
+
|
114
|
+
// Get platform-specific options if provided
|
115
|
+
const platformOptions =
|
116
|
+
post.options && post.options[platformLower]
|
117
|
+
? post.options[platformLower]
|
118
|
+
: {};
|
119
|
+
|
120
|
+
// Create platform-specific post object
|
121
|
+
const platformPost = {
|
122
|
+
text: post.text,
|
123
|
+
title: post.title,
|
124
|
+
mediaUrls: post.mediaUrls,
|
125
|
+
options: platformOptions,
|
126
|
+
};
|
127
|
+
|
128
|
+
// Post to the platform
|
129
|
+
const result = await platformInstance.post(platformPost);
|
130
|
+
|
131
|
+
// Store result
|
132
|
+
results[platformLower] = {
|
133
|
+
success: true,
|
134
|
+
postId: result.id || result.uri || result.publishId,
|
135
|
+
...result,
|
136
|
+
};
|
137
|
+
|
138
|
+
// Log the action
|
139
|
+
logAction("post_created", {
|
140
|
+
platform: platformLower,
|
141
|
+
postId: result.id || result.uri || result.publishId,
|
142
|
+
content: post.text?.substring(0, 100),
|
143
|
+
});
|
144
|
+
} catch (error) {
|
145
|
+
console.error(`Error posting to ${platform}:`, error);
|
146
|
+
|
147
|
+
// Store error
|
148
|
+
results[platform.toLowerCase()] = {
|
149
|
+
success: false,
|
150
|
+
error: error.message,
|
151
|
+
};
|
152
|
+
|
153
|
+
errors.push({
|
154
|
+
platform,
|
155
|
+
message: error.message,
|
156
|
+
});
|
157
|
+
|
158
|
+
// Log the error
|
159
|
+
logAction("post_error", {
|
160
|
+
platform: platform.toLowerCase(),
|
161
|
+
error: error.message,
|
162
|
+
content: post.text?.substring(0, 100),
|
163
|
+
});
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
return {
|
168
|
+
results,
|
169
|
+
success: errors.length === 0,
|
170
|
+
errors,
|
171
|
+
};
|
172
|
+
}
|
173
|
+
|
174
|
+
/**
|
175
|
+
* Get status of posts across platforms
|
176
|
+
* @param {Object} postIds - Map of platform to post ID
|
177
|
+
* @returns {Promise<Object>} Status for each platform
|
178
|
+
*/
|
179
|
+
async getPostStatus(postIds) {
|
180
|
+
const results = {};
|
181
|
+
const errors = [];
|
182
|
+
|
183
|
+
for (const [platform, postId] of Object.entries(postIds)) {
|
184
|
+
try {
|
185
|
+
const platformLower = platform.toLowerCase();
|
186
|
+
const platformInstance = this.getPlatform(platformLower);
|
187
|
+
|
188
|
+
if (!platformInstance) {
|
189
|
+
throw new Error(`Platform ${platform} not initialized`);
|
190
|
+
}
|
191
|
+
|
192
|
+
if (!platformInstance.authenticated) {
|
193
|
+
await platformInstance.authenticate();
|
194
|
+
}
|
195
|
+
|
196
|
+
const status = await platformInstance.getPostStatus(postId);
|
197
|
+
|
198
|
+
results[platformLower] = {
|
199
|
+
success: true,
|
200
|
+
status,
|
201
|
+
};
|
202
|
+
} catch (error) {
|
203
|
+
console.error(`Error getting status from ${platform}:`, error);
|
204
|
+
|
205
|
+
results[platform.toLowerCase()] = {
|
206
|
+
success: false,
|
207
|
+
error: error.message,
|
208
|
+
};
|
209
|
+
|
210
|
+
errors.push({
|
211
|
+
platform,
|
212
|
+
message: error.message,
|
213
|
+
});
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
return {
|
218
|
+
results,
|
219
|
+
success: errors.length === 0,
|
220
|
+
errors,
|
221
|
+
};
|
222
|
+
}
|
223
|
+
|
224
|
+
/**
|
225
|
+
* Delete posts across platforms
|
226
|
+
* @param {Object} postIds - Map of platform to post ID
|
227
|
+
* @returns {Promise<Object>} Results for each platform
|
228
|
+
*/
|
229
|
+
async deletePosts(postIds) {
|
230
|
+
const results = {};
|
231
|
+
const errors = [];
|
232
|
+
|
233
|
+
for (const [platform, postId] of Object.entries(postIds)) {
|
234
|
+
try {
|
235
|
+
const platformLower = platform.toLowerCase();
|
236
|
+
const platformInstance = this.getPlatform(platformLower);
|
237
|
+
|
238
|
+
if (!platformInstance) {
|
239
|
+
throw new Error(`Platform ${platform} not initialized`);
|
240
|
+
}
|
241
|
+
|
242
|
+
if (!platformInstance.authenticated) {
|
243
|
+
await platformInstance.authenticate();
|
244
|
+
}
|
245
|
+
|
246
|
+
const success = await platformInstance.deletePost(postId);
|
247
|
+
|
248
|
+
results[platformLower] = {
|
249
|
+
success,
|
250
|
+
};
|
251
|
+
|
252
|
+
// Log the action
|
253
|
+
logAction("post_deleted", {
|
254
|
+
platform: platformLower,
|
255
|
+
postId,
|
256
|
+
});
|
257
|
+
} catch (error) {
|
258
|
+
console.error(`Error deleting post from ${platform}:`, error);
|
259
|
+
|
260
|
+
results[platform.toLowerCase()] = {
|
261
|
+
success: false,
|
262
|
+
error: error.message,
|
263
|
+
};
|
264
|
+
|
265
|
+
errors.push({
|
266
|
+
platform,
|
267
|
+
message: error.message,
|
268
|
+
});
|
269
|
+
|
270
|
+
// Log the error
|
271
|
+
logAction("delete_error", {
|
272
|
+
platform: platform.toLowerCase(),
|
273
|
+
postId,
|
274
|
+
error: error.message,
|
275
|
+
});
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
return {
|
280
|
+
results,
|
281
|
+
success: errors.length === 0,
|
282
|
+
errors,
|
283
|
+
};
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
// Export a singleton instance
|
288
|
+
let instance = null;
|
289
|
+
|
290
|
+
/**
|
291
|
+
* Get the Social API instance
|
292
|
+
* @param {Object} options - Configuration options
|
293
|
+
* @returns {SocialAPI} SocialAPI instance
|
294
|
+
*/
|
295
|
+
export const getSocialAPI = (options = {}) => {
|
296
|
+
if (!instance) {
|
297
|
+
instance = new SocialAPI(options);
|
298
|
+
}
|
299
|
+
return instance;
|
300
|
+
};
|