diff --git a/analysis/mlsentiment/locales/de/mlsentiment.json b/analysis/mlsentiment/locales/de/mlsentiment.json new file mode 100644 index 00000000..5931483e --- /dev/null +++ b/analysis/mlsentiment/locales/de/mlsentiment.json @@ -0,0 +1,8 @@ +{ + "mlsentiment": { + "sentiment": "sentiment", + "label": { + "language": "Sprache" + } + } +} diff --git a/analysis/mlsentiment/locales/en-US/mlsentiment.html b/analysis/mlsentiment/locales/en-US/mlsentiment.html new file mode 100644 index 00000000..8e9f5c9d --- /dev/null +++ b/analysis/mlsentiment/locales/en-US/mlsentiment.html @@ -0,0 +1,29 @@ + + diff --git a/analysis/mlsentiment/locales/ja/mlsentiment.html b/analysis/mlsentiment/locales/ja/mlsentiment.html index b55a6e74..ae7b5fa7 100644 --- a/analysis/mlsentiment/locales/ja/mlsentiment.html +++ b/analysis/mlsentiment/locales/ja/mlsentiment.html @@ -1,5 +1,5 @@ - - - - diff --git a/function/PID/locales/en-US/pidcontrol.html b/function/PID/locales/en-US/pidcontrol.html new file mode 100644 index 00000000..f1222218 --- /dev/null +++ b/function/PID/locales/en-US/pidcontrol.html @@ -0,0 +1,8 @@ + + diff --git a/function/PID/pidcontrol.html b/function/PID/pidcontrol.html index 06116a32..3ab0d646 100644 --- a/function/PID/pidcontrol.html +++ b/function/PID/pidcontrol.html @@ -1,5 +1,5 @@ - - - - - diff --git a/function/datagenerator/package.json b/function/datagenerator/package.json index 3dc05d16..16339dd9 100644 --- a/function/datagenerator/package.json +++ b/function/datagenerator/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-data-generator", - "version" : "0.1.1", + "version" : "0.2.0", "description" : "A Node-RED node to create a string of dummy data values from a template. Useful for test-cases.", "dependencies" : { "dummy-json": "^2.0.0" diff --git a/function/random/locales/de/random.json b/function/random/locales/de/random.json new file mode 100644 index 00000000..197c13d9 --- /dev/null +++ b/function/random/locales/de/random.json @@ -0,0 +1,13 @@ +{ + "random": { + "label": { + "generate": "Generiere", + "wholeNumber": "eine Ganzzahl (integer)", + "realNumber": "eine reelle Zahl (floating point)", + "from": "Von", + "lowestNumber": "kleinste Zahl", + "to": "Bis", + "highestNumber": "größte Zahl" + } + } +} diff --git a/function/random/package.json b/function/random/package.json index 2f2f4dc0..24946b69 100644 --- a/function/random/package.json +++ b/function/random/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-random", - "version" : "0.3.1", + "version" : "0.4.0", "description" : "A Node-RED node that when triggered generates a random number between two values.", "dependencies" : { }, diff --git a/function/rbe/locales/de/rbe.html b/function/rbe/locales/de/rbe.html new file mode 100644 index 00000000..99fcd318 --- /dev/null +++ b/function/rbe/locales/de/rbe.html @@ -0,0 +1,40 @@ + diff --git a/function/rbe/locales/de/rbe.json b/function/rbe/locales/de/rbe.json new file mode 100644 index 00000000..dcdded6b --- /dev/null +++ b/function/rbe/locales/de/rbe.json @@ -0,0 +1,29 @@ +{ + "rbe": { + "rbe": "rbe", + "label": { + "func": "Modus", + "init": "Sende Anfangswert", + "start": "Startwert", + "name": "Name", + "septopics": "Modus für jedes msg.topic separat anwenden" + }, + "placeholder":{ + "bandgap": "z.B. 10 oder 5%", + "start": "Leer lassen, um erste empfangenen Daten zu nutzen" + }, + "opts": { + "rbe": "Blockieren bis Wertänderung", + "rbei": "Blockieren bis Wertänderung (Anfangswert ignorieren)", + "deadband": "Blockieren bis Wertänderung ist größer als", + "deadbandEq": "Blockieren bis Wertänderung ist größer-gleich", + "narrowband": "Blockieren wenn Wertänderung ist größer als", + "narrowbandEq": "Blockieren wenn Wertänderung ist größer-gleich", + "in": "verglichen mit letzten Eingangswert", + "out": "verglichen mit letzten gültigen Ausgangswert" + }, + "warn": { + "nonumber": "Keine Zahl gefunden in den Nutzdaten (Payload)" + } + } +} diff --git a/function/rbe/locales/en-US/rbe.html b/function/rbe/locales/en-US/rbe.html index 44d2068d..0bca2cf0 100644 --- a/function/rbe/locales/en-US/rbe.html +++ b/function/rbe/locales/en-US/rbe.html @@ -1,18 +1,19 @@ diff --git a/function/rbe/locales/en-US/rbe.json b/function/rbe/locales/en-US/rbe.json index 8adbb62a..0a493e3a 100644 --- a/function/rbe/locales/en-US/rbe.json +++ b/function/rbe/locales/en-US/rbe.json @@ -5,7 +5,8 @@ "func": "Mode", "init": "Send initial value", "start": "Start value", - "name": "Name" + "name": "Name", + "septopics": "Apply mode for each msg.topic separately" }, "placeholder":{ "bandgap": "e.g. 10 or 5%", diff --git a/function/rbe/package.json b/function/rbe/package.json index c66f41ee..eb11db4d 100644 --- a/function/rbe/package.json +++ b/function/rbe/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-rbe", - "version" : "0.2.9", + "version" : "0.5.0", "description" : "A Node-RED node that provides report-by-exception (RBE) and deadband capabilities.", "dependencies" : { }, diff --git a/function/rbe/rbe.html b/function/rbe/rbe.html index 70cad4c5..58a5a9be 100644 --- a/function/rbe/rbe.html +++ b/function/rbe/rbe.html @@ -27,6 +27,11 @@ +
+ + + +
@@ -43,6 +48,7 @@ gap: {value:"",validate:RED.validators.regex(/^(\d*[.]*\d*|)(%|)$/)}, start: {value:""}, inout: {value:"out"}, + septopics: {value:true}, property: {value:"payload",required:true} }, inputs:1, @@ -59,6 +65,9 @@ if (this.property === undefined) { $("#node-input-property").val("payload"); } + if (this.septopics === undefined) { + $("#node-input-septopics").prop('checked', true); + } $("#node-input-property").typedInput({default:'msg',types:['msg']}); //$( "#node-input-gap" ).spinner({min:0}); if ($("#node-input-inout").val() === null) { diff --git a/function/rbe/rbe.js b/function/rbe/rbe.js index dc98496a..c5885101 100644 --- a/function/rbe/rbe.js +++ b/function/rbe/rbe.js @@ -14,20 +14,25 @@ module.exports = function(RED) { } this.g = this.gap; this.property = n.property||"payload"; + this.septopics = true; + if (n.septopics !== undefined && n.septopics === false) { + this.septopics = false; + } var node = this; node.previous = {}; this.on("input",function(msg) { if (msg.hasOwnProperty("reset")) { - if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { + if (node.septopics && msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { delete node.previous[msg.topic]; } else { node.previous = {}; } } var value = RED.util.getMessageProperty(msg,node.property); if (value !== undefined) { - var t = msg.topic || "_no_topic"; + var t = "_no_topic"; + if (node.septopics) { t = msg.topic || t; } if ((this.func === "rbe") || (this.func === "rbei")) { var doSend = (this.func !== "rbei") || (node.previous.hasOwnProperty(t)) || false; if (typeof(value) === "object") { diff --git a/function/smooth/17-smooth.html b/function/smooth/17-smooth.html index 75103f05..81643a1b 100644 --- a/function/smooth/17-smooth.html +++ b/function/smooth/17-smooth.html @@ -45,16 +45,6 @@
Tip: This node ONLY works with numbers.
- - diff --git a/hardware/Arduino/35-arduino.html b/hardware/Arduino/35-arduino.html index b492fa28..dd509759 100644 --- a/hardware/Arduino/35-arduino.html +++ b/hardware/Arduino/35-arduino.html @@ -24,14 +24,6 @@
- - - - + + diff --git a/hardware/PiFace/37-rpi-piface.html b/hardware/PiFace/37-rpi-piface.html index 9a2f7a59..e42989c0 100644 --- a/hardware/PiFace/37-rpi-piface.html +++ b/hardware/PiFace/37-rpi-piface.html @@ -1,5 +1,5 @@ - - - - diff --git a/hardware/PiLcd/locales/de/pilcd.json b/hardware/PiLcd/locales/de/pilcd.json new file mode 100644 index 00000000..b980cdc2 --- /dev/null +++ b/hardware/PiLcd/locales/de/pilcd.json @@ -0,0 +1,19 @@ +{ + "pilcd": { + "label": { + "pins": "Pins" + }, + "tip": { + "tip": "Tipp: Bei Pins muss eine Komma-getrennte Liste von 6 GPIO-Anschlusspin-Nummern eingetragen werden, die mit RS, E, D4, D5, D6 und D7 des LCD verbunden sind." + }, + "status": { + "not-available": "Nicht verfügbar", + "na": "Nicht anwendbar: __value__" + }, + "errors": { + "ignorenode": "Raspberry-Pi-spezifische Nodes inaktiv gesetzt", + "libnotfound": "RPi.GPIO-Python-Bibliothek nicht gefunden", + "needtobeexecutable": "__command__ muss ausführbar sein" + } + } +} diff --git a/hardware/PiLcd/locales/en-US/pilcd.html b/hardware/PiLcd/locales/en-US/pilcd.html index 966815b6..0211a4da 100644 --- a/hardware/PiLcd/locales/en-US/pilcd.html +++ b/hardware/PiLcd/locales/en-US/pilcd.html @@ -1,5 +1,11 @@ + + diff --git a/hardware/pigpiod/locales/de/pi-gpiod.json b/hardware/pigpiod/locales/de/pi-gpiod.json new file mode 100644 index 00000000..912ac260 --- /dev/null +++ b/hardware/pigpiod/locales/de/pi-gpiod.json @@ -0,0 +1,59 @@ +{ + "pi-gpiod": { + "label": { + "gpiopin": "GPIO", + "selectpin": "Pin-Auswahl", + "host": "Host", + "resistor": "Widerstand", + "readinitial": "Initalzustand bei Übernahme/Neustart lesen", + "type": "Typ", + "initpin": "Pin-Zustand initialisieren", + "debounce": "Entprellung", + "limits": "Limits", + "min": "min.", + "max": "max.", + "freq": "Frequenz" + }, + "place": { + "host": "Lokale oder entfernte IP", + "port": "Port" + }, + "resistor": { + "none": "Kein", + "pullup": "Pull-Up", + "pulldown": "Pull-Down" + }, + "digout": "Digitaler Ausgang", + "pwmout": "PWM-Ausgang", + "servo": "Servo-Ausgang", + "initpin0": "Initalzustand low (0)", + "initpin1": "Initalzustand high (1)", + "pinname": "Pin", + "tip": { + "pin": "Pins in Verwendung: ", + "in": "Nur digitaler Eingang unterstützt - Eingangszustand muss 0 oder 1 sein", + "dig": "Digitaler Ausgang - Node-Eingangswert muss 0 oder 1 sein", + "pwm": "PWM-Ausgang - Node-Eingangswert muss zwischen 0 und 100 sein", + "ser": "Servo-Ausgang - Node-Eingangswert muss zwischen 0 und 100 sein. 50 ist die Mitte.
Min. muss mindestens 500 µs und Max. muss 2500 µs oder weniger sein.", + "dual": "Blaue Pins sind mehrfach verwendbar. Es ist sicherzustellen, dass keine andere Verwendung aktiviert ist, um sie als GPIO zu verwenden." + }, + "types": { + "digout": "Digitaler Ausgang", + "input": "Eingang", + "pullup": "Eingang mit Pull-Up", + "pulldown": "Eingang mit Pull-Down", + "pwmout": "PWM-Ausgang", + "servo": "Servo-Ausgang" + }, + "status": { + "stopped": "Gestoppt", + "closed": "Geschlossen", + "not-running": "Nicht aktiv" + }, + "errors": { + "invalidpin": "Ungültiger GPIO-Pin", + "invalidinput": "Ungültige Eingabe", + "error": "FEHLER: __error__" + } + } +} diff --git a/hardware/pigpiod/locales/en-US/pi-gpiod.html b/hardware/pigpiod/locales/en-US/pi-gpiod.html new file mode 100644 index 00000000..84b017b2 --- /dev/null +++ b/hardware/pigpiod/locales/en-US/pi-gpiod.html @@ -0,0 +1,36 @@ + + + + diff --git a/hardware/pigpiod/locales/en-US/pi-gpiod.json b/hardware/pigpiod/locales/en-US/pi-gpiod.json index 5eaa33e6..047560e5 100644 --- a/hardware/pigpiod/locales/en-US/pi-gpiod.json +++ b/hardware/pigpiod/locales/en-US/pi-gpiod.json @@ -11,7 +11,8 @@ "debounce": "Debounce", "limits": "Limits", "min": "min", - "max": "max" + "max": "max", + "freq": "Frequency" }, "place": { "host": "local or remote ip", @@ -33,7 +34,8 @@ "in": "Only Digital Input is supported - input must be 0 or 1.", "dig": "Digital output - input must be 0 or 1.", "pwm": "PWM output - input must be between 0 to 100.", - "ser": "Servo output - input must be between 0 to 100. 50 is centre.
Min must be 500uS or more, Max must be 2500uS or less." + "ser": "Servo output - input must be between 0 to 100. 50 is centre.
min. must be 500 us or more, max. must be 2500 us or less.", + "dual": "Pins marked in blue are dual use. Make sure they are not enabled for their other use before using as GPIO." }, "types": { "digout": "digital output", diff --git a/hardware/pigpiod/package.json b/hardware/pigpiod/package.json index ffa0a018..0c07689e 100644 --- a/hardware/pigpiod/package.json +++ b/hardware/pigpiod/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-pi-gpiod", - "version": "0.2.0", + "version": "0.4.0", "description": "A node-red node for PiGPIOd", "dependencies" : { "js-pigpio": "*" diff --git a/hardware/pigpiod/pi-gpiod.html b/hardware/pigpiod/pi-gpiod.html index 86018771..113028a5 100644 --- a/hardware/pigpiod/pi-gpiod.html +++ b/hardware/pigpiod/pi-gpiod.html @@ -164,26 +164,9 @@
-
Pins marked in blue are dual use. Make sure they are not enabled for - their other use before using as GPIO.
+
- - - - + + diff --git a/hardware/sensehat/package.json b/hardware/sensehat/package.json index d893dfa8..7babe47e 100644 --- a/hardware/sensehat/package.json +++ b/hardware/sensehat/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-pi-sense-hat", - "version" : "0.0.18", + "version" : "0.1.0", "description" : "A Node-RED node to interact with a Raspberry Pi Sense HAT", "repository" : { "type":"git", diff --git a/hardware/sensehat/sensehat.html b/hardware/sensehat/sensehat.html index 41c42dd6..8de10721 100644 --- a/hardware/sensehat/sensehat.html +++ b/hardware/sensehat/sensehat.html @@ -1,132 +1,33 @@ - - - - - - @@ -66,7 +65,8 @@ }); - - - - - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/io/serialport/package.json b/io/serialport/package.json index aefb7123..caa00dd3 100644 --- a/io/serialport/package.json +++ b/io/serialport/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-serialport", - "version" : "0.11.1", + "version" : "0.13.0", "description" : "Node-RED nodes to talk to serial ports", "dependencies" : { - "serialport" : "^9.0.2" + "serialport" : "^9.0.7" }, "repository" : { "type":"git", diff --git a/io/stomp/18-stomp.html b/io/stomp/18-stomp.html index 4aab1258..e643807c 100644 --- a/io/stomp/18-stomp.html +++ b/io/stomp/18-stomp.html @@ -1,5 +1,5 @@ - - - - - + + + + \ No newline at end of file diff --git a/social/email/locales/de/61-email.json b/social/email/locales/de/61-email.json new file mode 100644 index 00000000..f990b48f --- /dev/null +++ b/social/email/locales/de/61-email.json @@ -0,0 +1,67 @@ +{ + "email": { + "email": "email", + "label": { + "getmail":"Mailempfang", + "auto": "automatisch", + "trigger": "wenn getriggert", + "to": "An", + "server": "Server", + "port": "Port", + "useSecureConnection": "Sichere Verbindung verwenden", + "userid": "Benutzer-ID", + "password": "Passwort", + "repeat": "alle", + "seconds": "Sekunden", + "folder": "Verzeichnis", + "protocol": "Protokoll", + "useSSL": "SSL", + "useTLS": "TLS", + "disposition": "Behandlung", + "none": "Keine", + "read": "Gelesen markieren", + "delete": "Löschen", + "criteria": "Kriterium", + "criteriaFromMsg": "Gesetzt durch msg.criteria", + "all": "Alle", + "answered": "Beantwortet", + "flagged": "Markiert", + "seen": "Gesichtet", + "unanswered": "Unbeantwortet", + "unflagged": "Unmarkiert", + "unseen": "Ungesehen" + }, + "default-message": "__description__\n\nDatei von Node-RED ist angehängt: __filename__", + "tip": { + "cred": "Hinweis: Berechtigungen von globaler emailkeys.js-Datei kopiert", + "recent": "Tipp: Es wird nur die letzte E-Mail abgerufen", + "mta": "Hinweis: Um Ports unter 1024 zu verwenden könnten höhere (root) Rechte benötigt werden. Siehe Hilfe-Seitenleiste." + }, + "status": { + "messagesent": "Nachricht gesendet: __response__", + "fetching": "Rufe ab", + "foldererror": "Fehler bei Verzeichnisabruf", + "messageerror": "Fehler bei Nachrichtenabruf", + "message": "Nachricht #__number__", + "newemail": "Neue E-Mail empfangen: __topic__", + "duplicate": "Duplikat nicht gesendet: __topic__", + "inboxzero": "Posteingang leer", + "sending": "Sende", + "sendfail": "Senden fehlgeschlagen", + "parseerror": "Analyse der Nachricht fehlgeschlagen", + "connecterror": "Verbindungsfehler" + }, + "errors": { + "nouserid": "Kein E-Mail-Benutzer-ID angegeben", + "nopassword": "Kein E-Mail-Passwort angegeben", + "nocredentials": "Keine E-Mail-Berechtigungen gefunden. Siehe Info-Anzeige.", + "nosmtptransport": "Kein SMTP-Transport. Siehe Info-Anzeige.", + "nopayload": "Keine sendbaren Nutzdaten (Payload)", + "fetchfail": "Verzeichnisabruf fehlgeschlagen: __folder__", + "parsefail": "Analyse der Nachricht fehlgeschlagen", + "messageerror": "Nachrichtenabruf fehlgeschlagen: __error__", + "refreshtoolarge": "Abrufintervall ist zu groß. Max. Limit sind 2147483 Sekunden.", + "invalidattachment": "Ungültiger Anhang-Inhalt. Es muss ein String oder ein Buffer sein." + } + } +} diff --git a/social/email/locales/en-US/61-email.html b/social/email/locales/en-US/61-email.html index 3806b8de..e0f5b392 100644 --- a/social/email/locales/en-US/61-email.html +++ b/social/email/locales/en-US/61-email.html @@ -6,11 +6,13 @@ msg.inReplyTo, msg.references, msg.headers, or msg.priority properties.

You may optionally set msg.from in the payload which will override the userid default value.

-

GMail users

-

If you are accessing GMail you may need to either enable an application password, +

Gmail users

+

If you are accessing Gmail you may need to either enable an application password, or enable less secure access via your Google account settings.

Details

-

The payload can be html format.

+

The payload can be html format. You may supply a separate plaintext version using msg.plaintext. + If you don't and msg.payload contains html, it will also be used for the plaintext. + msg.plaintext will be ignored if msg.payload doesn't contain html.

If the payload is a binary buffer then it will be converted to an attachment. The filename should be set using msg.filename. Optionally msg.description can be added for the body text.

Alternatively you may provide msg.attachments which should contain an array of one or @@ -21,7 +23,7 @@ diff --git a/social/feedparser/locales/de/32-feedparse.json b/social/feedparser/locales/de/32-feedparse.json new file mode 100644 index 00000000..4bbe5591 --- /dev/null +++ b/social/feedparser/locales/de/32-feedparse.json @@ -0,0 +1,15 @@ +{ + "feedparse": { + "feedparse": "feedparser", + "label": { + "feedurl": "Feed-URL", + "refresh": "Aktualisierung", + "minutes": "Minuten" + }, + "errors": { + "badstatuscode": "Fehler - Fehlerhafter Statuscode", + "invalidurl": "Ungültige URL", + "invalidinterval": "Aktualisierungsintervall zu groß" + } + } +} diff --git a/social/feedparser/locales/en-US/32-feedparse.html b/social/feedparser/locales/en-US/32-feedparse.html index 87189b64..0f27140d 100644 --- a/social/feedparser/locales/en-US/32-feedparse.html +++ b/social/feedparser/locales/en-US/32-feedparse.html @@ -1,7 +1,15 @@ diff --git a/social/feedparser/package.json b/social/feedparser/package.json index 5e658d02..34c69077 100644 --- a/social/feedparser/package.json +++ b/social/feedparser/package.json @@ -1,10 +1,10 @@ { "name": "node-red-node-feedparser", - "version": "0.1.16", + "version": "0.2.1", "description": "A Node-RED node to get RSS Atom feeds.", "dependencies": { "feedparser": "^2.2.10", - "request": "^2.88.0" + "request": "^2.88.2" }, "repository": { "type": "git", diff --git a/social/irc/91-irc.html b/social/irc/91-irc.html index 2849914d..beba8041 100644 --- a/social/irc/91-irc.html +++ b/social/irc/91-irc.html @@ -1,5 +1,5 @@ - - - - - - - - - + + diff --git a/social/irc/package.json b/social/irc/package.json index 5fcc70ca..dcb7a518 100644 --- a/social/irc/package.json +++ b/social/irc/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-irc", - "version" : "0.0.8", + "version" : "0.1.0", "description" : "A Node-RED node to talk to an IRC server", "dependencies" : { - "irc" : "~0.4.0" + "irc" : "^0.5.2" }, "repository" : { "type":"git", diff --git a/social/notify/57-notify.html b/social/notify/57-notify.html index fe3244a2..db576917 100644 --- a/social/notify/57-notify.html +++ b/social/notify/57-notify.html @@ -10,13 +10,6 @@ - - diff --git a/social/pushover/57-pushover.html b/social/pushover/57-pushover.html index dabbb4cc..942f2285 100644 --- a/social/pushover/57-pushover.html +++ b/social/pushover/57-pushover.html @@ -82,7 +82,10 @@

msg.attachment: attach an image (Buffer or file path)

msg.url: to add a web address

msg.url_title: to add a url title if not already set in the properties

+

msg.html: set to true or 1 if message is HTML formatted, see the supported tags

msg.sound: set the notification sound, see the available options

+

msg.retry: set retry interval for Emergency priority (2) messages, see details

+

msg.expire: set retry duration for Emergency priority (2) messages, see details

Uses Pushover. See this link for more details.

diff --git a/social/pushover/57-pushover.js b/social/pushover/57-pushover.js index 947528c1..2a197712 100644 --- a/social/pushover/57-pushover.js +++ b/social/pushover/57-pushover.js @@ -37,12 +37,30 @@ module.exports = function(RED) { var sound = node.sound || msg.sound || null; var url = node.url || msg.url || null; var url_title = node.url_title || msg.url_title || null; - var html = node.html || false; + var html = node.html || msg.html || false; var attachment = msg.attachment || null; + var retry = msg.retry || 30; + var expire = msg.expire || 600; if (isNaN(pri)) {pri=0;} if (pri > 2) {pri = 2;} if (pri < -2) {pri = -2;} - if (!msg.payload) { msg.payload = ""; } + if (isNaN(retry)) { + retry = 30; + node.warn("No valid number for retry found, using default 30s retry interval"); + } + if (isNaN(expire)) { + expire = 600; + node.warn("No valid number for expire time found, using default 600s retry duration"); + } + if (retry < 30) { + retry = 30; + node.warn("Retry interval too low, using minimal 30s retry interval"); + } + if (expire > 10800) { + expire = 10800; + node.warn("Expire time too high, using maximum setting of 10800s (3 hours) retry duration"); + } + if (typeof msg.payload === 'undefined') { msg.payload = "(undefined msg.payload)"; } if (typeof(msg.payload) === 'object') { msg.payload = JSON.stringify(msg.payload); } @@ -52,8 +70,8 @@ module.exports = function(RED) { message: msg.payload, title: title, priority: pri, - retry: 30, - expire: 600, + retry: retry, + expire: expire, html: html }; if (dev) { pushmsg.device = dev; } diff --git a/social/pushover/README.md b/social/pushover/README.md index fd948db6..55acef20 100644 --- a/social/pushover/README.md +++ b/social/pushover/README.md @@ -24,7 +24,10 @@ Optionally uses `msg.topic` to set the configuration, if not already set in the - `msg.attachment`: to specify an image to attach to message (path as a string or Buffer containing image) - `msg.url`: to add a web address - `msg.url_title`: to add a url title + - `msg.html`: set to true or 1 if message is HTML formatted, see the [supported tags](https://pushover.net/api#html) - `msg.sound`: to set the alert sound, see the [available options](https://pushover.net/api#sounds) + - `msg.retry`: to set retry interval for Emergency priority (2) messages, see [priority](https://pushover.net/api#priority) + - `msg.expire`: to set retry duration for Emergency priority (2) messages, see [priority](https://pushover.net/api#priority) The User-key and API-token are stored in a separate credentials file. diff --git a/social/pushover/package.json b/social/pushover/package.json index 6b39a6dc..d0aa3a3b 100644 --- a/social/pushover/package.json +++ b/social/pushover/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-pushover", - "version" : "0.0.20", + "version" : "0.0.24", "description" : "A Node-RED node to send alerts via Pushover", "dependencies" : { "pushover-notifications" : "^1.2.2" diff --git a/social/twitter/27-twitter.html b/social/twitter/27-twitter.html index 3d72a66c..57ea833b 100644 --- a/social/twitter/27-twitter.html +++ b/social/twitter/27-twitter.html @@ -1,4 +1,4 @@ - - - @@ -69,6 +90,10 @@ +
+ + +
@@ -79,7 +104,6 @@ @@ -95,6 +119,9 @@ join: {value:false}, sendObject: {value:false} }, + credentials: { + password: {type:"password"} + }, inputs:1, outputs:0, icon: "xmpp.png", @@ -104,6 +131,16 @@ }, labelStyle: function() { return (this.name)?"node_label_italic":""; + }, + oneditprepare: function() { + $('#node-input-join').change(function() { + if ($("#node-input-join").is(':checked') && $("#node-input-to").val() && $("#node-input-to").val().indexOf(':') === -1) { $("#node-room-pwd").show(); } + else { $("#node-room-pwd").hide(); } + }); + $('#node-input-to').change(function() { + if ($("#node-input-join").is(':checked') && $("#node-input-to").val() && $("#node-input-to").val().indexOf(':') === -1) { $("#node-room-pwd").show(); } + else { $("#node-room-pwd").hide(); } + }); } }); @@ -124,7 +161,7 @@
- +
diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 59e0ac36..6326c7a8 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -15,13 +15,13 @@ module.exports = function(RED) { if ("undefined" === typeof n.server || n.server === "") { this.server = n.user.split('@')[1]; } - else{ + else { this.server = n.server; } if ("undefined" === typeof n.port || n.port === "") { this.port = 5222; } - else{ + else { this.port = parseInt(n.port); } @@ -50,6 +50,8 @@ module.exports = function(RED) { this.connected = false; // store the nodes that have us as config so we know when to tear it all down. this.users = {}; + // store the chatrooms (MUC) that we've joined (sent "presence" XML to) already + this.MUCs = {}; // helper variable, because "this" changes definition inside a callback var that = this; @@ -57,7 +59,7 @@ module.exports = function(RED) { this.register = function(xmppThat) { if (RED.settings.verbose || LOGITALL) {that.log("registering "+xmppThat.id); } that.users[xmppThat.id] = xmppThat; - // So we could start the connection here, but we already have the logic in the thats. + // So we could start the connection here, but we already have the logic in the nodes that takes care of that. // if (Object.keys(that.users).length === 1) { // this.client.start(); // } @@ -73,7 +75,8 @@ module.exports = function(RED) { if (Object.keys(that.users).length === 0) { if (that.client && that.client.connected) { return that.client.stop(done); - } else { + } + else { return done(); } } @@ -82,6 +85,7 @@ module.exports = function(RED) { // store the last node to use us, in case we get an error back this.lastUsed = undefined; + // function for a node to tell us it has just sent a message to our server // so we know which node to blame if it all goes Pete Tong this.used = function(xmppThat) { @@ -89,11 +93,11 @@ module.exports = function(RED) { that.lastUsed = xmppThat; } - // Some errors come back as a message :-( // this means we need to figure out which node might have sent it // we also deal with subscriptions (i.e. presence information) here - this.client.on('stanza', async (stanza) =>{ + this.client.on('stanza', async (stanza) => { + //console.log("STANZA",stanza.toString()) if (stanza.is('message')) { if (stanza.attrs.type == 'error') { if (RED.settings.verbose || LOGITALL) { @@ -103,23 +107,27 @@ module.exports = function(RED) { var err = stanza.getChild('error'); if (err) { var textObj = err.getChild('text'); - var text = "node-red:common.status.error"; - if ("undefined" !== typeof textObj) { + var text = "error"; + if (typeof textObj !== "undefined") { text = textObj.getText(); } - else{ - textObj = err.getChild('code'); - if ("undefined" !== typeof textObj) { - text = textObj.getText(); + else { + textObj = err.getAttr('code'); + if (typeof textObj !== "undefined") { + text = textObj; } } - if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed); } - if ("undefined" !== typeof that.lastUsed) { - that.lastUsed.status({fill:"red",shape:"ring",text:text}); - that.lastUsed.warn(text); + if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed.id); } + if (typeof that.lastUsed !== "undefined") { + that.lastUsed.status({fill:"yellow",shape:"dot",text:"warning. "+text}); + that.lastUsed.warn("Warning. "+text); + if (that.lastUsed.join) { + // it was trying to MUC things up + clearMUC(that); + } } if (RED.settings.verbose || LOGITALL) { - that.log("We did wrong: "+text); + that.log("We did wrong: Error "+text); that.log(stanza); } @@ -144,6 +152,24 @@ module.exports = function(RED) { that.log("Was told we've "+stanza.attrs.type+" from "+stanza.attrs.from+" but we don't really care"); } } + if (stanza.attrs.to && stanza.attrs.to.indexOf(that.jid) !== -1) { + var _x = stanza.getChild("x") + if (_x !== undefined) { + var _stat = _x.getChildren("status"); + for (var i = 0; i<_stat.length; i++) { + if (_stat[i].attrs.code == 201) { + if (RED.settings.verbose || LOGITALL) {that.log("created new room"); } + var stanza = xml('iq', + {type:'set', id:that.id, from:that.jid, to:stanza.attrs.from.split('/')[0]}, + xml('query', 'http://jabber.org/protocol/muc#owner', + xml('x', {xmlns:'jabber:x:data', type:'submit'}) + ) + ); + that.client.send(stanza); + } + } + } + } } else if (stanza.is('iq')) { if (RED.settings.verbose || LOGITALL) {that.log("got an iq query"); } @@ -154,17 +180,9 @@ module.exports = function(RED) { that.lastUsed.warn(stanza.getChild('error')); } } - else if (stanza.attrs.type === 'result') { - // AM To-Do check for 'bind' result with our current jid - var query = stanza.getChild('query'); - if (RED.settings.verbose || LOGITALL) {that.log("result!"); } - if (RED.settings.verbose || LOGITALL) {that.log(query); } - - } } }); - // We shouldn't have any errors here that the input/output nodes can't handle // if you need to see everything though; uncomment this block // this.client.on('error', err => { @@ -189,8 +207,12 @@ module.exports = function(RED) { // gets called when the node is destroyed, e.g. if N-R is being stopped. this.on("close", async done => { - if (that.client.connected) { - await that.client.send(xml('presence', {type: 'unavailable'})); + const rooms = Object.keys(this.MUCs) + for (const room of rooms) { + await that.client.send(xml('presence', {to:room, type:'unavailable'})); + } + if (that.connected) { + await that.client.send(xml('presence', {type:'unavailable'})); try{ if (RED.settings.verbose || LOGITALL) { that.log("Calling stop() after close, status is "+that.client.status); @@ -211,6 +233,103 @@ module.exports = function(RED) { } }); + function getItems(thing,id,xmpp) { + // Now try to get a list of all items/conference rooms available on this server + var stanza = xml('iq', + {type:'get', id:id, to:thing}, + xml('query', 'http://jabber.org/protocol/disco#items') + ); + xmpp.send(stanza); + } + + function joinMUC(node, xmpp, name) { + // the presence with the muc x element signifies we want to join the muc + // if we want to support passwords, we need to add that as a child of the x element + // (third argument to the x/muc/children ) + // We also turn off chat history (maxstanzas 0) because that's not what this node is about. + // Yes, there's a race condition, but it's not a huge problem to send two messages + // so we don't care. + var mu = name.split("/")[0]; + if (mu in node.serverConfig.MUCs) { + if (RED.settings.verbose || LOGITALL) { node.log("already joined MUC "+name); } + } + else { + var stanza = xml('presence', + {"to":name}, + xml("x",'http://jabber.org/protocol/muc', + xml("history", {maxstanzas:0, seconds:1}) // We don't want any history + ) + ); + if (node.hasOwnProperty("credentials") && node.credentials.hasOwnProperty("password")) { + stanza = xml('presence', + {"to":name}, + xml("x",'http://jabber.org/protocol/muc', + xml("history", {maxstanzas:0, seconds:1}), // We don't want any history + xml("password", {}, node.credentials.password) // Add the password + ) + ); + } + node.serverConfig.used(node); + node.serverConfig.MUCs[mu] = "joined"; + if (RED.settings.verbose || LOGITALL) { node.log("JOINED "+mu); } + xmpp.send(stanza); + } + } + + function clearMUC(config) { + //something has happened, so clear out our presence indicators + if (RED.settings.verbose || LOGITALL) { + config.log("cleared all MUC membership"); + } + config.MUCs = {}; + } + + // separated out since we want the same functionality from both in and out nodes + function errorHandler(node, err){ + if (!node.quiet) { + node.quiet = true; + // if the error has a "stanza" then we've probably done something wrong and the + // server is unhappy with us + if (err.hasOwnProperty("stanza")) { + if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } + else { node.error(err.stanza.name,err); } + node.status({fill:"red",shape:"ring",text:"bad login"}); + } + // The error might be a string + else if (err == "TimeoutError") { + // OK, this happens with OpenFire, suppress it, but invalidate MUC membership as it will need to be re-established. + clearMUC(node.serverConfig); + node.status({fill:"grey",shape:"dot",text:"TimeoutError"}); + node.log("Timed out! ",err); + // node.status({fill:"red",shape:"ring",text:"XMPP timeout"}); + } + else if (err === "XMPP authentication failure") { + node.error(err,err); + node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); + } + // or it might have a name that tells us what's wrong + else if (err.name === "SASLError") { + node.error("Authorization error! "+err.condition,err); + node.status({fill:"red",shape:"ring",text:"XMPP authorization failure"}); + } + // or it might have the errno set. + else if (err.errno === "ETIMEDOUT") { + node.error("Timeout connecting to server",err); + node.status({fill:"red",shape:"ring",text:"timeout"}); + } + else if (err.errno === "ENOTFOUND") { + node.error("Server doesn't exist "+node.serverConfig.server,err); + node.status({fill:"red",shape:"ring",text:"bad address"}); + } + // nothing we've seen before! + else { + node.error("Unknown error: "+err,err); + node.status({fill:"red",shape:"ring",text:"error"}); + } + } + } + + function XmppInNode(n) { RED.nodes.createNode(this,n); this.server = n.server; @@ -219,9 +338,27 @@ module.exports = function(RED) { this.join = n.join || false; this.sendAll = n.sendObject; // Yes, it's called "from", don't ask me why; I don't know why - this.from = n.to || ""; + // (because it's where you are asking to get messages from...) + this.from = ((n.to || "").split(':')).map(s => s.trim()); + this.quiet = false; + this.subject = {}; + // MUC == Multi-User-Chat == chatroom + //this.muc = this.join && (this.from !== "") var node = this; + var joinrooms = function() { + if (node.from[0] === "") { + // try to get list of all rooms and join them all. + getItems(node.serverConfig.server, node.serverConfig.id, xmpp); + } + else { + // if we want to use a chatroom, we need to tell the server we want to join it + for (var i=0; i { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if ((node.join) && (node.from !== "")) { - var to = node.from+'/'+node.nick; - // the presence with the muc x element signifies we want to join the muc - // if we want to support passwords, we need to add that as a child of the x element - // (third argument to the x/muc/children ) - // We also turn off chat history (maxstanzas 0) because that's not what this node is about. - var stanza = xml('presence', - {"to": to}, - xml("x",'http://jabber.org/protocol/muc'), - { maxstanzas:0, seconds:1 } - ); - node.serverConfig.used(node); - xmpp.send(stanza).catch(error => {node.warn("Got error when sending presence: "+error)}); + node.quiet = false; + node.status({fill:"green",shape:"dot",text:"connected"}); + if (node.join) { + node.jointick = setInterval(function() { joinrooms(); }, 60000); + joinrooms(); } }); xmpp.on('connecting', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + if (!node.quiet) { + node.status({fill:"grey",shape:"dot",text:"connecting"}); + } }); xmpp.on('connect', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"grey",shape:"dot",text:"connected"}); }); xmpp.on('opening', async address => { node.status({fill:"grey",shape:"dot",text:"opening"}); @@ -271,7 +409,7 @@ module.exports = function(RED) { node.status({fill:"grey",shape:"dot",text:"closing"}); }); xmpp.on('close', async address => { - node.status({fill:"grey",shape:"dot",text:"closed"}); + node.status({fill:"grey",shape:"ring",text:"closed"}); }); xmpp.on('disconnecting', async address => { node.status({fill:"grey",shape:"dot",text:"disconnecting"}); @@ -281,57 +419,29 @@ module.exports = function(RED) { // Should we listen on other's status (chatstate) or a chatroom state (groupbuddy)? xmpp.on('error', err => { if (RED.settings.verbose || LOGITALL) { node.log("XMPP Error: "+err); } - if (err.hasOwnProperty("stanza")) { - if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } - else { node.error(err.stanza.name,err); } - node.status({fill:"red",shape:"ring",text:"bad login"}); - } - else { - if (err.errno === "ETIMEDOUT") { - node.error("Timeout connecting to server",err); - node.status({fill:"red",shape:"ring",text:"timeout"}); - } - if (err.errno === "ENOTFOUND") { - node.error("Server doesn't exist "+xmpp.options.service,err); - node.status({fill:"red",shape:"ring",text:"bad address"}); - } - else if (err === "XMPP authentication failure") { - node.error("Authentication failure! "+err,err); - node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); - } - else if (err.name === "SASLError") { - node.error("Authorization error! "+err.condition,err); - node.status({fill:"red",shape:"ring",text:"XMPP authorization failure"}); - } - else if (err == "TimeoutError") { - // Suppress it! - node.warn("Timed out! "); - node.status({fill:"grey",shape:"dot",text:"opening"}); - //node.status({fill:"red",shape:"ring",text:"XMPP timeout"}); - } - else { - node.error(err,err); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); - } - } + errorHandler(node, err); }); // Meat of it, a stanza object contains chat messages (and other things) - xmpp.on('stanza', async (stanza) =>{ - // node.log("Received stanza"); - if (RED.settings.verbose || LOGITALL) {node.log(stanza); } + xmpp.on('stanza', async (stanza) => { + if (RED.settings.verbose || LOGITALL) { node.log(stanza); } if (stanza.is('message')) { + var subj = stanza.getChild("subject"); + if (subj) { + subj = subj.getText(); + if (subj.trim() !== "") { node.subject[stanza.attrs.from.split('/')[0]] = subj; } + } if (stanza.attrs.type == 'chat') { var body = stanza.getChild('body'); if (body) { - var msg = { payload:body.getText() }; + var msg = { payload:body.getText(), subject:node.subject[stanza.attrs.from.split('/')[0]] }; var ids = stanza.attrs.from.split('/'); if (ids[1].length !== 36) { msg.topic = stanza.attrs.from } else { msg.topic = ids[0]; } - // if (RED.settings.verbose || LOGITALL) {node.log("Received a message from "+stanza.attrs.from); } - if (!node.join && ((node.from === "") || (node.from === stanza.attrs.to))) { + // if (RED.settings.verbose || LOGITALL) { node.log("Received a message from "+stanza.attrs.from); } + if (!node.join && ((node.from[0] === "") || (node.from.includes(stanza.attrs.to)))) { node.send([msg,null]); } } @@ -340,42 +450,74 @@ module.exports = function(RED) { const parts = stanza.attrs.from.split("/"); var conference = parts[0]; var from = parts[1]; + var msg = { topic:from, room:conference, subject:node.subject[stanza.attrs.from.split('/')[0]] }; var body = stanza.getChild('body'); - var payload = ""; - if ("undefined" !== typeof body) { - payload = body.getText(); - } - var msg = { topic:from, payload:payload, room:conference }; - if (stanza.attrs.from != node.nick) { - if ((node.join) && (node.from === conference)) { + if (typeof body !== "undefined") { + msg.payload = body.getText(); + //if (from && stanza.attrs.from != node.nick && from != node.nick) { + if (from && node.join && (node.from[0] === "" || node.from.includes(conference))) { node.send([msg,null]); } } + //} } } else if (stanza.is('presence')) { if (['subscribe','subscribed','unsubscribe','unsubscribed'].indexOf(stanza.attrs.type) > -1) { // this isn't for us, let the config node deal with it. - } - else{ + else { + if (stanza.attrs.type === 'error') { + var error = stanza.getChild('error'); + if (error.attrs.code) { + var reas = ""; + try { + reas = error.toString().split('><')[1].split(" xml")[0].trim(); + if (reas == "registration-required") { reas = "membership-required"; } + } + catch(e) {} + var msg = { + topic:stanza.attrs.from, + payload: { + code:error.attrs.code, + status:"error", + reason:reas, + name:node.serverConfig.MUCs[stanza.attrs.from.split('/')[0]] + } + }; + node.send([null,msg]); + node.status({fill:"red",shape:"ring",text:"error : "+error.attrs.code+", "+error.attrs.type+", "+reas}); + node.error(error.attrs.type+" error. "+error.attrs.code+" "+reas,msg); + } + } + + var state = stanza.getChild('show'); + if (state) { state = state.getText(); } + else { state = "available"; } var statusText=""; if (stanza.attrs.type === 'unavailable') { // the user might not exist, but the server doesn't tell us that! statusText = "offline"; + state = "offline"; } var status = stanza.getChild('status'); - if ("undefined" !== typeof status) { + if (typeof status !== "undefined") { statusText = status.getText(); } // right, do we care if there's no status? if (statusText !== "") { var from = stanza.attrs.from; - var state = stanza.attrs.show; - var msg = {topic:from, payload: {presence:state, status:statusText} }; + var msg = { + topic:from, + payload: { + presence:state, + status:statusText, + name:node.serverConfig.MUCs[stanza.attrs.from.split('/')[0]] + } + }; node.send([null,msg]); } - else{ + else { if (RED.settings.verbose || LOGITALL) { node.log("not propagating blank status"); node.log(stanza); @@ -383,6 +525,49 @@ module.exports = function(RED) { } } } + else if (stanza.attrs.type === 'result') { + // AM To-Do check for 'bind' result with our current jid + var query = stanza.getChild('query'); + if (RED.settings.verbose || LOGITALL) { this.log("result!"); } + if (RED.settings.verbose || LOGITALL) { this.log(query); } + + // handle query for list of rooms available + if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#items") { + var _items = stanza.getChild('query').getChildren('item'); + for (var i = 0; i<_items.length; i++) { + if ( _items[i].attrs.jid.indexOf('@') === -1 ) { + // if no @ in jid then it's probably the root or the room server so ask again + getItems(_items[i].attrs.jid,this.serverConfig.jid,xmpp); + } + else { + var name = _items[i].attrs.jid+'/'+node.serverConfig.username; + if (!(name in node.serverConfig.MUCs)) { + if (RED.settings.verbose || LOGITALL) { node.log("Need to Join room:"+name); } + joinMUC(node, xmpp, name); + node.serverConfig.MUCs[name.split('/')[0]] = _items[i].attrs.name.split('/')[0]; + } + else { + if (RED.settings.verbose || LOGITALL) { node.log("Already joined:"+name); } + } + } + } + } + if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#info") { + var fe = []; + var _items = stanza.getChild('query').getChildren('feature'); + for (var i = 0; i<_items.length; i++) { + fe.push(_items[i].attrs); + } + var id = [] + var _idents = stanza.getChild('query').getChildren('identity'); + for (var i = 0; i<_idents.length; i++) { + id.push(_idents[i].attrs); + } + var from = stanza.attrs.from; + var msg = {topic:from, payload: { identity:id, features:fe} }; + node.send([null,msg]); + } + } }); // xmpp.on('subscribe', from => { @@ -394,31 +579,38 @@ module.exports = function(RED) { // Now actually make the connection try { if (xmpp.status === "online") { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"green",shape:"dot",text:"connected"}); } - else{ - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + else { + node.status({fill:"grey",shape:"dot",text:"connecting"}); if (xmpp.status === "offline") { if (RED.settings.verbose || LOGITALL) { node.log("starting xmpp client"); } - xmpp.start().catch(error => {node.warn("Got error on start: "+error); node.warn("XMPP Status is now: "+xmpp.status)}); + xmpp.start().catch(error => { + node.warn("Got error on start: "+error); + node.warn("XMPP Status is now: "+xmpp.status) + }); } } } catch(e) { node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid); - node.warn(e); node.warn(e.stack); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + node.status({fill:"red",shape:"ring",text:"disconnected"}); } node.on("close", function(removed, done) { - node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + if (node.jointick) { clearInterval(node.jointick); } + node.status({fill:"grey",shape:"ring",text:"disconnected"}); node.serverConfig.deregister(node, done); }); } - RED.nodes.registerType("xmpp in",XmppInNode); + RED.nodes.registerType("xmpp in",XmppInNode,{ + credentials: { + password: {type: "password"} + } + }); function XmppOutNode(n) { @@ -429,6 +621,9 @@ module.exports = function(RED) { this.join = n.join || false; this.sendAll = n.sendObject; this.to = n.to || ""; + this.quiet = false; + // MUC == Multi-User-Chat == chatroom + this.muc = this.join && (this.to !== "") var node = this; var xmpp = this.serverConfig.client; @@ -446,30 +641,31 @@ module.exports = function(RED) { disconnect: Socket is disconnected */ + // if we're already connected, then do the actions now, otherwise register a callback + // if (xmpp.status === "online") { + // node.status({fill:"green",shape:"dot",text:"connected"}); + // if (node.muc){ + // // if we want to use a chatroom, we need to tell the server we want to join it + // joinMUC(node, xmpp, node.from+'/'+node.nick); + // } + // } + // sod it, register it anyway, that way things will work better on a reconnect: xmpp.on('online', function(data) { - node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); - if ((node.join) && (node.to !== "")) { - // disable chat history - var to = node.to+'/'+node.nick; - // the presence with the muc x element signifies we want to join the muc - // if we want to support passwords, we need to add that as a child of the x element - // (third argument to the x/muc/children ) - // We also turn off chat history (maxstanzas 0) because that's not what this node is about. - var stanza = xml('presence', - {"to": to}, - xml("x",'http://jabber.org/protocol/muc'), - { maxstanzas:0, seconds:1 } - ); - node.serverConfig.used(node); - xmpp.send(stanza); + node.quiet = false; + node.status({fill:"green",shape:"dot",text:"connected"}); + if (node.muc) { + // if we want to use a chatroom, we need to tell the server we want to join it + joinMUC(node, xmpp, node.from+'/'+node.nick); } }); xmpp.on('connecting', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + if (!node.quiet) { + node.status({fill:"grey",shape:"dot",text:"connecting"}); + } }); xmpp.on('connect', async address => { - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); + node.status({fill:"grey",shape:"dot",text:"connected"}); }); xmpp.on('opening', async address => { node.status({fill:"grey",shape:"dot",text:"opening"}); @@ -490,33 +686,30 @@ module.exports = function(RED) { xmpp.on('error', function(err) { if (RED.settings.verbose || LOGITALL) { node.log(err); } - if (err.hasOwnProperty("stanza")) { - if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } - else { node.error(err.stanza.name,err); } - node.status({fill:"red",shape:"ring",text:"bad login"}); - } - else { - if (err.errno === "ETIMEDOUT") { - node.error("Timeout connecting to server",err); - node.status({fill:"red",shape:"ring",text:"timeout"}); - } - else if (err.errno === "ENOTFOUND") { - node.error("Server doesn't exist "+xmpp.options.service,err); - node.status({fill:"red",shape:"ring",text:"bad address"}); - } - else if (err === "XMPP authentication failure") { - node.error(err,err); - node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); - } - else if (err == "TimeoutError") { - // OK, this happens with OpenFire, suppress it. - node.status({fill:"grey",shape:"dot",text:"opening"}); - node.log("Timed out! ",err); - // node.status({fill:"red",shape:"ring",text:"XMPP timeout"}); - } - else { - node.error("Unknown error: "+err,err); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); + errorHandler(node, err) + }); + + xmpp.on('stanza', async (stanza) => { + if (stanza.attrs.type === 'error') { + var error = stanza.getChild('error'); + if (error.attrs.code) { + var reas = ""; + try { + reas = error.toString().split('><')[1].split(" xml")[0].trim(); + if (reas == "registration-required") { reas = "membership-required"; } + } + catch(e) {} + var msg = { + topic:stanza.attrs.from, + payload: { + code:error.attrs.code, + status:"error", + reason:reas, + name:node.serverConfig.MUCs[stanza.attrs.from.split('/')[0]] + } + }; + node.status({fill:"red",shape:"ring",text:"error : "+error.attrs.code+", "+error.attrs.type+", "+reas}); + node.error(error.attrs.type+" error. "+error.attrs.code+" "+reas,msg); } } }); @@ -527,25 +720,24 @@ module.exports = function(RED) { if (xmpp.status === "online") { node.status({fill:"green",shape:"dot",text:"online"}); } - else{ - node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + else { + node.status({fill:"grey",shape:"dot",text:"connecting"}); if (xmpp.status === "offline") { xmpp.start().catch(error => { node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid); node.warn(error); node.warn(error.stack); - node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); + node.status({fill:"red",shape:"ring",text:"error"}); }); } } // Let's get down to business and actually send a message node.on("input", function(msg) { + var to = node.to || msg.topic || ""; if (msg.presence) { if (['away', 'dnd', 'xa', 'chat'].indexOf(msg.presence) > -1 ) { - var stanza = xml('presence', - {"show":msg.presence}, - xml('status',{},msg.payload)); + var stanza = xml('presence', {"show":msg.presence}, xml('status', {}, msg.payload)); node.serverConfig.used(node); xmpp.send(stanza); } @@ -553,35 +745,56 @@ module.exports = function(RED) { } else if (msg.command) { if (msg.command === "subscribe") { - var stanza = xml('presence', - {type:'subscribe', to: msg.payload}); + var stanza = xml('presence', {type:'subscribe', to:msg.payload}); node.serverConfig.used(node); xmpp.send(stanza); } else if (msg.command === "get") { - var to = node.to || msg.topic || ""; var stanza = xml('iq', - {type:'get', id:node.id, to: to}, + {type:'get', id:node.id, to:to}, xml('query', 'http://jabber.org/protocol/muc#admin', - xml('item',{affiliation:msg.payload}))); + xml('item', {affiliation:msg.payload}) + ) + ); node.serverConfig.used(node); - if (RED.settings.verbose || LOGITALL) {node.log("sending stanza "+stanza.toString()); } + if (RED.settings.verbose || LOGITALL) { node.log("sending stanza "+stanza.toString()); } + xmpp.send(stanza); + } + else if (msg.command === "info") { + var stanza = xml('iq', + {type:'get', id:node.id, to:to}, + xml('query', 'http://jabber.org/protocol/disco#info') + ); + node.serverConfig.used(node); + if (RED.settings.verbose || LOGITALL) { node.log("sending stanza "+stanza.toString()); } xmpp.send(stanza); } - } else { - var to = node.to || msg.topic || ""; if (to !== "") { var message; - var type = node.join? "groupchat":"chat"; + var type = "chat"; + if (node.join) { + // we want to connect to groupchat / chatroom / MUC + type = "groupchat"; + // joinMUC will do nothing if we're already joined + joinMUC(node, xmpp, to+'/'+node.nick); + } + if (msg.subject) { + var stanza = xml( + "message", + { type:type, to:to, from:node.serverConfig.jid }, + xml("subject", {}, msg.subject.toString()) + ); + node.serverConfig.used(node); + xmpp.send(stanza); + } if (node.sendAll) { message = xml( "message", { type: type, to: to }, xml("body", {}, JSON.stringify(msg)) ); - } else if (msg.payload) { if (typeof(msg.payload) === "object") { @@ -599,17 +812,23 @@ module.exports = function(RED) { ); } } - node.serverConfig.used(node); - xmpp.send(message); + if (message) { + node.serverConfig.used(node); + xmpp.send(message); + } } } }); node.on("close", function(removed, done) { - if (RED.settings.verbose || LOGITALL) {node.log("Closing"); } - node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + if (RED.settings.verbose || LOGITALL) { node.log("Closing"); } + node.status({fill:"grey",shape:"ring",text:"disconnected"}); node.serverConfig.deregister(node, done); }); } - RED.nodes.registerType("xmpp out",XmppOutNode); + RED.nodes.registerType("xmpp out",XmppOutNode,{ + credentials: { + password: {type: "password"} + } + }); } diff --git a/social/xmpp/package.json b/social/xmpp/package.json index 44381f89..7b611b78 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,10 +1,13 @@ { "name": "node-red-node-xmpp", - "version": "0.3.1", + "version": "0.5.1", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { - "@xmpp/client": "^0.11.1" + "@xmpp/client": "^0.12.0" }, + "bundledDependencies": [ + "@xmpp/client" + ], "repository": { "type": "git", "url": "https://github.com/node-red/node-red-nodes/tree/master/social/xmpp" diff --git a/storage/leveldb/67-leveldb.html b/storage/leveldb/67-leveldb.html index 60b75414..ae7969ce 100644 --- a/storage/leveldb/67-leveldb.html +++ b/storage/leveldb/67-leveldb.html @@ -6,7 +6,7 @@
- +
+
+ + +
+
@@ -266,20 +876,17 @@
- - - - diff --git a/utility/daemon/daemon.html b/utility/daemon/daemon.html index 8b89688c..1e99b8a9 100644 --- a/utility/daemon/daemon.html +++ b/utility/daemon/daemon.html @@ -49,16 +49,6 @@ All parameters should be passed in as arguments. - - diff --git a/utility/daemon/package.json b/utility/daemon/package.json index 308023c4..1cf49665 100644 --- a/utility/daemon/package.json +++ b/utility/daemon/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-daemon", - "version" : "0.1.2", + "version" : "0.2.1", "description" : "A Node-RED node that runs and monitors a long running system command.", "dependencies" : { }, diff --git a/utility/exif/94-exif.html b/utility/exif/94-exif.html index 3f7d99ac..7d5c23a9 100644 --- a/utility/exif/94-exif.html +++ b/utility/exif/94-exif.html @@ -17,17 +17,6 @@ - -