@auxiora/connector-social 1.0.0
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/LICENSE +191 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/instagram.d.ts +2 -0
- package/dist/instagram.d.ts.map +1 -0
- package/dist/instagram.js +205 -0
- package/dist/instagram.js.map +1 -0
- package/dist/linkedin.d.ts +2 -0
- package/dist/linkedin.d.ts.map +1 -0
- package/dist/linkedin.js +239 -0
- package/dist/linkedin.js.map +1 -0
- package/dist/reddit.d.ts +2 -0
- package/dist/reddit.d.ts.map +1 -0
- package/dist/reddit.js +259 -0
- package/dist/reddit.js.map +1 -0
- package/dist/twitter.d.ts +2 -0
- package/dist/twitter.d.ts.map +1 -0
- package/dist/twitter.js +245 -0
- package/dist/twitter.js.map +1 -0
- package/package.json +25 -0
- package/src/index.ts +4 -0
- package/src/instagram.ts +212 -0
- package/src/linkedin.ts +243 -0
- package/src/reddit.ts +268 -0
- package/src/twitter.ts +253 -0
- package/tests/instagram.test.ts +108 -0
- package/tests/linkedin.test.ts +104 -0
- package/tests/reddit.test.ts +127 -0
- package/tests/twitter.test.ts +120 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/linkedin.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { defineConnector } from '@auxiora/connectors';
|
|
2
|
+
import type { TriggerEvent } from '@auxiora/connectors';
|
|
3
|
+
|
|
4
|
+
async function linkedinFetch(token: string, path: string, options?: { method?: string; body?: unknown }) {
|
|
5
|
+
const res = await fetch(`https://api.linkedin.com/v2${path}`, {
|
|
6
|
+
method: options?.method ?? 'GET',
|
|
7
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
8
|
+
body: options?.body ? JSON.stringify(options.body) : undefined,
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok) throw new Error(`LinkedIn API error: ${res.status} ${await res.text().catch(() => res.statusText)}`);
|
|
11
|
+
return res.json() as Promise<Record<string, unknown>>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const linkedinConnector = defineConnector({
|
|
15
|
+
id: 'linkedin',
|
|
16
|
+
name: 'LinkedIn',
|
|
17
|
+
description: 'Integration with LinkedIn for posts, connections, and messaging',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
category: 'social',
|
|
20
|
+
icon: 'linkedin',
|
|
21
|
+
|
|
22
|
+
auth: {
|
|
23
|
+
type: 'oauth2',
|
|
24
|
+
oauth2: {
|
|
25
|
+
authUrl: 'https://www.linkedin.com/oauth/v2/authorization',
|
|
26
|
+
tokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken',
|
|
27
|
+
scopes: ['r_liteprofile', 'r_emailaddress', 'w_member_social', 'r_basicprofile'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
actions: [
|
|
32
|
+
{
|
|
33
|
+
id: 'feed-read',
|
|
34
|
+
name: 'Read Feed',
|
|
35
|
+
description: 'Read the LinkedIn news feed',
|
|
36
|
+
trustMinimum: 1,
|
|
37
|
+
trustDomain: 'integrations',
|
|
38
|
+
reversible: false,
|
|
39
|
+
sideEffects: false,
|
|
40
|
+
params: {},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'post-update',
|
|
44
|
+
name: 'Post Update',
|
|
45
|
+
description: 'Post a status update on LinkedIn',
|
|
46
|
+
trustMinimum: 3,
|
|
47
|
+
trustDomain: 'integrations',
|
|
48
|
+
reversible: false,
|
|
49
|
+
sideEffects: true,
|
|
50
|
+
params: {
|
|
51
|
+
text: { type: 'string', description: 'Post text', required: true },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'post-article',
|
|
56
|
+
name: 'Post Article',
|
|
57
|
+
description: 'Share an article on LinkedIn',
|
|
58
|
+
trustMinimum: 3,
|
|
59
|
+
trustDomain: 'integrations',
|
|
60
|
+
reversible: false,
|
|
61
|
+
sideEffects: true,
|
|
62
|
+
params: {
|
|
63
|
+
title: { type: 'string', description: 'Article title', required: true },
|
|
64
|
+
url: { type: 'string', description: 'Article URL', required: true },
|
|
65
|
+
commentary: { type: 'string', description: 'Commentary text' },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'connections-list',
|
|
70
|
+
name: 'List Connections',
|
|
71
|
+
description: 'List LinkedIn connections',
|
|
72
|
+
trustMinimum: 1,
|
|
73
|
+
trustDomain: 'integrations',
|
|
74
|
+
reversible: false,
|
|
75
|
+
sideEffects: false,
|
|
76
|
+
params: {},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'messages-list',
|
|
80
|
+
name: 'List Messages',
|
|
81
|
+
description: 'List LinkedIn messages',
|
|
82
|
+
trustMinimum: 1,
|
|
83
|
+
trustDomain: 'messaging',
|
|
84
|
+
reversible: false,
|
|
85
|
+
sideEffects: false,
|
|
86
|
+
params: {},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'message-send',
|
|
90
|
+
name: 'Send Message',
|
|
91
|
+
description: 'Send a message on LinkedIn',
|
|
92
|
+
trustMinimum: 3,
|
|
93
|
+
trustDomain: 'messaging',
|
|
94
|
+
reversible: false,
|
|
95
|
+
sideEffects: true,
|
|
96
|
+
params: {
|
|
97
|
+
recipientId: { type: 'string', description: 'Recipient profile ID', required: true },
|
|
98
|
+
text: { type: 'string', description: 'Message text', required: true },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'profile-get',
|
|
103
|
+
name: 'Get Profile',
|
|
104
|
+
description: 'Get a LinkedIn profile',
|
|
105
|
+
trustMinimum: 1,
|
|
106
|
+
trustDomain: 'integrations',
|
|
107
|
+
reversible: false,
|
|
108
|
+
sideEffects: false,
|
|
109
|
+
params: {
|
|
110
|
+
profileId: { type: 'string', description: 'Profile ID (default: authenticated user)' },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
|
|
115
|
+
triggers: [
|
|
116
|
+
{
|
|
117
|
+
id: 'new-message',
|
|
118
|
+
name: 'New Message',
|
|
119
|
+
description: 'Triggered when a new LinkedIn message is received',
|
|
120
|
+
type: 'poll',
|
|
121
|
+
pollIntervalMs: 120_000,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'post-engagement',
|
|
125
|
+
name: 'Post Engagement',
|
|
126
|
+
description: 'Triggered when a post receives engagement',
|
|
127
|
+
type: 'poll',
|
|
128
|
+
pollIntervalMs: 300_000,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
|
|
132
|
+
entities: [
|
|
133
|
+
{
|
|
134
|
+
id: 'post',
|
|
135
|
+
name: 'Post',
|
|
136
|
+
description: 'A LinkedIn post',
|
|
137
|
+
fields: { id: 'string', text: 'string', authorName: 'string', likes: 'number', comments: 'number', shares: 'number' },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'connection',
|
|
141
|
+
name: 'Connection',
|
|
142
|
+
description: 'A LinkedIn connection',
|
|
143
|
+
fields: { id: 'string', name: 'string', headline: 'string', company: 'string' },
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
|
|
147
|
+
async executeAction(actionId: string, params: Record<string, unknown>, token: string): Promise<unknown> {
|
|
148
|
+
switch (actionId) {
|
|
149
|
+
case 'feed-read': {
|
|
150
|
+
const me = await linkedinFetch(token, '/me');
|
|
151
|
+
const urn = `urn:li:person:${me.id as string}`;
|
|
152
|
+
try {
|
|
153
|
+
const posts = await linkedinFetch(token, `/ugcPosts?q=authors&authors=List(${encodeURIComponent(urn)})&count=10`);
|
|
154
|
+
return { posts: posts.elements };
|
|
155
|
+
} catch (err) {
|
|
156
|
+
return { posts: [], error: `Feed access restricted: ${(err as Error).message}` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
case 'post-update': {
|
|
160
|
+
const me = await linkedinFetch(token, '/me');
|
|
161
|
+
const authorUrn = `urn:li:person:${me.id as string}`;
|
|
162
|
+
const res = await linkedinFetch(token, '/ugcPosts', {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
body: {
|
|
165
|
+
author: authorUrn,
|
|
166
|
+
lifecycleState: 'PUBLISHED',
|
|
167
|
+
specificContent: {
|
|
168
|
+
'com.linkedin.ugc.ShareContent': {
|
|
169
|
+
shareCommentary: { text: params.text },
|
|
170
|
+
shareMediaCategory: 'NONE',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
return { postId: res.id, status: 'posted' };
|
|
177
|
+
}
|
|
178
|
+
case 'post-article': {
|
|
179
|
+
const me = await linkedinFetch(token, '/me');
|
|
180
|
+
const authorUrn = `urn:li:person:${me.id as string}`;
|
|
181
|
+
const res = await linkedinFetch(token, '/ugcPosts', {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
body: {
|
|
184
|
+
author: authorUrn,
|
|
185
|
+
lifecycleState: 'PUBLISHED',
|
|
186
|
+
specificContent: {
|
|
187
|
+
'com.linkedin.ugc.ShareContent': {
|
|
188
|
+
shareCommentary: { text: (params.commentary as string) ?? '' },
|
|
189
|
+
shareMediaCategory: 'ARTICLE',
|
|
190
|
+
media: [{
|
|
191
|
+
status: 'READY',
|
|
192
|
+
originalUrl: params.url,
|
|
193
|
+
title: { text: params.title },
|
|
194
|
+
}],
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
return { postId: res.id, status: 'shared' };
|
|
201
|
+
}
|
|
202
|
+
case 'connections-list': {
|
|
203
|
+
try {
|
|
204
|
+
const res = await linkedinFetch(token, '/connections?q=viewer&count=50');
|
|
205
|
+
return { connections: res.elements };
|
|
206
|
+
} catch (err) {
|
|
207
|
+
return { connections: [], error: `Connections access restricted: ${(err as Error).message}` };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
case 'messages-list': {
|
|
211
|
+
try {
|
|
212
|
+
const res = await linkedinFetch(token, '/messages');
|
|
213
|
+
return { messages: res.elements };
|
|
214
|
+
} catch (err) {
|
|
215
|
+
return { messages: [], error: `Messages access restricted: ${(err as Error).message}` };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
case 'message-send': {
|
|
219
|
+
const res = await linkedinFetch(token, '/messages', {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
body: {
|
|
222
|
+
recipients: [`urn:li:person:${params.recipientId as string}`],
|
|
223
|
+
body: params.text,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
return { messageId: res.id, status: 'sent' };
|
|
227
|
+
}
|
|
228
|
+
case 'profile-get': {
|
|
229
|
+
const profileId = params.profileId as string | undefined;
|
|
230
|
+
const path = profileId ? `/people/(id:${profileId})` : '/me';
|
|
231
|
+
const profile = await linkedinFetch(token, path);
|
|
232
|
+
return profile;
|
|
233
|
+
}
|
|
234
|
+
default:
|
|
235
|
+
throw new Error(`Unknown action: ${actionId}`);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// LinkedIn doesn't support efficient polling for triggers
|
|
240
|
+
async pollTrigger(_triggerId: string, _token: string, _lastPollAt?: number): Promise<TriggerEvent[]> {
|
|
241
|
+
return [];
|
|
242
|
+
},
|
|
243
|
+
});
|
package/src/reddit.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { defineConnector } from '@auxiora/connectors';
|
|
2
|
+
import type { TriggerEvent } from '@auxiora/connectors';
|
|
3
|
+
|
|
4
|
+
const REDDIT_BASE = 'https://oauth.reddit.com';
|
|
5
|
+
const REDDIT_UA = 'auxiora:v1.0.0 (by /u/auxiora)';
|
|
6
|
+
|
|
7
|
+
async function redditGet(token: string, path: string): Promise<Record<string, unknown>> {
|
|
8
|
+
const res = await fetch(`${REDDIT_BASE}${path}`, {
|
|
9
|
+
headers: {
|
|
10
|
+
'Authorization': `Bearer ${token}`,
|
|
11
|
+
'User-Agent': REDDIT_UA,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok) throw new Error(`Reddit API error: ${res.status} ${await res.text().catch(() => res.statusText)}`);
|
|
15
|
+
return res.json() as Promise<Record<string, unknown>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function redditPost(token: string, path: string, body: Record<string, string>): Promise<Record<string, unknown>> {
|
|
19
|
+
const res = await fetch(`${REDDIT_BASE}${path}`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Authorization': `Bearer ${token}`,
|
|
23
|
+
'User-Agent': REDDIT_UA,
|
|
24
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
25
|
+
},
|
|
26
|
+
body: new URLSearchParams(body).toString(),
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) throw new Error(`Reddit API error: ${res.status} ${await res.text().catch(() => res.statusText)}`);
|
|
29
|
+
return res.json() as Promise<Record<string, unknown>>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function extractPosts(listing: Record<string, unknown>): unknown[] {
|
|
33
|
+
const data = listing.data as Record<string, unknown> | undefined;
|
|
34
|
+
const children = (data?.children ?? []) as Array<Record<string, unknown>>;
|
|
35
|
+
return children.map((c) => c.data);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const redditConnector = defineConnector({
|
|
39
|
+
id: 'reddit',
|
|
40
|
+
name: 'Reddit',
|
|
41
|
+
description: 'Integration with Reddit for browsing, posting, and messaging',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
category: 'social',
|
|
44
|
+
icon: 'reddit',
|
|
45
|
+
|
|
46
|
+
auth: {
|
|
47
|
+
type: 'oauth2',
|
|
48
|
+
oauth2: {
|
|
49
|
+
authUrl: 'https://www.reddit.com/api/v1/authorize',
|
|
50
|
+
tokenUrl: 'https://www.reddit.com/api/v1/access_token',
|
|
51
|
+
scopes: ['read', 'submit', 'privatemessages', 'vote', 'save', 'identity'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
actions: [
|
|
56
|
+
{
|
|
57
|
+
id: 'front-page',
|
|
58
|
+
name: 'Read Front Page',
|
|
59
|
+
description: 'Read posts from the Reddit front page',
|
|
60
|
+
trustMinimum: 1,
|
|
61
|
+
trustDomain: 'messaging',
|
|
62
|
+
reversible: false,
|
|
63
|
+
sideEffects: false,
|
|
64
|
+
params: {},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'subreddit-read',
|
|
68
|
+
name: 'Read Subreddit',
|
|
69
|
+
description: 'Read posts from a specific subreddit',
|
|
70
|
+
trustMinimum: 1,
|
|
71
|
+
trustDomain: 'messaging',
|
|
72
|
+
reversible: false,
|
|
73
|
+
sideEffects: false,
|
|
74
|
+
params: {
|
|
75
|
+
subreddit: { type: 'string', description: 'Subreddit name', required: true },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'post-submit',
|
|
80
|
+
name: 'Submit Post',
|
|
81
|
+
description: 'Submit a new post to a subreddit',
|
|
82
|
+
trustMinimum: 3,
|
|
83
|
+
trustDomain: 'messaging',
|
|
84
|
+
reversible: false,
|
|
85
|
+
sideEffects: true,
|
|
86
|
+
params: {
|
|
87
|
+
subreddit: { type: 'string', description: 'Subreddit name', required: true },
|
|
88
|
+
title: { type: 'string', description: 'Post title', required: true },
|
|
89
|
+
body: { type: 'string', description: 'Post body', required: true },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'comment',
|
|
94
|
+
name: 'Post Comment',
|
|
95
|
+
description: 'Post a comment on a Reddit post',
|
|
96
|
+
trustMinimum: 3,
|
|
97
|
+
trustDomain: 'messaging',
|
|
98
|
+
reversible: false,
|
|
99
|
+
sideEffects: true,
|
|
100
|
+
params: {
|
|
101
|
+
postId: { type: 'string', description: 'Post ID to comment on', required: true },
|
|
102
|
+
body: { type: 'string', description: 'Comment body', required: true },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'inbox-read',
|
|
107
|
+
name: 'Read Inbox',
|
|
108
|
+
description: 'Read Reddit inbox messages',
|
|
109
|
+
trustMinimum: 1,
|
|
110
|
+
trustDomain: 'messaging',
|
|
111
|
+
reversible: false,
|
|
112
|
+
sideEffects: false,
|
|
113
|
+
params: {},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'search',
|
|
117
|
+
name: 'Search Reddit',
|
|
118
|
+
description: 'Search across Reddit',
|
|
119
|
+
trustMinimum: 1,
|
|
120
|
+
trustDomain: 'messaging',
|
|
121
|
+
reversible: false,
|
|
122
|
+
sideEffects: false,
|
|
123
|
+
params: {
|
|
124
|
+
query: { type: 'string', description: 'Search query', required: true },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'save-post',
|
|
129
|
+
name: 'Save Post',
|
|
130
|
+
description: 'Save a Reddit post',
|
|
131
|
+
trustMinimum: 1,
|
|
132
|
+
trustDomain: 'messaging',
|
|
133
|
+
reversible: true,
|
|
134
|
+
sideEffects: true,
|
|
135
|
+
params: {
|
|
136
|
+
postId: { type: 'string', description: 'Post ID to save', required: true },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'upvote',
|
|
141
|
+
name: 'Upvote',
|
|
142
|
+
description: 'Upvote or downvote a post',
|
|
143
|
+
trustMinimum: 2,
|
|
144
|
+
trustDomain: 'messaging',
|
|
145
|
+
reversible: true,
|
|
146
|
+
sideEffects: true,
|
|
147
|
+
params: {
|
|
148
|
+
postId: { type: 'string', description: 'Post ID to vote on', required: true },
|
|
149
|
+
direction: { type: 'string', description: 'Vote direction (up or down)', default: 'up' },
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
|
|
154
|
+
triggers: [
|
|
155
|
+
{
|
|
156
|
+
id: 'new-inbox',
|
|
157
|
+
name: 'New Inbox Message',
|
|
158
|
+
description: 'Triggered when a new inbox message is received',
|
|
159
|
+
type: 'poll',
|
|
160
|
+
pollIntervalMs: 120_000,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'subreddit-new',
|
|
164
|
+
name: 'New Subreddit Post',
|
|
165
|
+
description: 'Triggered when a new post appears in a monitored subreddit',
|
|
166
|
+
type: 'poll',
|
|
167
|
+
pollIntervalMs: 300_000,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
|
|
171
|
+
entities: [
|
|
172
|
+
{
|
|
173
|
+
id: 'post',
|
|
174
|
+
name: 'Post',
|
|
175
|
+
description: 'A Reddit post',
|
|
176
|
+
fields: { id: 'string', title: 'string', subreddit: 'string', author: 'string', score: 'number', commentCount: 'number' },
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'comment',
|
|
180
|
+
name: 'Comment',
|
|
181
|
+
description: 'A Reddit comment',
|
|
182
|
+
fields: { id: 'string', body: 'string', author: 'string', score: 'number', postId: 'string' },
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
|
|
186
|
+
async executeAction(actionId: string, params: Record<string, unknown>, token: string): Promise<unknown> {
|
|
187
|
+
switch (actionId) {
|
|
188
|
+
case 'front-page': {
|
|
189
|
+
const res = await redditGet(token, '/best?limit=25');
|
|
190
|
+
return { posts: extractPosts(res) };
|
|
191
|
+
}
|
|
192
|
+
case 'subreddit-read': {
|
|
193
|
+
const sub = params.subreddit as string;
|
|
194
|
+
const res = await redditGet(token, `/r/${sub}/hot?limit=25`);
|
|
195
|
+
return { posts: extractPosts(res) };
|
|
196
|
+
}
|
|
197
|
+
case 'post-submit': {
|
|
198
|
+
const res = await redditPost(token, '/api/submit', {
|
|
199
|
+
sr: params.subreddit as string,
|
|
200
|
+
kind: 'self',
|
|
201
|
+
title: params.title as string,
|
|
202
|
+
text: params.body as string,
|
|
203
|
+
});
|
|
204
|
+
const json = res.json as Record<string, unknown> | undefined;
|
|
205
|
+
const data = json?.data as Record<string, unknown> | undefined;
|
|
206
|
+
return { postId: data?.id ?? data?.name, status: 'submitted' };
|
|
207
|
+
}
|
|
208
|
+
case 'comment': {
|
|
209
|
+
const postId = params.postId as string;
|
|
210
|
+
const thingId = postId.startsWith('t3_') ? postId : `t3_${postId}`;
|
|
211
|
+
const res = await redditPost(token, '/api/comment', {
|
|
212
|
+
thing_id: thingId,
|
|
213
|
+
text: params.body as string,
|
|
214
|
+
});
|
|
215
|
+
const json = res.json as Record<string, unknown> | undefined;
|
|
216
|
+
const data = json?.data as Record<string, unknown> | undefined;
|
|
217
|
+
const things = data?.things as Array<Record<string, unknown>> | undefined;
|
|
218
|
+
const commentData = things?.[0]?.data as Record<string, unknown> | undefined;
|
|
219
|
+
return { commentId: commentData?.id ?? commentData?.name, status: 'posted' };
|
|
220
|
+
}
|
|
221
|
+
case 'inbox-read': {
|
|
222
|
+
const res = await redditGet(token, '/message/inbox?limit=25');
|
|
223
|
+
return { messages: extractPosts(res) };
|
|
224
|
+
}
|
|
225
|
+
case 'search': {
|
|
226
|
+
const query = encodeURIComponent(params.query as string);
|
|
227
|
+
const res = await redditGet(token, `/search?q=${query}&limit=25`);
|
|
228
|
+
return { posts: extractPosts(res) };
|
|
229
|
+
}
|
|
230
|
+
case 'save-post': {
|
|
231
|
+
const postId = params.postId as string;
|
|
232
|
+
const fullId = postId.startsWith('t3_') ? postId : `t3_${postId}`;
|
|
233
|
+
await redditPost(token, '/api/save', { id: fullId });
|
|
234
|
+
return { postId: params.postId, status: 'saved' };
|
|
235
|
+
}
|
|
236
|
+
case 'upvote': {
|
|
237
|
+
const postId = params.postId as string;
|
|
238
|
+
const fullId = postId.startsWith('t3_') ? postId : `t3_${postId}`;
|
|
239
|
+
const direction = (params.direction as string) ?? 'up';
|
|
240
|
+
const dir = direction === 'up' ? '1' : '-1';
|
|
241
|
+
await redditPost(token, '/api/vote', { id: fullId, dir });
|
|
242
|
+
return { postId: params.postId, status: 'voted' };
|
|
243
|
+
}
|
|
244
|
+
default:
|
|
245
|
+
throw new Error(`Unknown action: ${actionId}`);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async pollTrigger(triggerId: string, token: string, _lastPollAt?: number): Promise<TriggerEvent[]> {
|
|
250
|
+
switch (triggerId) {
|
|
251
|
+
case 'new-inbox': {
|
|
252
|
+
const res = await redditGet(token, '/message/unread?limit=25');
|
|
253
|
+
const messages = extractPosts(res) as Array<Record<string, unknown>>;
|
|
254
|
+
return messages.map((m) => ({
|
|
255
|
+
triggerId: 'new-inbox',
|
|
256
|
+
connectorId: 'reddit',
|
|
257
|
+
data: m,
|
|
258
|
+
timestamp: typeof m.created_utc === 'number' ? (m.created_utc as number) * 1000 : Date.now(),
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
case 'subreddit-new':
|
|
262
|
+
// Cannot poll specific subreddit without config
|
|
263
|
+
return [];
|
|
264
|
+
default:
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
});
|