1
0
mirror of https://github.com/node-red/node-red-nodes.git synced 2023-10-10 13:36:58 +02:00
node-red-nodes/social/twitter/27-twitter.js
shahramdj 6aea983855
Update 27-twitter.js
Update the public search and user look up parts to work with twitter API V2.0
2022-10-07 10:29:26 -04:00

924 lines
43 KiB
JavaScript

//new updated code on Oct 3rd
module.exports = function (RED) {
"use strict";
var Ntwitter = require('twitter-ng');
var request = require('request');
var crypto = require('crypto');
// var fileType = require('file-type');
var twitterRateTimeout;
var retry = 60000; // 60 secs backoff for now
var localUserCache = {};
var userObjectCache = {};
var userSreenNameToIdCache = {};
RED.nodes.registerType("twitter-credentials", TwitterCredentialsNode, {
credentials: {
consumer_key: { type: "password" },
consumer_secret: { type: "password" },
access_token: { type: "password" },
access_token_secret: { type: "password" },
access_token_bearer: { type: "password" }
}
});
function TwitterCredentialsNode(n) {
RED.nodes.createNode(this, n);
this.screen_name = n.screen_name;
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.credentials.access_token_bearer) {
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,
token_bearer: this.credentials.access_token_bearer
}
this.credHash = crypto.createHash('sha1').update(
this.credentials.consumer_key + this.credentials.consumer_secret +
this.credentials.access_token + this.credentials.access_token_secret
+ this.credentials.access_token_bearer
).digest('base64');
var self = this;
const needle = require('needle');
var credentials = RED.nodes.getCredentials(self);
// The code below sets the bearer token from your environment variables
// To set environment variables on macOS or Linux, run the export command below from the terminal:
// export BEARER_TOKEN='YOUR-TOKEN'
const token = this.credentials.access_token_bearer;
const endpointUserURL = "https://api.twitter.com/2/users/by?usernames="
async function getRequest() {
// These are the parameters for the API request
// specify User names to fetch, and any additional fields that are required
// by default, only the User ID, name and user name are returned
const params = {
usernames: `${self.screen_name}`, // Edit usernames to look up
"user.fields": "created_at,description", // Edit optional query parameters here
"expansions": "pinned_tweet_id"
}
console.log(self.screen_name);
// this is the HTTP header that adds bearer token authentication
const res = await needle('get', endpointUserURL, params, {
headers: {
"authorization": `Bearer ${token}`
}
})
if (res.statusCode === 200) {
return res.body;
} else {
node.send("Failed to get user profile");
}
}
(async () => {
try {
// Make request
const response = await getRequest();
// console.dir(response, {
// depth: null
// });
} catch (e) {
console.log(e);
}
})();
}
}
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
});
}
});
})
}
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;
});
})
}
/**
* Populate msg.location based on data found in msg.tweet.
*/
function addLocationToTweet(msg) {
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) {
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";
}
}
else if (msg.tweet.coordinates) { // otherwise attempt go get it from coordinates
if (msg.tweet.coordinates.coordinates && msg.tweet.coordinates.coordinates.length === 2) {
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 = "tweets";
this.twitterConfig = RED.nodes.getNode(this.twitter);
this.poll_ids = [];
this.timeout_ids = [];
var credentials = RED.nodes.getCredentials(this.twitter);
this.status({});
if (this.twitterConfig.oauth) {
var node = this;
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;
}
// 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,
access_token_bearer: credentials.access_token_bearer
});
// Stream public tweets
const needle = require('needle');
// The code below sets the bearer token from your environment variables
// To set environment variables on macOS or Linux, run the export command below from the terminal:
// export BEARER_TOKEN='YOUR-TOKEN'
const token = credentials.access_token_bearer;
const rulesURL = 'https://api.twitter.com/2/tweets/search/stream/rules';
const streamURL = 'https://api.twitter.com/2/tweets/search/stream';
// this sets up two rules - the value is the search terms to match on, and the tag is an identifier that
// will be applied to the Tweets return to show which rule they matched
// with a standard project with Basic Access, you can add up to 25 concurrent rules to your stream, and
// each rule can be up to 512 characters long
// Edit rules as desired below
const rules = [{
'value': node.tags,
'tag': node.tags
}];
async function getAllRules() {
const response = await needle('get', rulesURL, {
headers: {
"authorization": `Bearer ${token}`
}
})
if (response.statusCode !== 200) {
node.send("Error:", response.statusMessage, response.statusCode)
throw new Error(response.body);
}
return (response.body);
}
async function deleteAllRules(rules) {
if (!Array.isArray(rules.data)) {
return null;
}
const ids = rules.data.map(rule => rule.id);
const data = {
"delete": {
"ids": ids
}
}
const response = await needle('post', rulesURL, data, {
headers: {
"content-type": "application/json",
"authorization": `Bearer ${token}`
}
})
if (response.statusCode !== 200) {
throw new Error(response.body);
}
return (response.body);
}
async function setRules() {
const data = {
"add": rules
}
const response = await needle('post', rulesURL, data, {
headers: {
"content-type": "application/json",
"authorization": `Bearer ${token}`
}
})
if (response.statusCode !== 201) {
throw new Error(response.body);
}
return (response.body);
}
function streamConnect(retryAttempt) {
var flag = false;
const stream = needle.get(streamURL, {
headers: {
"User-Agent": "v2FilterStreamJS",
"Authorization": `Bearer ${token}`
},
timeout: 20000
});
node.stream = stream;
stream.on('data', data => {
try {
const json = JSON.parse(data);
// console.log(json);
node.status({ fill: "green", shape: "dot", text: (tags || " ") });
node.send({ topic: "tweet", payload: json.data.text });
// A successful connection resets retry count.
retryAttempt = 0;
} catch (e) {
if (data.detail === "This stream is currently at the maximum allowed connection limit.") {
console.log(data.detail)
process.exit(1)
} else {
// Keep alive signal received. Do nothing.
}
}
}).on('err', error => {
if (error.code !== 'ECONNRESET') {
console.log(error.code);
process.exit(1);
} else {
// This reconnection logic will attempt to reconnect when a disconnection is detected.
// To avoid rate limits, this logic implements exponential backoff, so the wait time
// will increase if the client cannot reconnect to the stream.
setTimeout(() => {
console.warn("A connection error occurred. Reconnecting...")
streamConnect(++retryAttempt);
node.status({ fill: "red", shape: "ring", text: RED._("twitter.errors") });
}, 2 ** retryAttempt)
}
if (node.restart) {
node.tout = setTimeout(function () { setupStream() }, retry);
}
}).on('limit', limit => {
//node.status({fill:"grey", shape:"dot", text:RED._("twitter.errors.limitrate")});
node.status({ fill: "grey", shape: "dot", text: (tags || " ") });
}).on('destroy', function (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 () { setupStream() }, 15000);
}
});
return stream;
}
try {
var tags = node.tags;
var st = { track: [tags] };
var setupStream = function () {
if (node.restart) {
(async () => {
let currentRules;
console.warn = ({ topic: "warning", payload: node.restart })
try {
// Gets the complete list of rules currently applied to the stream
currentRules = await getAllRules();
// Delete all rules. Comment the line below if you want to keep your existing rules.
await deleteAllRules(currentRules);
// Add rules to the stream. Comment the line below if you don't want to add new rules.
await setRules();
} catch (e) {
console.error(e);
process.exit(1);
}
// Listen to the stream.
// node.status({fill:"green", shape:"dot", text:(node.tags||" ")});
console.log("Twitter API is steraming public tweets with search term " + node.tags || " ");
var flag = false;
const stream = needle.get(streamURL, {
headers: {
"User-Agent": "v2FilterStreamJS",
"Authorization": `Bearer ${token}`
},
timeout: 20000
});
node.stream = stream;
stream.on('data', data => {
try {
const json = JSON.parse(data);
// console.log(json);
node.status({ fill: "green", shape: "dot", text: (tags || " ") });
var msg = { topic: "tweet", payload: json.data.text };
node.send(msg);
// A successful connection resets retry count.
retryAttempt = 0;
} catch (e) {
if (data.detail === "This stream is currently at the maximum allowed connection limit.") {
console.log(data.detail)
// process.exit(1)
} else {
// Keep alive signal received. Do nothing.
}
}
}).on('err', error => {
if (error.code !== 'ECONNRESET') {
console.log(error.code);
// process.exit(1);
} else {
// This reconnection logic will attempt to reconnect when a disconnection is detected.
// To avoid rate limits, this logic implements exponential backoff, so the wait time
// will increase if the client cannot reconnect to the stream.
setTimeout(() => {
console.warn("A connection error occurred. Reconnecting...")
streamConnect(++retryAttempt);
node.status({ fill: "red", shape: "ring", text: RED._("twitter.errors") });
}, 2 ** retryAttempt)
}
if (node.restart) {
node.tout = setTimeout(function () { setupStream() }, retry);
}
}).on('limit', limit => {
//node.status({fill:"grey", shape:"dot", text:RED._("twitter.errors.limitrate")});
node.status({ fill: "grey", shape: "dot", text: (tags || " ") });
}).on('destroy', function (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 () { setupStream() }, 15000);
}
});
})();
}
}
// all public tweets
if (this.user === "false") {
node.on("input", function (msg) {
if (this.tags === '') {
if (node.tout) { clearTimeout(node.tout); }
if (node.tout2) { clearTimeout(node.tout2); }
if (this.stream) {
this.restart = false;
node.stream.removeAllListeners();
this.stream.request.abort();
}
if ((typeof msg.payload === "string") && (msg.payload !== "")) {
st = { track: [msg.payload] };
tags = msg.payload;
this.restart = true;
if ((twitterRateTimeout - Date.now()) > 0) {
node.status({ fill: "red", shape: "ring", text: tags });
node.tout = setTimeout(function () {
setupStream();
}, twitterRateTimeout - Date.now());
}
else {
setupStream();
}
}
else {
node.status({ fill: "yellow", shape: "ring", text: RED._("twitter.warn.waiting") });
}
}
});
}
// wait for input or start the stream
if ((this.user === "false") && (tags === '')) {
node.status({ fill: "yellow", shape: "ring", text: RED._("twitter.warn.waiting") });
}
else {
this.restart = true;
setupStream();
}
}
catch (err) {
node.error(err);
}
}
this.on('close', function () {
if (this.tout) { clearTimeout(this.tout); }
if (this.tout2) { clearTimeout(this.tout2); }
if (this.stream) {
this.restart = false;
node.stream.removeAllListeners();
this.stream.request.abort();
}
if (this.timeout_ids) {
for (var i = 0; i < this.timeout_ids.length; i++) {
clearTimeout(this.timeout_ids[i]);
}
}
if (this.poll_ids) {
for (var i = 0; i < this.poll_ids.length; i++) {
clearInterval(this.poll_ids[i]);
}
}
});
} else {
this.error(RED._("twitter.errors.missingcredentials"));
}
}
RED.nodes.registerType("twitter in", TwitterInNode);
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);
if (res.errors[0].code === 44) {
// 'since_id parameter is invalid' - reset it for next time
delete opts.since_id;
}
clearInterval(pollId);
node.timeout_ids.push(setTimeout(function () {
node.poll(interval, url, opts);
}, interval))
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) {
node.error(res.errors[0].message);
node.timeout_ids.push(setTimeout(function () {
node.pollDirectMessages();
}, interval))
return;
}
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);
clearInterval(pollId);
node.timeout_ids.push(setTimeout(function () {
node.pollDirectMessages();
}, interval))
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;
for (var i = 0; i < len; i++) {
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))
})
}
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;
node.status({});
if (this.twitterConfig.oauth) {
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
});
node.on("input", function (msg) {
if (msg.hasOwnProperty("payload")) {
node.status({ fill: "blue", shape: "dot", text: "twitter.status.tweeting" });
if (msg.payload.slice(0, 2).toLowerCase() === "d ") {
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 () {
node.status({});
}).catch(function (err) {
node.error(err, msg);
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]);
}
});
} else {
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 {
node.status({ fill: "red", shape: "ring", text: "twitter.status.failed" });
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);
}
}
}).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({});
// });
// }
}
}
});
} else {
this.error(RED._("twitter.errors.missingcredentials"));
}
}
RED.nodes.registerType("twitter out", TwitterOutNode);
}