nodebb-plugin-openai 1.4.3 → 2.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.
@@ -2,6 +2,7 @@
2
2
  "error.need-x-reputation-to-mention": "You need at least %1 reputation for mentioning chatgpt",
3
3
  "summarize-topic": "Summarize Topic",
4
4
  "topic-summary": "Topic Summary",
5
+ "summary-cooldown-error": "You must wait before summarizing a topic again.",
5
6
 
6
7
  "admin.general": "General",
7
8
  "admin.apikey": "API Key",
@@ -2,6 +2,7 @@
2
2
  "error.need-x-reputation-to-mention": "You need at least %1 reputation for mentioning chatgpt",
3
3
  "summarize-topic": "Summarize Topic",
4
4
  "topic-summary": "Topic Summary",
5
+ "summary-cooldown-error": "You must wait before summarizing a topic again.",
5
6
 
6
7
  "admin.general": "General",
7
8
  "admin.apikey": "API Key",
@@ -2,6 +2,7 @@
2
2
  "error.need-x-reputation-to-mention": "דרושה לך מוניטין של %1 לפחות כדי לאזכר את chatgpt",
3
3
  "summarize-topic": "סכם נושא",
4
4
  "topic-summary": "סיכום הנושא",
5
+ "summary-cooldown-error": "עליך להמתין לפני שתסכם את הנושא שוב.",
5
6
 
6
7
  "admin.general": "כללי",
7
8
  "admin.apikey": "מפתח API",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "error.need-x-reputation-to-mention": "提及 chatgpt 至少需要 %1 声望值",
3
3
  "summarize-topic": "总结主题",
4
- "topic-summary": "主题摘要"
4
+ "topic-summary": "主题摘要",
5
+ "summary-cooldown-error": "在再次总结主题之前,您必须等待。"
5
6
  }
@@ -1,14 +1,57 @@
1
1
  'use strict';
2
2
 
3
- const groups = require.main.require('./src/groups');
3
+ const groups = nodebb.require('./src/groups');
4
4
 
5
5
  const Controllers = module.exports;
6
6
 
7
7
  Controllers.renderAdminPage = async function (req, res/* , next */) {
8
8
  const groupsData = await groups.getNonPrivilegeGroups('groups:createtime', 0, -1);
9
9
  groupsData.sort((a, b) => b.system - a.system);
10
+ // Uncomment the following lines to list available models
11
+ // const list = await openai.models.list();
12
+ // for await (const model of list) {
13
+ // console.log(model.id);
14
+ // }
15
+
10
16
  res.render('admin/plugins/openai', {
11
17
  title: 'OpenAI',
12
18
  groups: groupsData,
19
+ models: [
20
+ { id: 'gpt-3.5-turbo' },
21
+ { id: 'gpt-4' },
22
+ { id: 'gpt-4-turbo' },
23
+ { id: 'gpt-4.1' },
24
+ { id: 'gpt-4.1-mini' },
25
+ { id: 'gpt-4.1-nano' },
26
+ { id: 'gpt-4o' },
27
+ { id: 'gpt-4o-mini' },
28
+ { id: 'gpt-5-codex' },
29
+ { id: 'gpt-5-mini' },
30
+ { id: 'gpt-5-nano' },
31
+ { id: 'gpt-5-pro' },
32
+ { id: 'gpt-5.1' },
33
+ { id: 'gpt-5.1-codex' },
34
+ { id: 'gpt-5.1-codex-max' },
35
+ { id: 'gpt-5.1-codex-mini' },
36
+ { id: 'gpt-5.2' },
37
+ { id: 'gpt-5.2-codex' },
38
+ { id: 'gpt-5.2-pro' },
39
+ { id: 'gpt-5.3-codex' },
40
+ { id: 'gpt-5.4' },
41
+ { id: 'gpt-5.4-mini' },
42
+ { id: 'gpt-5.4-nano' },
43
+ { id: 'gpt-5.4-pro' },
44
+ { id: 'gpt-5.5' },
45
+ { id: 'gpt-5.5-pro' },
46
+ { id: 'gpt-5.6-luna' },
47
+ { id: 'gpt-5.6-sol' },
48
+ { id: 'gpt-5.6-terra' },
49
+ { id: 'gpt-5.6' },
50
+ { id: 'gpt-6-astra' },
51
+ { id: 'gpt-6-luna' },
52
+ { id: 'gpt-6-sol' },
53
+ { id: 'gpt-6.1-sol' },
54
+ { id: 'gemini-2.0-flash' },
55
+ ],
13
56
  });
14
57
  };
package/lib/summary.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const db = require.main.require('./src/database');
4
- const batch = require.main.require('./src/batch');
5
- const topics = require.main.require('./src/topics');
6
- const posts = require.main.require('./src/posts');
7
- const user = require.main.require('./src/user');
3
+ const db = nodebb.require('./src/database');
4
+ const batch = nodebb.require('./src/batch');
5
+ const topics = nodebb.require('./src/topics');
6
+ const posts = nodebb.require('./src/posts');
7
+ const user = nodebb.require('./src/user');
8
8
 
9
9
  function formatPosts(posts) {
10
10
  return posts
package/library.js CHANGED
@@ -4,21 +4,28 @@ const { OpenAI } = require('openai');
4
4
 
5
5
  let openai;
6
6
 
7
- const db = require.main.require('./src/database');
8
- const meta = require.main.require('./src/meta');
7
+ const db = nodebb.require('./src/database');
8
+ const meta = nodebb.require('./src/meta');
9
9
  const controllers = require('./lib/controllers');
10
- const routeHelpers = require.main.require('./src/routes/helpers');
11
- const socketHelpers = require.main.require('./src/socket.io/helpers');
12
- const topics = require.main.require('./src/topics');
13
- const user = require.main.require('./src/user');
14
- const messaging = require.main.require('./src/messaging');
15
- const api = require.main.require('./src/api');
16
- const privileges = require.main.require('./src/privileges');
17
- const groups = require.main.require('./src/groups');
18
- const sockets = require.main.require('./src/socket.io');
19
- const socketPlugins = require.main.require('./src/socket.io/plugins');
20
10
  const summary = require('./lib/summary');
21
11
 
12
+ const routeHelpers = nodebb.require('./src/routes/helpers');
13
+ const socketHelpers = nodebb.require('./src/socket.io/helpers');
14
+ const topics = nodebb.require('./src/topics');
15
+ const user = nodebb.require('./src/user');
16
+ const messaging = nodebb.require('./src/messaging');
17
+ const api = nodebb.require('./src/api');
18
+ const privileges = nodebb.require('./src/privileges');
19
+ const groups = nodebb.require('./src/groups');
20
+ const sockets = nodebb.require('./src/socket.io');
21
+ const socketPlugins = nodebb.require('./src/socket.io/plugins');
22
+ const cacheCreate = nodebb.require('./src/cache/lru');
23
+
24
+ const summaryCache = cacheCreate({
25
+ name: 'openai-summary',
26
+ max: 200,
27
+ });
28
+
22
29
  const plugin = module.exports;
23
30
 
24
31
  const defaults = {
@@ -26,7 +33,7 @@ const defaults = {
26
33
  'chatgpt-username': '',
27
34
  enablePrivateMessages: 'off',
28
35
  model: 'gpt-3.5-turbo',
29
- minimumReputation: 0,
36
+ minimumReputation: 2,
30
37
  allowedGroups: '[]',
31
38
  systemPrompt: 'You are a helpful assistant',
32
39
  };
@@ -41,12 +48,6 @@ plugin.init = async (params) => {
41
48
  baseURL: settings.apiBaseUrl || 'https://api.openai.com/v1',
42
49
  });
43
50
 
44
- // Uncomment the following lines to list available models
45
- // const list = await openai.models.list();
46
- // for await (const model of list) {
47
- // console.log(model.id);
48
- // }
49
-
50
51
  plugin.openai = openai;
51
52
  }
52
53
 
@@ -330,16 +331,22 @@ socketPlugins.openai.summarizeTopic = async function (socket, data) {
330
331
  if (!await privileges.topics.can('topics:read', tid, socket.uid)) {
331
332
  throw new Error('[[error:no-privileges]]');
332
333
  }
333
- const settings = await getSettings();
334
- if (!await canUseOpenAI(socket.uid, settings)) {
335
- return;
336
- }
337
334
 
338
335
  let openaiSummary = await topics.getTopicField(tid, 'openai:summary');
339
336
  if (openaiSummary) {
340
337
  return openaiSummary;
341
338
  }
339
+ const settings = await getSettings();
340
+ if (!await canUseOpenAI(socket.uid, settings)) {
341
+ return;
342
+ }
343
+ const cooldown = summaryCache.get(`uid:${socket.uid}`);
344
+ if (cooldown) {
345
+ throw new Error('[[openai:summary-cooldown-error]]');
346
+ }
347
+
342
348
  openaiSummary = await summary.summarizeTopic(tid, openai, settings);
349
+ summaryCache.set(`uid:${socket.uid}`, 1, 60000);
343
350
  await topics.setTopicField(tid, 'openai:summary', openaiSummary);
344
351
  return openaiSummary;
345
352
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-openai",
3
- "version": "1.4.3",
3
+ "version": "2.0.0",
4
4
  "description": "A NodeBB OpenAI plugin",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "readmeFilename": "README.md",
35
35
  "nbbpm": {
36
- "compatibility": "^3.2.0 || ^4.0.0"
36
+ "compatibility": "4.15.0"
37
37
  },
38
38
  "dependencies": {
39
39
  "openai": "4.103.0"
@@ -41,7 +41,7 @@
41
41
  "devDependencies": {
42
42
  "@commitlint/cli": "19.3.0",
43
43
  "@commitlint/config-angular": "19.3.0",
44
- "eslint": "10.5.0",
44
+ "eslint": "10.11.0",
45
45
  "eslint-config-nodebb": "^2.0.3",
46
46
  "husky": "9.0.11",
47
47
  "lint-staged": "15.2.7"
@@ -12,30 +12,30 @@ $('document').ready(function () {
12
12
  $('.openai-summarize-topic').on('click', summarizeTopic);
13
13
  });
14
14
 
15
- function summarizeTopic() {
15
+ async function summarizeTopic() {
16
16
  const tid = ajaxify.data.tid;
17
- require(['bootbox'], function (bootbox) {
18
- const modal = bootbox.dialog({
19
- title: '[[openai:topic-summary]]',
20
- message: `<div class="openai-summarize-topic"><div class="loading text-center"><i class="fa-solid fa-spinner fa-spin fa-2x"></i></div></div>`,
21
- size: 'large',
22
- buttons: {
23
- ok: {
24
- label: '[[global:ok]]',
25
- className: 'btn-primary',
26
- },
17
+ const modals = await app.require('modals');
18
+
19
+ const modal = await modals.dialog({
20
+ title: '[[openai:topic-summary]]',
21
+ message: `<div class="openai-summarize-topic"><div class="loading text-center"><i class="fa-solid fa-spinner fa-spin fa-2x"></i></div></div>`,
22
+ size: 'large',
23
+ buttons: {
24
+ ok: {
25
+ label: '[[modules:bootbox.ok]]',
26
+ className: 'btn-primary',
27
27
  },
28
- });
29
- socket.emit('plugins.openai.summarizeTopic', { tid }, function (err, data) {
30
- if (err) {
31
- return alertType('error', err.message);
32
- }
33
- if (data) {
34
- modal.find('.openai-summarize-topic').text(data);
35
- } else {
36
- modal.modal('hide');
37
- }
38
- });
28
+ },
29
+ });
30
+ socket.emit('plugins.openai.summarizeTopic', { tid }, function (err, data) {
31
+ if (err) {
32
+ return alertType('error', err.message);
33
+ }
34
+ if (data) {
35
+ modal.find('.openai-summarize-topic').text(data);
36
+ } else {
37
+ modal.modal('hide');
38
+ }
39
39
  });
40
40
  }
41
41
  });
@@ -5,78 +5,73 @@
5
5
  <div id="spy-container" class="col-12 col-md-8 px-0 mb-4" tabindex="0">
6
6
  <form role="form" class="openai-settings">
7
7
  <div class="mb-4">
8
- <h5 class="fw-bold tracking-tight settings-header">[[openai:admin.general]]</h5>
8
+ <h5 class="fw-bold tracking-tight settings-header">{{tx("openai:admin.general")}}</h5>
9
9
 
10
10
  <div class="mb-3">
11
- <label class="form-label" for="apikey">[[openai:admin.apikey]]</label>
11
+ <label class="form-label" for="apikey">{{tx("openai:admin.apikey")}}</label>
12
12
  <input type="text" id="apikey" name="apikey" title="[[openai:admin.apikey]]" class="form-control">
13
13
  <p class="form-text">
14
- [[openai:admin.apikey-help]]
14
+ {{tx("openai:admin.apikey-help")}}
15
15
  </p>
16
16
  </div>
17
17
 
18
18
  <div class="mb-3">
19
- <label class="form-label" for="apiBaseUrl">[[openai:admin.api-base-url]]</label>
19
+ <label class="form-label" for="apiBaseUrl">{{tx("openai:admin.api-base-url")}}</label>
20
20
  <input type="text" id="apiBaseUrl" name="apiBaseUrl" title="[[openai:admin.api-base-url]]" class="form-control">
21
21
  <p class="form-text">
22
- [[openai:admin.api-base-url-help]]
22
+ {{tx("openai:admin.api-base-url-help")}}
23
23
  </p>
24
24
  </div>
25
25
 
26
26
  <div class="mb-3">
27
- <label class="form-label" for="chatgpt-username">[[openai:admin.chatgpt-username]]</label>
27
+ <label class="form-label" for="chatgpt-username">{{tx("openai:admin.chatgpt-username")}}</label>
28
28
  <input type="text" id="chatgpt-username" name="chatgpt-username" title="[[openai:admin.chatgpt-username]]" class="form-control">
29
29
  <p class="form-text">
30
- [[openai:admin.chatgpt-username-help, {config.relative_path}]]
30
+ {{tx("openai:admin.chatgpt-username-help", config.relative_path)}}
31
31
  </p>
32
32
  </div>
33
33
 
34
34
  <div class="form-check form-switch">
35
35
  <input type="checkbox" class="form-check-input" id="enablePrivateMessages" name="enablePrivateMessages">
36
- <label for="enablePrivateMessages" class="form-check-label">[[openai:admin.enable-private-messages]]</label>
36
+ <label for="enablePrivateMessages" class="form-check-label">{{tx("openai:admin.enable-private-messages")}}</label>
37
37
  <p class="form-text">
38
- [[openai:admin.enable-private-messages-help]]
38
+ {{tx("openai:admin.enable-private-messages-help")}}
39
39
  </p>
40
40
  </div>
41
41
 
42
42
  <div class="mb-3">
43
- <label class="form-label" for="model">[[openai:admin.model]]</label>
43
+ <label class="form-label" for="model">{{tx("openai:admin.model")}}</label>
44
44
  <select class="form-select" id="model" name="model" title="[[openai:admin.model]]">
45
- <option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
46
- <option value="gpt-4o-mini">gpt-4o-mini</option>
47
- <option value="gpt-4o">gpt-4o</option>
48
- <option value="gpt-4-turbo">gpt-4-turbo</option>
49
- <option value="gpt-4">gpt-4</option>
50
- <option value="gpt-4.1-mini">gpt-4.1-mini</option>
51
- <option value="gpt-4.1">gpt-4.1</option>
52
- <option value="gemini-2.0-flash">gemini-2.0-flash</option>
45
+ {{{ each models }}}
46
+ <option value="{./id}">{./id}</option>
47
+ {{{ end }}}
53
48
  </select>
54
49
  </div>
55
50
  <div class="">
56
- <label class="form-label" for="systemPrompt">[[openai:admin.system-prompt]]</label>
57
- <textarea class="form-control" id="systemPrompt" name="systemPrompt" title="[[openai:admin.system-prompt]]" placeholder="[[openai:admin.system-prompt-placeholder]]" rows="8"></textarea>
51
+ <label class="form-label" for="systemPrompt">{{tx("openai:admin.system-prompt")}}</label>
52
+ <textarea class="form-control" id="systemPrompt" name="systemPrompt" title="{{tx("openai:admin.system-prompt")}}" placeholder="{{tx("openai:admin.system-prompt-placeholder")}}" rows="8"></textarea>
58
53
  </div>
59
54
  </div>
60
55
 
61
56
  <div class="">
62
- <h5 class="fw-bold tracking-tight settings-header">[[openai:admin.restrictions]]</h5>
57
+ <h5 class="fw-bold tracking-tight settings-header">{{tx("openai:admin.restrictions")}}</h5>
63
58
 
64
59
  <div class="mb-3">
65
- <label class="form-label" for="minimumReputation">[[openai:admin.minimum-reputation]]</label>
60
+ <label class="form-label" for="minimumReputation">{{tx("openai:admin.minimum-reputation")}}</label>
66
61
  <input type="text" id="minimumReputation" name="minimumReputation" title="[[openai:admin.minimum-reputation]]" class="form-control">
67
62
  <p class="form-text">
68
- [[openai:admin.minimum-reputation-help]]
63
+ {{tx("openai:admin.minimum-reputation-help")}}
69
64
  </p>
70
65
  </div>
71
66
  <div class="mb-3">
72
- <label class="form-label" form="allowedGroups">[[openai:admin.allowed-groups]]</label>
67
+ <label class="form-label" form="allowedGroups">{{tx("openai:admin.allowed-groups")}}</label>
73
68
  <select class="form-select" multiple id="allowedGroups" name="allowedGroups" size="10">
74
69
  {{{ each groups }}}
75
70
  <option value="{./displayName}">{./displayName}</option>
76
71
  {{{ end }}}
77
72
  </select>
78
73
  <p class="form-text">
79
- [[openai:admin.allowed-groups-help]]
74
+ {{tx("openai:admin.allowed-groups-help")}}
80
75
  </p>
81
76
  </div>
82
77
  </div>