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 request = require('request');
|
2018-08-15 16:23:08 +02:00
|
|
|
var crypto = require('crypto');
|
|
|
|
// var fileType = require('file-type');
|
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-08-15 16:23:08 +02:00
|
|
|
var localUserCache = {};
|
|
|
|
var userObjectCache = {};
|
|
|
|
var userSreenNameToIdCache = {};
|
|
|
|
|
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-08-15 16:23:08 +02:00
|
|
|
if (this.screen_name && this.screen_name[0] === "@") {
|
|
|
|
this.screen_name = this.screen_name.substring(1);
|
|
|
|
}
|
|
|
|
if (this.credentials.consumer_key &&
|
|
|
|
this.credentials.consumer_secret &&
|
|
|
|
this.credentials.access_token &&
|
|
|
|
this.credentials.access_token_secret) {
|
|
|
|
|
|
|
|
this.oauth = {
|
|
|
|
consumer_key: this.credentials.consumer_key,
|
|
|
|
consumer_secret: this.credentials.consumer_secret,
|
|
|
|
token: this.credentials.access_token,
|
|
|
|
token_secret: this.credentials.access_token_secret
|
|
|
|
}
|
|
|
|
this.credHash = crypto.createHash('sha1').update(
|
|
|
|
this.credentials.consumer_key+this.credentials.consumer_secret+
|
|
|
|
this.credentials.access_token+this.credentials.access_token_secret
|
|
|
|
).digest('base64');
|
|
|
|
var self = this;
|
|
|
|
if (localUserCache.hasOwnProperty(self.credHash)) {
|
|
|
|
this.localIdentityPromise = Promise.resolve(localUserCache[self.credHash]);
|
|
|
|
} else {
|
|
|
|
this.localIdentityPromise = this.get("https://api.twitter.com/1.1/account/settings.json").then(function(body) {
|
|
|
|
if (body.status === 200) {
|
|
|
|
localUserCache[self.credHash] = body.body.screen_name;
|
|
|
|
self.screen_name = body.body.screen_name;
|
|
|
|
} else {
|
|
|
|
self.warn("Failed to get user profile");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-06-08 13:55:19 +02:00
|
|
|
}
|
2015-06-13 19:46:44 +02:00
|
|
|
}
|
2018-08-15 16:23:08 +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"}
|
|
|
|
}
|
|
|
|
});
|
2018-08-15 16:23:08 +02:00
|
|
|
TwitterCredentialsNode.prototype.get = function(url,opts) {
|
|
|
|
var node = this;
|
|
|
|
opts = opts || {};
|
|
|
|
opts.tweet_mode = 'extended';
|
|
|
|
return new Promise(function(resolve,reject) {
|
|
|
|
request.get({
|
|
|
|
url: url,
|
|
|
|
oauth: node.oauth,
|
|
|
|
json: true,
|
|
|
|
qs: opts
|
|
|
|
}, function(err, response,body) {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve({
|
|
|
|
status: response.statusCode,
|
|
|
|
rateLimitRemaining: response.headers['x-rate-limit-remaining'],
|
|
|
|
rateLimitTimeout: 5000+parseInt(response.headers['x-rate-limit-reset'])*1000 - Date.now(),
|
|
|
|
body: body
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}
|
|
|
|
TwitterCredentialsNode.prototype.post = function(url,data,opts,formData) {
|
|
|
|
var node = this;
|
|
|
|
opts = opts || {};
|
|
|
|
var options = {
|
|
|
|
url: url,
|
|
|
|
oauth: node.oauth,
|
|
|
|
json: true,
|
|
|
|
qs: opts,
|
|
|
|
};
|
|
|
|
if (data) {
|
|
|
|
options.body = data;
|
|
|
|
}
|
|
|
|
if (formData) {
|
|
|
|
options.formData = formData;
|
|
|
|
}
|
|
|
|
return new Promise(function(resolve,reject) {
|
|
|
|
request.post(options, function(err, response,body) {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve({
|
|
|
|
status: response.statusCode,
|
|
|
|
rateLimitRemaining: response.headers['x-rate-limit-remaining'],
|
|
|
|
rateLimitTimeout: 5000+parseInt(response.headers['x-rate-limit-reset'])*1000 - Date.now(),
|
|
|
|
body: body
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}
|
2015-06-13 19:46:44 +02:00
|
|
|
|
|
|
|
|
2018-08-15 16:23:08 +02:00
|
|
|
TwitterCredentialsNode.prototype.getUsers = function(users,getBy) {
|
|
|
|
if (users.length === 0) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
var params = {};
|
|
|
|
params[getBy||"user_id"] = users;
|
|
|
|
|
|
|
|
return this.get("https://api.twitter.com/1.1/users/lookup.json",params).then(function(result) {
|
|
|
|
var res = result.body;
|
|
|
|
if (res.errors) {
|
|
|
|
throw new Error(res.errors[0].message);
|
|
|
|
}
|
|
|
|
res.forEach(user => {
|
|
|
|
userObjectCache[user.id_str] = user
|
|
|
|
userSreenNameToIdCache[user.screen_name] = user.id_str;
|
|
|
|
});
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-06-13 19:46:44 +02:00
|
|
|
/**
|
|
|
|
* 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,'');
|
2018-08-15 16:23:08 +02:00
|
|
|
this.tags = n.tags||"";
|
2015-06-13 19:46:44 +02:00
|
|
|
this.twitter = n.twitter;
|
2018-08-15 16:23:08 +02:00
|
|
|
this.topic = "tweets";
|
2015-06-13 19:46:44 +02:00
|
|
|
this.twitterConfig = RED.nodes.getNode(this.twitter);
|
2018-08-15 16:23:08 +02:00
|
|
|
this.poll_ids = [];
|
|
|
|
this.timeout_ids = [];
|
2015-06-13 19:46:44 +02:00
|
|
|
|
2018-08-15 16:23:08 +02:00
|
|
|
var credentials = RED.nodes.getCredentials(this.twitter);
|
|
|
|
this.status({});
|
2015-06-13 19:46:44 +02:00
|
|
|
|
2018-08-15 16:23:08 +02:00
|
|
|
if (this.twitterConfig.oauth) {
|
2018-06-07 12:25:25 +02:00
|
|
|
var node = this;
|
2018-08-15 16:23:08 +02:00
|
|
|
if (this.user === "true") {
|
|
|
|
// Poll User Home Timeline 1/min
|
|
|
|
this.poll(60000,"https://api.twitter.com/1.1/statuses/home_timeline.json");
|
|
|
|
} else if (this.user === "user") {
|
|
|
|
var users = node.tags.split(/\s*,\s*/).filter(v=>!!v);
|
|
|
|
if (users.length === 0) {
|
|
|
|
node.error(RED._("twitter.warn.nousers"));
|
|
|
|
return;
|
2015-06-13 19:46:44 +02:00
|
|
|
}
|
2018-08-15 16:23:08 +02:00
|
|
|
// Poll User timeline
|
|
|
|
users.forEach(function(user) {
|
|
|
|
node.poll(60000,"https://api.twitter.com/1.1/statuses/user_timeline.json",{screen_name: user});
|
|
|
|
})
|
|
|
|
} else if (this.user === "dm") {
|
|
|
|
node.pollDirectMessages();
|
|
|
|
} else if (this.user === "event") {
|
|
|
|
this.error("This Twitter node is configured to access a user's activity stream. Twitter removed this API in August 2018 and is no longer available.");
|
|
|
|
return;
|
|
|
|
} else if (this.user === "false") {
|
|
|
|
var twit = new Ntwitter({
|
|
|
|
consumer_key: credentials.consumer_key,
|
|
|
|
consumer_secret: credentials.consumer_secret,
|
|
|
|
access_token_key: credentials.access_token,
|
|
|
|
access_token_secret: credentials.access_token_secret
|
2015-06-13 19:46:44 +02:00
|
|
|
});
|
2018-08-15 16:23:08 +02:00
|
|
|
|
|
|
|
// Stream public tweets
|
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
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
2018-08-15 16:23:08 +02:00
|
|
|
if (this.timeout_ids) {
|
|
|
|
for (var i=0; i<this.timeout_ids.length; i++) {
|
|
|
|
clearTimeout(this.timeout_ids[i]);
|
|
|
|
}
|
|
|
|
}
|
2018-06-07 13:07:01 +02:00
|
|
|
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);
|
|
|
|
|
2018-08-15 16:23:08 +02:00
|
|
|
TwitterInNode.prototype.poll = function(interval, url, opts) {
|
|
|
|
var node = this;
|
|
|
|
var opts = opts || {};
|
|
|
|
var pollId;
|
|
|
|
opts.count = 1;
|
|
|
|
this.twitterConfig.get(url,opts).then(function(result) {
|
|
|
|
if (result.status === 429) {
|
|
|
|
node.warn("Rate limit hit. Waiting "+Math.floor(result.rateLimitTimeout/1000)+" seconds to try again");
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.poll(interval,url,opts);
|
|
|
|
},result.rateLimitTimeout))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
node.debug("Twitter Poll, rateLimitRemaining="+result.rateLimitRemaining+" rateLimitTimeout="+Math.floor(result.rateLimitTimeout/1000)+"s");
|
|
|
|
var res = result.body;
|
|
|
|
opts.count = 200;
|
|
|
|
var since = "0";
|
|
|
|
if (res.length > 0) {
|
|
|
|
since = res[0].id_str;
|
|
|
|
}
|
|
|
|
pollId = setInterval(function() {
|
|
|
|
opts.since_id = since;
|
|
|
|
node.twitterConfig.get(url,opts).then(function(result) {
|
|
|
|
if (result.status === 429) {
|
|
|
|
node.warn("Rate limit hit. Waiting "+Math.floor(result.rateLimitTimeout/1000)+" seconds to try again");
|
|
|
|
clearInterval(pollId);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.poll(interval,url,opts);
|
|
|
|
},result.rateLimitTimeout))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
node.debug("Twitter Poll, rateLimitRemaining="+result.rateLimitRemaining+" rateLimitTimeout="+Math.floor(result.rateLimitTimeout/1000)+"s");
|
|
|
|
var res = result.body;
|
|
|
|
if (res.errors) {
|
|
|
|
node.error(res.errors[0].message);
|
2018-08-17 10:18:53 +02:00
|
|
|
if (res.errors[0].code === 44) {
|
|
|
|
// 'since_id parameter is invalid' - reset it for next time
|
|
|
|
delete opts.since_id;
|
|
|
|
}
|
2018-08-16 15:45:54 +02:00
|
|
|
clearInterval(pollId);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.poll(interval,url,opts);
|
|
|
|
},interval))
|
2018-08-15 16:23:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (res.length > 0) {
|
|
|
|
since = res[0].id_str;
|
|
|
|
var len = res.length;
|
|
|
|
for (var i = len-1; i >= 0; i--) {
|
|
|
|
var tweet = res[i];
|
|
|
|
if (tweet.user !== undefined) {
|
|
|
|
var where = tweet.user.location;
|
|
|
|
var la = tweet.lang || tweet.user.lang;
|
|
|
|
tweet.text = tweet.text || tweet.full_text;
|
|
|
|
var msg = {
|
|
|
|
topic:"tweets/"+tweet.user.screen_name,
|
|
|
|
payload:tweet.text,
|
|
|
|
lang:la,
|
|
|
|
tweet:tweet
|
|
|
|
};
|
|
|
|
if (where) {
|
|
|
|
msg.location = {place:where};
|
|
|
|
addLocationToTweet(msg);
|
|
|
|
}
|
|
|
|
node.send(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).catch(function(err) {
|
|
|
|
node.error(err);
|
|
|
|
clearInterval(pollId);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
delete opts.since_id;
|
|
|
|
delete opts.count;
|
|
|
|
node.poll(interval,url,opts);
|
|
|
|
},interval))
|
|
|
|
})
|
|
|
|
},interval)
|
|
|
|
node.poll_ids.push(pollId);
|
|
|
|
}).catch(function(err) {
|
|
|
|
node.error(err);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
delete opts.since_id;
|
|
|
|
delete opts.count;
|
|
|
|
node.poll(interval,url,opts);
|
|
|
|
},interval))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
TwitterInNode.prototype.pollDirectMessages = function() {
|
|
|
|
var interval = 70000;
|
|
|
|
var node = this;
|
|
|
|
var opts = {};
|
|
|
|
var url = "https://api.twitter.com/1.1/direct_messages/events/list.json";
|
|
|
|
var pollId;
|
|
|
|
opts.count = 50;
|
|
|
|
this.twitterConfig.get(url,opts).then(function(result) {
|
|
|
|
if (result.status === 429) {
|
|
|
|
node.warn("Rate limit hit. Waiting "+Math.floor(result.rateLimitTimeout/1000)+" seconds to try again");
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.pollDirectMessages();
|
|
|
|
},result.rateLimitTimeout))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
node.debug("Twitter DM Poll, rateLimitRemaining="+result.rateLimitRemaining+" rateLimitTimeout="+Math.floor(result.rateLimitTimeout/1000)+"s");
|
|
|
|
var res = result.body;
|
|
|
|
if (res.errors) {
|
2018-08-16 15:45:54 +02:00
|
|
|
node.error(res.errors[0].message);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.pollDirectMessages();
|
|
|
|
},interval))
|
|
|
|
return;
|
2018-08-15 16:23:08 +02:00
|
|
|
}
|
|
|
|
var since = "0";
|
|
|
|
var messages = res.events.filter(tweet => tweet.type === 'message_create' && tweet.id > since);
|
|
|
|
if (messages.length > 0) {
|
|
|
|
since = messages[0].id;
|
|
|
|
}
|
|
|
|
pollId = setInterval(function() {
|
|
|
|
node.twitterConfig.get(url,opts).then(function(result) {
|
|
|
|
if (result.status === 429) {
|
|
|
|
node.warn("Rate limit hit. Waiting "+Math.floor(result.rateLimitTimeout/1000)+" seconds to try again");
|
|
|
|
clearInterval(pollId);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.pollDirectMessages();
|
|
|
|
},result.rateLimitTimeout))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
node.debug("Twitter DM Poll, rateLimitRemaining="+result.rateLimitRemaining+" rateLimitTimeout="+Math.floor(result.rateLimitTimeout/1000)+"s");
|
|
|
|
var res = result.body;
|
|
|
|
if (res.errors) {
|
|
|
|
node.error(res.errors[0].message);
|
2018-08-16 15:45:54 +02:00
|
|
|
clearInterval(pollId);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.pollDirectMessages();
|
|
|
|
},interval))
|
2018-08-15 16:23:08 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
var messages = res.events.filter(tweet => tweet.type === 'message_create' && tweet.id > since);
|
|
|
|
if (messages.length > 0) {
|
|
|
|
since = messages[0].id;
|
|
|
|
var len = messages.length;
|
|
|
|
var missingUsers = {};
|
|
|
|
var tweets = [];
|
|
|
|
for (var i = len-1; i >= 0; i--) {
|
|
|
|
var tweet = messages[i];
|
|
|
|
// node.log(JSON.stringify(tweet," ",4));
|
|
|
|
var output = {
|
|
|
|
id: tweet.id,
|
|
|
|
id_str: tweet.id,
|
|
|
|
text: tweet.message_create.message_data.text,
|
|
|
|
created_timestamp: tweet.created_timestamp,
|
|
|
|
entities: tweet.message_create.message_data.entities
|
|
|
|
}
|
|
|
|
if (!userObjectCache.hasOwnProperty(tweet.message_create.sender_id)) {
|
|
|
|
missingUsers[tweet.message_create.sender_id] = true;
|
|
|
|
}
|
|
|
|
if (!userObjectCache.hasOwnProperty(tweet.message_create.target.recipient_id)) {
|
|
|
|
missingUsers[tweet.message_create.target.recipient_id] = true;
|
|
|
|
}
|
|
|
|
tweets.push(output);
|
|
|
|
}
|
|
|
|
|
|
|
|
var missingUsernames = Object.keys(missingUsers).join(",");
|
|
|
|
return node.twitterConfig.getUsers(missingUsernames).then(function() {
|
|
|
|
var len = tweets.length;
|
2019-08-11 14:37:15 +02:00
|
|
|
for (var i = 0; i < len; i++) {
|
2018-08-15 16:23:08 +02:00
|
|
|
var tweet = messages[i];
|
|
|
|
var output = tweets[i];
|
|
|
|
output.sender = userObjectCache[tweet.message_create.sender_id];
|
|
|
|
output.sender_id = output.sender.id;
|
|
|
|
output.sender_id_str = output.sender.id_str;
|
|
|
|
output.sender_screen_name = output.sender.screen_name;
|
|
|
|
|
|
|
|
output.recipient = userObjectCache[tweet.message_create.target.recipient_id];
|
|
|
|
output.recipient_id = output.recipient.id;
|
|
|
|
output.recipient_id_str = output.recipient.id_str;
|
|
|
|
output.recipient_screen_name = output.recipient.screen_name;
|
|
|
|
|
|
|
|
if (output.sender_screen_name !== node.twitterConfig.screen_name) {
|
|
|
|
var msg = {
|
|
|
|
topic:"tweets/"+output.sender_screen_name,
|
|
|
|
payload:output.text,
|
|
|
|
tweet:output
|
|
|
|
};
|
|
|
|
node.send(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}).catch(function(err) {
|
|
|
|
node.error(err);
|
|
|
|
clearInterval(pollId);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.pollDirectMessages();
|
|
|
|
},interval))
|
|
|
|
})
|
|
|
|
},interval)
|
|
|
|
node.poll_ids.push(pollId);
|
|
|
|
}).catch(function(err) {
|
|
|
|
node.error(err);
|
|
|
|
node.timeout_ids.push(setTimeout(function() {
|
|
|
|
node.pollDirectMessages();
|
|
|
|
},interval))
|
|
|
|
})
|
|
|
|
}
|
2015-06-13 19:46:44 +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;
|
2018-08-15 16:23:08 +02:00
|
|
|
node.status({});
|
|
|
|
if (this.twitterConfig.oauth) {
|
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
|
|
|
|
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"});
|
2018-10-01 18:31:18 +02:00
|
|
|
if (msg.payload.slice(0,2).toLowerCase() === "d ") {
|
2018-08-15 16:23:08 +02:00
|
|
|
var dm_user;
|
|
|
|
// direct message syntax: "D user message"
|
|
|
|
var t = msg.payload.match(/D\s+(\S+)\s+(.*)/).slice(1);
|
|
|
|
dm_user = t[0];
|
|
|
|
msg.payload = t[1];
|
|
|
|
var lookupPromise;
|
|
|
|
if (userSreenNameToIdCache.hasOwnProperty(dm_user)) {
|
|
|
|
lookupPromise = Promise.resolve();
|
|
|
|
} else {
|
|
|
|
lookupPromise = node.twitterConfig.getUsers(dm_user,"screen_name")
|
|
|
|
}
|
|
|
|
lookupPromise.then(function() {
|
|
|
|
if (userSreenNameToIdCache.hasOwnProperty(dm_user)) {
|
|
|
|
// Send a direct message
|
|
|
|
node.twitterConfig.post("https://api.twitter.com/1.1/direct_messages/events/new.json",{
|
|
|
|
event: {
|
|
|
|
type: "message_create",
|
|
|
|
"message_create": {
|
|
|
|
"target": {
|
|
|
|
"recipient_id": userSreenNameToIdCache[dm_user]
|
|
|
|
},
|
|
|
|
"message_data": {"text": msg.payload}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).then(function() {
|
2015-06-13 19:46:44 +02:00
|
|
|
node.status({});
|
2018-08-15 16:23:08 +02:00
|
|
|
}).catch(function(err) {
|
2018-03-29 10:42:22 +02:00
|
|
|
node.error(err,msg);
|
2018-08-15 16:23:08 +02:00
|
|
|
node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
node.error("Unknown user",msg);
|
|
|
|
node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
}
|
|
|
|
}).catch(function(err) {
|
|
|
|
node.error(err,msg);
|
|
|
|
node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
if (msg.payload.length > 280) {
|
|
|
|
msg.payload = msg.payload.slice(0,279);
|
|
|
|
node.warn(RED._("twitter.errors.truncated"));
|
|
|
|
}
|
|
|
|
var mediaPromise;
|
|
|
|
if (msg.media && Buffer.isBuffer(msg.media)) {
|
|
|
|
// var mediaType = fileType(msg.media);
|
|
|
|
// if (mediaType === null) {
|
|
|
|
// node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
// node.error("msg.media is not a valid media object",msg);
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
mediaPromise = node.twitterConfig.post("https://upload.twitter.com/1.1/media/upload.json",null,null,{
|
|
|
|
media: msg.media
|
|
|
|
}).then(function(result) {
|
|
|
|
if (result.status === 200) {
|
|
|
|
return result.body.media_id_string;
|
|
|
|
} else {
|
|
|
|
throw new Error(result.body.errors[0]);
|
2018-03-29 10:42:22 +02:00
|
|
|
}
|
|
|
|
});
|
2018-08-15 16:23:08 +02:00
|
|
|
|
2018-03-29 10:42:22 +02:00
|
|
|
} else {
|
2018-08-15 16:23:08 +02:00
|
|
|
mediaPromise = Promise.resolve();
|
|
|
|
}
|
|
|
|
mediaPromise.then(function(mediaId) {
|
|
|
|
var params = msg.params || {};
|
|
|
|
params.status = msg.payload;
|
|
|
|
if (mediaId) {
|
|
|
|
params.media_ids = mediaId;
|
|
|
|
}
|
|
|
|
node.twitterConfig.post("https://api.twitter.com/1.1/statuses/update.json",{},params).then(function(result) {
|
|
|
|
if (result.status === 200) {
|
|
|
|
node.status({});
|
|
|
|
} else {
|
2018-03-29 10:42:22 +02:00
|
|
|
node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
2020-11-11 12:26:56 +01:00
|
|
|
|
|
|
|
if ('error' in result.body && typeof result.body.error === 'string') {
|
|
|
|
node.error(result.body.error,msg);
|
|
|
|
} else {
|
|
|
|
node.error(result.body.errors[0].message,msg);
|
|
|
|
}
|
2018-03-29 10:42:22 +02:00
|
|
|
}
|
2018-08-15 16:23:08 +02:00
|
|
|
}).catch(function(err) {
|
|
|
|
node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
node.error(err,msg);
|
|
|
|
})
|
|
|
|
}).catch(function(err) {
|
|
|
|
node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
node.error(err,msg);
|
|
|
|
});
|
|
|
|
// if (msg.payload.length > 280) {
|
|
|
|
// msg.payload = msg.payload.slice(0,279);
|
|
|
|
// node.warn(RED._("twitter.errors.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,msg);
|
|
|
|
// node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
// }
|
|
|
|
// else {
|
|
|
|
// var response = JSON.parse(body);
|
|
|
|
// if (response.errors) {
|
|
|
|
// var errorList = response.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
|
|
|
|
// node.error(RED._("twitter.errors.sendfail",{error:errorList}),msg);
|
|
|
|
// node.status({fill:"red",shape:"ring",text:"twitter.status.failed"});
|
|
|
|
// }
|
|
|
|
// else {
|
|
|
|
// node.status({});
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
// var form = r.form();
|
|
|
|
// form.append("status",msg.payload);
|
|
|
|
// form.append("media[]",msg.media,{filename:"image"});
|
|
|
|
//
|
|
|
|
// } else {
|
|
|
|
// if (typeof msg.params === 'undefined') { msg.params = {}; }
|
|
|
|
// 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
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
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);
|
|
|
|
}
|