2015-06-13 19:46:44 +02:00
module . exports = function ( RED ) {
"use strict" ;
2015-06-16 15:38:36 +02:00
var Ntwitter = require ( 'twitter-ng' ) ;
2015-06-13 19:46:44 +02:00
var OAuth = require ( 'oauth' ) . OAuth ;
var request = require ( 'request' ) ;
2016-09-30 18:00:20 +02:00
var twitterRateTimeout ;
2018-06-07 13:07:01 +02:00
var retry = 60000 ; // 60 secs backoff for now
2015-06-13 19:46:44 +02:00
2018-06-07 12:25:25 +02:00
function TwitterCredentialsNode ( n ) {
2015-06-13 19:46:44 +02:00
RED . nodes . createNode ( this , n ) ;
this . screen _name = n . screen _name ;
2018-06-08 13:55:19 +02:00
if ( this . screen _name && this . screen _name [ 0 ] !== "@" ) {
this . screen _name = "@" + this . screen _name ;
}
2015-06-13 19:46:44 +02:00
}
2018-06-07 12:25:25 +02:00
RED . nodes . registerType ( "twitter-credentials" , TwitterCredentialsNode , {
2015-06-13 19:46:44 +02:00
credentials : {
2018-06-07 12:25:25 +02:00
consumer _key : { type : "password" } ,
consumer _secret : { type : "password" } ,
2015-06-13 19:46:44 +02:00
access _token : { type : "password" } ,
access _token _secret : { type : "password" }
}
} ) ;
/ * *
* Populate msg . location based on data found in msg . tweet .
* /
function addLocationToTweet ( msg ) {
2015-06-16 15:38:36 +02:00
if ( msg . tweet ) {
if ( msg . tweet . geo ) { // if geo is set, always set location from geo
if ( msg . tweet . geo . coordinates && msg . tweet . geo . coordinates . length === 2 ) {
2015-06-13 19:46:44 +02:00
if ( ! msg . location ) { msg . location = { } ; }
// coordinates[0] is lat, coordinates[1] is lon
msg . location . lat = msg . tweet . geo . coordinates [ 0 ] ;
msg . location . lon = msg . tweet . geo . coordinates [ 1 ] ;
msg . location . icon = "twitter" ;
}
2017-01-29 18:45:44 +01:00
}
else if ( msg . tweet . coordinates ) { // otherwise attempt go get it from coordinates
2015-06-16 15:38:36 +02:00
if ( msg . tweet . coordinates . coordinates && msg . tweet . coordinates . coordinates . length === 2 ) {
2015-06-13 19:46:44 +02:00
if ( ! msg . location ) { msg . location = { } ; }
// WARNING! coordinates[1] is lat, coordinates[0] is lon!!!
msg . location . lat = msg . tweet . coordinates . coordinates [ 1 ] ;
msg . location . lon = msg . tweet . coordinates . coordinates [ 0 ] ;
msg . location . icon = "twitter" ;
}
} // if none of these found then just do nothing
} // if no msg.tweet then just do nothing
}
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 ) ;
2018-06-07 13:07:01 +02:00
if ( credentials && credentials . consumer _key && credentials . consumer _secret && credentials . access _token && credentials . access _token _secret ) {
2015-06-16 15:38:36 +02:00
var twit = new Ntwitter ( {
2018-06-07 12:25:25 +02:00
consumer _key : credentials . consumer _key ,
consumer _secret : credentials . consumer _secret ,
2015-06-13 19:46:44 +02:00
access _token _key : credentials . access _token ,
access _token _secret : credentials . access _token _secret
} ) ;
//setInterval(function() {
// twit.get("/application/rate_limit_status.json",null,function(err,cb) {
// console.log("direct_messages:",cb["resources"]["direct_messages"]);
// });
//
//},10000);
2018-06-07 12:25:25 +02:00
var node = this ;
2015-06-13 19:46:44 +02:00
if ( this . user === "user" ) {
node . poll _ids = [ ] ;
node . since _ids = { } ;
2016-09-27 22:21:28 +02:00
node . status ( { } ) ;
2015-06-13 19:46:44 +02:00
var users = node . tags . split ( "," ) ;
2016-09-27 22:21:28 +02:00
if ( users === '' ) { node . warn ( RED . _ ( "twitter.warn.nousers" ) ) ; }
//if (users.length === 0) { node.warn(RED._("twitter.warn.nousers")); }
else {
for ( var i = 0 ; i < users . length ; i ++ ) {
var user = users [ i ] . replace ( " " , "" ) ;
twit . getUserTimeline ( {
screen _name : user ,
trim _user : 0 ,
count : 1
} , ( function ( ) {
var u = user + "" ;
return function ( err , cb ) {
if ( err ) {
node . error ( err ) ;
return ;
}
if ( cb [ 0 ] ) {
node . since _ids [ u ] = cb [ 0 ] . id _str ;
2017-01-29 18:45:44 +01:00
}
else {
2016-09-27 22:21:28 +02:00
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 ;
var msg = { topic : node . topic + "/" + tweet . user . screen _name , payload : tweet . text , lang : la , tweet : tweet } ;
if ( where ) {
msg . location = { place : where } ;
addLocationToTweet ( msg ) ;
}
node . send ( msg ) ;
if ( t === 0 ) {
node . since _ids [ u ] = tweet . id _str ;
}
2015-06-13 19:46:44 +02:00
}
}
2016-09-27 22:21:28 +02:00
if ( err ) {
node . error ( err ) ;
}
} ) ;
} , 60000 ) ) ;
}
} ( ) ) ) ;
}
2015-06-13 19:46:44 +02:00
}
2016-09-27 22:21:28 +02:00
}
else if ( this . user === "dm" ) {
2015-06-13 19:46:44 +02:00
node . poll _ids = [ ] ;
2016-09-27 22:21:28 +02:00
node . status ( { } ) ;
2015-06-13 19:46:44 +02:00
twit . getDirectMessages ( {
2015-06-16 15:38:36 +02:00
screen _name : node . twitterConfig . screen _name ,
trim _user : 0 ,
count : 1
2015-06-13 19:46:44 +02:00
} , function ( err , cb ) {
if ( err ) {
node . error ( err ) ;
return ;
}
if ( cb [ 0 ] ) {
node . since _id = cb [ 0 ] . id _str ;
2017-01-29 18:45:44 +01:00
}
else {
2015-06-13 19:46:44 +02:00
node . since _id = '0' ;
}
node . poll _ids . push ( setInterval ( function ( ) {
2015-06-16 15:38:36 +02:00
twit . getDirectMessages ( {
screen _name : node . twitterConfig . screen _name ,
trim _user : 0 ,
since _id : node . since _id
} , function ( err , cb ) {
2015-06-13 19:46:44 +02:00
if ( cb ) {
2017-04-12 14:32:08 +02:00
for ( var t = cb . length - 1 ; t >= 0 ; t -= 1 ) {
2015-06-13 19:46:44 +02:00
var tweet = cb [ t ] ;
var where = tweet . sender . location ;
var la = tweet . lang || tweet . sender . lang ;
var msg = { topic : node . topic + "/" + tweet . sender . screen _name , payload : tweet . text , lang : la , tweet : tweet } ;
if ( where ) {
msg . location = { place : where } ;
addLocationToTweet ( msg ) ;
}
node . send ( msg ) ;
2015-06-16 15:38:36 +02:00
if ( t === 0 ) {
2015-06-13 19:46:44 +02:00
node . since _id = tweet . id _str ;
}
}
}
if ( err ) {
node . error ( err ) ;
}
} ) ;
} , 120000 ) ) ;
} ) ;
2016-09-27 22:21:28 +02:00
}
2016-11-12 12:43:25 +01:00
else if ( this . user === "event" ) {
2018-05-25 14:16:34 +02:00
this . error ( "This Twitter node is configured to access a user's activity stream. Twitter are withdrawing this API in August 2018 so this feature will be removed from the node in the near future. See https://bit.ly/2kr7InE for details." )
2016-11-12 12:43:25 +01:00
try {
var thingu = 'user' ;
var setupEvStream = function ( ) {
if ( node . active ) {
twit . stream ( thingu , st , function ( stream ) {
node . status ( { fill : "green" , shape : "dot" , text : " " } ) ;
node . stream = stream ;
stream . on ( 'data' , function ( tweet ) {
if ( tweet . event !== undefined ) {
var where = tweet . source . location ;
var la = tweet . source . lang ;
var msg = { topic : node . topic + "/" + tweet . source . screen _name , payload : tweet . event , lang : la , tweet : tweet } ;
if ( where ) {
msg . location = { place : where } ;
addLocationToTweet ( msg ) ;
}
node . send ( msg ) ;
}
} ) ;
stream . on ( 'limit' , function ( tweet ) {
node . status ( { fill : "grey" , shape : "dot" , text : " " } ) ;
node . tout2 = setTimeout ( function ( ) { node . status ( { fill : "green" , shape : "dot" , text : " " } ) ; } , 10000 ) ;
} ) ;
stream . on ( 'error' , function ( tweet , rc ) {
//console.log("ERRO",rc,tweet);
if ( rc == 420 ) {
node . status ( { fill : "red" , shape : "ring" , text : RED . _ ( "twitter.errors.ratelimit" ) } ) ;
2017-01-29 18:45:44 +01:00
}
else {
2016-11-12 12:43:25 +01:00
node . status ( { fill : "red" , shape : "ring" , text : " " } ) ;
node . warn ( RED . _ ( "twitter.errors.streamerror" , { error : tweet . toString ( ) , rc : rc } ) ) ;
}
twitterRateTimeout = Date . now ( ) + retry ;
if ( node . restart ) {
node . tout = setTimeout ( function ( ) { setupEvStream ( ) } , retry ) ;
}
} ) ;
stream . on ( 'destroy' , function ( response ) {
//console.log("DEST",response)
twitterRateTimeout = Date . now ( ) + 15000 ;
if ( node . restart ) {
node . status ( { fill : "red" , shape : "dot" , text : " " } ) ;
node . warn ( RED . _ ( "twitter.errors.unexpectedend" ) ) ;
node . tout = setTimeout ( function ( ) { setupEvStream ( ) } , 15000 ) ;
}
} ) ;
} ) ;
}
}
setupEvStream ( ) ;
}
catch ( err ) {
node . error ( err ) ;
}
}
2016-09-27 22:21:28 +02:00
else {
2015-06-13 19:46:44 +02:00
try {
var thing = 'statuses/filter' ;
2016-09-28 18:09:58 +02:00
var tags = node . tags ;
var st = { track : [ tags ] } ;
2015-06-13 19:46:44 +02:00
var setupStream = function ( ) {
2016-09-30 09:54:55 +02:00
if ( node . restart ) {
2016-09-30 18:00:20 +02:00
node . status ( { fill : "green" , shape : "dot" , text : ( tags || " " ) } ) ;
2015-06-13 19:46:44 +02:00
twit . stream ( thing , st , function ( stream ) {
2016-09-30 09:54:55 +02:00
//console.log("ST",st);
2015-06-13 19:46:44 +02:00
node . stream = stream ;
stream . on ( 'data' , function ( tweet ) {
if ( tweet . user !== undefined ) {
var where = tweet . user . location ;
var la = tweet . lang || tweet . user . lang ;
var msg = { topic : node . topic + "/" + tweet . user . screen _name , payload : tweet . text , lang : la , tweet : tweet } ;
if ( where ) {
msg . location = { place : where } ;
addLocationToTweet ( msg ) ;
}
node . send ( msg ) ;
2016-09-30 21:38:05 +02:00
//node.status({fill:"green", shape:"dot", text:(tags||" ")});
2015-06-13 19:46:44 +02:00
}
} ) ;
stream . on ( 'limit' , function ( tweet ) {
2016-09-30 21:38:05 +02:00
//node.status({fill:"grey", shape:"dot", text:RED._("twitter.errors.limitrate")});
node . status ( { fill : "grey" , shape : "dot" , text : ( tags || " " ) } ) ;
2016-09-30 22:21:44 +02:00
node . tout2 = setTimeout ( function ( ) { node . status ( { fill : "green" , shape : "dot" , text : ( tags || " " ) } ) ; } , 10000 ) ;
2015-06-13 19:46:44 +02:00
} ) ;
stream . on ( 'error' , function ( tweet , rc ) {
2016-09-30 21:38:05 +02:00
//console.log("ERRO",rc,tweet);
2015-06-13 19:46:44 +02:00
if ( rc == 420 ) {
2016-09-30 18:00:20 +02:00
node . status ( { fill : "red" , shape : "ring" , text : RED . _ ( "twitter.errors.ratelimit" ) } ) ;
2017-01-29 18:45:44 +01:00
}
else {
2016-09-30 21:38:05 +02:00
node . status ( { fill : "red" , shape : "ring" , text : tweet . toString ( ) } ) ;
2015-06-16 11:36:19 +02:00
node . warn ( RED . _ ( "twitter.errors.streamerror" , { error : tweet . toString ( ) , rc : rc } ) ) ;
2015-06-13 19:46:44 +02:00
}
2016-09-30 18:00:20 +02:00
twitterRateTimeout = Date . now ( ) + retry ;
2016-09-30 09:54:55 +02:00
if ( node . restart ) {
2016-09-30 12:13:38 +02:00
node . tout = setTimeout ( function ( ) { setupStream ( ) } , retry ) ;
2016-09-30 09:54:55 +02:00
}
2015-06-13 19:46:44 +02:00
} ) ;
stream . on ( 'destroy' , function ( response ) {
2016-09-30 21:38:05 +02:00
//console.log("DEST",response)
twitterRateTimeout = Date . now ( ) + 15000 ;
2016-09-30 09:54:55 +02:00
if ( node . restart ) {
2016-09-30 18:00:20 +02:00
node . status ( { fill : "red" , shape : "dot" , text : " " } ) ;
2015-06-16 11:36:19 +02:00
node . warn ( RED . _ ( "twitter.errors.unexpectedend" ) ) ;
2016-09-30 12:13:38 +02:00
node . tout = setTimeout ( function ( ) { setupStream ( ) } , 15000 ) ;
2015-06-13 19:46:44 +02:00
}
} ) ;
} ) ;
}
}
2016-09-30 00:30:13 +02:00
// ask for users stream instead of public
if ( this . user === "true" ) {
thing = 'user' ;
// twit.getFriendsIds(node.twitterConfig.screen_name.substr(1), function(err,list) {
// friends = list;
// });
st = null ;
2016-09-27 18:56:35 +02:00
}
2016-09-30 00:30:13 +02:00
// if 4 numeric tags that look like a geo area then set geo area
var bits = node . tags . split ( "," ) ;
if ( bits . length == 4 ) {
if ( ( Number ( bits [ 0 ] ) < Number ( bits [ 2 ] ) ) && ( Number ( bits [ 1 ] ) < Number ( bits [ 3 ] ) ) ) {
st = { locations : node . tags } ;
node . log ( RED . _ ( "twitter.status.using-geo" , { location : node . tags . toString ( ) } ) ) ;
}
2016-09-27 18:56:35 +02:00
}
2016-09-30 00:30:13 +02:00
// all public tweets
2016-09-28 18:09:58 +02:00
if ( this . user === "false" ) {
node . on ( "input" , function ( msg ) {
if ( this . tags === '' ) {
2016-09-30 22:21:44 +02:00
if ( node . tout ) { clearTimeout ( node . tout ) ; }
if ( node . tout2 ) { clearTimeout ( node . tout2 ) ; }
2016-09-30 09:54:55 +02:00
if ( this . stream ) {
this . restart = false ;
node . stream . removeAllListeners ( ) ;
this . stream . destroy ( ) ;
}
2016-09-28 18:09:58 +02:00
if ( ( typeof msg . payload === "string" ) && ( msg . payload !== "" ) ) {
st = { track : [ msg . payload ] } ;
tags = msg . payload ;
2016-09-30 18:00:20 +02:00
2016-09-30 09:54:55 +02:00
this . restart = true ;
2016-09-30 18:00:20 +02:00
if ( ( twitterRateTimeout - Date . now ( ) ) > 0 ) {
node . status ( { fill : "red" , shape : "ring" , text : tags } ) ;
node . tout = setTimeout ( function ( ) {
setupStream ( ) ;
} , twitterRateTimeout - Date . now ( ) ) ;
}
else {
setupStream ( ) ;
}
2016-09-28 18:09:58 +02:00
}
else {
node . status ( { fill : "yellow" , shape : "ring" , text : RED . _ ( "twitter.warn.waiting" ) } ) ;
}
2016-09-27 19:03:54 +02:00
}
2016-09-28 18:09:58 +02:00
} ) ;
}
2016-09-30 00:30:13 +02:00
// wait for input or start the stream
if ( ( this . user === "false" ) && ( tags === '' ) ) {
node . status ( { fill : "yellow" , shape : "ring" , text : RED . _ ( "twitter.warn.waiting" ) } ) ;
}
else {
2016-09-30 21:38:05 +02:00
this . restart = true ;
2016-09-30 00:30:13 +02:00
setupStream ( ) ;
}
2015-06-13 19:46:44 +02:00
}
catch ( err ) {
node . error ( err ) ;
}
2016-09-27 18:56:35 +02:00
}
2018-06-07 13:07:01 +02:00
this . on ( 'close' , function ( ) {
if ( this . tout ) { clearTimeout ( this . tout ) ; }
if ( this . tout2 ) { clearTimeout ( this . tout2 ) ; }
if ( this . stream ) {
this . restart = false ;
this . stream . removeAllListeners ( ) ;
this . stream . destroy ( ) ;
}
if ( this . poll _ids ) {
for ( var i = 0 ; i < this . poll _ids . length ; i ++ ) {
clearInterval ( this . poll _ids [ i ] ) ;
}
}
} ) ;
} else {
2015-06-16 11:36:19 +02:00
this . error ( RED . _ ( "twitter.errors.missingcredentials" ) ) ;
2015-06-13 19:46:44 +02:00
}
}
RED . nodes . registerType ( "twitter in" , TwitterInNode ) ;
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 ;
2018-06-07 12:25:25 +02:00
var dm _user ;
2015-06-13 19:46:44 +02:00
2018-06-07 13:07:01 +02:00
if ( credentials && credentials . consumer _key && credentials . consumer _secret && credentials . access _token && credentials . access _token _secret ) {
2015-06-16 15:38:36 +02:00
var twit = new Ntwitter ( {
2018-06-07 12:25:25 +02:00
consumer _key : credentials . consumer _key ,
consumer _secret : credentials . consumer _secret ,
2015-06-13 19:46:44 +02:00
access _token _key : credentials . access _token ,
access _token _secret : credentials . access _token _secret
} ) ;
2018-06-08 13:55:19 +02:00
var oa = new OAuth (
"https://api.twitter.com/oauth/request_token" ,
"https://api.twitter.com/oauth/access_token" ,
credentials . consumer _key ,
credentials . consumer _secret ,
"1.0" ,
null ,
"HMAC-SHA1"
) ;
2015-06-13 19:46:44 +02:00
node . on ( "input" , function ( msg ) {
if ( msg . hasOwnProperty ( "payload" ) ) {
2015-07-07 22:31:28 +02:00
node . status ( { fill : "blue" , shape : "dot" , text : "twitter.status.tweeting" } ) ;
2015-06-13 19:46:44 +02:00
2018-03-29 10:42:22 +02:00
if ( msg . payload . slice ( 0 , 2 ) == "D " ) {
// direct message syntax: "D user message"
2018-05-18 19:40:14 +02:00
var t = msg . payload . match ( /D\s+(\S+)\s+(.*)/ ) . slice ( 1 ) ;
dm _user = t [ 0 ] ;
msg . payload = t [ 1 ] ;
2018-03-29 10:42:22 +02:00
}
2017-11-10 16:15:53 +01:00
if ( msg . payload . length > 280 ) {
msg . payload = msg . payload . slice ( 0 , 279 ) ;
2015-06-16 11:36:19 +02:00
node . warn ( RED . _ ( "twitter.errors.truncated" ) ) ;
2015-06-13 19:46:44 +02:00
}
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 , msg ) ;
2015-07-07 22:31:28 +02:00
node . status ( { fill : "red" , shape : "ring" , text : "twitter.status.failed" } ) ;
2017-01-29 18:45:44 +01:00
}
else {
2015-06-13 19:46:44 +02:00
var response = JSON . parse ( body ) ;
if ( response . errors ) {
var errorList = response . errors . map ( function ( er ) { return er . code + ": " + er . message } ) . join ( ", " ) ;
2015-06-16 11:36:19 +02:00
node . error ( RED . _ ( "twitter.errors.sendfail" , { error : errorList } ) , msg ) ;
2015-07-07 22:31:28 +02:00
node . status ( { fill : "red" , shape : "ring" , text : "twitter.status.failed" } ) ;
2017-01-29 18:45:44 +01:00
}
else {
2015-06-13 19:46:44 +02:00
node . status ( { } ) ;
}
}
} ) ;
var form = r . form ( ) ;
form . append ( "status" , msg . payload ) ;
form . append ( "media[]" , msg . media , { filename : "image" } ) ;
2017-01-29 18:45:44 +01:00
}
else {
2015-09-04 10:20:37 +02:00
if ( typeof msg . params === 'undefined' ) { msg . params = { } ; }
2018-03-29 10:42:22 +02:00
if ( dm _user ) {
twit . newDirectMessage ( dm _user , msg . payload , msg . params , function ( err , data ) {
if ( err ) {
node . status ( { fill : "red" , shape : "ring" , text : "twitter.status.failed" } ) ;
node . error ( err , msg ) ;
}
node . status ( { } ) ;
} ) ;
} else {
twit . updateStatus ( msg . payload , msg . params , function ( err , data ) {
if ( err ) {
node . status ( { fill : "red" , shape : "ring" , text : "twitter.status.failed" } ) ;
node . error ( err , msg ) ;
}
node . status ( { } ) ;
} ) ;
}
2015-06-13 19:46:44 +02:00
}
}
2015-06-16 11:36:19 +02:00
else { node . warn ( RED . _ ( "twitter.errors.nopayload" ) ) ; }
2015-06-13 19:46:44 +02:00
} ) ;
2018-06-07 13:07:01 +02:00
} else {
this . error ( RED . _ ( "twitter.errors.missingcredentials" ) ) ;
2015-06-13 19:46:44 +02:00
}
}
RED . nodes . registerType ( "twitter out" , TwitterOutNode ) ;
}