nodebb-plugin-integration 0.5.0 → 0.6.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/README.md +156 -145
- package/README.ru.md +147 -136
- package/docs/MCP.md +718 -0
- package/docs/MCP.ru.md +719 -0
- package/package.json +3 -2
- package/src/mcp/server.js +1 -1
- package/src/mcp/tools.js +294 -76
- package/src/nodebb/posts.js +72 -21
- package/src/nodebb/topics.js +121 -24
- package/src/nodebb/users.js +19 -4
- package/src/routes/api.js +297 -35
- package/src/routes/index.js +24 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-integration",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI integration layer for NodeBB forums with MCP, semantic search, RAG and Qdrant knowledge pipeline",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"languages",
|
|
17
17
|
"README.md",
|
|
18
18
|
"README.ru.md",
|
|
19
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"docs"
|
|
20
21
|
],
|
|
21
22
|
"keywords": [
|
|
22
23
|
"nodebb",
|
package/src/mcp/server.js
CHANGED
package/src/mcp/tools.js
CHANGED
|
@@ -12,24 +12,20 @@ const definitions = [
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
name: "get_post",
|
|
15
|
-
description: "Get a forum post by
|
|
15
|
+
description: "Get a forum post by ID. NodeBB access permissions are checked.",
|
|
16
16
|
inputSchema: {
|
|
17
17
|
type: "object",
|
|
18
|
-
properties: {
|
|
19
|
-
pid: { type: "integer", minimum: 1 }
|
|
20
|
-
},
|
|
18
|
+
properties: { pid: { type: "integer", minimum: 1 } },
|
|
21
19
|
required: ["pid"],
|
|
22
20
|
additionalProperties: false
|
|
23
21
|
}
|
|
24
22
|
},
|
|
25
23
|
{
|
|
26
24
|
name: "get_topic",
|
|
27
|
-
description: "Get a forum topic by
|
|
25
|
+
description: "Get a forum topic by ID. NodeBB access permissions are checked.",
|
|
28
26
|
inputSchema: {
|
|
29
27
|
type: "object",
|
|
30
|
-
properties: {
|
|
31
|
-
tid: { type: "integer", minimum: 1 }
|
|
32
|
-
},
|
|
28
|
+
properties: { tid: { type: "integer", minimum: 1 } },
|
|
33
29
|
required: ["tid"],
|
|
34
30
|
additionalProperties: false
|
|
35
31
|
}
|
|
@@ -39,9 +35,7 @@ const definitions = [
|
|
|
39
35
|
description: "Get public profile information for a forum user",
|
|
40
36
|
inputSchema: {
|
|
41
37
|
type: "object",
|
|
42
|
-
properties: {
|
|
43
|
-
uid: { type: "integer", minimum: 1 }
|
|
44
|
-
},
|
|
38
|
+
properties: { uid: { type: "integer", minimum: 1 } },
|
|
45
39
|
required: ["uid"],
|
|
46
40
|
additionalProperties: false
|
|
47
41
|
}
|
|
@@ -58,66 +52,292 @@ const definitions = [
|
|
|
58
52
|
required: ["query"],
|
|
59
53
|
additionalProperties: false
|
|
60
54
|
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "create_topic",
|
|
58
|
+
description: "Create a topic in a NodeBB category as the configured MCP service user",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
cid: { type: "integer", minimum: 1 },
|
|
63
|
+
title: { type: "string", minLength: 1, maxLength: 255 },
|
|
64
|
+
content: { type: "string", minLength: 1, maxLength: 20000 },
|
|
65
|
+
tags: {
|
|
66
|
+
type: "array",
|
|
67
|
+
maxItems: 10,
|
|
68
|
+
items: { type: "string", minLength: 1, maxLength: 32 }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
required: ["cid", "title", "content"],
|
|
72
|
+
additionalProperties: false
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "delete_topic",
|
|
77
|
+
description: "Soft-delete a topic using NodeBB permissions and hooks",
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: { tid: { type: "integer", minimum: 1 } },
|
|
81
|
+
required: ["tid"],
|
|
82
|
+
additionalProperties: false
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "restore_topic",
|
|
87
|
+
description: "Restore a soft-deleted topic using NodeBB permissions and hooks",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: { tid: { type: "integer", minimum: 1 } },
|
|
91
|
+
required: ["tid"],
|
|
92
|
+
additionalProperties: false
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "list_topics",
|
|
97
|
+
description: "List topics visible to the configured MCP service user",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
start: { type: "integer", minimum: 0 },
|
|
102
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
103
|
+
sort: {
|
|
104
|
+
type: "string",
|
|
105
|
+
enum: ["recent", "old", "create", "posts", "votes", "views"]
|
|
106
|
+
},
|
|
107
|
+
cid: { type: "integer", minimum: 1 }
|
|
108
|
+
},
|
|
109
|
+
additionalProperties: false
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "create_post",
|
|
114
|
+
description: "Create a reply in a topic using NodeBB permissions and hooks",
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
tid: { type: "integer", minimum: 1 },
|
|
119
|
+
content: { type: "string", minLength: 1, maxLength: 20000 }
|
|
120
|
+
},
|
|
121
|
+
required: ["tid", "content"],
|
|
122
|
+
additionalProperties: false
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "edit_post",
|
|
127
|
+
description: "Edit a post using NodeBB permissions and edit restrictions",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
pid: { type: "integer", minimum: 1 },
|
|
132
|
+
content: { type: "string", minLength: 1, maxLength: 20000 }
|
|
133
|
+
},
|
|
134
|
+
required: ["pid", "content"],
|
|
135
|
+
additionalProperties: false
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "delete_post",
|
|
140
|
+
description: "Soft-delete a post using NodeBB permissions and hooks",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: "object",
|
|
143
|
+
properties: { pid: { type: "integer", minimum: 1 } },
|
|
144
|
+
required: ["pid"],
|
|
145
|
+
additionalProperties: false
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "restore_post",
|
|
150
|
+
description: "Restore a soft-deleted post using NodeBB permissions and hooks",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: { pid: { type: "integer", minimum: 1 } },
|
|
154
|
+
required: ["pid"],
|
|
155
|
+
additionalProperties: false
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "delete_user_posts",
|
|
160
|
+
description: "Soft-delete up to 100 replies by a user. The first post of every topic is skipped. Use start to process subsequent batches.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
uid: { type: "integer", minimum: 1 },
|
|
165
|
+
start: { type: "integer", minimum: 0 },
|
|
166
|
+
limit: { type: "integer", minimum: 1, maximum: 100 }
|
|
167
|
+
},
|
|
168
|
+
required: ["uid"],
|
|
169
|
+
additionalProperties: false
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "list_posts",
|
|
174
|
+
description: "List posts in a topic with NodeBB access filtering and pagination",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
tid: { type: "integer", minimum: 1 },
|
|
179
|
+
start: { type: "integer", minimum: 0 },
|
|
180
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
181
|
+
reverse: { type: "boolean" }
|
|
182
|
+
},
|
|
183
|
+
required: ["tid"],
|
|
184
|
+
additionalProperties: false
|
|
185
|
+
}
|
|
61
186
|
}
|
|
62
187
|
];
|
|
63
188
|
|
|
189
|
+
const allowedArguments = {
|
|
190
|
+
get_forum_info: [],
|
|
191
|
+
get_post: ["pid"],
|
|
192
|
+
get_topic: ["tid"],
|
|
193
|
+
get_user: ["uid"],
|
|
194
|
+
search_knowledge: ["query", "limit"],
|
|
195
|
+
create_topic: ["cid", "title", "content", "tags"],
|
|
196
|
+
delete_topic: ["tid"],
|
|
197
|
+
restore_topic: ["tid"],
|
|
198
|
+
list_topics: ["start", "limit", "sort", "cid"],
|
|
199
|
+
create_post: ["tid", "content"],
|
|
200
|
+
edit_post: ["pid", "content"],
|
|
201
|
+
delete_post: ["pid"],
|
|
202
|
+
restore_post: ["pid"],
|
|
203
|
+
delete_user_posts: ["uid", "start", "limit"],
|
|
204
|
+
list_posts: ["tid", "start", "limit", "reverse"]
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
function requireId(args, key) {
|
|
208
|
+
if (!Number.isSafeInteger(args[key]) || args[key] < 1) {
|
|
209
|
+
throw new Error(`Invalid ${key}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function requireText(args, key, maximum) {
|
|
214
|
+
if (typeof args[key] !== "string" ||
|
|
215
|
+
!args[key].trim() || args[key].length > maximum) {
|
|
216
|
+
throw new Error(`Invalid ${key}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function pageValue(value, fallback, maximum, name) {
|
|
221
|
+
const result = value === undefined ? fallback : value;
|
|
222
|
+
|
|
223
|
+
if (!Number.isSafeInteger(result) || result < 0 || result > maximum) {
|
|
224
|
+
throw new Error(`Invalid ${name}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
64
230
|
function validateArguments(name, args) {
|
|
65
231
|
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
66
232
|
throw new Error("Tool arguments must be an object");
|
|
67
233
|
}
|
|
68
234
|
|
|
69
|
-
const allowed =
|
|
70
|
-
get_forum_info: [],
|
|
71
|
-
get_post: ["pid"],
|
|
72
|
-
get_topic: ["tid"],
|
|
73
|
-
get_user: ["uid"],
|
|
74
|
-
search_knowledge: ["query", "limit"]
|
|
75
|
-
}[name];
|
|
235
|
+
const allowed = allowedArguments[name];
|
|
76
236
|
|
|
77
|
-
if (!allowed) {
|
|
78
|
-
throw new Error(`Unknown MCP tool: ${name}`);
|
|
79
|
-
}
|
|
237
|
+
if (!allowed) throw new Error(`Unknown MCP tool: ${name}`);
|
|
80
238
|
|
|
81
|
-
if (Object.keys(args).some(
|
|
239
|
+
if (Object.keys(args).some(key => !allowed.includes(key))) {
|
|
82
240
|
throw new Error("Unexpected tool argument");
|
|
83
241
|
}
|
|
84
242
|
|
|
85
|
-
if (name === "get_forum_info") {
|
|
86
|
-
|
|
243
|
+
if (name === "get_forum_info") return {};
|
|
244
|
+
|
|
245
|
+
if (["get_post", "get_topic", "get_user",
|
|
246
|
+
"delete_topic", "restore_topic",
|
|
247
|
+
"delete_post", "restore_post"].includes(name)) {
|
|
248
|
+
const key = name.endsWith("_post") || name === "get_post"
|
|
249
|
+
? "pid" : name.endsWith("_topic") || name === "get_topic"
|
|
250
|
+
? "tid" : "uid";
|
|
251
|
+
|
|
252
|
+
requireId(args, key);
|
|
253
|
+
return { [key]: args[key] };
|
|
87
254
|
}
|
|
88
255
|
|
|
89
|
-
if (name === "
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
: name === "get_topic"
|
|
93
|
-
? "tid"
|
|
94
|
-
: "uid";
|
|
256
|
+
if (name === "search_knowledge") {
|
|
257
|
+
requireText(args, "query", 2000);
|
|
258
|
+
const limit = args.limit === undefined ? 5 : args.limit;
|
|
95
259
|
|
|
96
|
-
if (!Number.isSafeInteger(
|
|
97
|
-
throw new Error(
|
|
260
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 20) {
|
|
261
|
+
throw new Error("Search limit must be from 1 to 20");
|
|
98
262
|
}
|
|
99
263
|
|
|
100
|
-
return {
|
|
264
|
+
return { query: args.query.trim(), limit };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (name === "create_topic") {
|
|
268
|
+
requireId(args, "cid");
|
|
269
|
+
requireText(args, "title", 255);
|
|
270
|
+
requireText(args, "content", 20000);
|
|
271
|
+
|
|
272
|
+
const tags = args.tags === undefined ? [] : args.tags;
|
|
273
|
+
|
|
274
|
+
if (!Array.isArray(tags) || tags.length > 10 ||
|
|
275
|
+
tags.some(tag => typeof tag !== "string" ||
|
|
276
|
+
!tag.trim() || tag.length > 32)) {
|
|
277
|
+
throw new Error("Invalid tags");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
cid: args.cid,
|
|
282
|
+
title: args.title.trim(),
|
|
283
|
+
content: args.content,
|
|
284
|
+
tags
|
|
285
|
+
};
|
|
101
286
|
}
|
|
102
287
|
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
args
|
|
107
|
-
|
|
108
|
-
throw new Error("Search query must contain 1 to 2000 characters");
|
|
288
|
+
if (name === "create_post" || name === "edit_post") {
|
|
289
|
+
const key = name === "create_post" ? "tid" : "pid";
|
|
290
|
+
requireId(args, key);
|
|
291
|
+
requireText(args, "content", 20000);
|
|
292
|
+
return { [key]: args[key], content: args.content };
|
|
109
293
|
}
|
|
110
294
|
|
|
111
|
-
|
|
295
|
+
if (name === "list_topics") {
|
|
296
|
+
const result = {
|
|
297
|
+
start: pageValue(args.start, 0, 10000000, "start"),
|
|
298
|
+
limit: pageValue(args.limit, 20, 100, "limit"),
|
|
299
|
+
sort: args.sort === undefined ? "recent" : args.sort
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (!["recent", "old", "create", "posts", "votes", "views"]
|
|
303
|
+
.includes(result.sort)) {
|
|
304
|
+
throw new Error("Invalid sort");
|
|
305
|
+
}
|
|
112
306
|
|
|
113
|
-
|
|
114
|
-
|
|
307
|
+
if (args.cid !== undefined) {
|
|
308
|
+
requireId(args, "cid");
|
|
309
|
+
result.cid = args.cid;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return result;
|
|
115
313
|
}
|
|
116
314
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
315
|
+
if (name === "delete_user_posts") {
|
|
316
|
+
requireId(args, "uid");
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
uid: args.uid,
|
|
320
|
+
start: pageValue(args.start, 0, 10000000, "start"),
|
|
321
|
+
limit: pageValue(args.limit, 100, 100, "limit")
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (name === "list_posts") {
|
|
326
|
+
requireId(args, "tid");
|
|
327
|
+
|
|
328
|
+
if (args.reverse !== undefined && typeof args.reverse !== "boolean") {
|
|
329
|
+
throw new Error("Invalid reverse");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
tid: args.tid,
|
|
334
|
+
start: pageValue(args.start, 0, 10000000, "start"),
|
|
335
|
+
limit: pageValue(args.limit, 20, 100, "limit"),
|
|
336
|
+
reverse: args.reverse ?? false
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
throw new Error(`Unknown MCP tool: ${name}`);
|
|
121
341
|
}
|
|
122
342
|
|
|
123
343
|
async function execute(name, args = {}, api) {
|
|
@@ -127,25 +347,27 @@ async function execute(name, args = {}, api) {
|
|
|
127
347
|
|
|
128
348
|
const validated = validateArguments(name, args);
|
|
129
349
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
350
|
+
const routes = {
|
|
351
|
+
get_forum_info: ["GET", "/api/v3/integration/internal/info"],
|
|
352
|
+
get_post: ["POST", "/api/v3/integration/internal/post"],
|
|
353
|
+
get_topic: ["POST", "/api/v3/integration/internal/topic"],
|
|
354
|
+
get_user: ["POST", "/api/v3/integration/internal/user"],
|
|
355
|
+
search_knowledge: ["POST", "/api/v3/integration/internal/search"],
|
|
356
|
+
create_topic: ["POST", "/api/v3/integration/internal/topic/create"],
|
|
357
|
+
delete_topic: ["POST", "/api/v3/integration/internal/topic/delete"],
|
|
358
|
+
restore_topic: ["POST", "/api/v3/integration/internal/topic/restore"],
|
|
359
|
+
list_topics: ["POST", "/api/v3/integration/internal/topics"],
|
|
360
|
+
create_post: ["POST", "/api/v3/integration/internal/post/create"],
|
|
361
|
+
edit_post: ["POST", "/api/v3/integration/internal/post/edit"],
|
|
362
|
+
delete_post: ["POST", "/api/v3/integration/internal/post/delete"],
|
|
363
|
+
restore_post: ["POST", "/api/v3/integration/internal/post/restore"],
|
|
364
|
+
delete_user_posts: ["POST", "/api/v3/integration/internal/user/delete-posts"],
|
|
365
|
+
list_posts: ["POST", "/api/v3/integration/internal/posts"]
|
|
366
|
+
};
|
|
142
367
|
|
|
143
|
-
|
|
144
|
-
return api.request("POST", "/api/v3/integration/internal/search", validated);
|
|
368
|
+
const [method, path] = routes[name];
|
|
145
369
|
|
|
146
|
-
|
|
147
|
-
throw new Error(`Unknown MCP tool: ${name}`);
|
|
148
|
-
}
|
|
370
|
+
return api.request(method, path, validated);
|
|
149
371
|
}
|
|
150
372
|
|
|
151
373
|
function register(server, api) {
|
|
@@ -158,7 +380,7 @@ function register(server, api) {
|
|
|
158
380
|
tools: definitions
|
|
159
381
|
}));
|
|
160
382
|
|
|
161
|
-
server.setRequestHandler(CallToolRequestSchema, async
|
|
383
|
+
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
162
384
|
const name = request.params.name;
|
|
163
385
|
const args = request.params.arguments || {};
|
|
164
386
|
|
|
@@ -166,22 +388,18 @@ function register(server, api) {
|
|
|
166
388
|
const result = await execute(name, args, api);
|
|
167
389
|
|
|
168
390
|
return {
|
|
169
|
-
content: [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
]
|
|
391
|
+
content: [{
|
|
392
|
+
type: "text",
|
|
393
|
+
text: JSON.stringify(result, null, 2)
|
|
394
|
+
}]
|
|
175
395
|
};
|
|
176
396
|
} catch (error) {
|
|
177
397
|
return {
|
|
178
398
|
isError: true,
|
|
179
|
-
content: [
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
]
|
|
399
|
+
content: [{
|
|
400
|
+
type: "text",
|
|
401
|
+
text: error.message || "Tool execution failed"
|
|
402
|
+
}]
|
|
185
403
|
};
|
|
186
404
|
}
|
|
187
405
|
});
|
package/src/nodebb/posts.js
CHANGED
|
@@ -3,18 +3,10 @@
|
|
|
3
3
|
const NodeBB = require("./index");
|
|
4
4
|
|
|
5
5
|
const META_FIELDS = [
|
|
6
|
-
"pid",
|
|
7
|
-
"tid",
|
|
8
|
-
"uid",
|
|
9
|
-
"timestamp",
|
|
10
|
-
"deleted",
|
|
11
|
-
"cid"
|
|
6
|
+
"pid", "tid", "uid", "timestamp", "deleted", "cid"
|
|
12
7
|
];
|
|
13
8
|
|
|
14
|
-
const CONTENT_FIELDS = [
|
|
15
|
-
"pid",
|
|
16
|
-
"content"
|
|
17
|
-
];
|
|
9
|
+
const CONTENT_FIELDS = ["pid", "content"];
|
|
18
10
|
|
|
19
11
|
function validatePid(pid) {
|
|
20
12
|
pid = Number(pid);
|
|
@@ -26,9 +18,18 @@ function validatePid(pid) {
|
|
|
26
18
|
return pid;
|
|
27
19
|
}
|
|
28
20
|
|
|
21
|
+
function validateUid(uid) {
|
|
22
|
+
uid = Number(uid);
|
|
23
|
+
|
|
24
|
+
if (!Number.isSafeInteger(uid) || uid <= 0) {
|
|
25
|
+
throw new TypeError("Invalid user ID");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return uid;
|
|
29
|
+
}
|
|
30
|
+
|
|
29
31
|
async function getPostMeta(pid) {
|
|
30
32
|
pid = validatePid(pid);
|
|
31
|
-
|
|
32
33
|
const posts = NodeBB.get("posts");
|
|
33
34
|
|
|
34
35
|
if (typeof posts.getPostFields !== "function") {
|
|
@@ -40,7 +41,6 @@ async function getPostMeta(pid) {
|
|
|
40
41
|
|
|
41
42
|
async function getPostContent(pid) {
|
|
42
43
|
pid = validatePid(pid);
|
|
43
|
-
|
|
44
44
|
const posts = NodeBB.get("posts");
|
|
45
45
|
|
|
46
46
|
if (typeof posts.getPostFields !== "function") {
|
|
@@ -61,17 +61,13 @@ async function getPosts(pids = [], uid) {
|
|
|
61
61
|
throw new TypeError("Invalid user ID");
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const unique = [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.map(Number)
|
|
68
|
-
.filter(pid => Number.isSafeInteger(pid) && pid > 0)
|
|
64
|
+
const unique = [...new Set(
|
|
65
|
+
pids.map(Number).filter(
|
|
66
|
+
pid => Number.isSafeInteger(pid) && pid > 0
|
|
69
67
|
)
|
|
70
|
-
];
|
|
68
|
+
)];
|
|
71
69
|
|
|
72
|
-
if (!unique.length)
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
70
|
+
if (!unique.length) return [];
|
|
75
71
|
|
|
76
72
|
const posts = NodeBB.get("posts");
|
|
77
73
|
|
|
@@ -84,10 +80,65 @@ async function getPosts(pids = [], uid) {
|
|
|
84
80
|
return result.posts || result;
|
|
85
81
|
}
|
|
86
82
|
|
|
83
|
+
async function editPost(pid, uid, content) {
|
|
84
|
+
pid = validatePid(pid);
|
|
85
|
+
uid = validateUid(uid);
|
|
86
|
+
|
|
87
|
+
const posts = NodeBB.get("posts");
|
|
88
|
+
|
|
89
|
+
if (typeof posts.edit !== "function") {
|
|
90
|
+
throw new Error("NodeBB posts.edit is unavailable");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return posts.edit({ pid, uid, content });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function deletePost(pid, uid) {
|
|
97
|
+
pid = validatePid(pid);
|
|
98
|
+
uid = validateUid(uid);
|
|
99
|
+
|
|
100
|
+
const posts = NodeBB.get("posts");
|
|
101
|
+
|
|
102
|
+
if (!posts.tools || typeof posts.tools.delete !== "function") {
|
|
103
|
+
throw new Error("NodeBB posts.tools.delete is unavailable");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return posts.tools.delete(uid, pid);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function restorePost(pid, uid) {
|
|
110
|
+
pid = validatePid(pid);
|
|
111
|
+
uid = validateUid(uid);
|
|
112
|
+
|
|
113
|
+
const posts = NodeBB.get("posts");
|
|
114
|
+
|
|
115
|
+
if (!posts.tools || typeof posts.tools.restore !== "function") {
|
|
116
|
+
throw new Error("NodeBB posts.tools.restore is unavailable");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return posts.tools.restore(uid, pid);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function getUserPostIds(uid, start, stop) {
|
|
123
|
+
uid = validateUid(uid);
|
|
124
|
+
|
|
125
|
+
const users = NodeBB.get("users");
|
|
126
|
+
|
|
127
|
+
if (typeof users.getPostIds !== "function") {
|
|
128
|
+
throw new Error("NodeBB users.getPostIds is unavailable");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return users.getPostIds(uid, start, stop);
|
|
132
|
+
}
|
|
133
|
+
|
|
87
134
|
module.exports = {
|
|
88
135
|
getPostMeta,
|
|
89
136
|
getPostContent,
|
|
90
137
|
getPosts,
|
|
138
|
+
editPost,
|
|
139
|
+
deletePost,
|
|
140
|
+
restorePost,
|
|
141
|
+
getUserPostIds,
|
|
91
142
|
META_FIELDS,
|
|
92
143
|
CONTENT_FIELDS
|
|
93
144
|
};
|