nodebb-plugin-mentions 4.4.0 → 4.4.2

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.
Files changed (2) hide show
  1. package/library.js +94 -39
  2. package/package.json +1 -1
package/library.js CHANGED
@@ -11,6 +11,7 @@ const winston = require.main.require('winston');
11
11
 
12
12
  const db = require.main.require('./src/database');
13
13
  const api = require.main.require('./src/api');
14
+ const meta = require.main.require('./src/meta');
14
15
  const Topics = require.main.require('./src/topics');
15
16
  const posts = require.main.require('./src/posts');
16
17
  const User = require.main.require('./src/user');
@@ -78,34 +79,57 @@ function getNoMentionGroups() {
78
79
  return noMentionGroups;
79
80
  }
80
81
 
81
- Mentions.notify = async function (data) {
82
- const postData = data.post;
83
- const postOwner = parseInt(postData.uid, 10);
84
- const cleanedContent = Mentions.clean(postData.content, true, true, true);
85
- let matches = cleanedContent.match(regex);
86
- if (!matches) {
87
- return;
88
- }
82
+ Mentions.notify = async function ({ post }) {
83
+ const postOwner = parseInt(post.uid, 10);
89
84
 
90
- const noMentionGroups = getNoMentionGroups();
91
- matches = _.uniq(matches.map(match => slugify(match))).filter(match => match && !noMentionGroups.includes(match));
92
- if (!matches.length) {
93
- return;
94
- }
85
+ let uidsToNotify;
86
+ let groupsToNotify;
87
+ if (utils.isNumber(post.pid)) {
88
+ const cleanedContent = Mentions.clean(post.content, true, true, true);
89
+ let matches = cleanedContent.match(regex);
90
+ if (!matches) {
91
+ return;
92
+ }
95
93
 
96
- const [uidsToNotify, groupsToNotify] = await Promise.all([
97
- getUidsToNotify(matches),
98
- getGroupsToNotify(matches),
99
- ]);
94
+ const noMentionGroups = getNoMentionGroups();
95
+ matches = _.uniq(matches.map(match => slugify(match))).filter(match => match && !noMentionGroups.includes(match));
96
+ if (!matches.length) {
97
+ return;
98
+ }
99
+
100
+ ([uidsToNotify, groupsToNotify] = await Promise.all([
101
+ getUidsToNotify(matches),
102
+ getGroupsToNotify(matches),
103
+ ]));
104
+ } else if (post._activitypub) { // ActivityPub
105
+ const { tag } = post._activitypub;
106
+ groupsToNotify = []; // cannot mention groups for now
107
+
108
+ if (tag.length) {
109
+ const slugs = tag.reduce((slugs, tag) => {
110
+ if (tag.type === 'Mention') {
111
+ const [slug, hostname] = tag.name.slice(1).split('@');
112
+ if (hostname === nconf.get('url_parsed').hostname) {
113
+ slugs.push(slug);
114
+ }
115
+ }
116
+ return slugs;
117
+ }, []);
118
+
119
+ uidsToNotify = slugs.length ? await db.sortedSetScores('userslug:uid', slugs) : [];
120
+ } else {
121
+ uidsToNotify = [];
122
+ }
123
+ }
100
124
 
101
125
  if (!uidsToNotify.length && !groupsToNotify.length) {
102
126
  return;
103
127
  }
104
128
 
105
129
  const [topic, userData, topicFollowers] = await Promise.all([
106
- Topics.getTopicFields(postData.tid, ['title', 'cid']),
107
- User.getUserFields(postData.uid, ['username']),
108
- Mentions._settings.disableFollowedTopics === 'on' ? Topics.getFollowers(postData.tid) : [],
130
+ Topics.getTopicFields(post.tid, ['title', 'cid']),
131
+ User.getUserFields(post.uid, ['username']),
132
+ Mentions._settings.disableFollowedTopics === 'on' ? Topics.getFollowers(post.tid) : [],
109
133
  ]);
110
134
  const { displayname } = userData;
111
135
  const title = entitiesDecode(topic.title);
@@ -116,8 +140,8 @@ Mentions.notify = async function (data) {
116
140
  );
117
141
 
118
142
  if (Mentions._settings.privilegedDirectReplies === 'on') {
119
- const toPid = await posts.getPostField(data.post.pid, 'toPid');
120
- uids = await filterPrivilegedUids(uids, data.post.cid, toPid);
143
+ const toPid = await posts.getPostField(post.pid, 'toPid');
144
+ uids = await filterPrivilegedUids(uids, post.cid, toPid);
121
145
  }
122
146
 
123
147
  const groupMemberUids = {};
@@ -133,20 +157,20 @@ Mentions.notify = async function (data) {
133
157
  });
134
158
  });
135
159
 
136
- const filteredUids = await filterUidsAlreadyMentioned(uids, postData.pid);
160
+ const filteredUids = await filterUidsAlreadyMentioned(uids, post.pid);
137
161
  if (filteredUids.length) {
138
- await sendNotificationToUids(postData, filteredUids, 'user', `[[notifications:user-mentioned-you-in, ${displayname}, ${titleEscaped}]]`);
139
- await db.setAdd(`mentions:pid:${postData.pid}:uids`, filteredUids);
162
+ await sendNotificationToUids(post, filteredUids, 'user', `[[notifications:user-mentioned-you-in, ${displayname}, ${titleEscaped}]]`);
163
+ await db.setAdd(`mentions:pid:${post.pid}:uids`, filteredUids);
140
164
  }
141
165
 
142
166
  for (let i = 0; i < groupsToNotify.length; ++i) {
143
167
  if (groupsToNotify[i] && groupsToNotify[i].name && groupsToNotify[i].members) {
144
168
  const memberUids = groupsToNotify[i].members;
145
169
  const groupName = groupsToNotify[i].name;
146
- const groupMentionSent = await db.isSetMember(`mentions:pid:${postData.pid}:groups`, groupName);
170
+ const groupMentionSent = await db.isSetMember(`mentions:pid:${post.pid}:groups`, groupName);
147
171
  if (!groupMentionSent && memberUids.length) {
148
- await sendNotificationToUids(postData, memberUids, groupName, `[[notifications:user-mentioned-group-in, ${displayname} , ${groupName}, ${titleEscaped}]]`);
149
- await db.setAdd(`mentions:pid:${postData.pid}:groups`, groupName);
172
+ await sendNotificationToUids(post, memberUids, groupName, `[[notifications:user-mentioned-group-in, ${displayname} , ${groupName}, ${titleEscaped}]]`);
173
+ await db.setAdd(`mentions:pid:${post.pid}:groups`, groupName);
150
174
  }
151
175
  }
152
176
  }
@@ -295,7 +319,7 @@ async function createNotification(postData, nidType, notificationText) {
295
319
  pid: postData.pid,
296
320
  tid: postData.tid,
297
321
  from: postData.uid,
298
- path: `/post/${postData.pid}`,
322
+ path: `/post/${encodeURIComponent(postData.pid)}`,
299
323
  topicTitle: title ? utils.decodeHTMLEntities(title) : title,
300
324
  importance: 6,
301
325
  });
@@ -315,8 +339,8 @@ function removePunctuationSuffix(string) {
315
339
  return string.replace(/[!?.]*$/, '');
316
340
  }
317
341
 
318
- Mentions.parseRaw = async (content) => {
319
- let splitContent = utility.split(content, false, false, true);
342
+ function getMatches(content, isMarkdown = false) {
343
+ const splitContent = utility.split(content, isMarkdown, false, true);
320
344
  let matches = [];
321
345
  splitContent.forEach((cleanedContent, i) => {
322
346
  if ((i % 2) === 0) {
@@ -324,6 +348,33 @@ Mentions.parseRaw = async (content) => {
324
348
  }
325
349
  });
326
350
 
351
+ return { splitContent, matches };
352
+ }
353
+
354
+ Mentions.getMatches = async (content) => {
355
+ // Exported method only accepts markdown, also filters out dupes and matches to ensure slugs exist
356
+ let { matches } = getMatches(content, true);
357
+ matches = await filterMatches(matches);
358
+ const ids = await Promise.all(matches.map(async m => User.getUidByUserslug(m.slice(1).toLowerCase())));
359
+ matches = matches.map((slug, idx) => (ids[idx] ? {
360
+ id: ids[idx],
361
+ slug,
362
+ } : null)).filter(Boolean);
363
+
364
+ return new Set(matches);
365
+ };
366
+
367
+ async function filterMatches(matches) {
368
+ matches = Array.from(new Set(matches));
369
+ const exists = await Promise.all(matches.map(match => meta.userOrGroupExists(match.slice(1))));
370
+
371
+ return matches.filter((m, i) => exists[[i]]);
372
+ }
373
+
374
+ Mentions.parseRaw = async (content) => {
375
+ // Note: Mentions.clean explicitly can't be called here because I need the content unstripped
376
+ let { splitContent, matches } = getMatches(content);
377
+
327
378
  if (!matches.length) {
328
379
  return content;
329
380
  }
@@ -342,12 +393,18 @@ Mentions.parseRaw = async (content) => {
342
393
  const slug = slugify(match.slice(1));
343
394
  match = removePunctuationSuffix(match);
344
395
  const uid = await User.getUidByUserslug(slug);
345
- const results = await utils.promiseParallel({
396
+ const { groupExists, user } = await utils.promiseParallel({
346
397
  groupExists: Groups.existsBySlug(slug),
347
398
  user: User.getUserFields(uid, ['uid', 'username', 'fullname']),
348
399
  });
349
400
 
350
- if (results.user.uid || results.groupExists) {
401
+ if (user.uid || groupExists) {
402
+ let url;
403
+ if (user.uid) {
404
+ url = utils.isNumber(user.uid) ? `${nconf.get('url')}/uid/${user.uid}` : user.uid;
405
+ } else {
406
+ url = `${nconf.get('url')}/groups/${slug}`;
407
+ }
351
408
  const regex = isLatinMention.test(match) ?
352
409
  RegExp(`${parts.before}${match}${parts.after}`, 'gu') :
353
410
  RegExp(`${parts.before}${match}`, 'gu');
@@ -364,20 +421,18 @@ Mentions.parseRaw = async (content) => {
364
421
  const atIndex = match.indexOf('@');
365
422
  const plain = match.slice(0, atIndex);
366
423
  match = match.slice(atIndex);
367
- if (results.user.uid) {
424
+ if (user.uid) {
368
425
  switch (Mentions._settings.display) {
369
426
  case 'fullname':
370
- match = results.user.fullname || match;
427
+ match = user.fullname || match;
371
428
  break;
372
429
  case 'username':
373
- match = results.user.username;
430
+ match = user.username;
374
431
  break;
375
432
  }
376
433
  }
377
434
 
378
- const str = results.user.uid ?
379
- `<a class="plugin-mentions-user plugin-mentions-a" href="${nconf.get('url')}/uid/${results.user.uid}">${match}</a>` :
380
- `<a class="plugin-mentions-group plugin-mentions-a" href="${nconf.get('url')}/groups/${slug}">${match}</a>`;
435
+ const str = `<a class="mention plugin-mentions-${user.uid ? 'user' : 'group'} plugin-mentions-a" href="${url}">${match}</a>`;
381
436
 
382
437
  return plain + str;
383
438
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-mentions",
3
- "version": "4.4.0",
3
+ "version": "4.4.2",
4
4
  "description": "NodeBB Plugin that allows users to mention other users by prepending an '@' sign to their username",
5
5
  "main": "library.js",
6
6
  "scripts": {