mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
f44272877e
@ -30,8 +30,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="mqtt in">
|
<script type="text/x-red" data-help-name="mqtt in">
|
||||||
<p>MQTT input node. Connects to the specified broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
|
<p>MQTT input node. Connects to a broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
|
||||||
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>. <b>msg.payload</b> is a String.</p>
|
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>.</p>
|
||||||
|
<p><b>msg.payload</b> is a String.</p>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -105,6 +106,18 @@
|
|||||||
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
|
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
|
||||||
<input type="text" id="node-config-input-port" placeholder="Port" style="width:45px">
|
<input type="text" id="node-config-input-port" placeholder="Port" style="width:45px">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-config-input-clientid"><i class="icon-tag"></i> Client ID</label>
|
||||||
|
<input type="text" id="node-config-input-clientid" placeholder="Leave blank for auto generated">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-config-input-user"><i class="icon-user"></i> Username</label>
|
||||||
|
<input type="text" id="node-config-input-user">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-config-input-pass"><i class="icon-lock"></i> Password</label>
|
||||||
|
<input type="password" id="node-config-input-pass">
|
||||||
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -112,10 +125,49 @@
|
|||||||
category: 'config',
|
category: 'config',
|
||||||
defaults: {
|
defaults: {
|
||||||
broker: {value:"localhost",required:true},
|
broker: {value:"localhost",required:true},
|
||||||
port: {value:1883,required:true,validate:RED.validators.number()}
|
port: {value:1883,required:true,validate:RED.validators.number()},
|
||||||
|
clientid: { value:"" }
|
||||||
|
//user -> credentials
|
||||||
|
//pass -> credentials
|
||||||
|
|
||||||
},
|
},
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.broker+":"+this.port;
|
return this.broker+":"+this.port;
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
$.getJSON('mqtt-broker/'+this.id,function(data) {
|
||||||
|
if (data.user) {
|
||||||
|
$('#node-config-input-user').val(data.user);
|
||||||
|
}
|
||||||
|
if (data.hasPassword) {
|
||||||
|
$('#node-config-input-pass').val('__PWRD__');
|
||||||
|
} else {
|
||||||
|
$('#node-config-input-pass').val('');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var newUser = $('#node-config-input-user').val();
|
||||||
|
var newPass = $('#node-config-input-pass').val();
|
||||||
|
var credentials = {};
|
||||||
|
credentials.user = newUser;
|
||||||
|
if (newPass != '__PWRD__') {
|
||||||
|
credentials.password = newPass;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: 'mqtt-broker/'+this.id,
|
||||||
|
type: 'POST',
|
||||||
|
data: credentials,
|
||||||
|
success:function(result){}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
ondelete: function() {
|
||||||
|
$.ajax({
|
||||||
|
url: 'mqtt-broker/'+this.id,
|
||||||
|
type: 'DELETE',
|
||||||
|
success: function(result) {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -22,9 +22,54 @@ function MQTTBrokerNode(n) {
|
|||||||
RED.nodes.createNode(this,n);
|
RED.nodes.createNode(this,n);
|
||||||
this.broker = n.broker;
|
this.broker = n.broker;
|
||||||
this.port = n.port;
|
this.port = n.port;
|
||||||
|
this.clientid = n.clientid;
|
||||||
|
var credentials = RED.nodes.getCredentials(n.id);
|
||||||
|
if (credentials) {
|
||||||
|
this.username = credentials.user;
|
||||||
|
this.password = credentials.password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("mqtt-broker",MQTTBrokerNode);
|
RED.nodes.registerType("mqtt-broker",MQTTBrokerNode);
|
||||||
|
|
||||||
|
var querystring = require('querystring');
|
||||||
|
|
||||||
|
RED.app.get('/mqtt-broker/:id',function(req,res) {
|
||||||
|
var credentials = RED.nodes.getCredentials(req.params.id);
|
||||||
|
if (credentials) {
|
||||||
|
res.send(JSON.stringify({user:credentials.user,hasPassword:(credentials.password&&credentials.password!="")}));
|
||||||
|
} else {
|
||||||
|
res.send(JSON.stringify({}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
RED.app.delete('/mqtt-broker/:id',function(req,res) {
|
||||||
|
RED.nodes.deleteCredentials(req.params.id);
|
||||||
|
res.send(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
RED.app.post('/mqtt-broker/:id',function(req,res) {
|
||||||
|
var body = "";
|
||||||
|
req.on('data', function(chunk) {
|
||||||
|
body+=chunk;
|
||||||
|
});
|
||||||
|
req.on('end', function(){
|
||||||
|
var newCreds = querystring.parse(body);
|
||||||
|
var credentials = RED.nodes.getCredentials(req.params.id)||{};
|
||||||
|
if (newCreds.user == null || newCreds.user == "") {
|
||||||
|
delete credentials.user;
|
||||||
|
} else {
|
||||||
|
credentials.user = newCreds.user;
|
||||||
|
}
|
||||||
|
if (newCreds.password == "") {
|
||||||
|
delete credentials.password;
|
||||||
|
} else {
|
||||||
|
credentials.password = newCreds.password||credentials.password;
|
||||||
|
}
|
||||||
|
RED.nodes.addCredentials(req.params.id,credentials);
|
||||||
|
res.send(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
function MQTTInNode(n) {
|
function MQTTInNode(n) {
|
||||||
RED.nodes.createNode(this,n);
|
RED.nodes.createNode(this,n);
|
||||||
@ -32,7 +77,7 @@ function MQTTInNode(n) {
|
|||||||
this.broker = n.broker;
|
this.broker = n.broker;
|
||||||
this.brokerConfig = RED.nodes.getNode(this.broker);
|
this.brokerConfig = RED.nodes.getNode(this.broker);
|
||||||
if (this.brokerConfig) {
|
if (this.brokerConfig) {
|
||||||
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port);
|
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
|
||||||
var node = this;
|
var node = this;
|
||||||
this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
|
this.client.subscribe(this.topic,2,function(topic,payload,qos,retain) {
|
||||||
var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
|
var msg = {topic:topic,payload:payload,qos:qos,retain:retain};
|
||||||
@ -65,7 +110,7 @@ function MQTTOutNode(n) {
|
|||||||
this.brokerConfig = RED.nodes.getNode(this.broker);
|
this.brokerConfig = RED.nodes.getNode(this.broker);
|
||||||
|
|
||||||
if (this.brokerConfig) {
|
if (this.brokerConfig) {
|
||||||
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port);
|
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
|
||||||
this.on("input",function(msg) {
|
this.on("input",function(msg) {
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
if (this.topic) {
|
if (this.topic) {
|
||||||
|
@ -43,6 +43,8 @@ function WebSocketListenerNode(n) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node._clients = {};
|
||||||
|
|
||||||
RED.server.addListener('newListener',storeListener);
|
RED.server.addListener('newListener',storeListener);
|
||||||
|
|
||||||
// Create a WebSocket Server
|
// Create a WebSocket Server
|
||||||
@ -53,8 +55,14 @@ function WebSocketListenerNode(n) {
|
|||||||
RED.server.removeListener('newListener',storeListener);
|
RED.server.removeListener('newListener',storeListener);
|
||||||
|
|
||||||
node.server.on('connection', function(socket){
|
node.server.on('connection', function(socket){
|
||||||
|
var id = (1+Math.random()*4294967295).toString(16);
|
||||||
|
node._clients[id] = socket;
|
||||||
|
|
||||||
|
socket.on('close',function() {
|
||||||
|
delete node._clients[id];
|
||||||
|
});
|
||||||
socket.on('message',function(data,flags){
|
socket.on('message',function(data,flags){
|
||||||
node.handleEvent(socket,'message',data,flags);
|
node.handleEvent(id,socket,'message',data,flags);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,9 +88,9 @@ WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler){
|
|||||||
this._inputNodes.push(handler);
|
this._inputNodes.push(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketListenerNode.prototype.handleEvent = function(/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
|
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
|
||||||
for (var i = 0; i < this._inputNodes.length; i++) {
|
for (var i = 0; i < this._inputNodes.length; i++) {
|
||||||
this._inputNodes[i].send({payload:data});
|
this._inputNodes[i].send({session:{type:"websocket",id:id},payload:data});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +100,13 @@ WebSocketListenerNode.prototype.broadcast = function(data){
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebSocketListenerNode.prototype.send = function(id,data){
|
||||||
|
var session = this._clients[id];
|
||||||
|
if (session) {
|
||||||
|
session.send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function WebSocketInNode(n) {
|
function WebSocketInNode(n) {
|
||||||
RED.nodes.createNode(this,n);
|
RED.nodes.createNode(this,n);
|
||||||
this.server = n.server;
|
this.server = n.server;
|
||||||
@ -114,11 +129,23 @@ function WebSocketOutNode(n) {
|
|||||||
this.error("Missing server configuration");
|
this.error("Missing server configuration");
|
||||||
}
|
}
|
||||||
this.on("input", function(msg) {
|
this.on("input", function(msg) {
|
||||||
node.serverConfig.broadcast(msg.payload,function(error){
|
var payload = msg.payload;
|
||||||
|
if (Buffer.isBuffer(payload)) {
|
||||||
|
payload = payload.toString();
|
||||||
|
} else if (typeof payload === "object") {
|
||||||
|
payload = JSON.stringify(payload);
|
||||||
|
} else if (typeof payload !== "string") {
|
||||||
|
payload = ""+payload;
|
||||||
|
}
|
||||||
|
if (msg.session && msg.session.type == "websocket") {
|
||||||
|
node.serverConfig.send(msg.session.id,payload);
|
||||||
|
} else {
|
||||||
|
node.serverConfig.broadcast(payload,function(error){
|
||||||
if(!!error){
|
if(!!error){
|
||||||
node.warn("An error occurred while sending:" + inspect(error));
|
node.warn("An error occurred while sending:" + inspect(error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("websocket out",WebSocketOutNode);
|
RED.nodes.registerType("websocket out",WebSocketOutNode);
|
||||||
|
@ -25,13 +25,16 @@ function matchTopic(ts,t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
get: function(broker,port) {
|
get: function(broker,port,clientid,username,password) {
|
||||||
var id = broker+":"+port;
|
var id = "["+(username||"")+":"+(password||"")+"]["+(clientid||"")+"]@"+broker+":"+port;
|
||||||
if (!connections[id]) {
|
if (!connections[id]) {
|
||||||
connections[id] = function() {
|
connections[id] = function() {
|
||||||
var client = mqtt.createClient(port,broker);
|
var client = mqtt.createClient(port,broker);
|
||||||
client.setMaxListeners(0);
|
client.setMaxListeners(0);
|
||||||
var options = {keepalive:15,clientId:'mqtt_' + (1+Math.random()*4294967295).toString(16)};
|
var options = {keepalive:15};
|
||||||
|
options.clientId = clientid || 'mqtt_' + (1+Math.random()*4294967295).toString(16);
|
||||||
|
options.username = username;
|
||||||
|
options.password = password;
|
||||||
var queue = [];
|
var queue = [];
|
||||||
var subscriptions = [];
|
var subscriptions = [];
|
||||||
var connecting = false;
|
var connecting = false;
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
outputs: 1,
|
outputs: 1,
|
||||||
icon: "switch.png",
|
icon: "switch.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name;
|
return this.name||"switch";
|
||||||
},
|
},
|
||||||
oneditprepare: function() {
|
oneditprepare: function() {
|
||||||
|
|
||||||
|
111
nodes/core/logic/15-change.html
Normal file
111
nodes/core/logic/15-change.html
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<!--
|
||||||
|
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="change">
|
||||||
|
<div>
|
||||||
|
<select id="node-input-action" style="width:95%; margin-right:5px;">
|
||||||
|
<option value="change">Change the value of the property</option>
|
||||||
|
<!-- <option value="replace">Replace value of property</option> -->
|
||||||
|
<option value="replace">Add or replace property</option>
|
||||||
|
<option value="delete">Delete property</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-row" style="padding-top:10px;" id="node-prop1-row">
|
||||||
|
<label id="node-input-todo">called</label>msg.<input type="text" id="node-input-property" style="width: 63%;"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-row" id="node-from-row">
|
||||||
|
<label id="node-input-f">if it contains</label>
|
||||||
|
<input type="text" id="node-input-from" placeholder="this"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-row" id="node-to-row">
|
||||||
|
<label id="node-input-t">replace with</label>
|
||||||
|
<input type="text" id="node-input-to" placeholder="that"/>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
<div class="form-tips" id="node-tip"></div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="change">
|
||||||
|
<p>A simple function node to change, replace, add or delete properties of a message.</p>
|
||||||
|
<p>When a message arrives, the selected property is modified by the defined rules.
|
||||||
|
The message is then sent to the output.</p>
|
||||||
|
<p><b>Note:</b> Replace only operates on <b>strings</b>. Anything else will be passed straight through.</p>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('change', {
|
||||||
|
color: "#E2D96E",
|
||||||
|
category: 'function',
|
||||||
|
defaults: {
|
||||||
|
action: {value:"change",required:true},
|
||||||
|
property: {value:"payload"},
|
||||||
|
from: {value:""},
|
||||||
|
to: {value:""},
|
||||||
|
name: {value:""}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
icon: "swap.png",
|
||||||
|
label: function() {
|
||||||
|
return this.name || this.action || "change";
|
||||||
|
},
|
||||||
|
labelStyle: function() {
|
||||||
|
return this.name ? "node_label_italic" : "";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
$("#node-input-action").change( function() {
|
||||||
|
var a = $("#node-input-action").val();
|
||||||
|
if (a === "replace") {
|
||||||
|
$("#node-input-todo").html("called");
|
||||||
|
//$("#node-input-f").html("name");
|
||||||
|
$("#node-input-t").html("with value");
|
||||||
|
$("#node-from-row").hide();
|
||||||
|
$("#node-to-row").show();
|
||||||
|
$("#node-tip").html("Tip: expects a new property name and either a fixed value OR the full name of another msg.property eg: msg.sentiment.score");
|
||||||
|
}
|
||||||
|
if (a === "delete") {
|
||||||
|
$("#node-input-todo").html("called");
|
||||||
|
//$("#node-input-f").html("called");
|
||||||
|
//$("#node-input-t").html("to");
|
||||||
|
$("#node-from-row").hide();
|
||||||
|
$("#node-to-row").hide();
|
||||||
|
$("#node-tip").html("Tip: deletes the named property and all sub-properties");
|
||||||
|
}
|
||||||
|
if (a === "change") {
|
||||||
|
$("#node-input-todo").html("called");
|
||||||
|
$("#node-input-f").html("If it contains");
|
||||||
|
$("#node-input-t").html("replace with");
|
||||||
|
$("#node-from-row").show();
|
||||||
|
$("#node-to-row").show();
|
||||||
|
$("#node-tip").html("Tip: <i>if it contains</i> can be a regex, likewise <i>replace with</i> can accept regex results. Only works on strings.");
|
||||||
|
}
|
||||||
|
//if (a === "replace") {
|
||||||
|
// $("#node-input-todo").html("called");
|
||||||
|
// //$("#node-input-f").html("with");
|
||||||
|
// $("#node-input-t").html("with");
|
||||||
|
// $("#node-from-row").hide();
|
||||||
|
// $("#node-to-row").show();
|
||||||
|
// $("#node-tip").html("Tip: accepts either a fixed value OR the full name of another msg.property eg: msg.sentiment.score");
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
$("#node-input-action").change();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
66
nodes/core/logic/15-change.js
Normal file
66
nodes/core/logic/15-change.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
var RED = require(process.env.NODE_RED_HOME + "/red/red");
|
||||||
|
|
||||||
|
function ChangeNode(n) {
|
||||||
|
RED.nodes.createNode(this, n);
|
||||||
|
this.action = n.action;
|
||||||
|
this.property = n.property || "";
|
||||||
|
this.from = n.from || " ";
|
||||||
|
this.to = n.to || " ";
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
var makeNew = function( stem, path, value ) {
|
||||||
|
var lastPart = (arguments.length === 3) ? path.pop() : false;
|
||||||
|
for (var i = 0; i < path.length; i++) {
|
||||||
|
stem = stem[path[i]] = stem[path[i]] || {};
|
||||||
|
}
|
||||||
|
if (lastPart) { stem = stem[lastPart] = value; }
|
||||||
|
return stem;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.on('input', function (msg) {
|
||||||
|
if (node.action == "change") {
|
||||||
|
node.re = new RegExp(this.from, "g");
|
||||||
|
if (typeof msg[node.property] === "string") {
|
||||||
|
msg[node.property] = (msg[node.property]).replace(node.re, node.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//else if (node.action == "replace") {
|
||||||
|
//if (node.to.indexOf("msg.") == 0) {
|
||||||
|
//msg[node.property] = eval(node.to);
|
||||||
|
//}
|
||||||
|
//else {
|
||||||
|
//msg[node.property] = node.to;
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
else if (node.action == "replace") {
|
||||||
|
if (node.to.indexOf("msg.") == 0) {
|
||||||
|
makeNew( msg, node.property.split("."), eval(node.to) );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
makeNew( msg, node.property.split("."), node.to );
|
||||||
|
}
|
||||||
|
//makeNew( msg, node.property.split("."), node.to );
|
||||||
|
}
|
||||||
|
else if (node.action == "delete") {
|
||||||
|
delete(msg[node.property]);
|
||||||
|
}
|
||||||
|
node.send(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
RED.nodes.registerType("change", ChangeNode);
|
@ -129,11 +129,19 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="twitter in">
|
<script type="text/x-red" data-help-name="twitter in">
|
||||||
<p>Twitter input node. Watches either the public or the user's stream for tweets containing the configured search term.</p>
|
<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>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 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.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>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>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -142,7 +150,7 @@
|
|||||||
color:"#C0DEED",
|
color:"#C0DEED",
|
||||||
defaults: {
|
defaults: {
|
||||||
twitter: {type:"twitter-credentials",required:true},
|
twitter: {type:"twitter-credentials",required:true},
|
||||||
tags: {value:"",required:true},
|
tags: {value:"",validate:function(v) { return this.user == "dm" || v.length > 0;}},
|
||||||
user: {value:"false",required:true},
|
user: {value:"false",required:true},
|
||||||
name: {value:""},
|
name: {value:""},
|
||||||
topic: {value:"tweets"}
|
topic: {value:"tweets"}
|
||||||
@ -151,7 +159,16 @@
|
|||||||
outputs:1,
|
outputs:1,
|
||||||
icon: "twitter.png",
|
icon: "twitter.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||this.tags;
|
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() {
|
labelStyle: function() {
|
||||||
return this.name?"node_label_italic":"";
|
return this.name?"node_label_italic":"";
|
||||||
|
@ -43,6 +43,14 @@ function TwitterInNode(n) {
|
|||||||
access_token_secret: credentials.access_token_secret
|
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;
|
var node = this;
|
||||||
if (this.user === "user") {
|
if (this.user === "user") {
|
||||||
node.poll_ids = [];
|
node.poll_ids = [];
|
||||||
@ -118,10 +126,7 @@ function TwitterInNode(n) {
|
|||||||
if (cb) {
|
if (cb) {
|
||||||
for (var t=cb.length-1;t>=0;t-=1) {
|
for (var t=cb.length-1;t>=0;t-=1) {
|
||||||
var tweet = cb[t];
|
var tweet = cb[t];
|
||||||
var where = tweet.user.location||"";
|
var msg = { topic:node.topic+"/"+tweet.sender.screen_name, payload:tweet.text, tweet:tweet };
|
||||||
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);
|
node.send(msg);
|
||||||
if (t == 0) {
|
if (t == 0) {
|
||||||
node.since_id = tweet.id_str;
|
node.since_id = tweet.id_str;
|
||||||
@ -132,7 +137,7 @@ function TwitterInNode(n) {
|
|||||||
node.error(err);
|
node.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},65000));
|
},120000));
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (this.tags !== "") {
|
} else if (this.tags !== "") {
|
||||||
|
@ -35,23 +35,19 @@ function FeedParseNode(n) {
|
|||||||
node.error(error);
|
node.error(error);
|
||||||
})
|
})
|
||||||
.on('meta', function (meta) {})
|
.on('meta', function (meta) {})
|
||||||
.on('article', function (article) {
|
.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())) {
|
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;
|
node.seen[article.guid] = article.date?article.date.getTime():0;
|
||||||
var msg = {
|
var msg = {
|
||||||
topic:article.origlink||article.link,
|
topic:article.origlink||article.link,
|
||||||
payload: article.description,
|
payload: article.description,
|
||||||
article: {
|
article: article
|
||||||
summary:article.summary,
|
|
||||||
link:article.link,
|
|
||||||
date: article.date,
|
|
||||||
pubdate: article.pubdate,
|
|
||||||
author: article.author,
|
|
||||||
guid: article.guid,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
node.send(msg);
|
node.send(msg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.on('end', function () {
|
.on('end', function () {
|
||||||
});
|
});
|
||||||
|
@ -27,10 +27,11 @@
|
|||||||
|
|
||||||
<script type="text/x-red" data-help-name="imap">
|
<script type="text/x-red" data-help-name="imap">
|
||||||
<p>Repeatedly gets a <b>single email</b> from an IMAP server and forwards on as a msg if not already seen.</p>
|
<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 body text. <b>msg.from</b> is also set if you need it.</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 - you also need to pre-configure your email settings in a file emailkeys.js as per below.</p>
|
<p>Uses the imap module - you also need to pre-configure your email settings in a file emailkeys.js as per below.</p>
|
||||||
<p><pre>module.exports = { service: "Gmail", user: "blahblah@gmail.com", pass: "password", server: "imap.gmail.com", port: "993" }</pre></p>
|
<p><pre>module.exports = { service: "Gmail", user: "blahblah@gmail.com", pass: "password", server: "imap.gmail.com", port: "993" }</pre></p>
|
||||||
<p>This <b>must</b> be located in the diectory above node-red.</p>
|
<p>This <b>must</b> be located in the directory above node-red.</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>
|
<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>
|
||||||
|
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
var RED = require(process.env.NODE_RED_HOME+"/red/red");
|
var RED = require(process.env.NODE_RED_HOME+"/red/red");
|
||||||
var Imap = require('imap');
|
var Imap = require('imap');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var oldmail = {};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var emailkey = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
|
var emailkey = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
throw new Error("Failed to load Email credentials");
|
util.log("[imap] : Failed to load Email credentials");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var imap = new Imap({
|
var imap = new Imap({
|
||||||
@ -30,79 +30,98 @@ var imap = new Imap({
|
|||||||
password: emailkey.pass,
|
password: emailkey.pass,
|
||||||
host: emailkey.server||"imap.gmail.com",
|
host: emailkey.server||"imap.gmail.com",
|
||||||
port: emailkey.port||"993",
|
port: emailkey.port||"993",
|
||||||
secure: true
|
tls: true,
|
||||||
|
tlsOptions: { rejectUnauthorized: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
function fail(err) {
|
|
||||||
util.log('[imap] : ' + err);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openInbox(cb) {
|
function openInbox(cb) {
|
||||||
imap.connect(function(err) {
|
|
||||||
if (err) fail(err);
|
|
||||||
imap.openBox('INBOX', true, cb);
|
imap.openBox('INBOX', true, cb);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ImapNode(n) {
|
function ImapNode(n) {
|
||||||
RED.nodes.createNode(this,n);
|
RED.nodes.createNode(this,n);
|
||||||
this.name = n.name;
|
this.name = n.name;
|
||||||
this.repeat = n.repeat * 1000;
|
this.repeat = n.repeat * 1000 || 300000;
|
||||||
var node = this;
|
var node = this;
|
||||||
this.interval_id = null;
|
this.interval_id = null;
|
||||||
|
var oldmail = {};
|
||||||
|
|
||||||
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
|
if (!isNaN(this.repeat) && this.repeat > 0) {
|
||||||
this.log("repeat = "+this.repeat);
|
node.log("repeat = "+this.repeat);
|
||||||
this.interval_id = setInterval( function() {
|
this.interval_id = setInterval( function() {
|
||||||
node.emit("input",{});
|
node.emit("input",{});
|
||||||
}, this.repeat );
|
}, this.repeat );
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on("input", function(msg) {
|
this.on("input", function(msg) {
|
||||||
openInbox(function(err, mailbox) {
|
imap.once('ready', function() {
|
||||||
if (err) fail(err);
|
|
||||||
imap.seq.fetch(mailbox.messages.total + ':*', { struct: false },
|
|
||||||
{ headers: ['from', 'subject'],
|
|
||||||
body: true,
|
|
||||||
cb: function(fetch) {
|
|
||||||
fetch.on('message', function(msg) {
|
|
||||||
//node.log('Saw message no. ' + msg.seqno);
|
|
||||||
var pay = {};
|
var pay = {};
|
||||||
var body = '';
|
openInbox(function(err, box) {
|
||||||
msg.on('headers', function(hdrs) {
|
//if (err) throw err;
|
||||||
pay.from = hdrs.from[0];
|
var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM SUBJECT DATE)','TEXT'] });
|
||||||
pay.topic = hdrs.subject[0];
|
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 in parts) {
|
||||||
|
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('data', function(chunk) {
|
|
||||||
body += chunk.toString('utf8');
|
|
||||||
});
|
});
|
||||||
msg.on('end', function() {
|
msg.on('end', function() {
|
||||||
pay.payload = body;
|
//node.log('Finished: '+prefix);
|
||||||
if ((pay.topic !== oldmail.topic)|(pay.payload !== oldmail.payload)) {
|
});
|
||||||
oldmail = pay;
|
});
|
||||||
//node.log("From: "+pay.from);
|
f.on('error', function(err) {
|
||||||
node.log("Subj: "+pay.topic);
|
node.warn('fetch error: ' + err);
|
||||||
//node.log("Body: "+pay.payload);
|
});
|
||||||
|
f.on('end', function() {
|
||||||
|
if (JSON.stringify(pay) !== oldmail) {
|
||||||
node.send(pay);
|
node.send(pay);
|
||||||
|
oldmail = JSON.stringify(pay);
|
||||||
|
node.log('sent new message: '+pay.topic);
|
||||||
}
|
}
|
||||||
|
else { node.log('duplicate not sent: '+pay.topic); }
|
||||||
|
imap.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}, function(err) {
|
imap.connect();
|
||||||
if (err) node.log("Err : "+err);
|
|
||||||
//node.log("Done fetching messages.");
|
|
||||||
imap.logout();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
imap.on('error', function(err) {
|
||||||
|
util.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("error", function(err) {
|
||||||
|
node.log("error: ",err);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on("close", function() {
|
this.on("close", function() {
|
||||||
if (this.interval_id != null) {
|
if (this.interval_id != null) {
|
||||||
clearInterval(this.interval_id);
|
clearInterval(this.interval_id);
|
||||||
}
|
}
|
||||||
|
imap.destroy();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
|
node.emit("input",{});
|
||||||
|
}
|
||||||
RED.nodes.registerType("imap",ImapNode);
|
RED.nodes.registerType("imap",ImapNode);
|
||||||
|
BIN
public/icons/swap.png
Normal file
BIN
public/icons/swap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 580 B |
22
red.js
22
red.js
@ -18,12 +18,30 @@ var https = require('https');
|
|||||||
var util = require("util");
|
var util = require("util");
|
||||||
var express = require("express");
|
var express = require("express");
|
||||||
var crypto = require("crypto");
|
var crypto = require("crypto");
|
||||||
var settings = require("./settings");
|
|
||||||
var RED = require("./red/red.js");
|
var RED = require("./red/red.js");
|
||||||
|
|
||||||
var server;
|
var server;
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
|
var settingsFile = "./settings";
|
||||||
|
var flowFile;
|
||||||
|
|
||||||
|
for (var argp = 2;argp < process.argv.length;argp+=1) {
|
||||||
|
var v = process.argv[argp];
|
||||||
|
if (v == "--settings" || v == "-s") {
|
||||||
|
if (argp+1 == process.argv.length) {
|
||||||
|
console.log("Missing argument to --settings");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
argp++;
|
||||||
|
settingsFile = process.argv[argp];
|
||||||
|
} else {
|
||||||
|
flowFile = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = require(settingsFile);
|
||||||
|
|
||||||
if (settings.https) {
|
if (settings.https) {
|
||||||
server = https.createServer(settings.https,function(req,res){app(req,res);});
|
server = https.createServer(settings.https,function(req,res){app(req,res);});
|
||||||
} else {
|
} else {
|
||||||
@ -49,7 +67,7 @@ if (settings.httpAuth) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.flowFile = process.argv[2] || settings.flowFile;
|
settings.flowFile = flowFile || settings.flowFile;
|
||||||
|
|
||||||
var red = RED.init(server,settings);
|
var red = RED.init(server,settings);
|
||||||
app.use(settings.httpRoot,red);
|
app.use(settings.httpRoot,red);
|
||||||
|
30
red/nodes.js
30
red/nodes.js
@ -155,28 +155,36 @@ Node.prototype.send = function(msg) {
|
|||||||
if (!util.isArray(msg[i])) {
|
if (!util.isArray(msg[i])) {
|
||||||
msgs = [msg[i]];
|
msgs = [msg[i]];
|
||||||
}
|
}
|
||||||
if (wires.length == 1) {
|
//if (wires.length == 1) {
|
||||||
// Single recipient, don't need to clone the message
|
// // Single recipient, don't need to clone the message
|
||||||
var node = registry.get(wires[0]);
|
// var node = registry.get(wires[0]);
|
||||||
if (node) {
|
// if (node) {
|
||||||
for (var k in msgs) {
|
// for (var k in msgs) {
|
||||||
var mm = msgs[k];
|
// var mm = msgs[k];
|
||||||
node.receive(mm);
|
// node.receive(mm);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
//} else {
|
||||||
// Multiple recipients, must send message copies
|
// Multiple recipients, must send message copies
|
||||||
for (var j in wires) {
|
for (var j in wires) {
|
||||||
var node = registry.get(wires[j]);
|
var node = registry.get(wires[j]);
|
||||||
if (node) {
|
if (node) {
|
||||||
for (var k in msgs) {
|
for (var k in msgs) {
|
||||||
var mm = msgs[k];
|
var mm = msgs[k];
|
||||||
|
// Temporary fix for #97
|
||||||
|
// TODO: remove this http-node-specific fix somehow
|
||||||
|
var req = mm.req;
|
||||||
|
var res = mm.res;
|
||||||
|
mm.req = null;
|
||||||
|
mm.res = null;
|
||||||
var m = clone(mm);
|
var m = clone(mm);
|
||||||
|
m.req = req;
|
||||||
|
m.res = res;
|
||||||
node.receive(m);
|
node.receive(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user