2 * Copyright 2013 IBM Corp.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 module.exports = function(RED) {
19 var ntwitter = require('twitter-ng');
20 var OAuth= require('oauth').OAuth;
21 var request = require('request');
23 function TwitterNode(n) {
24 RED.nodes.createNode(this,n);
25 this.screen_name = n.screen_name;
27 RED.nodes.registerType("twitter-credentials",TwitterNode,{
29 screen_name: {type:"text"},
30 access_token: {type: "password"},
31 access_token_secret: {type:"password"}
35 function TwitterInNode(n) {
36 RED.nodes.createNode(this,n);
39 //this.tags = n.tags.replace(/ /g,'');
41 this.twitter = n.twitter;
42 this.topic = n.topic||"tweets";
43 this.twitterConfig = RED.nodes.getNode(this.twitter);
44 var credentials = RED.nodes.getCredentials(this.twitter);
46 if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
47 var twit = new ntwitter({
48 consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
49 consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
50 access_token_key: credentials.access_token,
51 access_token_secret: credentials.access_token_secret
55 //setInterval(function() {
56 // twit.get("/application/rate_limit_status.json",null,function(err,cb) {
57 // console.log("direct_messages:",cb["resources"]["direct_messages"]);
63 if (this.user === "user") {
66 var users = node.tags.split(",");
67 for (var i=0;i<users.length;i++) {
68 var user = users[i].replace(" ","");
69 twit.getUserTimeline({
75 return function(err,cb) {
81 node.since_ids[u] = cb[0].id_str;
83 node.since_ids[u] = '0';
85 node.poll_ids.push(setInterval(function() {
86 twit.getUserTimeline({
89 since_id:node.since_ids[u]
92 for (var t=cb.length-1;t>=0;t-=1) {
94 var where = tweet.user.location||"";
95 var la = tweet.lang || tweet.user.lang;
96 //console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
97 var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
100 node.since_ids[u] = tweet.id_str;
112 } else if (this.user === "dm") {
114 twit.getDirectMessages({
115 screen_name:node.twitterConfig.screen_name,
124 node.since_id = cb[0].id_str;
128 node.poll_ids.push(setInterval(function() {
129 twit.getDirectMessages({
130 screen_name:node.twitterConfig.screen_name,
132 since_id:node.since_id
135 for (var t=cb.length-1;t>=0;t-=1) {
137 var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, tweet:tweet };
140 node.since_id = tweet.id_str;
151 } else if (this.tags !== "") {
153 var thing = 'statuses/filter';
154 if (this.user === "true") { thing = 'user'; }
155 var st = { track: [node.tags] };
156 var bits = node.tags.split(",");
157 if ((bits.length > 0) && (bits.length % 4 == 0)) {
158 if ((Number(bits[0]) < Number(bits[2])) && (Number(bits[1]) < Number(bits[3]))) {
159 st = { locations: node.tags };
162 node.log("possible bad geo area format. Should be lower-left lon, lat, upper-right lon, lat");
166 var setupStream = function() {
168 twit.stream(thing, st, function(stream) {
170 //twit.stream('user', { track: [node.tags] }, function(stream) {
171 //twit.stream('site', { track: [node.tags] }, function(stream) {
172 //twit.stream('statuses/filter', { track: [node.tags] }, function(stream) {
173 node.stream = stream;
174 stream.on('data', function(tweet) {
175 //console.log(tweet.user);
176 if (tweet.user !== undefined) {
177 var where = tweet.user.location||"";
178 var la = tweet.lang || tweet.user.lang;
179 //console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay);
180 var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet };
184 stream.on('limit', function(tweet) {
185 node.log("tweet rate limit hit");
187 stream.on('error', function(tweet,rc) {
189 node.warn("Twitter rate limit hit");
191 node.warn("Stream error:"+tweet.toString()+" ("+rc+")");
193 setTimeout(setupStream,10000);
195 stream.on('destroy', function (response) {
197 node.warn("twitter ended unexpectedly");
198 setTimeout(setupStream,10000);
210 this.error("Invalid tag property");
213 this.error("missing twitter credentials");
216 this.on('close', function() {
219 this.stream.destroy();
222 for (var i=0;i<this.poll_ids.length;i++) {
223 clearInterval(this.poll_ids[i]);
228 RED.nodes.registerType("twitter in",TwitterInNode);
231 function TwitterOutNode(n) {
232 RED.nodes.createNode(this,n);
233 this.topic = n.topic;
234 this.twitter = n.twitter;
235 this.twitterConfig = RED.nodes.getNode(this.twitter);
236 var credentials = RED.nodes.getCredentials(this.twitter);
239 if (credentials && credentials.screen_name == this.twitterConfig.screen_name) {
240 var twit = new ntwitter({
241 consumer_key: "OKjYEd1ef2bfFolV25G5nQ",
242 consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
243 access_token_key: credentials.access_token,
244 access_token_secret: credentials.access_token_secret
246 node.on("input", function(msg) {
247 node.status({fill:"blue",shape:"dot",text:"tweeting"});
249 if (msg.payload.length > 140) {
250 msg.payload = msg.payload.slice(0,139);
251 node.warn("Tweet greater than 140 : truncated");
254 if (msg.media && Buffer.isBuffer(msg.media)) {
255 var apiUrl = "https://api.twitter.com/1.1/statuses/update_with_media.json";
256 var signedUrl = oa.signUrl(apiUrl,
257 credentials.access_token,
258 credentials.access_token_secret,
261 var r = request.post(signedUrl,function(err,httpResponse,body) {
263 node.error(err.toString());
264 node.status({fill:"red",shape:"ring",text:"failed"});
266 var response = JSON.parse(body);
268 var errorList = body.errors.map(function(er) { return er.code+": "+er.message }).join(", ");
269 node.error("tweet failed: "+errorList);
270 node.status({fill:"red",shape:"ring",text:"failed"});
277 form.append("status",msg.payload);
278 form.append("media[]",msg.media,{filename:"image"});
281 twit.updateStatus(msg.payload, function (err, data) {
283 node.status({fill:"red",shape:"ring",text:"failed"});
292 RED.nodes.registerType("twitter out",TwitterOutNode);
295 "https://api.twitter.com/oauth/request_token",
296 "https://api.twitter.com/oauth/access_token",
297 "OKjYEd1ef2bfFolV25G5nQ",
298 "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
304 RED.httpAdmin.get('/twitter-credentials/:id/auth', function(req, res){
305 var credentials = {};
306 oa.getOAuthRequestToken({
307 oauth_callback: req.query.callback
308 },function(error, oauth_token, oauth_token_secret, results){
310 var resp = '<h2>Oh no!</h2>'+
311 '<p>Something went wrong with the authentication process. The following error was returned:<p>'+
312 '<p><b>'+error.statusCode+'</b>: '+error.data+'</p>'+
313 '<p>One known cause of this type of failure is if the clock is wrong on system running Node-RED.';
316 credentials.oauth_token = oauth_token;
317 credentials.oauth_token_secret = oauth_token_secret;
318 res.redirect('https://twitter.com/oauth/authorize?oauth_token='+oauth_token)
319 RED.nodes.addCredentials(req.params.id,credentials);
324 RED.httpAdmin.get('/twitter-credentials/:id/auth/callback', function(req, res, next){
325 var credentials = RED.nodes.getCredentials(req.params.id);
326 credentials.oauth_verifier = req.query.oauth_verifier;
328 oa.getOAuthAccessToken(
329 credentials.oauth_token,
330 credentials.token_secret,
331 credentials.oauth_verifier,
332 function(error, oauth_access_token, oauth_access_token_secret, results){
335 res.send("yeah something broke.");
338 credentials.access_token = oauth_access_token;
339 credentials.access_token_secret = oauth_access_token_secret;
340 credentials.screen_name = "@"+results.screen_name;
341 RED.nodes.addCredentials(req.params.id,credentials);
342 res.send("<html><head></head><body>Authorised - you can close this window and return to Node-RED</body></html>");