[CCSDK-28] populated the seed code for dgbuilder
[ccsdk/distribution.git] / dgbuilder / core_nodes / social / 27-twitter.js
1 /**
2  * Copyright 2013 IBM Corp.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  **/
16
17 module.exports = function(RED) {
18     "use strict";
19     var ntwitter = require('twitter-ng');
20     var OAuth= require('oauth').OAuth;
21     var request = require('request');
22     
23     function TwitterNode(n) {
24         RED.nodes.createNode(this,n);
25         this.screen_name = n.screen_name;
26     }
27     RED.nodes.registerType("twitter-credentials",TwitterNode,{
28         credentials: {
29             screen_name: {type:"text"},
30             access_token: {type: "password"},
31             access_token_secret: {type:"password"}
32         }
33     });
34
35     function TwitterInNode(n) {
36         RED.nodes.createNode(this,n);
37         this.active = true;
38         this.user = n.user;
39         //this.tags = n.tags.replace(/ /g,'');
40         this.tags = n.tags;
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);
45
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
52             });
53
54
55             //setInterval(function() {
56             //        twit.get("/application/rate_limit_status.json",null,function(err,cb) {
57             //                console.log("direct_messages:",cb["resources"]["direct_messages"]);
58             //        });
59             //
60             //},10000);
61
62             var node = this;
63             if (this.user === "user") {
64                 node.poll_ids = [];
65                 node.since_ids = {};
66                 var users = node.tags.split(",");
67                 for (var i=0;i<users.length;i++) {
68                     var user = users[i].replace(" ","");
69                     twit.getUserTimeline({
70                             screen_name:user,
71                             trim_user:0,
72                             count:1
73                     },function() {
74                         var u = user+"";
75                         return function(err,cb) {
76                             if (err) {
77                                 node.error(err);
78                                 return;
79                             }
80                             if (cb[0]) {
81                                 node.since_ids[u] = cb[0].id_str;
82                             } else {
83                                 node.since_ids[u] = '0';
84                             }
85                             node.poll_ids.push(setInterval(function() {
86                                 twit.getUserTimeline({
87                                         screen_name:u,
88                                         trim_user:0,
89                                         since_id:node.since_ids[u]
90                                 },function(err,cb) {
91                                     if (cb) {
92                                         for (var t=cb.length-1;t>=0;t-=1) {
93                                             var tweet = cb[t];
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 };
98                                             node.send(msg);
99                                             if (t == 0) {
100                                                 node.since_ids[u] = tweet.id_str;
101                                             }
102                                         }
103                                     }
104                                     if (err) {
105                                         node.error(err);
106                                     }
107                                 });
108                             },60000));
109                         }
110                     }());
111                 }
112             } else if (this.user === "dm") {
113                 node.poll_ids = [];
114                 twit.getDirectMessages({
115                         screen_name:node.twitterConfig.screen_name,
116                         trim_user:0,
117                         count:1
118                 },function(err,cb) {
119                     if (err) {
120                         node.error(err);
121                         return;
122                     }
123                     if (cb[0]) {
124                         node.since_id = cb[0].id_str;
125                     } else {
126                         node.since_id = '0';
127                     }
128                     node.poll_ids.push(setInterval(function() {
129                             twit.getDirectMessages({
130                                     screen_name:node.twitterConfig.screen_name,
131                                     trim_user:0,
132                                     since_id:node.since_id
133                             },function(err,cb) {
134                                 if (cb) {
135                                     for (var t=cb.length-1;t>=0;t-=1) {
136                                         var tweet = cb[t];
137                                         var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, tweet:tweet };
138                                         node.send(msg);
139                                         if (t == 0) {
140                                             node.since_id = tweet.id_str;
141                                         }
142                                     }
143                                 }
144                                 if (err) {
145                                     node.error(err);
146                                 }
147                             });
148                     },120000));
149                 });
150
151             } else if (this.tags !== "") {
152                 try {
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 };
160                         }
161                         else {
162                             node.log("possible bad geo area format. Should be lower-left lon, lat, upper-right lon, lat");
163                         }
164                     }
165
166                     var setupStream = function() {
167                         if (node.active) {
168                             twit.stream(thing, st, function(stream) {
169                                 //console.log(st);
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 };
181                                         node.send(msg);
182                                     }
183                                 });
184                                 stream.on('limit', function(tweet) {
185                                     node.log("tweet rate limit hit");
186                                 });
187                                 stream.on('error', function(tweet,rc) {
188                                     if (rc == 420) {
189                                         node.warn("Twitter rate limit hit");
190                                     } else {
191                                         node.warn("Stream error:"+tweet.toString()+" ("+rc+")");
192                                     }
193                                     setTimeout(setupStream,10000);
194                                 });
195                                 stream.on('destroy', function (response) {
196                                     if (this.active) {
197                                         node.warn("twitter ended unexpectedly");
198                                         setTimeout(setupStream,10000);
199                                     }
200                                 });
201                             });
202                         }
203                     }
204                     setupStream();
205                 }
206                 catch (err) {
207                     node.error(err);
208                 }
209             } else {
210                 this.error("Invalid tag property");
211             }
212         } else {
213             this.error("missing twitter credentials");
214         }
215
216         this.on('close', function() {
217             if (this.stream) {
218                 this.active = false;
219                 this.stream.destroy();
220             }
221             if (this.poll_ids) {
222                 for (var i=0;i<this.poll_ids.length;i++) {
223                     clearInterval(this.poll_ids[i]);
224                 }
225             }
226         });
227     }
228     RED.nodes.registerType("twitter in",TwitterInNode);
229
230
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);
237         var node = this;
238
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
245             });
246             node.on("input", function(msg) {
247                 node.status({fill:"blue",shape:"dot",text:"tweeting"});
248
249                 if (msg.payload.length > 140) {
250                     msg.payload = msg.payload.slice(0,139);
251                     node.warn("Tweet greater than 140 : truncated");
252                 }
253
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,
259                         "POST");
260
261                     var r = request.post(signedUrl,function(err,httpResponse,body) {
262                         if (err) {
263                             node.error(err.toString());
264                             node.status({fill:"red",shape:"ring",text:"failed"});
265                         } else {
266                             var response = JSON.parse(body);
267                             if (body.errors) {
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"});
271                             } else {
272                                 node.status({});
273                             }
274                         }
275                     });
276                     var form = r.form();
277                     form.append("status",msg.payload);
278                     form.append("media[]",msg.media,{filename:"image"});
279
280                 } else {
281                     twit.updateStatus(msg.payload, function (err, data) {
282                         if (err) {
283                             node.status({fill:"red",shape:"ring",text:"failed"});
284                             node.error(err);
285                         }
286                         node.status({});
287                     });
288                 }
289             });
290         }
291     }
292     RED.nodes.registerType("twitter out",TwitterOutNode);
293
294     var oa = new OAuth(
295         "https://api.twitter.com/oauth/request_token",
296         "https://api.twitter.com/oauth/access_token",
297         "OKjYEd1ef2bfFolV25G5nQ",
298         "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g",
299         "1.0",
300         null,
301         "HMAC-SHA1"
302     );
303
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){
309             if (error) {
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.';
314                 res.send(resp)
315             } else {
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);
320             }
321         });
322     });
323
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;
327
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){
333                 if (error){
334                     console.log(error);
335                     res.send("yeah something broke.");
336                 } else {
337                     credentials = {};
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>");
343                 }
344             }
345         );
346     });
347 }