nodebb-plugin-openai 1.0.0 → 1.2.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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "error.need-x-reputation-to-mention": "You need at least %1 reputation for mentioning chatgpt"
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "error.need-x-reputation-to-mention": "You need at least %1 reputation for mentioning chatgpt"
3
+ }
@@ -1,9 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ const groups = require.main.require('./src/groups');
4
+
3
5
  const Controllers = module.exports;
4
6
 
5
- Controllers.renderAdminPage = function (req, res/* , next */) {
7
+ Controllers.renderAdminPage = async function (req, res/* , next */) {
8
+ const groupsData = await groups.getNonPrivilegeGroups('groups:createtime', 0, -1);
9
+ groupsData.sort((a, b) => b.system - a.system);
6
10
  res.render('admin/plugins/openai', {
7
11
  title: 'OpenAI',
12
+ groups: groupsData,
8
13
  });
9
14
  };
package/library.js CHANGED
@@ -13,9 +13,23 @@ const topics = require.main.require('./src/topics');
13
13
  const user = require.main.require('./src/user');
14
14
  const messaging = require.main.require('./src/messaging');
15
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');
16
19
 
17
20
  const plugin = module.exports;
18
21
 
22
+ const defaults = {
23
+ apikey: '',
24
+ 'chatgpt-username': '',
25
+ enablePrivateMessages: 'off',
26
+ model: 'gpt-3.5-turbo',
27
+ minimumReputation: 0,
28
+ allowedGroups: '[]',
29
+ systemPrompt: 'You are a helpful assistant',
30
+ };
31
+
32
+
19
33
  plugin.init = async (params) => {
20
34
  const { router /* , middleware , controllers */ } = params;
21
35
  const settings = await meta.settings.get('openai')
@@ -28,6 +42,11 @@ plugin.init = async (params) => {
28
42
  routeHelpers.setupAdminPageRoute(router, '/admin/plugins/openai', controllers.renderAdminPage);
29
43
  };
30
44
 
45
+ async function getSettings() {
46
+ const settings = await meta.settings.get('openai')
47
+ return {...defaults, ...settings };
48
+ }
49
+
31
50
  plugin.actionMentionsNotify = async function (hookData) {
32
51
  try {
33
52
  const { notification } = hookData;
@@ -35,13 +54,21 @@ plugin.actionMentionsNotify = async function (hookData) {
35
54
  return;
36
55
  }
37
56
 
38
- const settings = (await meta.settings.get('openai')) || {};
57
+ const settings = await getSettings();
58
+ if (!await canUseOpenAI(notification.from, settings)) {
59
+ return;
60
+ }
61
+
39
62
  const chatgptusername = settings['chatgpt-username'];
40
63
  const chatgptUid = await user.getUidByUsername(chatgptusername);
41
64
  if (!chatgptUid) {
42
65
  return;
43
66
  }
44
67
  if (notification.tid && notification.bodyLong.startsWith(`@${chatgptusername}`)) {
68
+ const canReply = await privileges.topics.can('topics:reply', notification.tid, chatgptUid);
69
+ if (!canReply) {
70
+ return;
71
+ }
45
72
  const message = notification.bodyLong.replace(new RegExp(`^@${chatgptusername}`), '');
46
73
  if (message.length) {
47
74
  const response = await chatComplete(message);
@@ -74,7 +101,11 @@ plugin.actionMessagingSave = async function (hookData) {
74
101
  if (message.system) {
75
102
  return;
76
103
  }
77
- const settings = (await meta.settings.get('openai')) || {};
104
+ const settings = await getSettings();
105
+ if (!await canUseOpenAI(message.fromuid, settings)) {
106
+ return;
107
+ }
108
+
78
109
  const chatgptusername = settings['chatgpt-username'];
79
110
  const chatgptUid = await user.getUidByUsername(chatgptusername);
80
111
  const { roomId, content, fromuid } = message;
@@ -135,6 +166,55 @@ plugin.actionMessagingSave = async function (hookData) {
135
166
  }
136
167
  };
137
168
 
169
+ async function canUseOpenAI(uid, settings) {
170
+ if (!await checkReputation(uid, settings)) {
171
+ return false;
172
+ }
173
+ if (!await checkGroupMembership(uid, settings)) {
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+
179
+ async function checkReputation(uid, settings) {
180
+ const reputation = await user.getUserField(uid, 'reputation');
181
+ const hasEnoughRep = parseInt(settings.minimumReputation, 10) === 0 || parseInt(reputation, 10) >= parseInt(settings.minimumReputation, 10);
182
+
183
+ if (!hasEnoughRep) {
184
+ sockets.server.in(`uid_${uid}`).emit('event:alert', {
185
+ type: 'danger',
186
+ title: '[[global:alert.error]]',
187
+ message: `[[openai:error.need-x-reputation-to-mention, ${settings.minimumReputation}]]`,
188
+ });
189
+ }
190
+ return hasEnoughRep;
191
+ }
192
+
193
+ async function checkGroupMembership(uid, settings) {
194
+ let allowedGroups = [];
195
+ try {
196
+ allowedGroups = JSON.parse(settings.allowedGroups) || [];
197
+ } catch (err) {
198
+ console.error(err);
199
+ allowedGroups = [];
200
+ }
201
+
202
+ if (!allowedGroups.length) {
203
+ return true;
204
+ }
205
+
206
+ const isMembers = await groups.isMemberOfGroups(uid, allowedGroups);
207
+ const memberOfAny = isMembers.includes(true);
208
+ if (!memberOfAny) {
209
+ sockets.server.in(`uid_${uid}`).emit('event:alert', {
210
+ type: 'danger',
211
+ title: '[[global:alert.error]]',
212
+ message: `[[error:no-privileges]]`,
213
+ });
214
+ }
215
+ return memberOfAny;
216
+ }
217
+
138
218
  async function getMessageIds(roomId, uid, start, stop) {
139
219
  const isPublic = await db.getObjectField(`chat:room:${roomId}`, 'public');
140
220
  if (parseInt(isPublic, 10) === 1) {
@@ -148,15 +228,25 @@ async function getMessageIds(roomId, uid, start, stop) {
148
228
  );
149
229
  }
150
230
 
151
- async function chatComplete(message) {
231
+ async function chatComplete(messages) {
152
232
  if (!openai) {
153
233
  throw new Error('API not created!');
154
234
  }
155
- const isConversation = Array.isArray(message);
156
- const { model } = await meta.settings.get('openai');
235
+ const isConversation = Array.isArray(messages);
236
+ const { model, systemPrompt } = await getSettings();
237
+ const conversation = [];
238
+ if (systemPrompt) {
239
+ conversation.push({ role: 'system', content: systemPrompt });
240
+ }
241
+ if (isConversation) {
242
+ conversation.push(...messages);
243
+ } else {
244
+ conversation.push({ role: 'user', content: messages });
245
+ }
246
+
157
247
  const chatCompletion = await openai.chat.completions.create({
158
248
  model: model || 'gpt-3.5-turbo',
159
- messages: isConversation ? message : [{ role: 'user', content: message }],
249
+ messages: conversation,
160
250
  });
161
251
 
162
252
  return chatCompletion.choices[0]?.message?.content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-openai",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "A NodeBB OpenAI plugin",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  "compatibility": "^3.2.0"
34
34
  },
35
35
  "dependencies": {
36
- "openai": "4.53.0"
36
+ "openai": "4.53.2"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@commitlint/cli": "19.3.0",
package/plugin.json CHANGED
@@ -11,5 +11,6 @@
11
11
  "modules": {
12
12
  "../admin/plugins/openai.js": "./public/lib/admin.js"
13
13
  },
14
- "templates": "templates"
14
+ "templates": "templates",
15
+ "languages": "languages"
15
16
  }
@@ -40,6 +40,33 @@
40
40
  <option value="gpt-4">gpt-4</option>
41
41
  </select>
42
42
  </div>
43
+ <div class="">
44
+ <label class="form-label" for="systemPrompt">System Prompt</label>
45
+ <textarea class="form-control" id="systemPrompt" name="systemPrompt" title="System prompt" placeholder="You are a helpful assistant"></textarea>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="">
50
+ <h5 class="fw-bold tracking-tight settings-header">Restrictions</h5>
51
+
52
+ <div class="mb-3">
53
+ <label class="form-label" for="minimumReputation">Minimum Reputation</label>
54
+ <input type="text" id="minimumReputation" name="minimumReputation" title="Minimum Reputation" class="form-control">
55
+ <p class="form-text">
56
+ Minimum reputation required to mention chatgpt user. (0 to disable)
57
+ </p>
58
+ </div>
59
+ <div class="mb-3">
60
+ <label class="form-label" form="allowedGroups">Allowed Groups</label>
61
+ <select class="form-select" multiple id="allowedGroups" name="allowedGroups" size="10">
62
+ {{{ each groups }}}
63
+ <option value="{./displayName}">{./displayName}</option>
64
+ {{{ end }}}
65
+ </select>
66
+ <p class="form-text">
67
+ Only users in these groups will be able to mention the chatgpt user. Leave blank to allow all groups.
68
+ </p>
69
+ </div>
43
70
  </div>
44
71
  </form>
45
72
  </div>