nodebb-plugin-integration 0.4.2 → 0.5.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 +199 -320
- package/README.ru.md +199 -347
- package/library.js +93 -115
- package/package.json +60 -57
- package/plugin.json +50 -33
- package/public/js/admin.js +58 -45
- package/public/templates/admin/plugins/integration.tpl +213 -0
- package/src/ai/embeddings.js +46 -253
- package/src/ai/indexer.js +156 -343
- package/src/ai/openai.js +74 -330
- package/src/ai/qdrant.js +114 -517
- package/src/ai/search.js +138 -251
- package/src/config/index.js +176 -133
- package/src/config/validation.js +250 -0
- package/src/events/index.js +174 -324
- package/src/mcp/manager.js +396 -0
- package/src/mcp/server.js +347 -177
- package/src/mcp/tools.js +158 -617
- package/src/nodebb/categories.js +39 -0
- package/src/nodebb/index.js +72 -305
- package/src/nodebb/meta.js +24 -273
- package/src/nodebb/posts.js +93 -348
- package/src/nodebb/privileges.js +68 -0
- package/src/nodebb/topics.js +91 -355
- package/src/nodebb/users.js +62 -345
- package/src/routes/api.js +255 -97
- package/src/routes/index.js +64 -49
- package/templates/admin/plugins/integration.tpl +0 -130
package/library.js
CHANGED
|
@@ -1,115 +1,93 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.error("[Plugin] topic save error:", err.message);
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* ACP menu item
|
|
102
|
-
*/
|
|
103
|
-
Plugin.addAdminNavigation = async function (header) {
|
|
104
|
-
|
|
105
|
-
header.plugins.push({
|
|
106
|
-
route: "/plugins/integration",
|
|
107
|
-
icon: "fa-robot",
|
|
108
|
-
name: "NodeBB Integration"
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return header;
|
|
112
|
-
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
module.exports = Plugin;
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const Routes = require("./src/routes");
|
|
4
|
+
const Events = require("./src/events");
|
|
5
|
+
const Config = require("./src/config");
|
|
6
|
+
const MCPManager = require("./src/mcp/manager");
|
|
7
|
+
|
|
8
|
+
const Plugin = {};
|
|
9
|
+
|
|
10
|
+
function registerSocketHandlers() {
|
|
11
|
+
if (typeof nodebb === "undefined" || !nodebb ||
|
|
12
|
+
typeof nodebb.require !== "function") {
|
|
13
|
+
throw new Error("Cannot access the NodeBB module loader");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SocketPlugins = nodebb.require("./src/socket.io/plugins");
|
|
17
|
+
const SocketAdmin = nodebb.require("./src/socket.io/admin");
|
|
18
|
+
|
|
19
|
+
SocketPlugins.integration = SocketPlugins.integration || {};
|
|
20
|
+
|
|
21
|
+
SocketPlugins.integration.save = async function (socket, data) {
|
|
22
|
+
await SocketAdmin.before(socket, "admin.settings.set");
|
|
23
|
+
|
|
24
|
+
await Config.set(data);
|
|
25
|
+
|
|
26
|
+
MCPManager.refresh().catch((error) => {
|
|
27
|
+
console.error(
|
|
28
|
+
"[NodeBB Integration] MCP configuration refresh failed:",
|
|
29
|
+
error.message
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return Config.info();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Plugin.init = async function (params = {}) {
|
|
38
|
+
console.log("[NodeBB Integration] initializing");
|
|
39
|
+
|
|
40
|
+
const { router, middleware } = params;
|
|
41
|
+
|
|
42
|
+
registerSocketHandlers();
|
|
43
|
+
|
|
44
|
+
if (router) {
|
|
45
|
+
Routes.init(router, middleware);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await Events.init();
|
|
49
|
+
|
|
50
|
+
MCPManager.init();
|
|
51
|
+
|
|
52
|
+
console.log("[NodeBB Integration] ready");
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
Plugin.onPostSave = async function (data) {
|
|
56
|
+
return Events.postSave(data);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
Plugin.onPostEdit = async function (data) {
|
|
60
|
+
return Events.postEdit(data);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
Plugin.onPostDelete = async function (data) {
|
|
64
|
+
return Events.postDelete(data);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
Plugin.onPostRestore = async function (data) {
|
|
68
|
+
return Events.postRestore(data);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
Plugin.onPostsPurge = async function (data) {
|
|
72
|
+
return Events.postsPurge(data);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
Plugin.onTopicDelete = async function (data) {
|
|
76
|
+
return Events.topicDelete(data);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
Plugin.onTopicRestore = async function (data) {
|
|
80
|
+
return Events.topicRestore(data);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
Plugin.addAdminNavigation = async function (header) {
|
|
84
|
+
header.plugins.push({
|
|
85
|
+
route: "/plugins/integration",
|
|
86
|
+
icon: "fa-robot",
|
|
87
|
+
name: "NodeBB Integration"
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return header;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
module.exports = Plugin;
|
package/package.json
CHANGED
|
@@ -1,57 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nodebb-plugin-integration",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI integration layer for NodeBB forums with MCP, semantic search, RAG and Qdrant knowledge pipeline",
|
|
5
|
-
"main": "library.js",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/Sergey-ek/nodebb-plugin-integration.git"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"library.js",
|
|
13
|
-
"plugin.json",
|
|
14
|
-
"src",
|
|
15
|
-
"public",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"README.md",
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"nodebb",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "nodebb-plugin-integration",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "AI integration layer for NodeBB forums with MCP, semantic search, RAG and Qdrant knowledge pipeline",
|
|
5
|
+
"main": "library.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Sergey-ek/nodebb-plugin-integration.git"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"library.js",
|
|
13
|
+
"plugin.json",
|
|
14
|
+
"src",
|
|
15
|
+
"public",
|
|
16
|
+
"languages",
|
|
17
|
+
"README.md",
|
|
18
|
+
"README.ru.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"nodebb",
|
|
23
|
+
"nodebb-plugin",
|
|
24
|
+
"ai",
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"rag",
|
|
28
|
+
"qdrant",
|
|
29
|
+
"embeddings",
|
|
30
|
+
"semantic-search",
|
|
31
|
+
"knowledge-base"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=22"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "cross-env NODE_ENV=test mocha test/*.test.js",
|
|
38
|
+
"check": "find . -type d -name node_modules -prune -o -type f -name \"*.js\" -print0 | xargs -0 -r -n1 node --check"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
42
|
+
"@qdrant/js-client-rest": "^1.12.0",
|
|
43
|
+
"dotenv": "^16.4.7",
|
|
44
|
+
"node-fetch": "^3.3.2",
|
|
45
|
+
"openai": "^6.45.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"cross-env": "^10.0.0",
|
|
49
|
+
"mocha": "^12.0.3",
|
|
50
|
+
"proxyquire": "^2.1.3"
|
|
51
|
+
},
|
|
52
|
+
"nbbpm": {
|
|
53
|
+
"compatibility": "^4.4.1"
|
|
54
|
+
},
|
|
55
|
+
"overrides": {
|
|
56
|
+
"undici": "^7.30.1",
|
|
57
|
+
"glob": "^14.0.2",
|
|
58
|
+
"minimatch": "^11.0.1"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/plugin.json
CHANGED
|
@@ -1,33 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "nodebb-plugin-integration",
|
|
3
|
-
"name": "NodeBB Integration Layer",
|
|
4
|
-
"description": "AI integration
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
"hook": "
|
|
18
|
-
"method": "
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"hook": "action:post.
|
|
22
|
-
"method": "
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"hook": "action:
|
|
26
|
-
"method": "
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"hook": "
|
|
30
|
-
"method": "
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
{
|
|
2
|
+
"id": "nodebb-plugin-integration",
|
|
3
|
+
"name": "NodeBB Integration Layer",
|
|
4
|
+
"description": "AI integration, semantic search, RAG and Qdrant knowledge pipeline for NodeBB",
|
|
5
|
+
"staticDirs": {
|
|
6
|
+
"public": "public"
|
|
7
|
+
},
|
|
8
|
+
"modules": {
|
|
9
|
+
"../admin/plugins/integration.js": "public/js/admin.js"
|
|
10
|
+
},
|
|
11
|
+
"hooks": [
|
|
12
|
+
{
|
|
13
|
+
"hook": "static:app.load",
|
|
14
|
+
"method": "init"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"hook": "action:post.save",
|
|
18
|
+
"method": "onPostSave"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"hook": "action:post.edit",
|
|
22
|
+
"method": "onPostEdit"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"hook": "action:post.delete",
|
|
26
|
+
"method": "onPostDelete"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"hook": "action:post.restore",
|
|
30
|
+
"method": "onPostRestore"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"hook": "action:posts.purge",
|
|
34
|
+
"method": "onPostsPurge"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"hook": "action:topic.delete",
|
|
38
|
+
"method": "onTopicDelete"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"hook": "action:topic.restore",
|
|
42
|
+
"method": "onTopicRestore"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"hook": "filter:admin.header.build",
|
|
46
|
+
"method": "addAdminNavigation"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"templates": "./public/templates"
|
|
50
|
+
}
|
package/public/js/admin.js
CHANGED
|
@@ -1,45 +1,58 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
define("admin/plugins/integration", [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
define("admin/plugins/integration", ["alerts"], function (Alerts) {
|
|
4
|
+
const ACP = {};
|
|
5
|
+
|
|
6
|
+
ACP.init = function () {
|
|
7
|
+
const container = $(".acp-page-container");
|
|
8
|
+
const form = container.find("form");
|
|
9
|
+
const saveButton = $("#save");
|
|
10
|
+
|
|
11
|
+
saveButton.on("click", function () {
|
|
12
|
+
const values = {};
|
|
13
|
+
|
|
14
|
+
form.find("[name]").each(function () {
|
|
15
|
+
const field = $(this);
|
|
16
|
+
const name = field.attr("name");
|
|
17
|
+
|
|
18
|
+
if (field.is("select[multiple]")) {
|
|
19
|
+
values[name] = field.val() || [];
|
|
20
|
+
} else if (field.is(":checkbox")) {
|
|
21
|
+
values[name] = field.is(":checked");
|
|
22
|
+
} else if (field.attr("type") === "number") {
|
|
23
|
+
values[name] = Number(field.val());
|
|
24
|
+
} else {
|
|
25
|
+
values[name] = field.val();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
for (const name of ["openaiApiKey", "mcpBearerToken"]) {
|
|
30
|
+
if (typeof values[name] !== "string" || !values[name].trim()) {
|
|
31
|
+
delete values[name];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
saveButton.prop("disabled", true);
|
|
36
|
+
|
|
37
|
+
socket.emit("plugins.integration.save", values, function (error, result) {
|
|
38
|
+
saveButton.prop("disabled", false);
|
|
39
|
+
|
|
40
|
+
if (error) {
|
|
41
|
+
Alerts.error(error.message || "Failed to save settings");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
form.find('[name="openaiApiKey"], [name="mcpBearerToken"]').val("");
|
|
46
|
+
|
|
47
|
+
container.find(".integration-save-status").remove();
|
|
48
|
+
|
|
49
|
+
$("<div>", {
|
|
50
|
+
class: "alert alert-success integration-save-status",
|
|
51
|
+
text: result ? "Settings saved successfully" : "Settings saved"
|
|
52
|
+
}).insertBefore(form);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return ACP;
|
|
58
|
+
});
|