2013-09-05 16:02:48 +02:00
|
|
|
/**
|
|
|
|
* Copyright 2013 IBM Corp.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
**/
|
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
module.exports = function(RED) {
|
2014-05-14 12:43:51 +02:00
|
|
|
"use strict";
|
2014-05-04 00:32:04 +02:00
|
|
|
var ntwitter = require('twitter-ng');
|
|
|
|
var OAuth= require('oauth').OAuth;
|
2014-09-01 11:15:25 +02:00
|
|
|
var request = require('request');
|
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
function TwitterNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
|
|
|
this.screen_name = n.screen_name;
|
|
|
|
}
|
2014-07-20 23:00:02 +02:00
|
|
|
RED.nodes.registerType("twitter-credentials",TwitterNode,{
|
|
|
|
credentials: {
|
|
|
|
screen_name: {type:"text"},
|
|
|
|
access_token: {type: "password"},
|
|
|
|
access_token_secret: {type:"password"}
|
2014-08-30 14:23:24 +02:00
|
|
|
}
|
2014-07-20 23:00:02 +02:00
|
|
|
});
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
function TwitterInNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
|
|
|
this.active = true;
|
|
|
|
this.user = n.user;
|
|
|
|
//this.tags = n.tags.replace(/ /g,'');
|
|
|
|
this.tags = n.tags;
|
|
|
|
this.twitter = n.twitter;
|
|
|
|
this.topic = n.topic||"tweets";
|
|
|
|
this.twitterConfig = RED.nodes.getNode(this.twitter);
|
|
|
|
var credentials = RED.nodes.getCredentials(this.twitter);
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
|
|
|
|
var twit = new ntwitter({
|
|
|
|
consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
|
|
|
|
consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
|
|
|
|
access_token_key: credentials.access_token,
|
|
|
|
access_token_secret: credentials.access_token_secret
|
|
|
|
});
|
2014-05-14 12:43:51 +02:00
|
|
|
|
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
//setInterval(function() {
|
|
|
|
// twit.get("/application/rate_limit_status.json",null,function(err,cb) {
|
|
|
|
// console.log("direct_messages:",cb["resources"]["direct_messages"]);
|
|
|
|
// });
|
2014-05-14 12:43:51 +02:00
|
|
|
//
|
2014-05-04 00:32:04 +02:00
|
|
|
//},10000);
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
var node = this;
|
|
|
|
if (this.user === "user") {
|
|
|
|
node.poll_ids = [];
|
|
|
|
node.since_ids = {};
|
|
|
|
var users = node.tags.split(",");
|
|
|
|
for (var i=0;i<users.length;i++) {
|
|
|
|
var user = users[i].replace(" ","");
|
|
|
|
twit.getUserTimeline({
|
|
|
|
screen_name:user,
|
|
|
|
trim_user:0,
|
|
|
|
count:1
|
2014-05-14 12:43:51 +02:00
|
|
|
},function() {
|
2014-05-04 00:32:04 +02:00
|
|
|
var u = user+"";
|
|
|
|
return function(err,cb) {
|
|
|
|
if (err) {
|
|
|
|
node.error(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (cb[0]) {
|
|
|
|
node.since_ids[u] = cb[0].id_str;
|
|
|
|
} else {
|
|
|
|
node.since_ids[u] = '0';
|
|
|
|
}
|
|
|
|
node.poll_ids.push(setInterval(function() {
|
|
|
|
twit.getUserTimeline({
|
|
|
|
screen_name:u,
|
|
|
|
trim_user:0,
|
|
|
|
since_id:node.since_ids[u]
|
|
|
|
},function(err,cb) {
|
|
|
|
if (cb) {
|
|
|
|
for (var t=cb.length-1;t>=0;t-=1) {
|
|
|
|
var tweet = cb[t];
|
|
|
|
var where = tweet.user.location||"";
|
|
|
|
var la = tweet.lang || tweet.user.lang;
|
|
|
|
//console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
|
|
|
|
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
|
|
|
|
node.send(msg);
|
|
|
|
if (t == 0) {
|
|
|
|
node.since_ids[u] = tweet.id_str;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (err) {
|
|
|
|
node.error(err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},60000));
|
|
|
|
}
|
|
|
|
}());
|
|
|
|
}
|
|
|
|
} else if (this.user === "dm") {
|
|
|
|
node.poll_ids = [];
|
|
|
|
twit.getDirectMessages({
|
|
|
|
screen_name:node.twitterConfig.screen_name,
|
2013-11-23 00:07:08 +01:00
|
|
|
trim_user:0,
|
|
|
|
count:1
|
2014-05-04 00:32:04 +02:00
|
|
|
},function(err,cb) {
|
|
|
|
if (err) {
|
|
|
|
node.error(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (cb[0]) {
|
|
|
|
node.since_id = cb[0].id_str;
|
|
|
|
} else {
|
|
|
|
node.since_id = '0';
|
|
|
|
}
|
|
|
|
node.poll_ids.push(setInterval(function() {
|
|
|
|
twit.getDirectMessages({
|
|
|
|
screen_name:node.twitterConfig.screen_name,
|
2013-11-23 00:07:08 +01:00
|
|
|
trim_user:0,
|
2014-05-04 00:32:04 +02:00
|
|
|
since_id:node.since_id
|
2013-11-23 00:07:08 +01:00
|
|
|
},function(err,cb) {
|
|
|
|
if (cb) {
|
|
|
|
for (var t=cb.length-1;t>=0;t-=1) {
|
|
|
|
var tweet = cb[t];
|
2014-05-04 00:32:04 +02:00
|
|
|
var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, tweet:tweet };
|
2013-11-23 00:07:08 +01:00
|
|
|
node.send(msg);
|
|
|
|
if (t == 0) {
|
2014-05-04 00:32:04 +02:00
|
|
|
node.since_id = tweet.id_str;
|
2013-11-23 00:07:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (err) {
|
|
|
|
node.error(err);
|
|
|
|
}
|
|
|
|
});
|
2014-05-04 00:32:04 +02:00
|
|
|
},120000));
|
|
|
|
});
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
} else if (this.tags !== "") {
|
|
|
|
try {
|
|
|
|
var thing = 'statuses/filter';
|
|
|
|
if (this.user === "true") { thing = 'user'; }
|
|
|
|
var st = { track: [node.tags] };
|
|
|
|
var bits = node.tags.split(",");
|
|
|
|
if ((bits.length > 0) && (bits.length % 4 == 0)) {
|
|
|
|
if ((Number(bits[0]) < Number(bits[2])) && (Number(bits[1]) < Number(bits[3]))) {
|
|
|
|
st = { locations: node.tags };
|
|
|
|
}
|
|
|
|
else {
|
2014-08-30 14:23:24 +02:00
|
|
|
node.log("possible bad geo area format. Should be lower-left lon, lat, upper-right lon, lat");
|
2014-05-04 00:32:04 +02:00
|
|
|
}
|
2013-11-23 00:07:08 +01:00
|
|
|
}
|
2014-05-14 12:43:51 +02:00
|
|
|
|
|
|
|
var setupStream = function() {
|
2014-05-04 00:32:04 +02:00
|
|
|
if (node.active) {
|
|
|
|
twit.stream(thing, st, function(stream) {
|
|
|
|
//console.log(st);
|
|
|
|
//twit.stream('user', { track: [node.tags] }, function(stream) {
|
|
|
|
//twit.stream('site', { track: [node.tags] }, function(stream) {
|
|
|
|
//twit.stream('statuses/filter', { track: [node.tags] }, function(stream) {
|
|
|
|
node.stream = stream;
|
|
|
|
stream.on('data', function(tweet) {
|
|
|
|
//console.log(tweet.user);
|
|
|
|
if (tweet.user !== undefined) {
|
|
|
|
var where = tweet.user.location||"";
|
|
|
|
var la = tweet.lang || tweet.user.lang;
|
|
|
|
//console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
|
|
|
|
var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
|
|
|
|
node.send(msg);
|
2013-11-23 22:48:17 +01:00
|
|
|
}
|
2014-05-04 00:32:04 +02:00
|
|
|
});
|
|
|
|
stream.on('limit', function(tweet) {
|
|
|
|
node.log("tweet rate limit hit");
|
|
|
|
});
|
|
|
|
stream.on('error', function(tweet,rc) {
|
2014-05-07 21:45:59 +02:00
|
|
|
if (rc == 420) {
|
|
|
|
node.warn("Twitter rate limit hit");
|
|
|
|
} else {
|
|
|
|
node.warn("Stream error:"+tweet.toString()+" ("+rc+")");
|
|
|
|
}
|
2013-11-15 09:59:06 +01:00
|
|
|
setTimeout(setupStream,10000);
|
2014-05-04 00:32:04 +02:00
|
|
|
});
|
|
|
|
stream.on('destroy', function (response) {
|
|
|
|
if (this.active) {
|
|
|
|
node.warn("twitter ended unexpectedly");
|
|
|
|
setTimeout(setupStream,10000);
|
|
|
|
}
|
|
|
|
});
|
2013-11-15 09:59:06 +01:00
|
|
|
});
|
2014-05-04 00:32:04 +02:00
|
|
|
}
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
2014-05-04 00:32:04 +02:00
|
|
|
setupStream();
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
2014-05-04 00:32:04 +02:00
|
|
|
catch (err) {
|
|
|
|
node.error(err);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.error("Invalid tag property");
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
|
|
|
} else {
|
2014-05-04 00:32:04 +02:00
|
|
|
this.error("missing twitter credentials");
|
2013-11-15 09:59:06 +01:00
|
|
|
}
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
this.on('close', function() {
|
|
|
|
if (this.stream) {
|
|
|
|
this.active = false;
|
|
|
|
this.stream.destroy();
|
2013-11-23 00:07:08 +01:00
|
|
|
}
|
2014-05-04 00:32:04 +02:00
|
|
|
if (this.poll_ids) {
|
|
|
|
for (var i=0;i<this.poll_ids.length;i++) {
|
|
|
|
clearInterval(this.poll_ids[i]);
|
|
|
|
}
|
2013-11-15 09:59:06 +01:00
|
|
|
}
|
2013-09-05 16:02:48 +02:00
|
|
|
});
|
|
|
|
}
|
2014-05-04 00:32:04 +02:00
|
|
|
RED.nodes.registerType("twitter in",TwitterInNode);
|
2014-05-14 12:43:51 +02:00
|
|
|
|
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
function TwitterOutNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
|
|
|
this.topic = n.topic;
|
|
|
|
this.twitter = n.twitter;
|
|
|
|
this.twitterConfig = RED.nodes.getNode(this.twitter);
|
|
|
|
var credentials = RED.nodes.getCredentials(this.twitter);
|
|
|
|
var node = this;
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
|
|
|
|
var twit = new ntwitter({
|
|
|
|
consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
|
|
|
|
consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
|
|
|
|
access_token_key: credentials.access_token,
|
|
|
|
access_token_secret: credentials.access_token_secret
|
2014-09-01 11:15:25 +02:00
|
|
|
});
|
|
|
|
node.on("input", function(msg) {
|
2014-09-08 22:10:06 +02:00
|
|
|
node.status({fill:"blue",shape:"dot",text:"tweeting"});
|
|
|
|
|
|
|
|
if (msg.payload.length > 140) {
|
|
|
|
msg.payload = msg.payload.slice(0,139);
|
|
|
|
node.warn("Tweet greater than 140 : truncated");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg.media && Buffer.isBuffer(msg.media)) {
|
|
|
|
var apiUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json";
|
|
|
|
var signedUrl = oa.signUrl(apiUrl,
|
|
|
|
credentials.access_token,
|
|
|
|
credentials.access_token_secret,
|
|
|
|
"POST");
|
|
|
|
|
|
|
|
var r = request.post(signedUrl,function(err,httpResponse,body) {
|
|
|
|
if (err) {
|
|
|
|
node.error(err.toString());
|
|
|
|
node.status({fill:"red",shape:"ring",text:"failed"});
|
|
|
|
} else {
|
|
|
|
var response = JSON.parse(body);
|
|
|
|
if (body.errors) {
|
|
|
|
var errorList = body.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
|
|
|
|
node.error("tweet failed: "+errorList);
|
2014-09-01 11:15:25 +02:00
|
|
|
node.status({fill:"red",shape:"ring",text:"failed"});
|
|
|
|
} else {
|
2014-09-08 22:10:06 +02:00
|
|
|
node.status({});
|
2014-09-01 11:15:25 +02:00
|
|
|
}
|
2014-09-08 22:10:06 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
var form = r.form();
|
|
|
|
form.append("status",msg.payload);
|
|
|
|
form.append("media[]",msg.media,{filename:"image"});
|
|
|
|
|
|
|
|
} else {
|
|
|
|
twit.updateStatus(msg.payload, function (err, data) {
|
|
|
|
if (err) {
|
|
|
|
node.status({fill:"red",shape:"ring",text:"failed"});
|
|
|
|
node.error(err);
|
|
|
|
}
|
|
|
|
node.status({});
|
|
|
|
});
|
2014-05-04 00:32:04 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2013-11-15 09:59:06 +01:00
|
|
|
}
|
2014-05-04 00:32:04 +02:00
|
|
|
RED.nodes.registerType("twitter out",TwitterOutNode);
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
var oa = new OAuth(
|
|
|
|
"https://api.twitter.com/oauth/request_token",
|
|
|
|
"https://api.twitter.com/oauth/access_token",
|
|
|
|
"OKjYEd1ef2bfFolV25G5nQ",
|
|
|
|
"meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
|
|
|
|
"1.0",
|
|
|
|
null,
|
|
|
|
"HMAC-SHA1"
|
|
|
|
);
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-07-20 23:00:02 +02:00
|
|
|
RED.httpAdmin.get('/twitter-credentials/:id/auth', function(req, res){
|
2014-05-04 00:32:04 +02:00
|
|
|
var credentials = {};
|
|
|
|
oa.getOAuthRequestToken({
|
|
|
|
oauth_callback: req.query.callback
|
|
|
|
},function(error, oauth_token, oauth_token_secret, results){
|
|
|
|
if (error) {
|
|
|
|
var resp = '<h2>Oh no!</h2>'+
|
|
|
|
'<p>Something went wrong with the authentication process. The following error was returned:<p>'+
|
|
|
|
'<p><b>'+error.statusCode+'</b>: '+error.data+'</p>'+
|
|
|
|
'<p>One known cause of this type of failure is if the clock is wrong on system running Node-RED.';
|
|
|
|
res.send(resp)
|
2013-11-15 09:59:06 +01:00
|
|
|
} else {
|
2014-05-04 00:32:04 +02:00
|
|
|
credentials.oauth_token = oauth_token;
|
|
|
|
credentials.oauth_token_secret = oauth_token_secret;
|
|
|
|
res.redirect('https://twitter.com/oauth/authorize?oauth_token='+oauth_token)
|
2013-11-15 09:59:06 +01:00
|
|
|
RED.nodes.addCredentials(req.params.id,credentials);
|
2013-09-05 16:02:48 +02:00
|
|
|
}
|
2014-05-04 00:32:04 +02:00
|
|
|
});
|
|
|
|
});
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-07-20 23:00:02 +02:00
|
|
|
RED.httpAdmin.get('/twitter-credentials/:id/auth/callback', function(req, res, next){
|
2014-05-04 00:32:04 +02:00
|
|
|
var credentials = RED.nodes.getCredentials(req.params.id);
|
|
|
|
credentials.oauth_verifier = req.query.oauth_verifier;
|
2014-05-14 12:43:51 +02:00
|
|
|
|
2014-05-04 00:32:04 +02:00
|
|
|
oa.getOAuthAccessToken(
|
|
|
|
credentials.oauth_token,
|
|
|
|
credentials.token_secret,
|
|
|
|
credentials.oauth_verifier,
|
|
|
|
function(error, oauth_access_token, oauth_access_token_secret, results){
|
|
|
|
if (error){
|
|
|
|
console.log(error);
|
|
|
|
res.send("yeah something broke.");
|
|
|
|
} else {
|
|
|
|
credentials = {};
|
|
|
|
credentials.access_token = oauth_access_token;
|
|
|
|
credentials.access_token_secret = oauth_access_token_secret;
|
|
|
|
credentials.screen_name = "@"+results.screen_name;
|
|
|
|
RED.nodes.addCredentials(req.params.id,credentials);
|
|
|
|
res.send("<html><head></head><body>Authorised - you can close this window and return to Node-RED</body></html>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|