nodebb-plugin-link-preview 1.3.1 → 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 -67
- 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,24 +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
|
-
|
|
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
|
+
}));
|
|
178
193
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
194
|
+
let content = $.html();
|
|
195
|
+
content += attachmentHtml ? `\n\n<div class="row">${attachmentHtml}</div>` : '';
|
|
183
196
|
|
|
184
197
|
// bust posts cache item
|
|
185
198
|
if (opts.hasOwnProperty('pid') && await posts.exists(opts.pid)) {
|
|
@@ -187,13 +200,12 @@ async function process(content, opts) {
|
|
|
187
200
|
|
|
188
201
|
// fire post edit event with mocked data
|
|
189
202
|
if (opts.hasOwnProperty('tid') && await topics.exists(opts.tid)) {
|
|
190
|
-
$anchor.replaceWith($(html));
|
|
191
203
|
websockets.in(`topic_${opts.tid}`).emit('event:post_edited', {
|
|
192
204
|
post: {
|
|
193
205
|
tid: opts.tid,
|
|
194
206
|
pid: opts.pid,
|
|
195
207
|
changed: true,
|
|
196
|
-
content
|
|
208
|
+
content,
|
|
197
209
|
},
|
|
198
210
|
topic: {},
|
|
199
211
|
});
|
|
@@ -202,7 +214,9 @@ async function process(content, opts) {
|
|
|
202
214
|
});
|
|
203
215
|
}
|
|
204
216
|
|
|
205
|
-
|
|
217
|
+
content = $.html();
|
|
218
|
+
content += attachmentHtml ? `\n\n<div class="row">${attachmentHtml}</div>` : '';
|
|
219
|
+
return content;
|
|
206
220
|
}
|
|
207
221
|
|
|
208
222
|
async function render(preview) {
|
|
@@ -281,8 +295,7 @@ plugin.onParse = async (payload) => {
|
|
|
281
295
|
if (typeof payload === 'string') { // raw
|
|
282
296
|
payload = await process(payload, {});
|
|
283
297
|
} else if (payload && payload.postData && payload.postData.content) { // post
|
|
284
|
-
|
|
285
|
-
content = await processAttachments({ content, pid, tid });
|
|
298
|
+
const { content, pid, tid } = payload.postData;
|
|
286
299
|
payload.postData.content = await process(content, { pid, tid });
|
|
287
300
|
}
|
|
288
301
|
|