nodebb-plugin-link-preview 1.2.5 → 1.3.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 +96 -38
- 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;
|
|
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
|
|
84
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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;
|