diff options
Diffstat (limited to 'dgbuilder/core_nodes/social')
-rw-r--r-- | dgbuilder/core_nodes/social/27-twitter.html | 223 | ||||
-rw-r--r-- | dgbuilder/core_nodes/social/27-twitter.js | 347 | ||||
-rw-r--r-- | dgbuilder/core_nodes/social/32-feedparse.html | 57 | ||||
-rw-r--r-- | dgbuilder/core_nodes/social/32-feedparse.js | 71 | ||||
-rw-r--r-- | dgbuilder/core_nodes/social/61-email.html | 189 | ||||
-rw-r--r-- | dgbuilder/core_nodes/social/61-email.js | 246 | ||||
-rw-r--r-- | dgbuilder/core_nodes/social/91-irc.html | 206 | ||||
-rw-r--r-- | dgbuilder/core_nodes/social/91-irc.js | 237 |
8 files changed, 1576 insertions, 0 deletions
diff --git a/dgbuilder/core_nodes/social/27-twitter.html b/dgbuilder/core_nodes/social/27-twitter.html new file mode 100644 index 00000000..99d45213 --- /dev/null +++ b/dgbuilder/core_nodes/social/27-twitter.html @@ -0,0 +1,223 @@ +<!-- + Copyright 2013 IBM Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<script type="text/x-red" data-template-name="twitter-credentials"> + <div class="form-row" id="node-config-twitter-row"></div> + <input type="hidden" id="node-config-input-screen_name"> +</script> + +<script type="text/javascript"> +(function() { + var twitterConfigNodeId = null; + var twitterConfigNodeIntervalId = null; + + function showTwitterAuthStart() { + var pathname = document.location.pathname; + if (pathname.slice(-1) != "/") { + pathname += "/"; + } + var callback = encodeURIComponent(location.protocol+"//"+location.hostname+":"+location.port+pathname+"twitter-credentials/"+twitterConfigNodeId+"/auth/callback"); + $("#node-config-dialog-ok").button("disable"); + $("#node-config-twitter-row").html('<div style="text-align: center; margin-top: 20px; "><a class="btn" id="node-config-twitter-start" href="twitter-credentials/'+twitterConfigNodeId+'/auth?callback='+callback+'" target="_blank">Click here to authenticate with Twitter.</a></div>'); + $("#node-config-twitter-start").click(function() { + twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000); + }); + } + function updateTwitterScreenName(sn) { + $("#node-config-input-screen_name").val(sn); + $("#node-config-twitter-row").html('<label><i class="fa fa-user"></i> Twitter ID</label><span class="input-xlarge uneditable-input">'+sn+'</span>'); + } + function pollTwitterCredentials(e) { + $.getJSON('credentials/twitter-credentials/'+twitterConfigNodeId,function(data) { + if (data.screen_name) { + updateTwitterScreenName(data.screen_name); + twitterConfigNodeIntervalId = null; + $("#node-config-dialog-ok").button("enable"); + } else { + twitterConfigNodeIntervalId = window.setTimeout(pollTwitterCredentials,2000); + } + }) + } + RED.nodes.registerType('twitter-credentials',{ + category: 'config', + defaults: { + screen_name: {value:""} + }, + credentials: { + screen_name: {type:"text"}, + access_token: {type: "password"}, + access_token_secret: {type:"password"} + }, + + label: function() { + return this.screen_name; + }, + exportable: false, + oneditprepare: function() { + twitterConfigNodeId = this.id; + if (!this.screen_name || this.screen_name == "") { + showTwitterAuthStart(); + } else { + if (this.credentials.screen_name) { + updateTwitterScreenName(this.credentials.screen_name); + } else { + showTwitterAuthStart(); + } + } + }, + oneditsave: function() { + if (twitterConfigNodeIntervalId) { + window.clearTimeout(twitterConfigNodeIntervalId); + } + }, + oneditcancel: function(adding) { + if (twitterConfigNodeIntervalId) { + window.clearTimeout(twitterConfigNodeIntervalId); + } + } + }); +})(); +</script> + +<script type="text/x-red" data-template-name="twitter in"> + <div class="form-row"> + <label for="node-input-twitter"><i class="fa fa-user"></i> Log in as</label> + <input type="text" id="node-input-twitter"> + </div> + <div class="form-row"> + <label for="node-input-user"><i class="fa fa-search"></i> Search</label> + <select type="text" id="node-input-user" style="display: inline-block; vertical-align: middle; width:60%;"> + <option value="false">all public tweets</option> + <option value="true">the tweets of who you follow</option> + <option value="user">the tweets of specific users</option> + <option value="dm">your direct messages</option> + </select> + </div> + <div class="form-row" id="node-input-tags-row"> + <label for="node-input-tags"><i class="fa fa-tags"></i> <span id="node-input-tags-label">for</span></label> + <input type="text" id="node-input-tags" placeholder="comma-separated words, @ids, #tags"> + </div> + <div class="form-row"> + <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> + <input type="text" id="node-input-name" placeholder="Name"> + </div> + <div class="form-tips">Tip: Use commas without spaces between multiple search terms. Comma = OR, Space = AND. + <br/>The Twitter API WILL NOT deliver 100% of all tweets. + <br/>Tweets of who you follow will include their retweets and favourites.</div> +</script> + +<script type="text/x-red" data-help-name="twitter in"> + <p>Twitter input node. Can be used to search either: + <ul><li>the public or a user's stream for tweets containing the configured search term</li> + <li>all tweets by specific users</li> + <li>direct messages received by the authenticated user</li> + </ul></p> + <p>Use space for <i>and</i> and comma , for <i>or</i> when searching for multiple terms.</p> + <p>Sets the <b>msg.topic</b> to <i>tweets/</i> and then appends the senders screen name.</p> + <p>Sets <b>msg.location</b> to the tweeters location if known.</p> + <p>Sets <b>msg.tweet</b> to the full tweet object as documented by <a href="https://dev.twitter.com/docs/platform-objects/tweets">Twitter</a>. + <p><b>Note:</b> when set to a specific user's tweets, or your direct messages, the node is subject to + Twitter's API rate limiting. If you deploy the flows multiple times within a 15 minute window, you may + exceed the limit and will see errors from the node. These errors will clear when the current 15 minute window + passes.</p> +</script> + +<script type="text/javascript"> + RED.nodes.registerType('twitter in',{ + category: 'social-input', + color:"#C0DEED", + defaults: { + twitter: {type:"twitter-credentials",required:true}, + tags: {value:"",validate:function(v) { return this.user == "dm" || v.length > 0;}}, + user: {value:"false",required:true}, + name: {value:""}, + topic: {value:"tweets"} + }, + inputs:0, + outputs:1, + icon: "twitter.png", + label: function() { + if (this.name) { + return this.name; + } + if (this.user == "dm") { + var user = RED.nodes.node(this.twitter); + return (user?user.label()+" ":"")+"DMs"; + } else if (this.user == "user") { + return this.tags+" tweets"; + } + return this.tags; + }, + labelStyle: function() { + return this.name?"node_label_italic":""; + }, + oneditprepare: function() { + $("#node-input-user").change(function() { + var type = $("#node-input-user option:selected").val(); + if (type == "user") { + $("#node-input-tags-row").show(); + $("#node-input-tags-label").html("User"); + $("#node-input-tags").attr("placeholder","comma-separated @twitter handles"); + } else if (type == "dm") { + $("#node-input-tags-row").hide(); + } else { + $("#node-input-tags-row").show(); + $("#node-input-tags-label").html("for"); + $("#node-input-tags").attr("placeholder","comma-separated words, @ids, #hashtags"); + } + + }); + $("#node-input-user").change(); + + } + }); +</script> + + +<script type="text/x-red" data-template-name="twitter out"> + <div class="form-row"> + <label for="node-input-twitter"><i class="fa fa-user"></i> Twitter</label> + <input type="text" id="node-input-twitter"> + </div> + <div class="form-row"> + <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> + <input type="text" id="node-input-name" placeholder="Name"> + </div> +</script> + +<script type="text/x-red" data-help-name="twitter out"> + <p>Twitter out node. Tweets the <b>msg.payload</b>.</p> + <p>If <b>msg.media</b> exists and is a Buffer object, this node will treat it + as an image and attach it to the tweet.</p> +</script> + +<script type="text/javascript"> + RED.nodes.registerType('twitter out',{ + category: 'social-output', + color:"#C0DEED", + defaults: { + twitter: {type:"twitter-credentials",required:true}, + name: {value:"Tweet"} + }, + inputs:1, + outputs:0, + icon: "twitter.png", + align: "right", + label: function() { + return this.name; + } + }); +</script> diff --git a/dgbuilder/core_nodes/social/27-twitter.js b/dgbuilder/core_nodes/social/27-twitter.js new file mode 100644 index 00000000..5cacd9eb --- /dev/null +++ b/dgbuilder/core_nodes/social/27-twitter.js @@ -0,0 +1,347 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + "use strict"; + var ntwitter = require('twitter-ng'); + var OAuth= require('oauth').OAuth; + var request = require('request'); + + function TwitterNode(n) { + RED.nodes.createNode(this,n); + this.screen_name = n.screen_name; + } + RED.nodes.registerType("twitter-credentials",TwitterNode,{ + credentials: { + screen_name: {type:"text"}, + access_token: {type: "password"}, + access_token_secret: {type:"password"} + } + }); + + 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); + + if (credentials && credentials.screen_name == this.twitterConfig.screen_name) { + var twit = new ntwitter({ + consumer_key: "OKjYEd1ef2bfFolV25G5nQ", + consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g", + 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); + + var node = this; + if (this.user === "user") { + node.poll_ids = []; + node.since_ids = {}; + var users = node.tags.split(","); + 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; + } else { + 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; + //console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay); + var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet }; + node.send(msg); + if (t == 0) { + node.since_ids[u] = tweet.id_str; + } + } + } + if (err) { + node.error(err); + } + }); + },60000)); + } + }()); + } + } else if (this.user === "dm") { + node.poll_ids = []; + twit.getDirectMessages({ + screen_name:node.twitterConfig.screen_name, + trim_user:0, + count:1 + },function(err,cb) { + if (err) { + node.error(err); + return; + } + if (cb[0]) { + node.since_id = cb[0].id_str; + } else { + node.since_id = '0'; + } + node.poll_ids.push(setInterval(function() { + twit.getDirectMessages({ + screen_name:node.twitterConfig.screen_name, + trim_user:0, + since_id:node.since_id + },function(err,cb) { + if (cb) { + for (var t=cb.length-1;t>=0;t-=1) { + var tweet = cb[t]; + var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, tweet:tweet }; + node.send(msg); + if (t == 0) { + node.since_id = tweet.id_str; + } + } + } + if (err) { + node.error(err); + } + }); + },120000)); + }); + + } else if (this.tags !== "") { + try { + var thing = 'statuses/filter'; + if (this.user === "true") { thing = 'user'; } + var st = { track: [node.tags] }; + var bits = node.tags.split(","); + if ((bits.length > 0) && (bits.length % 4 == 0)) { + if ((Number(bits[0]) < Number(bits[2])) && (Number(bits[1]) < Number(bits[3]))) { + st = { locations: node.tags }; + } + else { + node.log("possible bad geo area format. Should be lower-left lon, lat, upper-right lon, lat"); + } + } + + var setupStream = function() { + if (node.active) { + twit.stream(thing, st, function(stream) { + //console.log(st); + //twit.stream('user', { track: [node.tags] }, function(stream) { + //twit.stream('site', { track: [node.tags] }, function(stream) { + //twit.stream('statuses/filter', { track: [node.tags] }, function(stream) { + node.stream = stream; + stream.on('data', function(tweet) { + //console.log(tweet.user); + if (tweet.user !== undefined) { + var where = tweet.user.location||""; + var la = tweet.lang || tweet.user.lang; + //console.log(tweet.user.location,"=>",tweet.user.screen_name,"=>",pay); + var msg = { topic:node.topic+"/"+tweet.user.screen_name, payload:tweet.text, location:where, lang:la, tweet:tweet }; + node.send(msg); + } + }); + stream.on('limit', function(tweet) { + node.log("tweet rate limit hit"); + }); + stream.on('error', function(tweet,rc) { + if (rc == 420) { + node.warn("Twitter rate limit hit"); + } else { + node.warn("Stream error:"+tweet.toString()+" ("+rc+")"); + } + setTimeout(setupStream,10000); + }); + stream.on('destroy', function (response) { + if (this.active) { + node.warn("twitter ended unexpectedly"); + setTimeout(setupStream,10000); + } + }); + }); + } + } + setupStream(); + } + catch (err) { + node.error(err); + } + } else { + this.error("Invalid tag property"); + } + } else { + this.error("missing twitter credentials"); + } + + this.on('close', function() { + if (this.stream) { + this.active = false; + this.stream.destroy(); + } + if (this.poll_ids) { + for (var i=0;i<this.poll_ids.length;i++) { + clearInterval(this.poll_ids[i]); + } + } + }); + } + 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; + + if (credentials && credentials.screen_name == this.twitterConfig.screen_name) { + var twit = new ntwitter({ + consumer_key: "OKjYEd1ef2bfFolV25G5nQ", + consumer_secret: "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g", + access_token_key: credentials.access_token, + access_token_secret: credentials.access_token_secret + }); + node.on("input", function(msg) { + node.status({fill:"blue",shape:"dot",text:"tweeting"}); + + if (msg.payload.length > 140) { + msg.payload = msg.payload.slice(0,139); + node.warn("Tweet greater than 140 : 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.toString()); + node.status({fill:"red",shape:"ring",text:"failed"}); + } else { + var response = JSON.parse(body); + if (body.errors) { + var errorList = body.errors.map(function(er) { return er.code+": "+er.message }).join(", "); + node.error("tweet failed: "+errorList); + node.status({fill:"red",shape:"ring",text:"failed"}); + } else { + node.status({}); + } + } + }); + var form = r.form(); + form.append("status",msg.payload); + form.append("media[]",msg.media,{filename:"image"}); + + } else { + twit.updateStatus(msg.payload, function (err, data) { + if (err) { + node.status({fill:"red",shape:"ring",text:"failed"}); + node.error(err); + } + node.status({}); + }); + } + }); + } + } + RED.nodes.registerType("twitter out",TwitterOutNode); + + var oa = new OAuth( + "https://api.twitter.com/oauth/request_token", + "https://api.twitter.com/oauth/access_token", + "OKjYEd1ef2bfFolV25G5nQ", + "meRsltCktVMUI8gmggpXett7WBLd1k0qidYazoML6g", + "1.0", + null, + "HMAC-SHA1" + ); + + RED.httpAdmin.get('/twitter-credentials/:id/auth', function(req, res){ + var credentials = {}; + oa.getOAuthRequestToken({ + oauth_callback: req.query.callback + },function(error, oauth_token, oauth_token_secret, results){ + if (error) { + var resp = '<h2>Oh no!</h2>'+ + '<p>Something went wrong with the authentication process. The following error was returned:<p>'+ + '<p><b>'+error.statusCode+'</b>: '+error.data+'</p>'+ + '<p>One known cause of this type of failure is if the clock is wrong on system running Node-RED.'; + res.send(resp) + } else { + credentials.oauth_token = oauth_token; + credentials.oauth_token_secret = oauth_token_secret; + res.redirect('https://twitter.com/oauth/authorize?oauth_token='+oauth_token) + RED.nodes.addCredentials(req.params.id,credentials); + } + }); + }); + + RED.httpAdmin.get('/twitter-credentials/:id/auth/callback', function(req, res, next){ + var credentials = RED.nodes.getCredentials(req.params.id); + credentials.oauth_verifier = req.query.oauth_verifier; + + oa.getOAuthAccessToken( + credentials.oauth_token, + credentials.token_secret, + credentials.oauth_verifier, + function(error, oauth_access_token, oauth_access_token_secret, results){ + if (error){ + console.log(error); + res.send("yeah something broke."); + } else { + credentials = {}; + credentials.access_token = oauth_access_token; + credentials.access_token_secret = oauth_access_token_secret; + credentials.screen_name = "@"+results.screen_name; + RED.nodes.addCredentials(req.params.id,credentials); + res.send("<html><head></head><body>Authorised - you can close this window and return to Node-RED</body></html>"); + } + } + ); + }); +} diff --git a/dgbuilder/core_nodes/social/32-feedparse.html b/dgbuilder/core_nodes/social/32-feedparse.html new file mode 100644 index 00000000..a7954277 --- /dev/null +++ b/dgbuilder/core_nodes/social/32-feedparse.html @@ -0,0 +1,57 @@ +<!-- + Copyright 2013 IBM Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<script type="text/x-red" data-template-name="feedparse"> + <div class="form-row"> + <label for="node-input-url"><i class="fa fa-globe"></i> Feed url</label> + <input type="text" id="node-input-url"> + </div> + <div class="form-row"> + <label for="node-input-interval"><i class="fa fa-repeat"></i> Repeat <span style="font-size: 0.9em;">(M)</span></label> + <input type="text" id="node-input-interval" placeholder="minutes"> + </div> + <div class="form-row"> + <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> + <input type="text" id="node-input-name" placeholder="Name"> + </div> + <!-- <div class="form-tips"></div> --> +</script> + +<script type="text/x-red" data-help-name="feedparse"> + <p>Monitors an RSS/atom feed for new entries.</p> +</script> + +<script type="text/javascript"> + RED.nodes.registerType('feedparse',{ + category: 'advanced-input', + color:"#C0DEED", + defaults: { + name: {value:""}, + url: {value:"", required:true}, + interval: { value:15, required: true,validate:RED.validators.number()} + }, + inputs:0, + outputs:1, + icon: "feed.png", + label: function() { + return this.name||this.url; + }, + labelStyle: function() { + return this.name?"node_label_italic":""; + } + }); + +</script> diff --git a/dgbuilder/core_nodes/social/32-feedparse.js b/dgbuilder/core_nodes/social/32-feedparse.js new file mode 100644 index 00000000..97630e7d --- /dev/null +++ b/dgbuilder/core_nodes/social/32-feedparse.js @@ -0,0 +1,71 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + "use strict"; + var FeedParser = require("feedparser"); + var request = require("request"); + + function FeedParseNode(n) { + RED.nodes.createNode(this,n); + this.url = n.url; + this.interval = (parseInt(n.interval)||15)*60000; + var node = this; + this.interval_id = null; + this.seen = {}; + if (this.url !== "") { + var getFeed = function() { + request(node.url,function(err) { + if (err) node.error(err); + }) + .pipe(new FeedParser({feedurl:node.url})) + .on('error', function(error) { + node.error(error); + }) + .on('meta', function (meta) {}) + .on('readable', function () { + var stream = this, article; + while (article = stream.read()) { + if (!(article.guid in node.seen) || ( node.seen[article.guid] != 0 && node.seen[article.guid] != article.date.getTime())) { + node.seen[article.guid] = article.date?article.date.getTime():0; + var msg = { + topic:article.origlink||article.link, + payload: article.description, + article: article + }; + node.send(msg); + } + } + }) + .on('end', function () { + }); + }; + this.interval_id = setInterval(getFeed,node.interval); + getFeed(); + + } else { + this.error("Invalid url"); + } + } + + RED.nodes.registerType("feedparse",FeedParseNode); + + FeedParseNode.prototype.close = function() { + if (this.interval_id != null) { + clearInterval(this.interval_id); + } + } +} diff --git a/dgbuilder/core_nodes/social/61-email.html b/dgbuilder/core_nodes/social/61-email.html new file mode 100644 index 00000000..37397083 --- /dev/null +++ b/dgbuilder/core_nodes/social/61-email.html @@ -0,0 +1,189 @@ +<!-- + Copyright 2013,2014 IBM Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<script type="text/x-red" data-template-name="e-mail"> + <div class="form-row"> + <label for="node-input-name"><i class="fa fa-envelope"></i> To</label> + <input type="text" id="node-input-name" placeholder="email@address.com"> + </div> + <!-- <div class="form-row"> + <label for="node-input-pin"><i class="fa fa-asterisk"></i> Service</label> + <select type="text" id="node-input-pin" style="width: 150px;"> + <option value="-" disabled> </option> + <option value="DynectEmail">DynectEmail</option> + <option value="Gmail">Gmail</option> + <option value="hot.ee">hot.ee</option> + <option value="Hotmail">Hotmail</option> + <option value="iCloud">iCloud</option> + <option value="mail.ee">mail.ee</option> + <option value="Mail.Ru">Mail.Ru</option> + <option value="Mailgun">Mailgun</option> + <option value="Mailjet">Mailjet</option> + <option value="Mandrill">Mandrill</option> + <option value="Postmark">Postmark</option> + <option value="QQ">QQ</option> + <option value="QQex">QQex</option> + <option value="SendGrid">SendGrid</option> + <option value="SendCloud">SendCloud</option> + <option value="SES">SES</option> + <option value="Yahoo">Yahoo</option> + <option value="yandex">yandex</option> + <option value="Zoho">Zoho</option> + </select> + </div> --> + <div class="form-row"> + <label for="node-input-server"><i class="fa fa-globe"></i> Server</label> + <input type="text" id="node-input-server" placeholder="smtp.gmail.com"> + </div> + <div class="form-row"> + <label for="node-input-port"><i class="fa fa-random"></i> Port</label> + <input type="text" id="node-input-port" placeholder="465"> + </div> + <div class="form-row"> + <label for="node-input-userid"><i class="fa fa-user"></i> Userid</label> + <input type="text" id="node-input-userid"> + </div> + <div class="form-row"> + <label for="node-input-password"><i class="fa fa-lock"></i> Password</label> + <input type="password" id="node-input-password"> + </div> + <br/> + <div class="form-row"> + <label for="node-input-dname"><i class="fa fa-tag"></i> Name</label> + <input type="text" id="node-input-dname" placeholder="Name"> + </div> + <div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div> +</script> + +<script type="text/x-red" data-help-name="e-mail"> + <p>Sends the <b>msg.payload</b> as an email, with a subject of <b>msg.topic</b>.</p> + <!-- <p>It sends the message to the configured recipient <i>only</i>.</p> --> + <p>You may dynamically overide the default recipient by setting a <b>msg.to</b> property.</p> + <!-- <p><b>msg.topic</b> is used to set the subject of the email, and <b>msg.payload</b> is the body text.</p> --> +</script> + +<script type="text/javascript"> +(function() { + RED.nodes.registerType('e-mail',{ + category: 'social-output', + color:"#c7e9c0", + defaults: { + server: {value:"smtp.gmail.com",required:true}, + port: {value:"465",required:true}, + name: {value:"",required:true}, + dname: {value:""} + }, + credentials: { + userid: {type:"text"}, + password: {type: "password"}, + global: { type:"boolean"} + }, + + inputs:1, + outputs:0, + icon: "envelope.png", + align: "right", + label: function() { + return this.dname||this.name||"email"; + }, + labelStyle: function() { + return (this.dname||!this.topic)?"node_label_italic":""; + }, + oneditprepare: function() { + if (this.credentials.global) { + $('#node-tip').show(); + } else { + $('#node-tip').hide(); + }; + } + }); +})(); +</script> + + +<script type="text/x-red" data-template-name="e-mail in"> + <div class="form-row node-input-repeat"> + <label for="node-input-repeat"><i class="fa fa-repeat"></i> Check Repeat (S)</label> + <input type="text" id="node-input-repeat" placeholder="300"> + </div> + <div class="form-row"> + <label for="node-input-server"><i class="fa fa-globe"></i> Server</label> + <input type="text" id="node-input-server" placeholder="imap.gmail.com"> + </div> + <div class="form-row"> + <label for="node-input-port"><i class="fa fa-random"></i> Port</label> + <input type="text" id="node-input-port" placeholder="993"> + </div> + <div class="form-row"> + <label for="node-input-userid"><i class="fa fa-user"></i> Userid</label> + <input type="text" id="node-input-userid"> + </div> + <div class="form-row"> + <label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label> + <input type="password" id="node-input-password"> + </div> + <br/> + <div class="form-row"> + <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> + <input type="text" id="node-input-name" placeholder="Name"> + </div> + <div class="form-tips" id="node-tip"><b>Note:</b> Copied credentials from global emailkeys.js file.</div> + <div id="node-input-tip" class="form-tips">Tip: <b>ONLY</b> retrieves the single most recent email.</div> +</script> + +<script type="text/x-red" data-help-name="e-mail in"> + <p>Repeatedly gets a <b>single email</b> from an IMAP server and forwards on as a msg if not already seen.</p> + <p>The subject is loaded into <b>msg.topic</b> and <b>msg.payload</b> is the plain text body. + If there is text/html then that is returned in <b>msg.html</b>. <b>msg.from</b> and <b>msg.date</b> are also set if you need them.</p> + <p>Uses the imap module.</p> + <p><b>Note:</b> this node <i>only</i> gets the most recent single email from the inbox, so set the repeat (polling) time appropriately.</p> +</script> + +<script type="text/javascript"> +(function() { + RED.nodes.registerType('e-mail in',{ + category: 'social-input', + color:"#c7e9c0", + defaults: { + repeat: {value:"300",required:true}, + server: {value:"imap.gmail.com",required:true}, + port: {value:"993",required:true}, + name: {value:""} + }, + credentials: { + userid: {type:"text"}, + password: {type: "password"}, + global: { type:"boolean"} + }, + inputs:0, + outputs:1, + icon: "envelope.png", + label: function() { + return this.name||"email"; + }, + labelStyle: function() { + return (this.name||!this.topic)?"node_label_italic":""; + }, + oneditprepare: function() { + if (this.credentials.global) { + $('#node-tip').show(); + } else { + $('#node-tip').hide(); + }; + } + }); +})(); +</script> diff --git a/dgbuilder/core_nodes/social/61-email.js b/dgbuilder/core_nodes/social/61-email.js new file mode 100644 index 00000000..7d0f8cb9 --- /dev/null +++ b/dgbuilder/core_nodes/social/61-email.js @@ -0,0 +1,246 @@ +/** + * Copyright 2013,2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + "use strict"; + var nodemailer = require("nodemailer"); + var Imap = require('imap'); + + //console.log(nodemailer.Transport.transports.SMTP.wellKnownHosts); + + try { + var globalkeys = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js"); + } catch(err) { + } + + function EmailNode(n) { + RED.nodes.createNode(this,n); + this.topic = n.topic; + this.name = n.name; + this.outserver = n.server; + this.outport = n.port; + var flag = false; + if (this.credentials && this.credentials.hasOwnProperty("userid")) { + this.userid = this.credentials.userid; + } else { + if (globalkeys) { + this.userid = globalkeys.user; + flag = true; + } else { + this.error("No e-mail userid set"); + } + } + if (this.credentials && this.credentials.hasOwnProperty("password")) { + this.password = this.credentials.password; + } else { + if (globalkeys) { + this.password = globalkeys.pass; + flag = true; + } else { + this.error("No e-mail password set"); + } + } + if (flag) { + RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true}); + } + var node = this; + + var smtpTransport = nodemailer.createTransport({ + host: node.outserver, + port: node.outport, + secure: true, + auth: { + user: node.userid, + pass: node.password + } + }); + + this.on("input", function(msg) { + if (smtpTransport) { + node.status({fill:"blue",shape:"dot",text:"sending"}); + var payload = RED.util.ensureString(msg.payload); + smtpTransport.sendMail({ + from: node.userid, // sender address + to: msg.to || node.name, // comma separated list of addressees + subject: msg.topic, // subject line + text: payload // plaintext body + }, function(error, info) { + if (error) { + node.error(error); + node.status({fill:"red",shape:"ring",text:"send failed"}); + } else { + node.log("Message sent: " + info.response); + node.status({}); + } + }); + } + else { node.warn("No Email credentials found. See info panel."); } + }); + } + RED.nodes.registerType("e-mail",EmailNode,{ + credentials: { + userid: {type:"text"}, + password: {type: "password"}, + global: { type:"boolean"} + } + }); + + function EmailInNode(n) { + RED.nodes.createNode(this,n); + this.name = n.name; + this.repeat = n.repeat * 1000 || 300000; + this.inserver = n.server || globalkeys.server || "imap.gmail.com"; + this.inport = n.port || globalkeys.port || "993"; + var flag = false; + + if (this.credentials && this.credentials.hasOwnProperty("userid")) { + this.userid = this.credentials.userid; + } else { + if (globalkeys) { + this.userid = globalkeys.user; + flag = true; + } else { + this.error("No e-mail userid set"); + } + } + if (this.credentials && this.credentials.hasOwnProperty("password")) { + this.password = this.credentials.password; + } else { + if (globalkeys) { + this.password = globalkeys.pass; + flag = true; + } else { + this.error("No e-mail password set"); + } + } + if (flag) { + RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true}); + } + + var node = this; + this.interval_id = null; + var oldmail = {}; + + var imap = new Imap({ + user: node.userid, + password: node.password, + host: node.inserver, + port: node.inport, + tls: true, + tlsOptions: { rejectUnauthorized: false }, + connTimeout: node.repeat, + authTimeout: node.repeat + }); + + if (!isNaN(this.repeat) && this.repeat > 0) { + node.log("repeat = "+this.repeat); + this.interval_id = setInterval( function() { + node.emit("input",{}); + }, this.repeat ); + } + + this.on("input", function(msg) { + imap.once('ready', function() { + node.status({fill:"blue",shape:"dot",text:"fetching"}); + var pay = {}; + imap.openBox('INBOX', true, function(err, box) { + if (box.messages.total > 0) { + var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] }); + f.on('message', function(msg, seqno) { + node.log('message: #'+ seqno); + var prefix = '(#' + seqno + ') '; + msg.on('body', function(stream, info) { + var buffer = ''; + stream.on('data', function(chunk) { + buffer += chunk.toString('utf8'); + }); + stream.on('end', function() { + if (info.which !== 'TEXT') { + pay.from = Imap.parseHeader(buffer).from[0]; + pay.topic = Imap.parseHeader(buffer).subject[0]; + pay.date = Imap.parseHeader(buffer).date[0]; + } else { + var parts = buffer.split("Content-Type"); + for (var p = 0; p < parts.length; p++) { + if (parts[p].indexOf("text/plain") >= 0) { + pay.payload = parts[p].split("\n").slice(1,-2).join("\n").trim(); + } + if (parts[p].indexOf("text/html") >= 0) { + pay.html = parts[p].split("\n").slice(1,-2).join("\n").trim(); + } + } + //pay.body = buffer; + } + }); + }); + msg.on('end', function() { + //node.log('Finished: '+prefix); + }); + }); + f.on('error', function(err) { + node.warn('fetch error: ' + err); + node.status({fill:"red",shape:"ring",text:"fetch error"}); + }); + f.on('end', function() { + if (JSON.stringify(pay) !== oldmail) { + node.send(pay); + oldmail = JSON.stringify(pay); + node.log('received new email: '+pay.topic); + } + else { node.log('duplicate not sent: '+pay.topic); } + //node.status({fill:"green",shape:"dot",text:"ok"}); + node.status({}); + imap.end(); + }); + } + else { + node.log("you have achieved inbox zero"); + //node.status({fill:"green",shape:"dot",text:"ok"}); + node.status({}); + imap.end(); + } + }); + }); + node.status({fill:"grey",shape:"dot",text:"connecting"}); + imap.connect(); + }); + + imap.on('error', function(err) { + node.log(err); + node.status({fill:"red",shape:"ring",text:"connect error"}); + }); + + this.on("error", function(err) { + node.log("error: ",err); + }); + + this.on("close", function() { + if (this.interval_id != null) { + clearInterval(this.interval_id); + } + if (imap) { imap.destroy(); } + }); + + node.emit("input",{}); + } + RED.nodes.registerType("e-mail in",EmailInNode,{ + credentials: { + userid: {type:"text"}, + password: {type: "password"}, + global: { type:"boolean"} + } + }); +} diff --git a/dgbuilder/core_nodes/social/91-irc.html b/dgbuilder/core_nodes/social/91-irc.html new file mode 100644 index 00000000..11112374 --- /dev/null +++ b/dgbuilder/core_nodes/social/91-irc.html @@ -0,0 +1,206 @@ +<!-- + Copyright 2013 IBM Corp. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<script type="text/x-red" data-template-name="irc in"> + <div class="form-row"> + <label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label> + <input type="text" id="node-input-ircserver"> + </div> + <div class="form-row"> + <label for="node-input-channel"><i class="fa fa-random"></i> Channel</label> + <input type="text" id="node-input-channel" placeholder="#nodered"> + </div> + <div class="form-row"> + <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> + <input type="text" id="node-input-name" placeholder="Name"> + </div> + <div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/> + You may join multiple channels by comma separating a list - #chan1,#chan2,etc.</div> +</script> + +<script type="text/x-red" data-help-name="irc in"> + <p>Connects to a channel on an IRC server.</p> + <p>You may join multiple channels by comma separating a list - #chan1,#chan2,#etc.</p> + <p>Any messages on that channel will appear on the <code>msg.payload</code> at the output, + while <code>msg.topic</code> will contain who it is from. + <code>msg.to</code> contains either the name of the channel or PRIV in the case of a pm.</p> + <p>The second output provides a <code>msg.payload</code> that has any status messages such as joins, parts, kicks etc.</p> + <p>The type of the status message is set as <code>msg.payload.type</code>.</p> + <p>The possible status types are: <br /> + <table border="1" cellpadding="1" cellspacing="1"> + <thead> + <tr> + <th scope="col">Type</th> + <th scope="col">Description</th> + </tr> + </thead> + <tbody> + <tr> + <td>message</td> + <td>message is sent into the channel</td> + </tr> + <tr> + <td>pm</td> + <td>private message to the bot</td> + </tr> + <tr> + <td>join</td> + <td>a user joined the channel (also triggered when the bot joins a channel)</td> + </tr> + <tr> + <td>invite</td> + <td>the bot is being invited to a channel</td> + </tr> + <tr> + <td>part</td> + <td>a user leaves a channel</td> + </tr> + <tr> + <td>quit</td> + <td>a user quits a channel</td> + </tr> + <tr> + <td>kick</td> + <td>a user is kicked from a channel</td> + </tr> + <tr> + <td>names</td> + <td>retrieves the list of users when the bot joins a channel</td> + </tr> + </tbody> +</table> +</p> +</script> + +<script type="text/javascript"> + RED.nodes.registerType('irc in',{ + category: 'social-input', + defaults: { + name: {value:""}, + ircserver: {type:"irc-server", required:true}, + channel: {value:"",required:true,validate:RED.validators.regex(/^#/)} + }, + color:"Silver", + inputs:0, + outputs:2, + icon: "hash.png", + label: function() { + var ircNode = RED.nodes.node(this.ircserver); + return this.name || (ircNode ? ircNode.label() : "irc"); + }, + labelStyle: function() { + return this.name?"node_label_italic":""; + }, + oneditprepare: function() { + if ((this.ircserver !== undefined) && (this.ircserver !== "")) { + this.channel = this.channel || RED.nodes.node(this.ircserver).channel; + $("#node-input-channel").val(this.channel); + } + else { this.channel = this.channel; } + $("#node-input-channel").val(this.channel); + } + }); +</script> + + +<script type="text/x-red" data-template-name="irc out"> + <div class="form-row"> + <label for="node-input-ircserver"><i class="fa fa-globe"></i> IRC Server</label> + <input type="text" id="node-input-ircserver"> + </div> + <div class="form-row"> + <label for="node-input-channel"><i class="fa fa-random"></i> Channel</label> + <input type="text" id="node-input-channel" placeholder="#nodered"> + </div> + <div class="form-row"> + <label for="node-input-sendObject"><i class="fa fa-arrows"></i> Action</label> + <select type="text" id="node-input-sendObject" style="display: inline-block; vertical-align: middle; width:70%;"> + <option value="pay">Send payload to channel(s)</option> + <option value="true">Use msg.topic to set nickname or channel(s)</option> + <option value="false">Send complete msg object to channel(s)</option> + </select> + </div> + <div class="form-row"> + <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> + <input type="text" id="node-input-name" placeholder="Name"> + </div> + <div class="form-tips">The channel to join must start with a # (as per normal irc rules...)<br/> + Sending the complete object will stringify the whole msg object before sending.</div> +</script> + +<script type="text/x-red" data-help-name="irc out"> + <p>Sends messages to a channel on an IRC server</p> + <p>You can send just the <code>msg.payload</code>, or the complete <code>msg</code> object to the selected channel, + or you can select to use <code>msg.topic</code> to send the <code>msg.payload</code> to a specific user (private message) or channel.</p> + <p>If multiple output channels are listed (eg. #chan1,#chan2), then the message will be sent to all of them.</p> + <p><b>Note:</b> you can only send to channels you have previously joined so they MUST be specified in the node - even if you then decide to use a subset in msg.topic</p> + <p>You may send RAW commands using <code>msg.raw</code> - This must contain an array of parameters - eg. <pre>["privmsg","#nodered","Hello world"]</pre></p> +</script> + +<script type="text/javascript"> + RED.nodes.registerType('irc out',{ + category: 'social-output', + defaults: { + name: {value:""}, + sendObject: {value:"pay", required:true}, + ircserver: {type:"irc-server", required:true}, + channel: {value:"",required:true,validate:RED.validators.regex(/^#/)} + }, + color:"Silver", + inputs:1, + outputs:0, + icon: "hash.png", + align: "right", + label: function() { + return this.name || (this.ircserver ? RED.nodes.node(this.ircserver).label() : "irc"); + }, + labelStyle: function() { + return this.name?"node_label_italic":""; + }, + oneditprepare: function() { + if ((this.ircserver !== undefined) && (this.ircserver !== "")) { + this.channel = this.channel || RED.nodes.node(this.ircserver).channel; + $("#node-input-channel").val(this.channel); + } + else { this.channel = this.channel; } + } + }); +</script> + + +<script type="text/x-red" data-template-name="irc-server"> + <div class="form-row"> + <label for="node-config-input-server"><i class="fa fa-globe"></i> IRC Server</label> + <input type="text" id="node-config-input-server" placeholder="irc.freenode.net"> + </div> + <div class="form-row"> + <label for="node-config-input-nickname"><i class="fa fa-user"></i> Nickname</label> + <input type="text" id="node-config-input-nickname" placeholder="joe123"> + </div> +</script> + +<script type="text/javascript"> + RED.nodes.registerType('irc-server',{ + category: 'config', + defaults: { + server: {value:"",required:true}, + nickname: {value:"",required:true} + }, + label: function() { + return this.server; + } + }); +</script> diff --git a/dgbuilder/core_nodes/social/91-irc.js b/dgbuilder/core_nodes/social/91-irc.js new file mode 100644 index 00000000..c520e44f --- /dev/null +++ b/dgbuilder/core_nodes/social/91-irc.js @@ -0,0 +1,237 @@ +/** + * Copyright 2013 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + "use strict"; + var irc = require("irc"); + + // The Server Definition - this opens (and closes) the connection + function IRCServerNode(n) { + RED.nodes.createNode(this,n); + this.server = n.server; + this.channel = n.channel; + this.nickname = n.nickname; + this.lastseen = 0; + this.ircclient = null; + this.on("close", function() { + if (this.ircclient != null) { + this.ircclient.removeAllListeners(); + this.ircclient.disconnect(); + } + }); + } + RED.nodes.registerType("irc-server",IRCServerNode); + + + // The Input Node + function IrcInNode(n) { + RED.nodes.createNode(this,n); + this.ircserver = n.ircserver; + this.serverConfig = RED.nodes.getNode(this.ircserver); + this.channel = n.channel || this.serverConfig.channel; + var node = this; + if (node.serverConfig.ircclient === null) { + node.log("Connecting to "+node.serverConfig.server); + node.status({fill:"grey",shape:"dot",text:"connecting"}); + node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:false,retryDelay:20000}); + node.serverConfig.ircclient.setMaxListeners(0); + node.serverConfig.ircclient.addListener('error', function(message) { + node.log(JSON.stringify(message)); + }); + node.serverConfig.ircclient.addListener('netError', function(message) { + node.log(JSON.stringify("NET "+message)); + node.serverConfig.lastseen = Date.now(); + }); + node.serverConfig.ircclient.addListener('connect', function() { + node.serverConfig.lastseen = Date.now(); + }); + node.serverConfig.ircclient.addListener('ping', function(server) { + node.serverConfig.lastseen = Date.now(); + //node.log("PING "+JSON.stringify(server)); + }); + node.recon = setInterval( function() { + //console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000); + if ((Date.now()-node.serverConfig.lastseen) > 300000) { // if more than 5 mins since last seen + node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link + } + if ((Date.now()-node.serverConfig.lastseen) > 400000) { // If more than 6.5 mins + node.serverConfig.ircclient.disconnect(); + node.serverConfig.ircclient.connect(); + node.log("reconnect"); // then retry + } + node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link + }, 60000); // check every 1 min + } + else { node.status({text:""}); } + node.ircclient = node.serverConfig.ircclient; + + node.ircclient.addListener('registered', function(message) { + //node.log(node.ircclient.nick+" ONLINE"); + node.status({fill:"yellow",shape:"dot",text:"connected"}); + node.ircclient.join( node.channel, function(data) { + // node.log(data+" JOINED "+node.channel); + node.status({fill:"green",shape:"dot",text:"joined"}); + }); + }); + node.ircclient.addListener('message', function (from, to, message) { + //node.log(from + ' => ' + to + ' : ' + message); + if (~node.channel.toLowerCase().indexOf(to.toLowerCase())) { + var msg = { "topic":from, "from":from, "to":to, "payload":message }; + node.send([msg,null]); + } + //else { console.log(node.channel,to); } + }); + node.ircclient.addListener('pm', function(from, message) { + //node.log("PM => "+from + ': ' + message); + var msg = { "topic":from, "from":from, "to":"PRIV", "payload":message }; + node.send([msg,null]); + }); + node.ircclient.addListener('join', function(channel, who) { + var msg = { "payload": { "type":"join", "who":who, "channel":channel } }; + node.send([null,msg]); + //node.log(who+' has joined '+channel); + }); + node.ircclient.addListener('invite', function(channel, from, message) { + var msg = { "payload": { "type":"invite", "who":from, "channel":channel, "message":message } }; + node.send([null,msg]); + //node.log(from+' sent invite to '+channel+': '+message); + }); + node.ircclient.addListener('part', function(channel, who, reason) { + var msg = { "payload": { "type":"part", "who":who, "channel":channel, "reason":reason } }; + node.send([null,msg]); + //node.log(who+' has left '+channel+': '+reason); + }); + node.ircclient.addListener('quit', function(nick, reason, channels, message) { + var msg = { "payload": { "type":"quit", "who":nick, "channel":channels, "reason":reason } }; + node.send([null,msg]); + //node.log(nick+' has quit '+channels+': '+reason); + }); + node.ircclient.addListener('kick', function(channel, who, by, reason) { + var msg = { "payload": { "type":"kick", "who":who, "channel":channel, "by":by, "reason":reason } }; + node.send([null,msg]); + //node.log(who+' was kicked from '+channel+' by '+by+': '+reason); + }); + node.ircclient.addListener('names', function (channel, nicks) { + var msg = { "payload": { "type": "names", "channel": channel, "names": nicks} }; + node.send([null, msg]); + }); + node.ircclient.addListener('raw', function (message) { // any message means we are alive + node.serverConfig.lastseen = Date.now(); + }); + node.on("close", function() { + node.ircclient.removeAllListeners(); + if (node.recon) { clearInterval(node.recon); } + }); + } + RED.nodes.registerType("irc in",IrcInNode); + + + // The Output Node + function IrcOutNode(n) { + RED.nodes.createNode(this,n); + this.sendFlag = n.sendObject; + this.ircserver = n.ircserver; + this.serverConfig = RED.nodes.getNode(this.ircserver); + this.channel = n.channel || this.serverConfig.channel; + var node = this; + if (node.serverConfig.ircclient === null) { + node.log("connecting to "+node.serverConfig.server); + node.status({fill:"grey",shape:"dot",text:"connecting"}); + node.serverConfig.ircclient = new irc.Client(node.serverConfig.server, node.serverConfig.nickname,{autoConnect:false,retryDelay:20000}); + node.serverConfig.ircclient.setMaxListeners(0); + node.serverConfig.ircclient.addListener('error', function(message) { + node.log(JSON.stringify(message)); + }); + node.serverConfig.ircclient.addListener('netError', function(message) { + node.log(JSON.stringify("NET "+message)); + node.serverConfig.lastseen = Date.now(); + }); + node.serverConfig.ircclient.addListener('connect', function() { + node.serverConfig.lastseen = Date.now(); + }); + node.serverConfig.ircclient.addListener('ping', function(server) { + node.serverConfig.lastseen = Date.now(); + //node.log("PING "+JSON.stringify(server)); + }); + node.serverConfig.ircclient.addListener('raw', function (message) { // any message received means we are alive + if (message.commandType === "reply") { node.serverConfig.lastseen = Date.now(); } + }); + node.recon = setInterval( function() { + //console.log("CHK ",(Date.now()-node.serverConfig.lastseen)/1000); + if ((Date.now()-node.serverConfig.lastseen) > 300000) { // if more than 5 mins since last seen + node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link + } + if ((Date.now()-node.serverConfig.lastseen) > 400000) { // If more than 6.5 mins + node.serverConfig.ircclient.disconnect(); + node.serverConfig.ircclient.connect(); + node.log("reconnect"); // then retry + } + node.ircclient.send.apply(node.ircclient,["TIME"]); // request time to check link + }, 60000); // check every 1 min + node.serverConfig.ircclient.connect(); + } + else { node.status({text:""}); } + node.ircclient = node.serverConfig.ircclient; + + node.ircclient.addListener('registered', function(message) { + node.log(node.ircclient.nick+" ONLINE"); + node.status({fill:"yellow",shape:"dot",text:"connected"}); + node.ircclient.join( node.channel, function(data) { + //node.log(data+" JOINED "+node.channel); + node.status({fill:"green",shape:"dot",text:"joined"}); + }); + }); + + node.on("input", function(msg) { + if (Object.prototype.toString.call( msg.raw ) === '[object Array]') { + node.log("RAW command:"+msg.raw); + node.ircclient.send.apply(node.ircclient,msg.raw); + //var m = msg.raw; + //for (var i = 0; i < 10; i++) { + //if (typeof m[i] !== "string") { m[i] = ""; } + //m[i] = m[i].replace(/"/g, ""); + //} + //node.log("RAW command:"+m); + //node.ircclient.send(m[0],m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[9]); + } + else { + if (msg._topic) { delete msg._topic; } + var ch = node.channel.split(","); // split on , so we can send to multiple + if (node.sendFlag == "true") { // override channels with msg.topic + if ((msg.hasOwnProperty('topic'))&&(typeof msg.topic === "string")) { + ch = msg.topic.split(","); // split on , so we can send to multiple + } + else { node.warn("msg.topic not set"); } + } + for (var c = 0; c < ch.length; c++) { + if (node.sendFlag == "false") { // send whole message object to each channel + node.ircclient.say(ch[c], JSON.stringify(msg)); + } + else { // send just the payload to each channel + if (typeof msg.payload === "object") { msg.payload = JSON.stringify(msg.payload); } + node.ircclient.say(ch[c], msg.payload); + } + } + } + }); + + node.on("close", function() { + node.ircclient.removeAllListeners(); + if (node.recon) { clearInterval(node.recon); } + }); + } + RED.nodes.registerType("irc out",IrcOutNode); +} |