botkit-starter-web
botkit-starter-web copied to clipboard
Adding previews to links
I'm trying to adapt the starter kit to show previews of links (a la Facebook messenger, WhatsApp etc). I think the overall concept is sound, but as there's a delay getting the metadata from webpages, the next message will often show before the preview. Is there any way to stop the bot from processing while the metadata is fetched?
I've added an Express endpoint to my bot to get previews:
var og = require('open-graph');
module.exports = function(webserver, controller) {
webserver.post('/link_preview', function(req, res) {
og(req.body.url, function(err, meta){
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(meta));
})
});
}
Then I have a function that calls the endpoint in client.js:
getLinkPreview: function(url, done) {
var xhr = new XMLHttpRequest();
var postData = 'url=' + url
xhr.open('POST', '/link_preview', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function () {
var response = xhr.responseText;
var preview = null;
try {
preview = JSON.parse(response);
} catch (err) {
done(xhr.response);
return;
}
done(null, preview);
};
xhr.onerror = function () {
done(xhr.response)
};
xhr.send(postData);
},
And finally render the actual output when the message
event is called:
that.on('message', function(message) {
if (message.files) {
var url;
for (var i = 0; i < message.files.length; i++) {
url = message.files[i].url
that.getLinkPreview(url, function(err, response) {
if (err) { throw err; }
var message = document.createElement('div')
message.setAttribute('class', 'message message')
var meta = document.createElement('div');
meta.setAttribute('class', 'meta');
var link = document.createElement('a')
link.setAttribute('href', url)
link.innerHTML = url;
message.appendChild(link)
var title = document.createElement('h3')
title.innerHTML = response.title;
meta.appendChild(title)
if (response.image) {
var image = document.createElement('img')
image.setAttribute('src', response.image.url);
image.setAttribute('width', '120px');
meta.appendChild(image)
}
if (response.description) {
var description = document.createElement('p')
description.innerHTML = response.description;
meta.appendChild(description)
}
message.appendChild(meta)
that.message_list.appendChild(message)
});
}
I imagine I'm doing something wrong at the last point, but not sure where to start. Any help is appreciated!
Your code appends link preview to the bottom of message list. I think that it would be better to insert it right next to the message containing link.
I had to look it up, but something like this should do the job: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
Ah OK, that seems like a good call. Any idea how I'd get a reference to that message's element? I've tried a couple of things, but to no avail. I think there also might be an issue if there's a number of links. Is there any way I can put an artificial delay in place while the preview is being fetched?
- Create message element and append it to message list.
- Make preview request
- On receiving response, modify message div element that you can still access from outer scope.
Ah, never mind. I've solved this by using promises and getting the preview first, then rendering the response using the template:
addLinkPreview: function(message) {
return new Promise(function(resolve, reject) {
if (message.files !== undefined && message.files.length > 0) {
var url;
for (var i = 0; i < message.files.length; i++) {
url = message.files[i].url
that.getLinkPreview(url, function(err, response) {
if (err) { throw err; }
message.link_preview = {
url: url,
data: response
}
resolve();
});
}
} else {
resolve();
}
})
}
that.on('message', function(message) {
that.addLinkPreview(message).then(function() {
that.renderMessage(message);
});
});
{{#if message.link_preview}}
<div class="meta">
<a href="{{{ message.link_preview.url }}}">{{{ message.link_preview.url }}}</a>
<h3>{{{ message.link_preview.data.title }}}</h3>
{{#if message.link_preview.data.image}}
<img src="{{{ message.link_preview.data.image.url }}}" width="120px" />
{{/if}}
{{#if message.link_preview.data.description}}
<p>message.link_preview.data.description</p>
{{/if}}
</div>
{{/if}}
Pretty cool! Is there something here that could be contributed back to the project?
Quite possibly, I (mis)used the files field for this, but I'll see what I can tidy up and push up 👍