这篇文章由Jamie Shields , Edwin Reynoso和Tom Greco进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
Instagram是一个在线照片共享网络,可让其用户拍摄照片和视频,并在各种社交网络平台上共享它们。 用户可以个性化自己上传的每张照片(例如,通过添加各种效果),并使用井号标签对其进行分类。
在本教程中,我们将构建一个实时照片更新应用程序。 这将允许用户输入他们想要订阅的主题标签。 然后,每当将带有该标签的新照片发布到Instagram时,它将被发送到我们的应用程序,该应用程序将相应地显示它。
我们将使用Express作为框架在Node.js中实现我们的应用程序。 实时部分将使用Instagram的实时照片更新和Socket.io实现。 与以往一样,本教程的代码可在我们的Github存储库中找到 。
实时照片更新如何工作
Instagram的实时照片更新通过在每次将新照片发布到您选择的订阅时向您的服务器发送通知来工作。 这里有一个细分:
- 首先,您的服务器向Instagram发送订阅请求。
- Instagram接收到该请求,并通过发回服务器必须发回的响应来验证您是否确实要订阅。
- 您的服务器接收数据并将其发送回Instagram。
- 如果数据相同,Instagram将开始向您的服务器发送通知。
您可以从四种类型的订阅中接收实时更新:用户,标签,位置和地理位置。 您可以在文档中阅读有关这些内容的更多信息。 在本教程中,我们将仅使用标签订阅。 当您使用指定的标签标记新照片时,这使您可以接收通知。
注册一个应用
我们需要做的第一件事是创建一个Instagram帐户,然后注册为开发人员 。
接下来,我们需要注册一个新的Instagram应用程序 。 您可以放置网站的任何有效URL和重定向URL,因为它们对于我们的应用正常运行是不需要的。
创建应用程序后,请记下CLIENT ID和CLIENT SECRET因为稍后在向Instagram API发出请求时将需要它们。
服务器端设置
接下来要做的是克隆存储库并使用npm安装依赖项。
git clone git@github.com:sitepoint-editors/express-instagramrealtime.git
cd express-instagramrealtime
npm install
这将引入以下依赖关系:
- Express是Node.js的事实上的标准Web应用程序服务器框架。 它用于服务应用程序的面向公众的一面以及从Instagram接收照片通知。
- Express Handlebars用于在Express.js中实现视图。
- body-parser用于解析用户提交的表单数据。 在这种情况下,数据是用户要订阅的标签。
- instagram-node-lib是用于使用Instagram API的Node.js库。 一旦我们获得了用户提供的标签,就可以使用该库来订阅标签。
- socket.io-订阅特定标签后,Instagram会在每次使用我们使用的标签发布新照片时将通知发送到服务器。 这是socket.io的来源。它用于在服务器每次收到新通知时将照片数据发送到前端。
- moment用于格式化Instagram API提供的时间戳。
现在我们准备看一下该应用程序。 在app.js我们首先需要要求我们安装的依赖项。
var express = require('express');
var exphbs = require('express-handlebars');
var moment = require('moment');
var bodyParser = require('body-parser');
var instagram = require('instagram-node-lib');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// set the file name of the default layout
app.engine('handlebars', exphbs({defaultLayout: 'main'}));
// set the expressJS view engine to handlebars
app.set('view engine', 'handlebars');
// set the path to the front-end assets
app.use(express.static('public'));
现在,我们需要必要的依赖关系,我们需要设置Instagram CLIENT ID和CLIENT SECRET 。 您将用注册应用程序时生成的值替换这些值。
var instagram_client_id = 'YOUR-INSTAGRAM-CLIENT-ID';
var instagram_client_secret = 'YOUR-INSTAGRAM-CLIENT-SECRET';
instagram.set('client_id', instagram_client_id);
instagram.set('client_secret', instagram_client_secret);
配置完ID和Secret后,接下来要做的就是创建一个将在端口4000上运行的服务器实例。您可以使用console.log输出主机和端口,以检查服务器是否已开始运行。
var server = app.listen(4000, function(){
var host = server.address().address
var port = server.address().port
console.log('Example app listening at http://%s:%s', host, port)
});
接下来,让socket.io监听Express服务器。 这会将socket.io绑定到与Express服务器相同的端口,以便以后在客户端连接到此套接字时可以使用端口4000。
var io = require('socket.io').listen(server);
现在,让我们继续为应用程序的主页创建新路线。 它所做的只是渲染主模板。
app.get('/', function(req, res){
res.render('home');
});
创建视图
根据默认的车把配置,所有视图文件应存储在views目录中。 文件home.handlebars将呈现表单控件,用户将在其中输入用于实时照片更新的主题标签:
<div id="form-wrapper">
<div class="form-group">
<label for="tag" class="control-label">Hashtag</label>
<input type="text" class="form-control input-lg" id="tag" name="tag" autofocus>
</div>
<div class="form-group">
<button id="start" class="btn btn-lg btn-block btn-primary">Start</button>
</div>
</div>
<div id="results" class="hidden">
<div class="row"></div>
</div>
每当任何Instagram用户发布带有该标签的新照片时,该照片都会立即在div内的应用中显示,并带有results ID。
之前在app.js文件中,我们使用以下代码设置了默认布局的文件名:
app.engine('handlebars', exphbs({defaultLayout: 'main'}));
在车把中,布局文件存储在views/layouts 。 在此目录中,文件main.handlebars用作主布局。 主要内容以wrapper ID呈现在div内部:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Real-time Photo Updates</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div id="wrapper">
{{{body}}}
</div>
<script src="/js/jquery.min.js"></script>
<script src="/js/jquery.backstretch.min.js"></script>
<script src="/js/jquery.imagepreload.min.js"></script>
<script src="/js/vague.min.js"></script>
<script src="/js/socket.io.min.js"></script>
<script src="/js/handlebars.min.js"></script>
<script src="/js/moment.min.js"></script>
<script src="/js/livestamp.min.js"></script>
<script src="/js/script.js"></script>
</body>
</html>
如您所见,需要一些前端依赖项。 这是每个的简要说明:
- Bootstrap是一个前端框架。 对于几乎所有构建的Web东西,这都是我个人的选择。 如果您还想使用Bootstrap,可以在bootswatch.com上找到免费的主题 。
- jQuery用于处理HTML和侦听页面上的单击事件。 这也是下面3个库的依赖项。
- jQuery BackStretch用于将当前照片转换为整页背景图像。
- jQuery Image Preload插件用于在显示给用户之前预加载图像。
- 当从服务器端收到新照片的时间过长时, Livestamp有助于使timeago文本保持最新。
- Vague.js将模糊滤镜应用于背景图像。
- 车把用于生成用于显示照片的HTML。
- moment用于基于时间戳显示timeago文本。
这些依赖项可以在public/js和public/css 。 这是因为我们指定此文件夹( public )应包含前端资产:
app.use(express.static('public'));
您还可以使用Bower下载和管理这些资产。 如果这样选择,请确保更新.bowerrc文件 ,以使其使用您指定的静态目录。
这样,我们现在就可以创建script.js文件了。
肉和土豆
在文件script.js内部,发生所有前端操作。 在这里,我们需要使用jQuery的$.get函数从服务器获取车把模板。 提取后,必须对其进行编译并将其存储在变量中。 这是用于构造用于显示照片的HTML的模板。
var template;
$.get('/templates/row.hbs', function(data){
template = Handlebars.compile(data);
}, 'html');
这是车把模板的样子( public/templates/row.hbs ):
<div class="row">
<div class="photo-container">
<img src="{{image}}" class="photo">
</div>
<div class="photo-details">
<div class="timestamp" data-livestamp="{{created_time}}">{{human_time created_time}}</div>
<img src="{{profile_pic}}" class="userphoto" alt="{{user}}">
<a href="http://instagram.com/{{user}}" target="_blank" class="username">{{user}}</a>
<div class="caption">{{caption}}</div>
</div>
</div>
其中包含用于显示我们应用程序照片的代码。
回到script.js我们需要连接到socket.io服务器。
var socket = io.connect('http://your-server.com:4000');
并注册一个帮助程序以将Unix时间戳转换为人类友好的形式:
Handlebars.registerHelper('human_time', function(timestamp){
return moment.unix(timestamp).fromNow();
});
单击开始按钮后,我们需要将用户输入的主题标签发送到服务器。 成功完成后,我们要隐藏表单,并显示照片容器。
$('#start').click(function(){
var tag = $('#tag').val();
$.post(
'/tag/subscribe',
{ 'tag': tag },
function(response){
if(response.type == 'success'){
$('#form-wrapper').addClass('hidden');
$('#results').removeClass('hidden');
}
}
)
});
回到服务器端(在app.js ),我们的应用程序需要取消订阅所有当前的实时订阅,然后订阅用户提供的新标签。 我们可以通过利用的做到这一点subscribe的方法tags对象由Instagram的节点-lib库提供。 服务器收到Instagram的有效回复后,我们将发送一个订阅已完成的回复。
var current_tag;
app.post('/tag/subscribe', function(req, res){
current_tag = req.body.tag;
console.log('current tag: ' + current_tag);
instagram.tags.unsubscribe_all({
complete: function(unsubscribe_data) {
if(unsubscribe_data == null){
console.log('unsubscribed from everything!');
instagram.tags.subscribe({
object_id: current_tag,
callback_url: 'https://xxxxxxxx.ngrok.io/subscribe',
complete: function(subscribe_data){
if(subscribe_data){
res.send({type: 'success'});
}
}
});
}
}
});
});
当Instagram收到您的订阅新标签的请求时,它将向GET请求发送您的回调URL。 该请求包含一个查询参数。 服务器要做的就是将其发送回Instagram以通过验证。
app.get('/subscribe', function(req, res){
res.send(req.query['hub.challenge']);
});
每次在Instagram上发布带有该标签的新照片时,它都会自动向您的服务器发送通知。 这次是对您指定的callback_url的POST请求(您必须在部署部分中对此进行更改)。 请注意,此请求不包含有关发布的照片的任何数据。 它仅包含有关时间和数据来源的数据。 这就是为什么您必须单独要求获取最近发布的照片的原因。 返回响应后,创建一个名为photo的新对象,然后将要返回的所有数据存储在新变量中。 在这种情况下,仅需要以下内容:用户名,个人资料照片,发布图像的时间戳,照片的URL和标题文本。 最后,通知客户端有新照片可用。
app.post('/subscribe', function(req, res){
instagram.tags.recent({
name: current_tag,
count: 1,
complete: function(data){
var photo = {
'user': data[0].user.username,
'profile_pic': data[0].caption.from.profile_picture,
'created_time': data[0].created_time,
'image': data[0].images.standard_resolution.url,
'caption': data[0].caption.text
};
io.sockets.emit('new_photo', photo);
}
});
});
显示结果
回到客户端(script.js),让我们使用jQuery Image Preloader插件在新照片出现时预加载图像。这将在客户端上完全下载图像,然后再显示给用户。 图像预加载后,使用template和照片数据构建新的HTML。 接下来,我们将使用jQuery Backstretch插件将图像设置为整页背景图像,并使用vague.js来模糊背景。 之后,您可以将HTML附加到页面上,然后使用fadeIn效果显示它。 最后,删除显示的最后一张图像。
socket.on('new_photo', function(data){
$.imgpreload(data.image, function()
{
console.log('loaded a new image');
var first_row = $('#wrapper .row:first');
var html = template(data);
$.backstretch(data['image']);
var vague = $('.backstretch').Vague({
intensity: 10,
forceSVGUrl: false
});
vague.blur();
$(html).hide().insertBefore(first_row).fadeIn('slow');
$('#wrapper .row:last').remove();
});
});
当我们开始总结时,让我们快速向我们的应用程序中添加一些CSS。 您可以在public/css/style.css看到它。 在GitHub上查看文件 。
部署方式
此时,您现在可以运行该应用程序:
node app.js
但是,当您导航到http:// localhost:4000 /时 ,输入一个标签并单击START ,将不会发生任何事情。 并且,如果您查看控制台,则会看到以下错误:
APISubscriptionError occurred: Invalid response in _request
嗯! 是什么赋予了?
问题在于该应用程序应可通过互联网访问,以便接收Instagram的回复。 当我们在本地主机上运行该应用程序时,不幸的是这无法正常工作。 幸运的是,我们可以使用ngrok在互联网上公开我们的应用程序。 下载并安装ngrok之后,可以通过在终端中执行以下命令来运行它:
ngrok http 4000
这会将Express服务器公开到Internet。 确保更改app.js文件中的callback_url ,以使用ngrok返回的https URL。 如果您打算稍后再部署应用程序,则它也应该是https URL。
instagram.tags.subscribe({
object_id: tag,
callback_url: 'https://xxxxxxxx.ngrok.io/subscribe',
...
});
只需复制转发URL。 这是屏幕截图:
现在,如果您重新启动服务器,则事情应该按计划进行:
用户订阅后,该应用将开始通过socket.io从服务器接收照片数据,然后将其显示。
进一步发展
如果要尝试使用该应用程序并进行一些自己的更改,则可能需要研究一下nodemon 。 每当您对Node应用程序进行更改时,此操作都会自动重新启动服务器,并且非常便于开发。
然后是持久性的问题。 对事物感到满意之后,并且如果您使用的是基于Unix的系统,则可以在服务器上安装Supervisor 。 这使您可以持久运行该应用程序。 仅使用nodemon运行应用程序是不够的,因为该过程在您注销服务器后即终止。
在当前的终端窗口中,执行以下命令:
sudo apt-get install supervisor
为应用创建配置文件:
sudo nano /etc/supervisor/conf.d/instagram-realtime.conf
[program:instagram-realtime]
command=nodemon app.js
directory=/home/ubuntu/www
stdout_logfile=/home/ubuntu/logs/instagram-realtime.log
redirect_stderr=true
然后通过执行以下命令将其添加到Supervisor:
sudo supervisorctl
reread
add instagram-realtime
start instagram-realtime
最后的想法
而已! 在本教程中,您学习了如何使用socket.io使用Instagram API的实时功能。 请记住这些限制 ,即您可以对Instagram进行的API调用(这意味着可以订阅不同标签的用户数量是有限的-特别是在标签受欢迎的情况下)。 如果真是这样,那么服务器将收到来自Instagram的大量通知,并且您可以进行的API调用数量将很容易用完。 除此之外,您可以随意使用Instagram API。
此API还有很多其他可能性,您可以嵌入Instagram帖子或将其与移动应用程序集成 。 对于我们的应用程序,一个完美的用例是在一个活动上,您要求参与者发布带有特定标签的照片。 活动组织者可以订阅此标签并将该应用程序投射到屏幕上,以便每个人都可以看到正在共享的照片。
我很乐意在下面的评论中听到您对这个应用程序和Instagram API的看法。