@nextsparkjs/plugin-social-media-publisher 0.1.0-beta.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 +76 -0
- package/README.md +423 -0
- package/api/social/connect/callback/route.ts +669 -0
- package/api/social/connect/route.ts +327 -0
- package/api/social/disconnect/route.ts +187 -0
- package/api/social/publish/route.ts +402 -0
- package/docs/01-getting-started/01-introduction.md +471 -0
- package/docs/01-getting-started/02-installation.md +471 -0
- package/docs/01-getting-started/03-configuration.md +515 -0
- package/docs/02-core-features/01-oauth-integration.md +501 -0
- package/docs/02-core-features/02-publishing.md +527 -0
- package/docs/02-core-features/03-token-management.md +661 -0
- package/docs/02-core-features/04-audit-logging.md +646 -0
- package/docs/03-advanced-usage/01-provider-apis.md +764 -0
- package/docs/03-advanced-usage/02-custom-integrations.md +695 -0
- package/docs/03-advanced-usage/03-per-client-architecture.md +575 -0
- package/docs/04-use-cases/01-agency-management.md +661 -0
- package/docs/04-use-cases/02-content-publishing.md +668 -0
- package/docs/04-use-cases/03-analytics-reporting.md +748 -0
- package/entities/audit-logs/audit-logs.config.ts +150 -0
- package/lib/oauth-helper.ts +167 -0
- package/lib/providers/facebook.ts +672 -0
- package/lib/providers/index.ts +21 -0
- package/lib/providers/instagram.ts +791 -0
- package/lib/validation.ts +155 -0
- package/migrations/001_social_media_tables.sql +167 -0
- package/package.json +15 -0
- package/plugin.config.ts +81 -0
- package/tsconfig.json +47 -0
- package/types/social.types.ts +171 -0
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
# Provider APIs
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Social Media Publisher plugin provides two comprehensive API wrappers for interacting with Facebook Graph API and Instagram Business API. These wrappers abstract the complexity of API calls, handle authentication, and provide type-safe interfaces.
|
|
6
|
+
|
|
7
|
+
**Available Providers:**
|
|
8
|
+
- **FacebookAPI** - Facebook Pages publishing and insights
|
|
9
|
+
- **InstagramAPI** - Instagram Business publishing and analytics
|
|
10
|
+
|
|
11
|
+
## FacebookAPI
|
|
12
|
+
|
|
13
|
+
### Import
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { FacebookAPI } from '@/contents/plugins/social-media-publisher/lib/providers/facebook'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Publishing Methods
|
|
20
|
+
|
|
21
|
+
#### `publishTextPost()`
|
|
22
|
+
|
|
23
|
+
Publish text-only post to Facebook Page.
|
|
24
|
+
|
|
25
|
+
**Signature:**
|
|
26
|
+
```typescript
|
|
27
|
+
static async publishTextPost(options: {
|
|
28
|
+
pageId: string
|
|
29
|
+
pageAccessToken: string
|
|
30
|
+
message: string
|
|
31
|
+
}): Promise<FacebookPublishResult>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Parameters:**
|
|
35
|
+
- `pageId` - Facebook Page ID
|
|
36
|
+
- `pageAccessToken` - Page access token (decrypted)
|
|
37
|
+
- `message` - Post text content
|
|
38
|
+
|
|
39
|
+
**Returns:**
|
|
40
|
+
```typescript
|
|
41
|
+
{
|
|
42
|
+
success: boolean
|
|
43
|
+
postId?: string // e.g., "123456789_987654321"
|
|
44
|
+
postUrl?: string // e.g., "https://www.facebook.com/123456789/posts/987654321"
|
|
45
|
+
error?: string
|
|
46
|
+
errorDetails?: unknown
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Example:**
|
|
51
|
+
```typescript
|
|
52
|
+
const result = await FacebookAPI.publishTextPost({
|
|
53
|
+
pageId: '123456789',
|
|
54
|
+
pageAccessToken: decryptedToken,
|
|
55
|
+
message: 'Hello from our Facebook Page! 👋'
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (result.success) {
|
|
59
|
+
console.log(`Published: ${result.postUrl}`)
|
|
60
|
+
} else {
|
|
61
|
+
console.error(`Failed: ${result.error}`)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Graph API Call:**
|
|
66
|
+
```
|
|
67
|
+
POST https://graph.facebook.com/v18.0/{PAGE_ID}/feed
|
|
68
|
+
{
|
|
69
|
+
"message": "Post text here",
|
|
70
|
+
"access_token": "{PAGE_TOKEN}"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### `publishPhotoPost()`
|
|
75
|
+
|
|
76
|
+
Publish photo with optional caption to Facebook Page.
|
|
77
|
+
|
|
78
|
+
**Signature:**
|
|
79
|
+
```typescript
|
|
80
|
+
static async publishPhotoPost(options: {
|
|
81
|
+
pageId: string
|
|
82
|
+
pageAccessToken: string
|
|
83
|
+
message: string
|
|
84
|
+
imageUrl: string
|
|
85
|
+
}): Promise<FacebookPublishResult>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
- `pageId` - Facebook Page ID
|
|
90
|
+
- `pageAccessToken` - Page access token
|
|
91
|
+
- `message` - Photo caption
|
|
92
|
+
- `imageUrl` - Public HTTPS URL to image
|
|
93
|
+
|
|
94
|
+
**Example:**
|
|
95
|
+
```typescript
|
|
96
|
+
const result = await FacebookAPI.publishPhotoPost({
|
|
97
|
+
pageId: '123456789',
|
|
98
|
+
pageAccessToken: decryptedToken,
|
|
99
|
+
message: 'Check out our new product!',
|
|
100
|
+
imageUrl: 'https://cdn.example.com/product.jpg'
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Graph API Call:**
|
|
105
|
+
```
|
|
106
|
+
POST https://graph.facebook.com/v18.0/{PAGE_ID}/photos
|
|
107
|
+
{
|
|
108
|
+
"url": "https://example.com/image.jpg",
|
|
109
|
+
"message": "Photo caption",
|
|
110
|
+
"access_token": "{PAGE_TOKEN}"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Image Requirements:**
|
|
115
|
+
- Format: JPG, PNG, GIF, BMP
|
|
116
|
+
- Max size: 4MB (recommended), up to 15MB
|
|
117
|
+
- Must be publicly accessible via HTTPS
|
|
118
|
+
|
|
119
|
+
#### `publishLinkPost()`
|
|
120
|
+
|
|
121
|
+
Publish link with preview to Facebook Page.
|
|
122
|
+
|
|
123
|
+
**Signature:**
|
|
124
|
+
```typescript
|
|
125
|
+
static async publishLinkPost(options: {
|
|
126
|
+
pageId: string
|
|
127
|
+
pageAccessToken: string
|
|
128
|
+
message: string
|
|
129
|
+
link: string
|
|
130
|
+
}): Promise<FacebookPublishResult>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Example:**
|
|
134
|
+
```typescript
|
|
135
|
+
const result = await FacebookAPI.publishLinkPost({
|
|
136
|
+
pageId: '123456789',
|
|
137
|
+
pageAccessToken: decryptedToken,
|
|
138
|
+
message: 'Read our latest blog post!',
|
|
139
|
+
link: 'https://example.com/blog/post'
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Graph API Call:**
|
|
144
|
+
```
|
|
145
|
+
POST https://graph.facebook.com/v18.0/{PAGE_ID}/feed
|
|
146
|
+
{
|
|
147
|
+
"message": "Check this out!",
|
|
148
|
+
"link": "https://example.com/article",
|
|
149
|
+
"access_token": "{PAGE_TOKEN}"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Link Requirements:**
|
|
154
|
+
- Must use HTTPS
|
|
155
|
+
- Facebook auto-generates preview using Open Graph tags
|
|
156
|
+
- Preview customization via meta tags:
|
|
157
|
+
```html
|
|
158
|
+
<meta property="og:title" content="Article Title" />
|
|
159
|
+
<meta property="og:description" content="Description" />
|
|
160
|
+
<meta property="og:image" content="https://example.com/preview.jpg" />
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Page Management Methods
|
|
164
|
+
|
|
165
|
+
#### `getUserPages()`
|
|
166
|
+
|
|
167
|
+
Get list of Facebook Pages user manages.
|
|
168
|
+
|
|
169
|
+
**Signature:**
|
|
170
|
+
```typescript
|
|
171
|
+
static async getUserPages(userAccessToken: string): Promise<FacebookPageInfo[]>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Returns:**
|
|
175
|
+
```typescript
|
|
176
|
+
interface FacebookPageInfo {
|
|
177
|
+
id: string // Page ID
|
|
178
|
+
name: string // Page name
|
|
179
|
+
category: string // Page category
|
|
180
|
+
accessToken: string // Page-specific access token
|
|
181
|
+
tasks: string[] // Permissions granted
|
|
182
|
+
pictureUrl?: string // Profile picture URL
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Example:**
|
|
187
|
+
```typescript
|
|
188
|
+
const pages = await FacebookAPI.getUserPages(userAccessToken)
|
|
189
|
+
|
|
190
|
+
pages.forEach(page => {
|
|
191
|
+
console.log(`${page.name} (${page.id})`)
|
|
192
|
+
console.log(`Permissions: ${page.tasks.join(', ')}`)
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Graph API Call:**
|
|
197
|
+
```
|
|
198
|
+
GET https://graph.facebook.com/v18.0/me/accounts
|
|
199
|
+
?fields=id,name,category,access_token,tasks,picture
|
|
200
|
+
&access_token={USER_TOKEN}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### `getPageInfo()`
|
|
204
|
+
|
|
205
|
+
Get detailed information about Facebook Page.
|
|
206
|
+
|
|
207
|
+
**Signature:**
|
|
208
|
+
```typescript
|
|
209
|
+
static async getPageInfo(
|
|
210
|
+
pageId: string,
|
|
211
|
+
pageAccessToken: string
|
|
212
|
+
): Promise<FacebookPageStats>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Returns:**
|
|
216
|
+
```typescript
|
|
217
|
+
interface FacebookPageStats {
|
|
218
|
+
id: string
|
|
219
|
+
name: string
|
|
220
|
+
fanCount: number // Follower count
|
|
221
|
+
about?: string
|
|
222
|
+
category?: string
|
|
223
|
+
profilePictureUrl?: string
|
|
224
|
+
coverPhotoUrl?: string
|
|
225
|
+
link?: string // Page URL
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Example:**
|
|
230
|
+
```typescript
|
|
231
|
+
const pageInfo = await FacebookAPI.getPageInfo(pageId, pageAccessToken)
|
|
232
|
+
|
|
233
|
+
console.log(`${pageInfo.name}`)
|
|
234
|
+
console.log(`Followers: ${pageInfo.fanCount.toLocaleString()}`)
|
|
235
|
+
console.log(`About: ${pageInfo.about}`)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Graph API Call:**
|
|
239
|
+
```
|
|
240
|
+
GET https://graph.facebook.com/v18.0/{PAGE_ID}
|
|
241
|
+
?fields=id,name,fan_count,about,category,picture,cover,link
|
|
242
|
+
&access_token={PAGE_TOKEN}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `getPageInsights()`
|
|
246
|
+
|
|
247
|
+
Get analytics for Facebook Page.
|
|
248
|
+
|
|
249
|
+
**Signature:**
|
|
250
|
+
```typescript
|
|
251
|
+
static async getPageInsights(
|
|
252
|
+
pageId: string,
|
|
253
|
+
pageAccessToken: string
|
|
254
|
+
): Promise<FacebookInsights>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Returns:**
|
|
258
|
+
```typescript
|
|
259
|
+
interface FacebookInsights {
|
|
260
|
+
impressions: number // Total impressions
|
|
261
|
+
reach: number // Total reach
|
|
262
|
+
engagement: number // Total engagement
|
|
263
|
+
reactions: number // Total reactions
|
|
264
|
+
comments: number // Total comments
|
|
265
|
+
shares: number // Total shares
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Example:**
|
|
270
|
+
```typescript
|
|
271
|
+
const insights = await FacebookAPI.getPageInsights(pageId, pageAccessToken)
|
|
272
|
+
|
|
273
|
+
console.log(`Impressions: ${insights.impressions}`)
|
|
274
|
+
console.log(`Engagement Rate: ${(insights.engagement / insights.reach * 100).toFixed(2)}%`)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Note:** Requires `read_insights` permission.
|
|
278
|
+
|
|
279
|
+
### Instagram Integration Methods
|
|
280
|
+
|
|
281
|
+
#### `getInstagramBusinessAccount()`
|
|
282
|
+
|
|
283
|
+
Get Instagram Business Account linked to Facebook Page.
|
|
284
|
+
|
|
285
|
+
**Signature:**
|
|
286
|
+
```typescript
|
|
287
|
+
static async getInstagramBusinessAccount(
|
|
288
|
+
pageId: string,
|
|
289
|
+
pageAccessToken: string
|
|
290
|
+
): Promise<{
|
|
291
|
+
id: string
|
|
292
|
+
username: string
|
|
293
|
+
name?: string
|
|
294
|
+
profilePictureUrl?: string
|
|
295
|
+
followersCount?: number
|
|
296
|
+
followsCount?: number
|
|
297
|
+
mediaCount?: number
|
|
298
|
+
biography?: string
|
|
299
|
+
website?: string
|
|
300
|
+
} | null>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Returns:** Instagram account info or `null` if not connected
|
|
304
|
+
|
|
305
|
+
**Example:**
|
|
306
|
+
```typescript
|
|
307
|
+
const igAccount = await FacebookAPI.getInstagramBusinessAccount(
|
|
308
|
+
pageId,
|
|
309
|
+
pageAccessToken
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if (igAccount) {
|
|
313
|
+
console.log(`Instagram: @${igAccount.username}`)
|
|
314
|
+
console.log(`Followers: ${igAccount.followersCount}`)
|
|
315
|
+
} else {
|
|
316
|
+
console.log('No Instagram account connected to this Page')
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Graph API Call:**
|
|
321
|
+
```
|
|
322
|
+
GET https://graph.facebook.com/v18.0/{PAGE_ID}
|
|
323
|
+
?fields=instagram_business_account{id,username,name,profile_picture_url,followers_count,follows_count,media_count,biography,website}
|
|
324
|
+
&access_token={PAGE_TOKEN}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### `validatePagePermissions()`
|
|
328
|
+
|
|
329
|
+
Check which permissions are granted for a Page.
|
|
330
|
+
|
|
331
|
+
**Signature:**
|
|
332
|
+
```typescript
|
|
333
|
+
static async validatePagePermissions(
|
|
334
|
+
pageId: string,
|
|
335
|
+
pageAccessToken: string
|
|
336
|
+
): Promise<{
|
|
337
|
+
valid: boolean
|
|
338
|
+
permissions: string[]
|
|
339
|
+
missing: string[]
|
|
340
|
+
}>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Example:**
|
|
344
|
+
```typescript
|
|
345
|
+
const validation = await FacebookAPI.validatePagePermissions(
|
|
346
|
+
pageId,
|
|
347
|
+
pageAccessToken
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
console.log('Granted:', validation.permissions)
|
|
351
|
+
console.log('Missing:', validation.missing)
|
|
352
|
+
|
|
353
|
+
if (validation.missing.includes('pages_manage_posts')) {
|
|
354
|
+
console.warn('Cannot publish - missing pages_manage_posts permission')
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## InstagramAPI
|
|
359
|
+
|
|
360
|
+
### Import
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { InstagramAPI } from '@/contents/plugins/social-media-publisher/lib/providers/instagram'
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Publishing Methods
|
|
367
|
+
|
|
368
|
+
#### `publishPhoto()`
|
|
369
|
+
|
|
370
|
+
Publish photo to Instagram Business Account.
|
|
371
|
+
|
|
372
|
+
**Signature:**
|
|
373
|
+
```typescript
|
|
374
|
+
static async publishPhoto(options: {
|
|
375
|
+
igAccountId: string
|
|
376
|
+
accessToken: string
|
|
377
|
+
imageUrl: string
|
|
378
|
+
caption?: string
|
|
379
|
+
}): Promise<InstagramPublishResult>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Parameters:**
|
|
383
|
+
- `igAccountId` - Instagram Business Account ID
|
|
384
|
+
- `accessToken` - Page access token (for linked Page)
|
|
385
|
+
- `imageUrl` - Public HTTPS URL to image
|
|
386
|
+
- `caption` - Optional caption (max 2,200 chars)
|
|
387
|
+
|
|
388
|
+
**Returns:**
|
|
389
|
+
```typescript
|
|
390
|
+
{
|
|
391
|
+
success: boolean
|
|
392
|
+
postId?: string // e.g., "17899618652010220"
|
|
393
|
+
postUrl?: string // e.g., "https://www.instagram.com/p/ABC123"
|
|
394
|
+
error?: string
|
|
395
|
+
errorDetails?: unknown
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Example:**
|
|
400
|
+
```typescript
|
|
401
|
+
const result = await InstagramAPI.publishPhoto({
|
|
402
|
+
igAccountId: '17841401234567890',
|
|
403
|
+
accessToken: decryptedToken,
|
|
404
|
+
imageUrl: 'https://cdn.example.com/photo.jpg',
|
|
405
|
+
caption: 'My awesome Instagram post! 📸 #photography'
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
if (result.success) {
|
|
409
|
+
console.log(`Posted: ${result.postUrl}`)
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Process (2-Step Container):**
|
|
414
|
+
1. **Create Media Container:**
|
|
415
|
+
```
|
|
416
|
+
POST https://graph.facebook.com/v18.0/{IG_ACCOUNT_ID}/media
|
|
417
|
+
{
|
|
418
|
+
"image_url": "https://example.com/image.jpg",
|
|
419
|
+
"caption": "Caption here",
|
|
420
|
+
"access_token": "{TOKEN}"
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
2. **Publish Container:**
|
|
425
|
+
```
|
|
426
|
+
POST https://graph.facebook.com/v18.0/{IG_ACCOUNT_ID}/media_publish
|
|
427
|
+
{
|
|
428
|
+
"creation_id": "{CONTAINER_ID}",
|
|
429
|
+
"access_token": "{TOKEN}"
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Image Requirements:**
|
|
434
|
+
- Format: JPG or PNG
|
|
435
|
+
- Size: Max 8MB
|
|
436
|
+
- Dimensions: 320px to 1080px (width)
|
|
437
|
+
- Aspect Ratio: 4:5 to 1.91:1
|
|
438
|
+
- Must be publicly accessible via HTTPS
|
|
439
|
+
|
|
440
|
+
**Caption Requirements:**
|
|
441
|
+
- Max length: 2,200 characters
|
|
442
|
+
- Max hashtags: 30
|
|
443
|
+
- Max mentions: 20 (@username)
|
|
444
|
+
- Supports emojis and line breaks
|
|
445
|
+
|
|
446
|
+
#### `publishVideo()`
|
|
447
|
+
|
|
448
|
+
Publish video to Instagram Business Account.
|
|
449
|
+
|
|
450
|
+
**Signature:**
|
|
451
|
+
```typescript
|
|
452
|
+
static async publishVideo(options: {
|
|
453
|
+
igAccountId: string
|
|
454
|
+
accessToken: string
|
|
455
|
+
videoUrl: string
|
|
456
|
+
caption?: string
|
|
457
|
+
}): Promise<InstagramPublishResult>
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**Example:**
|
|
461
|
+
```typescript
|
|
462
|
+
const result = await InstagramAPI.publishVideo({
|
|
463
|
+
igAccountId: '17841401234567890',
|
|
464
|
+
accessToken: decryptedToken,
|
|
465
|
+
videoUrl: 'https://cdn.example.com/video.mp4',
|
|
466
|
+
caption: 'Watch this! 🎬 #video'
|
|
467
|
+
})
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Video Requirements:**
|
|
471
|
+
- Format: MP4 or MOV
|
|
472
|
+
- Size: Max 100MB
|
|
473
|
+
- Duration: 3 to 60 seconds
|
|
474
|
+
- Dimensions: Min 600px (any dimension)
|
|
475
|
+
- Aspect Ratio: 4:5 to 1.91:1
|
|
476
|
+
- Frame Rate: Max 30fps
|
|
477
|
+
|
|
478
|
+
**Processing Time:**
|
|
479
|
+
- Videos require processing before publishing
|
|
480
|
+
- Method waits up to 30 seconds for `FINISHED` status
|
|
481
|
+
- Polls every 2 seconds for completion
|
|
482
|
+
|
|
483
|
+
### Account Information Methods
|
|
484
|
+
|
|
485
|
+
#### `getAccountInfo()`
|
|
486
|
+
|
|
487
|
+
Get Instagram Business Account information.
|
|
488
|
+
|
|
489
|
+
**Signature:**
|
|
490
|
+
```typescript
|
|
491
|
+
static async getAccountInfo(
|
|
492
|
+
igAccountId: string,
|
|
493
|
+
accessToken: string
|
|
494
|
+
): Promise<InstagramAccountInfo>
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Returns:**
|
|
498
|
+
```typescript
|
|
499
|
+
interface InstagramAccountInfo {
|
|
500
|
+
id: string
|
|
501
|
+
username: string
|
|
502
|
+
accountType?: string
|
|
503
|
+
profilePictureUrl?: string
|
|
504
|
+
followersCount?: number
|
|
505
|
+
followsCount?: number
|
|
506
|
+
mediaCount?: number
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Example:**
|
|
511
|
+
```typescript
|
|
512
|
+
const info = await InstagramAPI.getAccountInfo(igAccountId, accessToken)
|
|
513
|
+
|
|
514
|
+
console.log(`@${info.username}`)
|
|
515
|
+
console.log(`Followers: ${info.followersCount?.toLocaleString()}`)
|
|
516
|
+
console.log(`Posts: ${info.mediaCount}`)
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**Graph API Call:**
|
|
520
|
+
```
|
|
521
|
+
GET https://graph.facebook.com/v18.0/{IG_ACCOUNT_ID}
|
|
522
|
+
?fields=id,username,account_type,profile_picture_url,followers_count,follows_count,media_count
|
|
523
|
+
&access_token={TOKEN}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### `getAccountInsights()`
|
|
527
|
+
|
|
528
|
+
Get Instagram account-level insights.
|
|
529
|
+
|
|
530
|
+
**Signature:**
|
|
531
|
+
```typescript
|
|
532
|
+
static async getAccountInsights(
|
|
533
|
+
igAccountId: string,
|
|
534
|
+
accessToken: string
|
|
535
|
+
): Promise<InstagramInsights>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Returns:**
|
|
539
|
+
```typescript
|
|
540
|
+
interface InstagramInsights {
|
|
541
|
+
impressions: number
|
|
542
|
+
reach: number
|
|
543
|
+
engagement: number
|
|
544
|
+
likes: number
|
|
545
|
+
comments: number
|
|
546
|
+
saves: number
|
|
547
|
+
profileViews: number
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Example:**
|
|
552
|
+
```typescript
|
|
553
|
+
const insights = await InstagramAPI.getAccountInsights(igAccountId, accessToken)
|
|
554
|
+
|
|
555
|
+
console.log(`Total Impressions: ${insights.impressions}`)
|
|
556
|
+
console.log(`Engagement Rate: ${(insights.engagement / insights.reach * 100).toFixed(2)}%`)
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**Note:** Requires `instagram_manage_insights` permission.
|
|
560
|
+
|
|
561
|
+
#### `getMediaInsights()`
|
|
562
|
+
|
|
563
|
+
Get insights for specific Instagram post.
|
|
564
|
+
|
|
565
|
+
**Signature:**
|
|
566
|
+
```typescript
|
|
567
|
+
static async getMediaInsights(
|
|
568
|
+
mediaId: string,
|
|
569
|
+
accessToken: string
|
|
570
|
+
): Promise<Partial<InstagramInsights>>
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Example:**
|
|
574
|
+
```typescript
|
|
575
|
+
const postInsights = await InstagramAPI.getMediaInsights(
|
|
576
|
+
'17899618652010220',
|
|
577
|
+
accessToken
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
console.log(`Likes: ${postInsights.likes}`)
|
|
581
|
+
console.log(`Comments: ${postInsights.comments}`)
|
|
582
|
+
console.log(`Saves: ${postInsights.saves}`)
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
**Graph API Call:**
|
|
586
|
+
```
|
|
587
|
+
GET https://graph.facebook.com/v18.0/{MEDIA_ID}/insights
|
|
588
|
+
?metric=impressions,reach,engagement,likes,comments,saves
|
|
589
|
+
&access_token={TOKEN}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
## Error Handling
|
|
593
|
+
|
|
594
|
+
### Common Error Types
|
|
595
|
+
|
|
596
|
+
**Invalid Token:**
|
|
597
|
+
```typescript
|
|
598
|
+
try {
|
|
599
|
+
await FacebookAPI.publishTextPost({ ... })
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (error.message.includes('Invalid OAuth')) {
|
|
602
|
+
// Token expired or revoked - need to reconnect
|
|
603
|
+
console.error('Token invalid - user must reconnect account')
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Rate Limiting:**
|
|
609
|
+
```typescript
|
|
610
|
+
try {
|
|
611
|
+
await InstagramAPI.publishPhoto({ ... })
|
|
612
|
+
} catch (error) {
|
|
613
|
+
if (error.message.includes('rate limit')) {
|
|
614
|
+
// Too many requests - wait and retry
|
|
615
|
+
console.error('Rate limit reached - retry later')
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**Permission Errors:**
|
|
621
|
+
```typescript
|
|
622
|
+
try {
|
|
623
|
+
await FacebookAPI.getPageInsights({ ... })
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (error.message.includes('read_insights')) {
|
|
626
|
+
// Missing required permission
|
|
627
|
+
console.error('User needs to grant read_insights permission')
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Image Not Accessible:**
|
|
633
|
+
```typescript
|
|
634
|
+
try {
|
|
635
|
+
await InstagramAPI.publishPhoto({ ... })
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (error.message.includes('publicly accessible')) {
|
|
638
|
+
// Image URL not reachable
|
|
639
|
+
console.error('Image must be publicly accessible via HTTPS')
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Error Handling Pattern
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
async function safePublish(options: PublishOptions): Promise<PublishResult> {
|
|
648
|
+
try {
|
|
649
|
+
const result = await InstagramAPI.publishPhoto(options)
|
|
650
|
+
|
|
651
|
+
if (!result.success) {
|
|
652
|
+
// Log failure
|
|
653
|
+
await logAuditEvent('post_failed', {
|
|
654
|
+
error: result.error,
|
|
655
|
+
errorDetails: result.errorDetails
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
return { success: false, error: result.error }
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Log success
|
|
662
|
+
await logAuditEvent('post_published', {
|
|
663
|
+
postId: result.postId,
|
|
664
|
+
postUrl: result.postUrl
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
return result
|
|
668
|
+
|
|
669
|
+
} catch (error) {
|
|
670
|
+
// Unexpected error
|
|
671
|
+
console.error('Unexpected publish error:', error)
|
|
672
|
+
|
|
673
|
+
await logAuditEvent('post_failed', {
|
|
674
|
+
error: 'Unexpected error',
|
|
675
|
+
details: error instanceof Error ? error.message : String(error)
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
return { success: false, error: 'Unexpected error occurred' }
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
## Usage Examples
|
|
684
|
+
|
|
685
|
+
### Batch Publishing
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
async function publishToMultiplePlatforms(
|
|
689
|
+
accounts: SocialAccount[],
|
|
690
|
+
imageUrl: string,
|
|
691
|
+
caption: string
|
|
692
|
+
) {
|
|
693
|
+
const results = await Promise.allSettled(
|
|
694
|
+
accounts.map(async (account) => {
|
|
695
|
+
const [encrypted, iv, keyId] = account.accessToken.split(':')
|
|
696
|
+
const token = await TokenEncryption.decrypt(encrypted, iv, keyId)
|
|
697
|
+
|
|
698
|
+
if (account.platform === 'instagram_business') {
|
|
699
|
+
return await InstagramAPI.publishPhoto({
|
|
700
|
+
igAccountId: account.platformAccountId,
|
|
701
|
+
accessToken: token,
|
|
702
|
+
imageUrl,
|
|
703
|
+
caption
|
|
704
|
+
})
|
|
705
|
+
} else {
|
|
706
|
+
return await FacebookAPI.publishPhotoPost({
|
|
707
|
+
pageId: account.platformAccountId,
|
|
708
|
+
pageAccessToken: token,
|
|
709
|
+
message: caption,
|
|
710
|
+
imageUrl
|
|
711
|
+
})
|
|
712
|
+
}
|
|
713
|
+
})
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
return {
|
|
717
|
+
successful: results.filter(r => r.status === 'fulfilled').length,
|
|
718
|
+
failed: results.filter(r => r.status === 'rejected').length,
|
|
719
|
+
results
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Account Sync
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
async function syncAccountStats(accountId: string) {
|
|
728
|
+
const account = await getAccountFromDB(accountId)
|
|
729
|
+
const [encrypted, iv, keyId] = account.accessToken.split(':')
|
|
730
|
+
const token = await TokenEncryption.decrypt(encrypted, iv, keyId)
|
|
731
|
+
|
|
732
|
+
if (account.platform === 'instagram_business') {
|
|
733
|
+
const info = await InstagramAPI.getAccountInfo(
|
|
734
|
+
account.platformAccountId,
|
|
735
|
+
token
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
await updateAccountMetadata(accountId, {
|
|
739
|
+
followersCount: info.followersCount,
|
|
740
|
+
mediaCount: info.mediaCount,
|
|
741
|
+
profilePictureUrl: info.profilePictureUrl,
|
|
742
|
+
lastSyncAt: new Date().toISOString()
|
|
743
|
+
})
|
|
744
|
+
} else {
|
|
745
|
+
const info = await FacebookAPI.getPageInfo(
|
|
746
|
+
account.platformAccountId,
|
|
747
|
+
token
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
await updateAccountMetadata(accountId, {
|
|
751
|
+
fanCount: info.fanCount,
|
|
752
|
+
about: info.about,
|
|
753
|
+
profilePictureUrl: info.profilePictureUrl,
|
|
754
|
+
lastSyncAt: new Date().toISOString()
|
|
755
|
+
})
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
## Next Steps
|
|
761
|
+
|
|
762
|
+
- **[Custom Integrations](./02-custom-integrations.md)** - Build custom features with providers
|
|
763
|
+
- **[Publishing](../02-core-features/02-publishing.md)** - Use providers in publishing endpoint
|
|
764
|
+
- **[Token Management](../02-core-features/03-token-management.md)** - Secure token handling
|