nodebb-plugin-link-preview 1.3.2 → 1.3.3
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 +80 -71
- package/package.json +1 -1
package/library.js
CHANGED
|
@@ -82,64 +82,29 @@ async function preview(url) {
|
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
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
|
-
|
|
137
85
|
async function process(content, opts) {
|
|
138
86
|
const { embedHtml, embedImage, embedAudio, embedVideo } = await meta.settings.get('link-preview');
|
|
139
87
|
if (![embedHtml, embedImage, embedAudio, embedVideo].some(prop => prop === 'on')) {
|
|
140
88
|
return content;
|
|
141
89
|
}
|
|
142
90
|
|
|
91
|
+
const requests = new Map();
|
|
92
|
+
|
|
93
|
+
// Retrieve attachments
|
|
94
|
+
if (opts.hasOwnProperty('pid') && await posts.exists(opts.pid)) {
|
|
95
|
+
const hashes = await db.getSortedSetMembers(`post:${opts.pid}:attachments`);
|
|
96
|
+
const keys = hashes.map(hash => `attachment:${hash}`);
|
|
97
|
+
const attachments = (await db.getObjects(keys)).filter(Boolean);
|
|
98
|
+
const urls = attachments
|
|
99
|
+
// .filter(attachment => cache.has(`link-preview:${attachment.url}`))
|
|
100
|
+
.map(attachment => attachment.url);
|
|
101
|
+
|
|
102
|
+
urls.forEach(url => requests.set(url, {
|
|
103
|
+
type: 'attachment',
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Parse inline urls
|
|
143
108
|
const $ = load(content, null, false);
|
|
144
109
|
for (const anchor of $('a')) {
|
|
145
110
|
const $anchor = $(anchor);
|
|
@@ -162,28 +127,72 @@ async function process(content, opts) {
|
|
|
162
127
|
continue;
|
|
163
128
|
}
|
|
164
129
|
|
|
130
|
+
requests.set(url, {
|
|
131
|
+
type: 'inline',
|
|
132
|
+
target: $anchor,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Render cache hits immediately
|
|
137
|
+
let attachmentHtml = '';
|
|
138
|
+
const cold = new Set();
|
|
139
|
+
await Promise.all(Array.from(requests.keys()).map(async (url) => {
|
|
140
|
+
const options = requests.get(url);
|
|
165
141
|
const cached = cache.get(`link-preview:${url}`);
|
|
166
142
|
if (cached) {
|
|
167
143
|
const html = await render(cached);
|
|
168
144
|
if (html) {
|
|
169
|
-
|
|
145
|
+
switch (options.type) {
|
|
146
|
+
case 'inline': {
|
|
147
|
+
const $anchor = options.target;
|
|
148
|
+
$anchor.replaceWith($(html));
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case 'attachment': {
|
|
153
|
+
attachmentHtml += `<div class="col-6">${html}</div>`;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
170
157
|
}
|
|
171
|
-
|
|
158
|
+
} else {
|
|
159
|
+
cold.add(url);
|
|
172
160
|
}
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
// Start preview for cache misses, but continue for now so as to not block response
|
|
164
|
+
if (cold.size) {
|
|
165
|
+
const coldArr = Array.from(cold);
|
|
166
|
+
Promise.all(coldArr.map(preview)).then(async (previews) => {
|
|
167
|
+
await Promise.all(previews.map(async (preview, idx) => {
|
|
168
|
+
if (!preview) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
173
171
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
const url = coldArr[idx];
|
|
173
|
+
const options = requests.get(url);
|
|
174
|
+
const parsedUrl = new URL(url);
|
|
175
|
+
preview.hostname = parsedUrl.hostname;
|
|
176
|
+
|
|
177
|
+
const html = await render(preview);
|
|
178
|
+
if (html) {
|
|
179
|
+
switch (options.type) {
|
|
180
|
+
case 'inline': {
|
|
181
|
+
const $anchor = options.target;
|
|
182
|
+
$anchor.replaceWith($(html));
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
case 'attachment': {
|
|
187
|
+
attachmentHtml += `<div class="col-6">${html}</div>`;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}));
|
|
182
193
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
194
|
+
let content = $.html();
|
|
195
|
+
content += attachmentHtml ? `\n\n<div class="row">${attachmentHtml}</div>` : '';
|
|
187
196
|
|
|
188
197
|
// bust posts cache item
|
|
189
198
|
if (opts.hasOwnProperty('pid') && await posts.exists(opts.pid)) {
|
|
@@ -191,13 +200,12 @@ async function process(content, opts) {
|
|
|
191
200
|
|
|
192
201
|
// fire post edit event with mocked data
|
|
193
202
|
if (opts.hasOwnProperty('tid') && await topics.exists(opts.tid)) {
|
|
194
|
-
$anchor.replaceWith($(html));
|
|
195
203
|
websockets.in(`topic_${opts.tid}`).emit('event:post_edited', {
|
|
196
204
|
post: {
|
|
197
205
|
tid: opts.tid,
|
|
198
206
|
pid: opts.pid,
|
|
199
207
|
changed: true,
|
|
200
|
-
content
|
|
208
|
+
content,
|
|
201
209
|
},
|
|
202
210
|
topic: {},
|
|
203
211
|
});
|
|
@@ -206,7 +214,9 @@ async function process(content, opts) {
|
|
|
206
214
|
});
|
|
207
215
|
}
|
|
208
216
|
|
|
209
|
-
|
|
217
|
+
content = $.html();
|
|
218
|
+
content += attachmentHtml ? `\n\n<div class="row">${attachmentHtml}</div>` : '';
|
|
219
|
+
return content;
|
|
210
220
|
}
|
|
211
221
|
|
|
212
222
|
async function render(preview) {
|
|
@@ -285,8 +295,7 @@ plugin.onParse = async (payload) => {
|
|
|
285
295
|
if (typeof payload === 'string') { // raw
|
|
286
296
|
payload = await process(payload, {});
|
|
287
297
|
} else if (payload && payload.postData && payload.postData.content) { // post
|
|
288
|
-
|
|
289
|
-
content = await processAttachments({ content, pid, tid });
|
|
298
|
+
const { content, pid, tid } = payload.postData;
|
|
290
299
|
payload.postData.content = await process(content, { pid, tid });
|
|
291
300
|
}
|
|
292
301
|
|