nodebb-plugin-link-preview 2.1.6 → 2.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.
- package/library.js +65 -46
- package/package-lock.json +2 -2
- package/package.json +1 -2
package/library.js
CHANGED
|
@@ -7,7 +7,6 @@ const nconf = require.main.require('nconf');
|
|
|
7
7
|
const dns = require('dns');
|
|
8
8
|
|
|
9
9
|
const { getLinkPreview } = require('link-preview-js');
|
|
10
|
-
const { load } = require('cheerio');
|
|
11
10
|
const { isURL } = require('validator');
|
|
12
11
|
|
|
13
12
|
const meta = require.main.require('./src/meta');
|
|
@@ -96,30 +95,46 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
96
95
|
const requests = new Map();
|
|
97
96
|
let attachmentHtml = '';
|
|
98
97
|
let placeholderHtml = '';
|
|
98
|
+
let hits = [];
|
|
99
99
|
|
|
100
100
|
// Parse inline urls
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
101
|
+
const anchorRegex = /<a [^>]+>.*?<\/a>/gi;
|
|
102
|
+
let match = anchorRegex.exec(content);
|
|
103
|
+
while (match !== null) {
|
|
104
|
+
const { index } = match;
|
|
105
|
+
const { length } = match[0];
|
|
106
|
+
const before = content.slice(Math.max(0, index - 20), index);
|
|
107
|
+
const after = content.slice(index + length, index + length + 20);
|
|
108
|
+
const wrapped = before.trim().endsWith('<p dir="auto">') || before.trim().endsWith('<p>');
|
|
109
|
+
const closed = after.trim().startsWith('</p>');
|
|
110
|
+
|
|
111
|
+
if (!wrapped || !closed) {
|
|
112
|
+
match = anchorRegex.exec(content);
|
|
111
113
|
continue;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const urlMatch = match[0].match(/href=["'](.*?)["']/);
|
|
117
|
+
let url = urlMatch ? decodeURI(urlMatch[1]) : '';
|
|
118
|
+
const text = match[0].replace(/<[^>]+>/g, ''); // Strip tags to get text
|
|
119
|
+
if (url === text) {
|
|
120
|
+
match = anchorRegex.exec(content);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Otherwise, process the anchor...
|
|
125
|
+
|
|
126
|
+
if (url.startsWith('//')) { // Handle protocol-relative URLs
|
|
127
|
+
url = `${nconf.get('url_parsed').protocol}${url}`;
|
|
128
|
+
} else if (!url.startsWith('http')) { // Handle relative URLs
|
|
116
129
|
url = `${nconf.get('url')}${url.startsWith('/') ? url : `/${url}`}`;
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
if (processInline) {
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
133
|
+
const html = await handleSpecialEmbed(url);
|
|
134
|
+
if (html) {
|
|
122
135
|
requests.delete(url);
|
|
136
|
+
hits.push({ index, length, html });
|
|
137
|
+
match = anchorRegex.exec(content);
|
|
123
138
|
continue;
|
|
124
139
|
}
|
|
125
140
|
}
|
|
@@ -127,8 +142,11 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
127
142
|
// Inline url takes precedence over attachment
|
|
128
143
|
requests.set(url, {
|
|
129
144
|
type: 'inline',
|
|
130
|
-
|
|
145
|
+
index,
|
|
146
|
+
length,
|
|
131
147
|
});
|
|
148
|
+
|
|
149
|
+
match = anchorRegex.exec(content);
|
|
132
150
|
}
|
|
133
151
|
|
|
134
152
|
// Post attachments
|
|
@@ -186,8 +204,8 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
186
204
|
switch (options.type) {
|
|
187
205
|
case 'inline': {
|
|
188
206
|
if (processInline) {
|
|
189
|
-
const
|
|
190
|
-
|
|
207
|
+
const { index, length } = options;
|
|
208
|
+
hits.push({ index, length, html });
|
|
191
209
|
}
|
|
192
210
|
break;
|
|
193
211
|
}
|
|
@@ -210,6 +228,7 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
210
228
|
if (cold.size) {
|
|
211
229
|
const coldArr = Array.from(cold);
|
|
212
230
|
const failures = new Set();
|
|
231
|
+
let successes = [];
|
|
213
232
|
Promise.all(coldArr.map(preview)).then(async (previews) => {
|
|
214
233
|
await Promise.all(previews.map(async (preview, idx) => {
|
|
215
234
|
if (!preview) {
|
|
@@ -226,8 +245,8 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
226
245
|
switch (options.type) {
|
|
227
246
|
case 'inline': {
|
|
228
247
|
if (processInline) {
|
|
229
|
-
const
|
|
230
|
-
|
|
248
|
+
const { index, length } = options;
|
|
249
|
+
successes.push({ index, length, html });
|
|
231
250
|
}
|
|
232
251
|
break;
|
|
233
252
|
}
|
|
@@ -247,15 +266,23 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
247
266
|
html += `<p><a href="${cur}" rel="nofollow ugc">${cur}</a></p>`;
|
|
248
267
|
return html;
|
|
249
268
|
}, '');
|
|
250
|
-
let
|
|
251
|
-
|
|
252
|
-
|
|
269
|
+
let modified = content;
|
|
270
|
+
|
|
271
|
+
successes = successes.sort((a, b) => b.index - a.index);
|
|
272
|
+
successes.forEach(({ html, index, length }) => {
|
|
273
|
+
modified =
|
|
274
|
+
modified.slice(0, index) +
|
|
275
|
+
html +
|
|
276
|
+
modified.slice(index + length);
|
|
277
|
+
});
|
|
278
|
+
modified += attachmentHtml ? `\n\n<div class="row mt-3">${attachmentHtml}</div>` : '';
|
|
279
|
+
modified += placeholderHtml ? `\n\n<div class="row mt-3"><div class="col-12 mt-3">${placeholderHtml}</div></div>` : '';
|
|
253
280
|
|
|
254
281
|
// bust posts cache item
|
|
255
282
|
if (pid) {
|
|
256
283
|
const cache = postsCache.getOrCreate();
|
|
257
284
|
const cacheKey = `${String(pid)}|${type}`;
|
|
258
|
-
cache.set(cacheKey,
|
|
285
|
+
cache.set(cacheKey, modified);
|
|
259
286
|
|
|
260
287
|
// fire post edit event with mocked data
|
|
261
288
|
if (type === 'default' && tid) {
|
|
@@ -264,7 +291,7 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
264
291
|
tid,
|
|
265
292
|
pid,
|
|
266
293
|
changed: true,
|
|
267
|
-
content,
|
|
294
|
+
content: modified,
|
|
268
295
|
},
|
|
269
296
|
topic: {},
|
|
270
297
|
});
|
|
@@ -273,10 +300,18 @@ async function process(content, { type, pid, tid, attachments }) {
|
|
|
273
300
|
});
|
|
274
301
|
}
|
|
275
302
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
303
|
+
let modified = content;
|
|
304
|
+
|
|
305
|
+
hits = hits.sort((a, b) => b.index - a.index);
|
|
306
|
+
hits.forEach(({ html, index, length }) => {
|
|
307
|
+
modified =
|
|
308
|
+
modified.slice(0, index) +
|
|
309
|
+
html +
|
|
310
|
+
modified.slice(index + length);
|
|
311
|
+
});
|
|
312
|
+
modified += attachmentHtml ? `\n\n<div class="row mt-3"><div class="col-12 mt-3">${attachmentHtml}</div></div>` : '';
|
|
313
|
+
modified += placeholderHtml ? `\n\n<div class="row mt-3"><div class="col-12 mt-3">${placeholderHtml}</div></div>` : '';
|
|
314
|
+
return modified;
|
|
280
315
|
}
|
|
281
316
|
|
|
282
317
|
async function render(preview) {
|
|
@@ -308,7 +343,7 @@ async function render(preview) {
|
|
|
308
343
|
return false;
|
|
309
344
|
}
|
|
310
345
|
|
|
311
|
-
async function handleSpecialEmbed(url
|
|
346
|
+
async function handleSpecialEmbed(url) {
|
|
312
347
|
const { app } = require.main.require('./src/webserver');
|
|
313
348
|
const { hostname, searchParams, pathname } = new URL(url);
|
|
314
349
|
const { embedYoutube, embedVimeo, embedTiktok } = await meta.settings.get('link-preview');
|
|
@@ -327,12 +362,6 @@ async function handleSpecialEmbed(url, $anchor) {
|
|
|
327
362
|
video = searchParams.get('v');
|
|
328
363
|
}
|
|
329
364
|
const html = await app.renderAsync(short ? 'partials/link-preview/youtube-short' : 'partials/link-preview/youtube', { video });
|
|
330
|
-
|
|
331
|
-
if ($anchor) {
|
|
332
|
-
$anchor.replaceWith(html);
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
365
|
return html;
|
|
337
366
|
}
|
|
338
367
|
|
|
@@ -340,11 +369,6 @@ async function handleSpecialEmbed(url, $anchor) {
|
|
|
340
369
|
const video = pathname.slice(1);
|
|
341
370
|
const html = await app.renderAsync('partials/link-preview/vimeo', { video });
|
|
342
371
|
|
|
343
|
-
if ($anchor) {
|
|
344
|
-
$anchor.replaceWith(html);
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
372
|
return html;
|
|
349
373
|
}
|
|
350
374
|
|
|
@@ -352,11 +376,6 @@ async function handleSpecialEmbed(url, $anchor) {
|
|
|
352
376
|
const video = pathname.split('/')[3];
|
|
353
377
|
const html = await app.renderAsync('partials/link-preview/tiktok', { video });
|
|
354
378
|
|
|
355
|
-
if ($anchor) {
|
|
356
|
-
$anchor.replaceWith(html);
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
379
|
return html;
|
|
361
380
|
}
|
|
362
381
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-link-preview",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "nodebb-plugin-link-preview",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.2.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"cheerio": "^1.0.0-rc.12",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-link-preview",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A starter kit for quickly creating NodeBB plugins",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
"lint-staged": "13.2.2"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"cheerio": "^1.0.0-rc.12",
|
|
47
46
|
"link-preview-js": "^3.0.4",
|
|
48
47
|
"validator": "^13.11.0"
|
|
49
48
|
}
|