nodebb-plugin-link-preview 1.2.5 → 1.3.1

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 +96 -38
  2. package/package.json +1 -1
package/library.js CHANGED
@@ -10,6 +10,7 @@ const dns = require('dns');
10
10
  const { getLinkPreview } = require('link-preview-js');
11
11
  const { load } = require('cheerio');
12
12
 
13
+ const db = require.main.require('./src/database');
13
14
  const meta = require.main.require('./src/meta');
14
15
  const cache = require.main.require('./src/cache');
15
16
  const posts = require.main.require('./src/posts');
@@ -43,6 +44,96 @@ plugin.applyDefaults = async (data) => {
43
44
  return data;
44
45
  };
45
46
 
47
+ async function preview(url) {
48
+ return getLinkPreview(url, {
49
+ resolveDNSHost: async url => new Promise((resolve, reject) => {
50
+ const { hostname } = new URL(url);
51
+ dns.lookup(hostname, (err, address) => {
52
+ if (err) {
53
+ reject(err);
54
+ return;
55
+ }
56
+
57
+ resolve(address); // if address resolves to localhost or '127.0.0.1' library will throw an error
58
+ });
59
+ }),
60
+ followRedirects: `manual`,
61
+ handleRedirects: (baseURL, forwardedURL) => {
62
+ const urlObj = new URL(baseURL);
63
+ const forwardedURLObj = new URL(forwardedURL);
64
+ if (
65
+ forwardedURLObj.hostname === urlObj.hostname ||
66
+ forwardedURLObj.hostname === `www.${urlObj.hostname}` ||
67
+ `www.${forwardedURLObj.hostname}` === urlObj.hostname
68
+ ) {
69
+ return true;
70
+ }
71
+
72
+ return false;
73
+ },
74
+ }).then((preview) => {
75
+ winston.verbose(`[link-preview] ${preview.url} (${preview.contentType}, cache: miss)`);
76
+ cache.set(`link-preview:${url}`, preview);
77
+
78
+ return preview;
79
+ }).catch(() => {
80
+ winston.verbose(`[link-preview] ${url} (invalid, cache: miss)`);
81
+ cache.set(`link-preview:${url}`, { url });
82
+ });
83
+ }
84
+
85
+ async function processAttachments({ content, pid, tid }) {
86
+ // Retrieve attachments
87
+ const hashes = await db.getSortedSetMembers(`post:${pid}:attachments`);
88
+ if (!hashes.length) {
89
+ return content;
90
+ }
91
+
92
+ const keys = hashes.map(hash => `attachment:${hash}`);
93
+ const attachments = (await db.getObjects(keys)).filter(Boolean);
94
+ const urls = attachments
95
+ .filter(attachment => cache.has(`link-preview:${attachment.url}`))
96
+ .map(attachment => attachment.url);
97
+
98
+ const previews = urls.map(url => cache.get(`link-preview:${url}`));
99
+ const html = await Promise.all(previews.map(async preview => await render(preview)));
100
+
101
+ // Append all readily-available previews to content
102
+ content = `${content}\n\n<div class="row">${html.map(html => `<div class="col-6">${html}</div>`).join('\n')}</div>`;
103
+
104
+ // Kickstart preview
105
+ const toFetch = attachments.filter(attachment => !cache.has(`link-preview:${attachment.url}`));
106
+ if (toFetch.length) {
107
+ Promise.all(toFetch.map(async attachment => preview(attachment.url))).then(async () => {
108
+ // bust posts cache item
109
+ if (await posts.exists(pid)) {
110
+ postsCache.del(String(pid));
111
+
112
+ // fire post edit event with mocked data
113
+ if (await topics.exists(tid)) {
114
+ const urls = attachments.map(attachment => attachment.url);
115
+
116
+ const previews = urls.map(url => cache.get(`link-preview:${url}`));
117
+ let html = await Promise.all(previews.map(async preview => await render(preview)));
118
+ html = `${content}\n\n<div class="row">${html.map(html => `<div class="col-6">${html}</div>`).join('\n')}</div>`;
119
+
120
+ websockets.in(`topic_${tid}`).emit('event:post_edited', {
121
+ post: {
122
+ tid,
123
+ pid,
124
+ changed: true,
125
+ content: html,
126
+ },
127
+ topic: {},
128
+ });
129
+ }
130
+ }
131
+ });
132
+ }
133
+
134
+ return content;
135
+ }
136
+
46
137
  async function process(content, opts) {
47
138
  const { embedHtml, embedImage, embedAudio, embedVideo } = await meta.settings.get('link-preview');
48
139
  if (![embedHtml, embedImage, embedAudio, embedVideo].some(prop => prop === 'on')) {
@@ -80,40 +171,11 @@ async function process(content, opts) {
80
171
  continue;
81
172
  }
82
173
 
83
- // Generate the preview, but return false for now so as to not block response
84
- getLinkPreview(url, {
85
- resolveDNSHost: async url => new Promise((resolve, reject) => {
86
- const { hostname } = new URL(url);
87
- dns.lookup(hostname, (err, address) => {
88
- if (err) {
89
- reject(err);
90
- return;
91
- }
92
-
93
- resolve(address); // if address resolves to localhost or '127.0.0.1' library will throw an error
94
- });
95
- }),
96
- followRedirects: `manual`,
97
- handleRedirects: (baseURL, forwardedURL) => {
98
- const urlObj = new URL(baseURL);
99
- const forwardedURLObj = new URL(forwardedURL);
100
- if (
101
- forwardedURLObj.hostname === urlObj.hostname ||
102
- forwardedURLObj.hostname === `www.${urlObj.hostname}` ||
103
- `www.${forwardedURLObj.hostname}` === urlObj.hostname
104
- ) {
105
- return true;
106
- }
107
-
108
- return false;
109
- },
110
- }).then(async (preview) => {
174
+ // Generate the preview, but continue for now so as to not block response
175
+ preview(url).then(async (preview) => {
111
176
  const parsedUrl = new URL(url);
112
177
  preview.hostname = parsedUrl.hostname;
113
178
 
114
- winston.verbose(`[link-preview] ${preview.url} (${preview.contentType}, cache: miss)`);
115
- cache.set(`link-preview:${url}`, preview);
116
-
117
179
  const html = await render(preview);
118
180
  if (!html) {
119
181
  return;
@@ -137,9 +199,6 @@ async function process(content, opts) {
137
199
  });
138
200
  }
139
201
  }
140
- }).catch(() => {
141
- winston.verbose(`[link-preview] ${url} (invalid, cache: miss)`);
142
- cache.set(`link-preview:${url}`, { url });
143
202
  });
144
203
  }
145
204
 
@@ -222,10 +281,9 @@ plugin.onParse = async (payload) => {
222
281
  if (typeof payload === 'string') { // raw
223
282
  payload = await process(payload, {});
224
283
  } else if (payload && payload.postData && payload.postData.content) { // post
225
- payload.postData.content = await process(payload.postData.content, {
226
- pid: payload.postData.pid,
227
- tid: payload.postData.tid,
228
- });
284
+ let { content, pid, tid } = payload.postData;
285
+ content = await processAttachments({ content, pid, tid });
286
+ payload.postData.content = await process(content, { pid, tid });
229
287
  }
230
288
 
231
289
  return payload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-link-preview",
3
- "version": "1.2.5",
3
+ "version": "1.3.1",
4
4
  "description": "A starter kit for quickly creating NodeBB plugins",
5
5
  "main": "library.js",
6
6
  "repository": {