commit 5a308ad8ba1a6bf3b41fd29b4b60230faee9c871 Author: zhongjin Date: Sat Sep 22 00:10:53 2018 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..077d3aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +.idea +tmp +admin/i18n/flat.txt +admin/i18n/*/flat.txt +iob_npm.done +package-lock.json \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0f2ae68 --- /dev/null +++ b/.npmignore @@ -0,0 +1,9 @@ +gulpfile.js +tasks +tmp +test +.travis.yml +appveyor.yml +admin/i18n +iob_npm.done +package-lock.json \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..69b8d9d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +os: + - linux + - osx +language: node_js +node_js: + - '4' + - '6' + - '8' + - '10' +before_script: + - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) + - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi' + - npm -v + - npm install winston@2.3.1 + - 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production' +env: + - CXX=g++-4.8 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..02e0166 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2014-2018 bluefox + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..11db7a0 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +![Logo](admin/pushover.png) +# ioBroker pushover Adapter +============== + +[![NPM version](http://img.shields.io/npm/v/iobroker.pushover.svg)](https://www.npmjs.com/package/iobroker.pushover) +[![Downloads](https://img.shields.io/npm/dm/iobroker.pushover.svg)](https://www.npmjs.com/package/iobroker.pushover) + +[![NPM](https://nodei.co/npm/iobroker.pushover.png?downloads=true)](https://nodei.co/npm/iobroker.pushover/) + + +Send pushover notifications from ioBroker. + +## Configuration +First of all it is required an account on pushover. +![Pushover configuration](img/Screen0.png) + +![API Token](img/Screen1.png) + +![Group Token](img/Screen3.png) + +## Usage + +To send notification from ScriptEngine just write: + +```javascript +// send notification to all instances of pushover adapter +sendTo("pushover", "message body"); + +// send notification to specific instance of pushover adapter +sendTo("pushover.1", "message body"); + +// To specify subject or other options +sendTo("pushover", { + message: 'Test text', // mandatory - your text message + title: 'SweetHome', // optional - your message's title, otherwise your app's name is used + sound: 'magic', // optional - the name of one of the sounds supported by device clients to override the user's default sound choice + // pushover, bike, bugle, cashregister, classical, cosmic, falling, + // gamelan, incoming, intermission, magic, mechanical, pianobar, siren, + // spacealarm, tugboat, alien, climb, persistent, echo, updown, none + priority: -1, // optional + // -1 to always send as a quiet notification, + // 1 to display as high-priority and bypass the user's quiet hours, or + // 2 to also require confirmation from the user + token: 'API/KEY token' // optional + // add other than configurated token to the call +url, // optional - a supplementary URL to show with your message + url_title, // optional - a title for your supplementary URL, otherwise just the URL is shown + device, // optional - your user's device name to send the message directly to that device, rather than all of the user's devices + timestamp // optional - a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API +}); + +``` + +## Changelog +### 1.1.0 (2018-09-02) +* (bluefox) Admin3 is supported now + +### 1.0.4 (2017-10-22) +* (janhuddel) callback is now possible (to receive the receipt from pushover if you use priority = 2) + +### 1.0.3 (2017-10-21) +* (Tan-DE) Change priorities in blockly + +### 1.0.2 (2016-10-12) +* (bluefox) support of blockly + +### 1.0.1 (2016-08-28) +* (bluefox) filter out double messages + +### 1.0.0 (2016-06-01) +* (bluefox) fix timestamp +* (bluefox) update grunt packages + +### 0.1.1 (2015-05-03) +* (bluefox) add readme link + +### 0.1.0 (2015-01-03) +* (bluefox) enable npm install + +### 0.0.4 (2014-11-22) +* (bluefox) support of new naming concept + +### 0.0.3 (2014-10-08) +* (bluefox) add "daemon" mode to "subscribe" + +## License + +The MIT License (MIT) + +Copyright (c) 2014-2018 bluefox + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/admin/blockly.js b/admin/blockly.js new file mode 100644 index 0000000..5fc7673 --- /dev/null +++ b/admin/blockly.js @@ -0,0 +1,217 @@ +'use strict'; + +goog.provide('Blockly.JavaScript.Sendto'); + +goog.require('Blockly.JavaScript'); + +/// --- SendTo pushover -------------------------------------------------- +Blockly.Words['pushover'] = {'en': 'pushover', 'de': 'pushover', 'ru': 'pushover'}; +Blockly.Words['pushover_message'] = {'en': 'message', 'de': 'Meldung', 'ru': 'сообщение'}; +Blockly.Words['pushover_title'] = {'en': 'title (optional)', 'de': 'Betreff (optional)', 'ru': 'заголовок (не обяз.)'}; +Blockly.Words['pushover_sound'] = {'en': 'sound', 'de': 'Klang', 'ru': 'звук'}; +Blockly.Words['pushover_priority'] = {'en': 'priority', 'de': 'Priorität', 'ru': 'приоритет'}; +Blockly.Words['pushover_url'] = {'en': 'URL (optional)', 'de': 'URL (optional)', 'ru': 'URL (не обяз.)'}; +Blockly.Words['pushover_url_title'] = {'en': 'URL title (optional)', 'de': 'URL Betreff (optional)', 'ru': 'заголовок для URL (не обяз.)'}; +Blockly.Words['pushover_device'] = {'en': 'device ID (optional)', 'de': 'Gerät ID (optional)', 'ru': 'ID устройства (не обяз.)'}; +Blockly.Words['pushover_timestamp'] = {'en': 'time in ms (optional)', 'de': 'Zeit in ms (optional)', 'ru': 'время в мс (не обяз.)'}; +Blockly.Words['pushover_normal'] = {'en': 'default', 'de': 'Normal', 'ru': 'по умолчанию'}; +Blockly.Words['pushover_high'] = {'en': 'high priority', 'de': 'Hohe Priorität', 'ru': 'приоритетное'}; +Blockly.Words['pushover_quiet'] = {'en': 'quiet', 'de': 'Leise', 'ru': 'тихое'}; +Blockly.Words['pushover_confirmation'] = {'en': 'with confirmation', 'de': 'Mit Bestätigung', 'ru': 'с подтверждением'}; + +Blockly.Words['pushover_sound_default'] = {'en': 'default', 'de': 'normal', 'ru': 'по умолчанию'}; +Blockly.Words['pushover_sound_pushover'] = {'en': 'pushover', 'de': 'pushover', 'ru': 'pushover'}; +Blockly.Words['pushover_sound_bike'] = {'en': 'bike', 'de': 'bike', 'ru': 'bike'}; +Blockly.Words['pushover_sound_bugle'] = {'en': 'bugle', 'de': 'bugle', 'ru': 'bugle'}; +Blockly.Words['pushover_sound_cashregister'] = {'en': 'cashregister', 'de': 'cashregister', 'ru': 'cashregister'}; +Blockly.Words['pushover_sound_classical'] = {'en': 'classical', 'de': 'classical', 'ru': 'classical'}; +Blockly.Words['pushover_sound_cosmic'] = {'en': 'cosmic', 'de': 'cosmic', 'ru': 'cosmic'}; +Blockly.Words['pushover_sound_falling'] = {'en': 'falling', 'de': 'falling', 'ru': 'falling'}; +Blockly.Words['pushover_sound_gamelan'] = {'en': 'gamelan', 'de': 'gamelan', 'ru': 'gamelan'}; +Blockly.Words['pushover_sound_incoming'] = {'en': 'incoming', 'de': 'incoming', 'ru': 'incoming'}; +Blockly.Words['pushover_sound_intermission'] = {'en': 'intermission', 'de': 'intermission', 'ru': 'intermission'}; +Blockly.Words['pushover_sound_magic'] = {'en': 'magic', 'de': 'magic', 'ru': 'magic'}; +Blockly.Words['pushover_sound_mechanical'] = {'en': 'mechanical', 'de': 'mechanical', 'ru': 'mechanical'}; +Blockly.Words['pushover_sound_pianobar'] = {'en': 'pianobar', 'de': 'pianobar', 'ru': 'pianobar'}; +Blockly.Words['pushover_sound_siren'] = {'en': 'siren', 'de': 'siren', 'ru': 'siren'}; +Blockly.Words['pushover_sound_spacealarm'] = {'en': 'spacealarm', 'de': 'spacealarm', 'ru': 'spacealarm'}; +Blockly.Words['pushover_sound_tugboat'] = {'en': 'tugboat', 'de': 'tugboat', 'ru': 'tugboat'}; +Blockly.Words['pushover_sound_alien'] = {'en': 'alien', 'de': 'alien', 'ru': 'alien'}; +Blockly.Words['pushover_sound_climb'] = {'en': 'climb', 'de': 'climb', 'ru': 'climb'}; +Blockly.Words['pushover_sound_persistent'] = {'en': 'persistent', 'de': 'persistent', 'ru': 'persistent'}; +Blockly.Words['pushover_sound_echo'] = {'en': 'echo', 'de': 'echo', 'ru': 'echo'}; +Blockly.Words['pushover_sound_updown'] = {'en': 'updown', 'de': 'updown', 'ru': 'updown'}; +Blockly.Words['pushover_sound_none'] = {'en': 'none', 'de': 'keins', 'ru': 'без звука'}; + +Blockly.Words['pushover_log'] = {'en': 'log level', 'de': 'Loglevel', 'ru': 'Протокол'}; +Blockly.Words['pushover_log_none'] = {'en': 'none', 'de': 'keins', 'ru': 'нет'}; +Blockly.Words['pushover_log_info'] = {'en': 'info', 'de': 'info', 'ru': 'инфо'}; +Blockly.Words['pushover_log_debug'] = {'en': 'debug', 'de': 'debug', 'ru': 'debug'}; +Blockly.Words['pushover_log_warn'] = {'en': 'warning', 'de': 'warning', 'ru': 'warning'}; +Blockly.Words['pushover_log_error'] = {'en': 'error', 'de': 'error', 'ru': 'ошибка'}; + +Blockly.Words['pushover_anyInstance'] = {'en': 'all instances', 'de': 'Alle Instanzen', 'ru': 'На все драйвера'}; +Blockly.Words['pushover_tooltip'] = {'en': 'Send message to pushover', 'de': 'Sende eine Meldung über Telegram', 'ru': 'Послать сообщение через Pushover'}; +Blockly.Words['pushover_help'] = {'en': 'https://github.com/ioBroker/ioBroker.pushover/blob/master/README.md', 'de': 'https://github.com/ioBroker/ioBroker.pushover/blob/master/README.md', 'ru': 'https://github.com/ioBroker/ioBroker.pushover/blob/master/README.md'}; + +Blockly.Sendto.blocks['pushover'] = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' text' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + +Blockly.Blocks['pushover'] = { + init: function() { + this.appendDummyInput('INSTANCE') + .appendField(Blockly.Words['pushover'][systemLang]) + .appendField(new Blockly.FieldDropdown([[Blockly.Words['pushover_anyInstance'][systemLang], ""], ["pushover.0", ".0"], ["pushover.1", ".1"], ["pushover.2", ".2"], ["pushover.3", ".3"], ["pushover.4", ".4"]]), "INSTANCE"); + + this.appendValueInput('MESSAGE') + .appendField(Blockly.Words['pushover_message'][systemLang]); + + this.appendDummyInput('SOUND') + .appendField(Blockly.Words['pushover_sound'][systemLang]) + .appendField(new Blockly.FieldDropdown([ + [Blockly.Words['pushover_sound_default'][systemLang], ""], + [Blockly.Words['pushover_sound_pushover'][systemLang], "pushover"], + [Blockly.Words['pushover_sound_bike'][systemLang], "bike"], + [Blockly.Words['pushover_sound_bugle'][systemLang], "bugle"], + [Blockly.Words['pushover_sound_cashregister'][systemLang], "cashregister"], + [Blockly.Words['pushover_sound_classical'][systemLang], "classical"], + [Blockly.Words['pushover_sound_cosmic'][systemLang], "cosmic"], + [Blockly.Words['pushover_sound_falling'][systemLang], "falling"], + [Blockly.Words['pushover_sound_gamelan'][systemLang], "gamelan"], + [Blockly.Words['pushover_sound_incoming'][systemLang], "incoming"], + [Blockly.Words['pushover_sound_intermission'][systemLang], "intermission"], + [Blockly.Words['pushover_sound_magic'][systemLang], "magic"], + [Blockly.Words['pushover_sound_mechanical'][systemLang], "mechanical"], + [Blockly.Words['pushover_sound_pianobar'][systemLang], "pianobar"], + [Blockly.Words['pushover_sound_siren'][systemLang], "siren"], + [Blockly.Words['pushover_sound_spacealarm'][systemLang], "spacealarm"], + [Blockly.Words['pushover_sound_tugboat'][systemLang], "tugboat"], + [Blockly.Words['pushover_sound_alien'][systemLang], "alien"], + [Blockly.Words['pushover_sound_climb'][systemLang], "climb"], + [Blockly.Words['pushover_sound_persistent'][systemLang], "persistent"], + [Blockly.Words['pushover_sound_echo'][systemLang], "echo"], + [Blockly.Words['pushover_sound_updown'][systemLang], "updown"], + [Blockly.Words['pushover_sound_none'][systemLang], "none"] + ]), 'SOUND'); + + this.appendDummyInput('PRIORITY') + .appendField(Blockly.Words['pushover_priority'][systemLang]) + .appendField(new Blockly.FieldDropdown([ + [Blockly.Words['pushover_normal'][systemLang], "0"], + [Blockly.Words['pushover_high'][systemLang], "1"], + [Blockly.Words['pushover_quiet'][systemLang], "-1"], + [Blockly.Words['pushover_confirmation'][systemLang], "2"] + ]), 'PRIORITY'); + + var input = this.appendValueInput('TITLE') + .setCheck('String') + .appendField(Blockly.Words['pushover_title'][systemLang]); + if (input.connection) input.connection._optional = true; + + + input = this.appendValueInput("URL") + .setCheck('String') + .appendField(Blockly.Words['pushover_url'][systemLang]); + if (input.connection) input.connection._optional = true; + + input = this.appendValueInput('URL_TITLE') + .setCheck('String') + .appendField(Blockly.Words['pushover_url_title'][systemLang]); + if (input.connection) input.connection._optional = true; + + input = this.appendValueInput('DEVICE') + .setCheck('String') + .appendField(Blockly.Words['pushover_device'][systemLang]); + if (input.connection) input.connection._optional = true; + + input = this.appendValueInput('TIMESTAMP') + .setCheck('Date') + .appendField(Blockly.Words['pushover_timestamp'][systemLang]); + if (input.connection) input.connection._optional = true; + + this.appendDummyInput('LOG') + .appendField(Blockly.Words['pushover_log'][systemLang]) + .appendField(new Blockly.FieldDropdown([ + [Blockly.Words['pushover_log_none'][systemLang], ''], + [Blockly.Words['pushover_log_info'][systemLang], 'log'], + [Blockly.Words['pushover_log_debug'][systemLang], 'debug'], + [Blockly.Words['pushover_log_warn'][systemLang], 'warn'], + [Blockly.Words['pushover_log_error'][systemLang], 'error'] + ]), 'LOG'); + + this.setInputsInline(false); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + + this.setColour(Blockly.Sendto.HUE); + this.setTooltip(Blockly.Words['pushover_tooltip'][systemLang]); + this.setHelpUrl(Blockly.Words['pushover_help'][systemLang]); + } +}; + +Blockly.JavaScript['pushover'] = function(block) { + var dropdown_instance = block.getFieldValue('INSTANCE'); + var logLevel = block.getFieldValue('LOG'); + var message = Blockly.JavaScript.valueToCode(block, 'MESSAGE', Blockly.JavaScript.ORDER_ATOMIC); + var text = '{\n'; + text += ' message: ' + message + ',\n'; + text += ' sound: "' + block.getFieldValue('SOUND') + '",\n'; + var value = parseInt(block.getFieldValue('PRIORITY'), 10); + if (value) text += ' priority: ' + value + ',\n'; + if (value === 2) { + text += ' retry: 60,\n'; + text += ' expire: 3600,\n'; + } + + value = Blockly.JavaScript.valueToCode(block, 'URL', Blockly.JavaScript.ORDER_ATOMIC); + if (value) text += ' url: ' + value + ',\n'; + + value = Blockly.JavaScript.valueToCode(block, 'URL_TITLE', Blockly.JavaScript.ORDER_ATOMIC); + if (value) text += ' url_title: ' + value + ',\n'; + + value = Blockly.JavaScript.valueToCode(block, 'TITLE', Blockly.JavaScript.ORDER_ATOMIC); + if (value) text += ' title: ' + value + ',\n'; + + value = Blockly.JavaScript.valueToCode(block, 'DEVICE', Blockly.JavaScript.ORDER_ATOMIC); + if (value) text += ' device: ' + value + ',\n'; + + value = Blockly.JavaScript.valueToCode(block, 'TIMESTAMP', Blockly.JavaScript.ORDER_ATOMIC); + if (value) text += ' timestamp: ' + value + ',\n'; + text = text.substring(0, text.length - 2); + text += '\n'; + + text += '}'; + var logText; + + if (logLevel) { + logText = 'console.' + logLevel + '("pushover: " + ' + message + ');\n' + } else { + logText = ''; + } + + return 'sendTo("pushover' + dropdown_instance + '", "send", ' + text + ');\n' + logText; +}; diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 0000000..caee9d5 --- /dev/null +++ b/admin/index.html @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + +
+ + + + +

Pushover adapter settings

+ +

Pushover settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Group Key:Group Key of Delivery Group
Token:API Token/Key of Application

Notification settings

Title:
Sound:
Priority:
+ + +
+ + diff --git a/admin/index_m.html b/admin/index_m.html new file mode 100644 index 0000000..98f5875 --- /dev/null +++ b/admin/index_m.html @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+
+ + + Group Key of Delivery Group +
+
+
+
+ + + API Token/Key of Application +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+
+
+ + diff --git a/admin/pushover.png b/admin/pushover.png new file mode 100644 index 0000000..86746e5 Binary files /dev/null and b/admin/pushover.png differ diff --git a/admin/words.js b/admin/words.js new file mode 100644 index 0000000..5448c8a --- /dev/null +++ b/admin/words.js @@ -0,0 +1,23 @@ +// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n +/*global systemDictionary:true */ +'use strict'; + +systemDictionary = { + "Pushover adapter settings": { "en": "Pushover adapter settings", "de": "Pushover Adaptereinstellungen", "ru": "Настройки адаптера Pushover", "pt": "Configurações do adaptador pushover", "nl": "Pushover adapter-instellingen", "fr": "Paramètres de l'adaptateur de pushover", "it": "Impostazioni dell'adattatore pushover", "es": "Configuración del adaptador Pushover", "pl": "Ustawienia adaptera pushover"}, + "Pushover settings": { "en": "Pushover settings", "de": "Pushover Einstellungen", "ru": "Настройки Pushover", "pt": "Configurações pushover", "nl": "Pushover instellingen", "fr": "Paramètres de pushover", "it": "Impostazioni pushover", "es": "Configuración de Pushover", "pl": "Ustawienia pushover"}, + "Group Key": { "en": "Group Key", "de": "Gruppentaste", "ru": "Ключ группы", "pt": "Chave de grupo", "nl": "Groepssleutel", "fr": "Clé de groupe", "it": "Chiave di gruppo", "es": "Clave grupal", "pl": "Klucz grupowy"}, + "Token": { "en": "Token", "de": "Zeichen", "ru": "знак", "pt": "Símbolo", "nl": "blijk", "fr": "Jeton", "it": "Gettone", "es": "Simbólico", "pl": "Znak"}, + "Title": { "en": "Title", "de": "Titel", "ru": "заглавие", "pt": "Título", "nl": "Titel", "fr": "Titre", "it": "Titolo", "es": "Título", "pl": "Tytuł"}, + "Sound": { "en": "Sound", "de": "Klingen", "ru": "звук", "pt": "Som", "nl": "Geluid", "fr": "Du son", "it": "Suono", "es": "Sonar", "pl": "Dźwięk"}, + "Priority": { "en": "Priority", "de": "Priorität", "ru": "приоритет", "pt": "Prioridade", "nl": "Prioriteit", "fr": "Priorité", "it": "Priorità", "es": "Prioridad", "pl": "Priorytet"}, + "Test": { "en": "Test", "de": "Prüfung", "ru": "Тест", "pt": "Teste", "nl": "Test", "fr": "Tester", "it": "Test", "es": "Prueba", "pl": "Test"}, + "Notification settings": { "en": "Notification settings", "de": "Benachrichtigungseinstellungen", "ru": "Настройки уведомлений", "pt": "Configurações de notificação", "nl": "Notificatie instellingen", "fr": "Paramètres de notification", "it": "Impostazioni di notifica", "es": "Configuración de las notificaciones", "pl": "Ustawienia powiadomień"}, + "Group Key of Delivery Group": { "en": "Group Key of Delivery Group", "de": "Gruppenschlüssel der Liefergruppe", "ru": "Групповой ключ группы доставки", "pt": "Chave de Grupo do Grupo de Entrega", "nl": "Groepssleutel van leveringsgroep", "fr": "Group Key of Delivery Group", "it": "Gruppo chiave del gruppo di consegna", "es": "Grupo clave del grupo de entrega", "pl": "Klucz grupowy grupy dostaw"}, + "API Token/Key of Application": { "en": "API Token/Key of Application", "de": "API Token / Schlüssel der Anwendung", "ru": "API-токен / ключ приложения", "pt": "Token da API / chave de aplicação", "nl": "API-token / sleutel van de applicatie", "fr": "API Token / clé d'application", "it": "Token API / Chiave di applicazione", "es": "Token API / Clave de aplicación", "pl": "Token API / klucz aplikacji"}, + "quiet": { "en": "quiet", "de": "ruhig", "ru": "тихо", "pt": "quieto", "nl": "rustig", "fr": "silencieux", "it": "silenzioso", "es": "tranquilo", "pl": "cichy"}, + "default": { "en": "default", "de": "Standard", "ru": "по умолчанию", "pt": "padrão", "nl": "standaard", "fr": "défaut", "it": "predefinito", "es": "defecto", "pl": "domyślna"}, + "high-priority": { "en": "high-priority", "de": "hohe Priorität", "ru": "высокий приоритет", "pt": "prioridade máxima", "nl": "hoge prioriteit", "fr": "haute priorité", "it": "priorità alta", "es": "alta prioridad", "pl": "wysoki priorytet"}, + "Enable first the adapter to test notification.": {"en": "Enable first the adapter to test notification.", "de": "Aktivieren Sie zuerst den Adapter, um die Benachrichtigung zu testen.", "ru": "Включите сначала адаптер для проверки уведомления.", "pt": "Ative primeiro o adaptador para testar a notificação.", "nl": "Schakel eerst de adapter in om de melding te testen.", "fr": "Activez d'abord l'adaptateur pour tester la notification.", "it": "Abilitare prima l'adattatore per verificare la notifica.", "es": "Primero habilite el adaptador para probar la notificación.", "pl": "Najpierw włącz adapter, aby przetestować powiadomienie."}, + "First save the settings": { "en": "First save the settings", "de": "Speichern Sie zuerst die Einstellungen", "ru": "Сначала сохраните настройки", "pt": "Primeiro salve as configurações", "nl": "Sla eerst de instellingen op", "fr": "Commencez par enregistrer les paramètres", "it": "Prima salva le impostazioni", "es": "Primero guarde la configuración", "pl": "Najpierw zapisz ustawienia"}, + "Check the log or your pushover app!": { "en": "Check the log or your pushover app!", "de": "Überprüfen Sie das Protokoll oder Ihre Pushover-App!", "ru": "Проверьте журнал или приложение pushover!", "pt": "Verifique o log ou o seu aplicativo pushover!", "nl": "Controleer het logboek of je pushover-app!", "fr": "Consultez le journal ou votre application pushover!", "it": "Controlla il registro o la tua app pushover!", "es": "¡Comprueba el registro o tu aplicación de pushover!", "pl": "Sprawdź dziennik lub swoją aplikację pushover!"} +}; \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..84ca0a9 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,25 @@ +version: 'test-{build}' +environment: + matrix: + - nodejs_version: '4' + - nodejs_version: '6' + - nodejs_version: '8' + - nodejs_version: '10' +platform: + - x86 + - x64 +clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%' +install: + - ps: 'Install-Product node $env:nodejs_version $env:platform' + - ps: '$NpmVersion = (npm -v).Substring(0,1)' + - ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }' + - ps: npm --version + - npm install + - npm install winston@2.3.1 + - 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production' +test_script: + - echo %cd% + - node --version + - npm --version + - npm test +build: 'off' diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..fd48c48 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,401 @@ +'use strict'; + +var gulp = require('gulp'); +var fs = require('fs'); +var pkg = require('./package.json'); +var iopackage = require('./io-package.json'); +var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; +/*var appName = getAppName(); + +function getAppName() { + var parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 1].split('.')[0].toLowerCase(); +} +*/ +const fileName = 'words.js'; +var languages = { + en: {}, + de: {}, + ru: {}, + pt: {}, + nl: {}, + fr: {}, + it: {}, + es: {}, + pl: {} +}; + +function lang2data(lang, isFlat) { + var str = isFlat ? '' : '{\n'; + var count = 0; + for (var w in lang) { + if (lang.hasOwnProperty(w)) { + count++; + if (isFlat) { + str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n'; + } else { + var key = ' "' + w.replace(/"/g, '\\"') + '": '; + str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; + } + } + } + if (!count) return isFlat ? '' : '{\n}'; + if (isFlat) { + return str; + } else { + return str.substring(0, str.length - 2) + '\n}'; + } +} + +function readWordJs(src) { + try { + var words; + if (fs.existsSync(src + 'js/' + fileName)) { + words = fs.readFileSync(src + 'js/' + fileName).toString(); + } else { + words = fs.readFileSync(src + fileName).toString(); + } + + var lines = words.split(/\r\n|\r|\n/g); + var i = 0; + while (!lines[i].match(/^systemDictionary = {/)) { + i++; + } + lines.splice(0, i); + + // remove last empty lines + i = lines.length - 1; + while (!lines[i]) { + i--; + } + if (i < lines.length - 1) { + lines.splice(i + 1); + } + + lines[0] = lines[0].replace('systemDictionary = ', ''); + lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}'); + words = lines.join('\n'); + var resultFunc = new Function('return ' + words + ';'); + + return resultFunc(); + } catch (e) { + return null; + } +} +function padRight(text, totalLength) { + return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); +} +function writeWordJs(data, src) { + var text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n'; + text += '/*global systemDictionary:true */\n'; + text += '\'use strict\';\n\n'; + + text += 'systemDictionary = {\n'; + for (var word in data) { + if (data.hasOwnProperty(word)) { + text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); + var line = ''; + for (var lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; + } + } + if (line) { + line = line.trim(); + line = line.substring(0, line.length - 1); + } + text += line + '},\n'; + } + } + text = text.replace(/},\n$/, '}\n'); + text += '};'; + + if (fs.existsSync(src + 'js/' + fileName)) { + fs.writeFileSync(src + 'js/' + fileName, text); + } else { + fs.writeFileSync(src + '' + fileName, text); + } +} + +const EMPTY = ''; + +function words2languages(src) { + var langs = Object.assign({}, languages); + var data = readWordJs(src); + if (data) { + for (var word in data) { + if (data.hasOwnProperty(word)) { + for (var lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (var j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (var l in langs) { + if (!langs.hasOwnProperty(l)) continue; + var keys = Object.keys(langs[l]); + //keys.sort(); + var obj = {}; + for (var k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + if (!fs.existsSync(src + 'i18n/' + l)) { + fs.mkdirSync(src + 'i18n/' + l); + } + + fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); + } + } else { + console.error('Cannot read or parse ' + fileName); + } +} +function words2languagesFlat(src) { + var langs = Object.assign({}, languages); + var data = readWordJs(src); + if (data) { + for (var word in data) { + if (data.hasOwnProperty(word)) { + for (var lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (var j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + var keys = Object.keys(langs.en); + //keys.sort(); + for (var l in langs) { + if (!langs.hasOwnProperty(l)) continue; + var obj = {}; + for (var k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + langs[l] = obj; + } + if (!fs.existsSync(src + 'i18n/')) { + fs.mkdirSync(src + 'i18n/'); + } + for (var ll in langs) { + if (!langs.hasOwnProperty(ll)) continue; + if (!fs.existsSync(src + 'i18n/' + ll)) { + fs.mkdirSync(src + 'i18n/' + ll); + } + + fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en)); + } + fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n')); + } else { + console.error('Cannot read or parse ' + fileName); + } +} +function languagesFlat2words(src) { + var dirs = fs.readdirSync(src + 'i18n/'); + var langs = {}; + var bigOne = {}; + var order = Object.keys(languages); + dirs.sort(function (a, b) { + var posA = order.indexOf(a); + var posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) return 1; + if (a < b) return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) return 1; + if (posA < posB) return -1; + return 0; + } + }); + var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n'); + + for (var l = 0; l < dirs.length; l++) { + if (dirs[l] === 'flat.txt') continue; + var lang = dirs[l]; + var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n'); + langs[lang] = {}; + keys.forEach(function (word, i) { + langs[lang][word] = values[i].replace(/<\/ i>/g, '').replace(/<\/ b>/g, '').replace(/<\/ span>/g, '').replace(/% s/g, ' %s'); + }); + + var words = langs[lang]; + for (var word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + var aWords = readWordJs(); + + var temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt']; + if (aWords) { + // Merge words together + for (var w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual words.js: ' + w); + bigOne[w] = aWords[w] + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src); +} +function languages2words(src) { + var dirs = fs.readdirSync(src + 'i18n/'); + var langs = {}; + var bigOne = {}; + var order = Object.keys(languages); + dirs.sort(function (a, b) { + var posA = order.indexOf(a); + var posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) return 1; + if (a < b) return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) return 1; + if (posA < posB) return -1; + return 0; + } + }); + for (var l = 0; l < dirs.length; l++) { + if (dirs[l] === 'flat.txt') continue; + var lang = dirs[l]; + langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); + langs[lang] = JSON.parse(langs[lang]); + var words = langs[lang]; + for (var word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + var aWords = readWordJs(); + + var temporaryIgnore = ['pt', 'fr', 'nl', 'it']; + if (aWords) { + // Merge words together + for (var w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn('Take from actual words.js: ' + w); + bigOne[w] = aWords[w] + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + + } + + writeWordJs(bigOne, src); +} + +gulp.task('adminWords2languages', function (done) { + words2languages('./admin/'); + done(); +}); + +gulp.task('adminWords2languagesFlat', function (done) { + words2languagesFlat('./admin/'); + done(); +}); + +gulp.task('adminLanguagesFlat2words', function (done) { + languagesFlat2words('./admin/'); + done(); +}); + +gulp.task('adminLanguages2words', function (done) { + languages2words('./admin/'); + done(); +}); + + +gulp.task('updatePackages', function (done) { + iopackage.common.version = pkg.version; + iopackage.common.news = iopackage.common.news || {}; + if (!iopackage.common.news[pkg.version]) { + var news = iopackage.common.news; + var newNews = {}; + + newNews[pkg.version] = { + en: 'news', + de: 'neues', + ru: 'новое' + }; + iopackage.common.news = Object.assign(newNews, news); + } + fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); + done(); +}); + +gulp.task('updateReadme', function (done) { + var readme = fs.readFileSync('README.md').toString(); + var pos = readme.indexOf('## Changelog\n'); + if (pos !== -1) { + var readmeStart = readme.substring(0, pos + '## Changelog\n'.length); + var readmeEnd = readme.substring(pos + '## Changelog\n'.length); + + if (readme.indexOf(version) === -1) { + var timestamp = new Date(); + var date = timestamp.getFullYear() + '-' + + ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + + ('0' + (timestamp.getDate()).toString(10)).slice(-2); + + var news = ''; + if (iopackage.common.news && iopackage.common.news[pkg.version]) { + news += '* ' + iopackage.common.news[pkg.version].en; + } + + fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); + } + } + done(); +}); + +gulp.task('default', ['updatePackages', 'updateReadme']); \ No newline at end of file diff --git a/img/Screen0.png b/img/Screen0.png new file mode 100644 index 0000000..5576d1b Binary files /dev/null and b/img/Screen0.png differ diff --git a/img/Screen1.png b/img/Screen1.png new file mode 100644 index 0000000..8e04e1a Binary files /dev/null and b/img/Screen1.png differ diff --git a/img/Screen3.png b/img/Screen3.png new file mode 100644 index 0000000..e610f5d Binary files /dev/null and b/img/Screen3.png differ diff --git a/io-package.json b/io-package.json new file mode 100644 index 0000000..ca99381 --- /dev/null +++ b/io-package.json @@ -0,0 +1,90 @@ +{ + "common": { + "name": "pushover", + "version": "1.1.0", + "news": { + "1.1.0": { + "en": "Admin3 is supported now", + "de": "Admin3 wird jetzt unterstützt", + "ru": "Admin3 теперь поддерживается", + "pt": "Admin3 é suportado agora", + "nl": "Admin3 wordt nu ondersteund", + "fr": "Admin3 est pris en charge maintenant", + "it": "Admin3 è supportato ora", + "es": "Admin3 es compatible ahora", + "pl": "Admin3 jest teraz obsługiwany" + }, + "1.0.4": { + "en": "callback is now possible (to receive the receipt from pushover if you use priority = 2)", + "de": "Callback ist jetzt möglich (um den Beleg von pushover zu erhalten, wenn Sie Priorität = 2 verwenden)", + "ru": "теперь возможен обратный вызов (чтобы получить результат от pushover, если вы используете приоритет = 2)" + }, + "1.0.3": { + "en": "Change priorities in blockly", + "de": "Ändere Prioritäten in Blockly", + "ru": "Изменены значения приоритетов в blockly" + }, + "1.0.2": { + "en": "support of blockly", + "de": "Blockly-Unterstützung", + "ru": "Поддержка blockly" + }, + "1.0.1": { + "en": "filter out double messages", + "de": "Ausfiltern doppelte Meldungen", + "ru": "Отфильтровывать двойные сообщения" + }, + "1.0.0": { + "en": "fix timestamp\nupdate grunt packages", + "de": "fix timestamp\nupdate grunt packages", + "ru": "fix timestamp\nupdate grunt packages" + } + }, + "title": "Pushover", + "desc": { + "en": "This adapter allows to send pushover notifications from ioBroker", + "de": "Dieser Adapter ermöglicht das Senden von Pushover-Benachrichtigungen von ioBroker", + "ru": "Этот адаптер позволяет отправлять pushover-уведомления от ioBroker", + "pt": "Este adaptador permite enviar notificações pushover do ioBroker", + "nl": "Met deze adapter kunt u pushover-meldingen van ioBroker verzenden", + "fr": "Cet adaptateur permet d'envoyer des notifications de transfert depuis ioBroker", + "it": "Questo adattatore consente di inviare notifiche pushover da ioBroker", + "es": "Este adaptador permite enviar notificaciones fáciles de ioBroker", + "pl": "Ten adapter umożliwia wysyłanie powiadomień push z programu ioBroker" + }, + "authors": [ + "bluefox " + ], + "license": "MIT", + "platform": "Javascript/Node.js", + "mode": "daemon", + "availableModes": [ + "daemon", + "subscribe" + ], + "loglevel": "info", + "messagebox": true, + "materialize": true, + "subscribe": "messagebox", + "readme": "https://github.com/ioBroker/ioBroker.pushover/blob/master/README.md", + "wakeup": true, + "enabled": true, + "icon": "pushover.png", + "keywords": [ + "notification", + "pushover", + "message" + ], + "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.pushover/master/admin/pushover.png", + "type": "messaging", + "blockly": true + }, + "native": { + "user": "xxxxx", + "token": "xxxxx", + "title": "ioBroker", + "sound": "", + "priority": 0 + }, + "objects": [] +} \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..c8a0eb7 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,83 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +let controllerDir; +let appName; + +/** + * returns application name + * + * The name of the application can be different and this function finds it out. + * + * @returns {string} + */ + function getAppName() { + const parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 2].split('.')[0]; +} + +/** + * looks for js-controller home folder + * + * @param {boolean} isInstall + * @returns {string} + */ +function getControllerDir(isInstall) { + // Find the js-controller location + const possibilities = [ + 'iobroker.js-controller', + 'ioBroker.js-controller', + ]; + /** @type {string} */ + let controllerPath; + for (const pkg of possibilities) { + try { + const possiblePath = require.resolve(pkg); + if (fs.existsSync(possiblePath)) { + controllerPath = possiblePath; + break; + } + } catch (e) { /* not found */ } + } + if (controllerPath == null) { + if (!isInstall) { + console.log('Cannot find js-controller'); + process.exit(10); + } else { + process.exit(); + } + } + // we found the controller + return path.dirname(controllerPath); +} + +/** + * reads controller base settings + * + * @alias getConfig + * @returns {object} + */ + function getConfig() { + let configPath; + if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', appName + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else { + throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json'); + } +} +appName = getAppName(); +controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1); +const adapter = require(path.join(controllerDir, 'lib/adapter.js')); + +exports.controllerDir = controllerDir; +exports.getConfig = getConfig; +exports.Adapter = adapter; +exports.appName = appName; diff --git a/main.js b/main.js new file mode 100644 index 0000000..057973e --- /dev/null +++ b/main.js @@ -0,0 +1,139 @@ +/** + * + * ioBroker pushover Adapter + * + * (c) 2014-2018 bluefox + * + * MIT License + * + */ + +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +'use strict'; +const utils = require(__dirname + '/lib/utils'); // Get common adapter utils +const Pushover = require('pushover-notifications'); + +const adapter = new utils.Adapter('pushover'); + +adapter.on('message', obj => { + if (obj && obj.command === 'send') { + processMessage(obj); + } + processMessages(); +}); + +adapter.on('ready', main); + +let stopTimer = null; +let pushover; +let lastMessageTime = 0; +let lastMessageText = ''; + +// Terminate adapter after 30 seconds idle +function stop() { + if (stopTimer) { + clearTimeout(stopTimer); + } + + // Stop only if subscribe mode + if (adapter.common && adapter.common.mode === 'subscribe') { + stopTimer = setTimeout(() => { + stopTimer = null; + adapter.stop(); + }, 30000); + } +} + +function processMessage(obj) { + if (!obj || !obj.message) return; + + // filter out double messages + const json = JSON.stringify(obj.message); + if (lastMessageTime && lastMessageText === JSON.stringify(obj.message) && new Date().getTime() - lastMessageTime < 1000) { + adapter.log.debug('Filter out double message [first was for ' + (new Date().getTime() - lastMessageTime) + 'ms]: ' + json); + return; + } + lastMessageTime = new Date().getTime(); + lastMessageText = json; + + if (stopTimer) clearTimeout(stopTimer); + + sendNotification(obj.message, (err, response) => { + if (obj.callback) adapter.sendTo(obj.from, 'send', { error: err, response: response}, obj.callback); + }); + + stop(); +} + +function processMessages() { + adapter.getMessage((err, obj) => { + if (obj) { + processMessage(obj); + processMessages(); + } + }); +} + +function main() { + // Adapter is started only if some one writes into "system.adapter.pushover.X.messagebox" new value + processMessages(); + stop(); +} + +function sendNotification(message, callback) { + if (!message) message = {}; + + if (!pushover) { + if (adapter.config.user && adapter.config.token) { + pushover = new Pushover({ + user: adapter.config.user, + token: adapter.config.token + }); + } else { + adapter.log.error('Cannot send notification while not configured'); + } + } + + if (!pushover) return; + + if (typeof message !== 'object') { + message = {message: message}; + } + if (message.hasOwnProperty('token')) { + pushover.token = message.token + } else { + pushover.token = adapter.config.token + } + message.title = message.title || adapter.config.title; + message.sound = message.sound || (adapter.config.sound ? adapter.config.sound : undefined); + message.priority = message.priority || adapter.config.priority; + message.url = message.url || adapter.config.url; + message.url_title = message.url_title || adapter.config.url_title; + message.device = message.device || adapter.config.device; + message.message = message.message || ''; + + // if timestamp in ms => make seconds // if greater than 2000.01.01 00:00:00 + if (message.timestamp && message.timestamp > 946681200000) { + message.timestamp = Math.round(message.timestamp / 1000); + } + + // mandatory parameters if priority is high (2) + if (message.priority === 2) { + message.retry = parseInt(message.retry, 10) || 60; + message.expire = parseInt(message.expire, 10) || 3600; + } + + adapter.log.info('Send pushover notification: ' + JSON.stringify(message)); + + pushover.send(message, (err, result) => { + if (err) { + adapter.log.error('Cannot send notification: ' + JSON.stringify(err)); + if (callback) callback(err); + return false; + } else { + if (callback) callback(null, result); + return true; + } + }); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..db01752 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "iobroker.pushover", + "description": "This adapter allows to send pushover notifications from ioBroker", + "version": "1.1.0", + "author": "bluefox ", + "contributors": [ + "bluefox " + ], + "homepage": "https://github.com/ioBroker/ioBroker.pushover", + "license": "MIT", + "keywords": [ + "ioBroker", + "pushover", + "home automation" + ], + "repository": { + "type": "git", + "url": "https://github.com/ioBroker/ioBroker.pushover" + }, + "dependencies": { + "pushover-notifications": "^1.0.0" + }, + "devDependencies": { + "gulp": "^3.9.1", + "mocha": "^5.2.0", + "chai": "^4.1.2" + }, + "bugs": { + "url": "https://github.com/ioBroker/ioBroker.pushover/issues" + }, + "main": "main.js", + "scripts": { + "test": "node node_modules/mocha/bin/mocha --exit" + } +} diff --git a/test/lib/setup.js b/test/lib/setup.js new file mode 100644 index 0000000..16857ed --- /dev/null +++ b/test/lib/setup.js @@ -0,0 +1,728 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +// check if tmp directory exists +var fs = require('fs'); +var path = require('path'); +var child_process = require('child_process'); +var rootDir = path.normalize(__dirname + '/../../'); +var pkg = require(rootDir + 'package.json'); +var debug = typeof v8debug === 'object'; +pkg.main = pkg.main || 'main.js'; + +var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/'); +adapterName = adapterName[adapterName.length - 2]; +var adapterStarted = false; + +function getAppName() { + var parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 3].split('.')[0]; +} + +var appName = getAppName().toLowerCase(); + +var objects; +var states; + +var pid = null; + +function copyFileSync(source, target) { + + var targetFile = target; + + //if target is a directory a new file with the same name will be created + if (fs.existsSync(target)) { + if ( fs.lstatSync( target ).isDirectory() ) { + targetFile = path.join(target, path.basename(source)); + } + } + + try { + fs.writeFileSync(targetFile, fs.readFileSync(source)); + } + catch (err) { + console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)"); + } +} + +function copyFolderRecursiveSync(source, target, ignore) { + var files = []; + + var base = path.basename(source); + if (base === adapterName) { + base = pkg.name; + } + //check if folder needs to be created or integrated + var targetFolder = path.join(target, base); + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + //copy + if (fs.lstatSync(source).isDirectory()) { + files = fs.readdirSync(source); + files.forEach(function (file) { + if (ignore && ignore.indexOf(file) !== -1) { + return; + } + + var curSource = path.join(source, file); + var curTarget = path.join(targetFolder, file); + if (fs.lstatSync(curSource).isDirectory()) { + // ignore grunt files + if (file.indexOf('grunt') !== -1) return; + if (file === 'chai') return; + if (file === 'mocha') return; + copyFolderRecursiveSync(curSource, targetFolder, ignore); + } else { + copyFileSync(curSource, curTarget); + } + }); + } +} + +if (!fs.existsSync(rootDir + 'tmp')) { + fs.mkdirSync(rootDir + 'tmp'); +} + +function storeOriginalFiles() { + console.log('Store original files...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) { + objects['system.adapter.admin.0'].common.enabled = false; + } + if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) { + objects['system.adapter.admin.1'].common.enabled = false; + } + + fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects)); + try { + f = fs.readFileSync(dataDir + 'states.json'); + fs.writeFileSync(dataDir + 'states.json.original', f); + } + catch (err) { + console.log('no states.json found - ignore'); + } +} + +function restoreOriginalFiles() { + console.log('restoreOriginalFiles...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json.original'); + fs.writeFileSync(dataDir + 'objects.json', f); + try { + f = fs.readFileSync(dataDir + 'states.json.original'); + fs.writeFileSync(dataDir + 'states.json', f); + } + catch (err) { + console.log('no states.json.original found - ignore'); + } + +} + +function checkIsAdapterInstalled(cb, counter, customName) { + customName = customName || pkg.name.split('.').pop(); + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + console.log('checkIsAdapterInstalled...'); + + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.' + customName + '.0']) { + console.log('checkIsAdapterInstalled: ready!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } else { + console.warn('checkIsAdapterInstalled: still not ready'); + } + } catch (err) { + + } + + if (counter > 20) { + console.error('checkIsAdapterInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsAdapterInstalled: wait...'); + setTimeout(function() { + checkIsAdapterInstalled(cb, counter + 1); + }, 1000); + } +} + +function checkIsControllerInstalled(cb, counter) { + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + console.log('checkIsControllerInstalled...'); + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0']) { + console.log('checkIsControllerInstalled: installed!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } + } catch (err) { + + } + + if (counter > 20) { + console.log('checkIsControllerInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsControllerInstalled: wait...'); + setTimeout(function() { + checkIsControllerInstalled(cb, counter + 1); + }, 1000); + } +} + +function installAdapter(customName, cb) { + if (typeof customName === 'function') { + cb = customName; + customName = null; + } + customName = customName || pkg.name.split('.').pop(); + console.log('Install adapter...'); + var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js'; + // make first install + if (debug) { + child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + } else { + // add controller + var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + + waitForEnd(_pid, function () { + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + }); + } +} + +function waitForEnd(_pid, cb) { + if (!_pid) { + cb(-1, -1); + return; + } + _pid.on('exit', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); + _pid.on('close', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); +} + +function installJsController(cb) { + console.log('installJsController...'); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || + !fs.existsSync(rootDir + 'tmp/' + appName + '-data')) { + // try to detect appName.js-controller in node_modules/appName.js-controller + // travis CI installs js-controller into node_modules + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"'); + // copy all + // stop controller + console.log('Stop controller if running...'); + var _pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js stop', { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // copy all files into + if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp'); + if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules'); + + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){ + console.log('Copy js-controller...'); + copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/'); + } + + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js setup first --console', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + __pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + waitForEnd(__pid, function () { + checkIsControllerInstalled(function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + console.log('Setup finished.'); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }); + } else { + // check if port 9000 is free, else admin adapter will be added to running instance + var client = new require('net').Socket(); + client.connect(9000, '127.0.0.1', function() { + console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.'); + process.exit(0); + }); + + setTimeout(function () { + client.destroy(); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => install from git'); + + child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', { + cwd: rootDir + 'tmp/', + stdio: [0, 1, 2] + }); + } else { + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + child_process.exec('node ' + appName + '.js setup first', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + child_process.fork(appName + '.js', ['setup', 'first'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + } + + // let npm install admin and run setup + checkIsControllerInstalled(function () { + var _pid; + + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }, 1000); + } + } else { + setTimeout(function () { + console.log('installJsController: js-controller installed'); + if (cb) cb(false); + }, 0); + } +} + +function copyAdapterToController() { + console.log('Copy adapter...'); + // Copy adapter to tmp/node_modules/appName.adapter + copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']); + console.log('Adapter copied.'); +} + +function clearControllerLog() { + var dirPath = rootDir + 'tmp/log'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear controller log...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Controller log cleared'); + } catch (err) { + console.error('cannot clear log: ' + err); + } + } +} + +function clearDB() { + var dirPath = rootDir + 'tmp/iobroker-data/sqlite'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear sqlite DB...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Clear sqlite DB'); + } catch (err) { + console.error('cannot clear DB: ' + err); + } + } +} + +function setupController(cb) { + installJsController(function (isInited) { + clearControllerLog(); + clearDB(); + + if (!isInited) { + restoreOriginalFiles(); + copyAdapterToController(); + } + // read system.config object + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var objs; + try { + objs = fs.readFileSync(dataDir + 'objects.json'); + objs = JSON.parse(objs); + } + catch (e) { + console.log('ERROR reading/parsing system configuration. Ignore'); + objs = {'system.config': {}}; + } + if (!objs || !objs['system.config']) { + objs = {'system.config': {}}; + } + + if (cb) cb(objs['system.config']); + }); +} + +function startAdapter(objects, states, callback) { + if (adapterStarted) { + console.log('Adapter already started ...'); + if (callback) callback(objects, states); + return; + } + adapterStarted = true; + console.log('startAdapter...'); + if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) { + try { + if (debug) { + // start controller + pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + } else { + // start controller + pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + } + } catch (error) { + console.error(JSON.stringify(error)); + } + } else { + console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main); + } + if (callback) callback(objects, states); +} + +function startController(isStartAdapter, onObjectChange, onStateChange, callback) { + if (typeof isStartAdapter === 'function') { + callback = onStateChange; + onStateChange = onObjectChange; + onObjectChange = isStartAdapter; + isStartAdapter = true; + } + + if (onStateChange === undefined) { + callback = onObjectChange; + onObjectChange = undefined; + } + + if (pid) { + console.error('Controller is already started!'); + } else { + console.log('startController...'); + adapterStarted = false; + var isObjectConnected; + var isStatesConnected; + + var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer'); + objects = new Objects({ + connection: { + "type" : "file", + "host" : "127.0.0.1", + "port" : 19001, + "user" : "", + "pass" : "", + "noFileCache": false, + "connectTimeout": 2000 + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.warn(msg); + }, + error: function (msg) { + console.error(msg); + } + }, + connected: function () { + isObjectConnected = true; + if (isStatesConnected) { + console.log('startController: started!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onObjectChange + }); + + // Just open in memory DB itself + var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer'); + states = new States({ + connection: { + type: 'file', + host: '127.0.0.1', + port: 19000, + options: { + auth_pass: null, + retry_max_delay: 15000 + } + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.log(msg); + }, + error: function (msg) { + console.log(msg); + } + }, + connected: function () { + isStatesConnected = true; + if (isObjectConnected) { + console.log('startController: started!!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onStateChange + }); + } +} + +function stopAdapter(cb) { + if (!pid) { + console.error('Controller is not running!'); + if (cb) { + setTimeout(function () { + cb(false); + }, 0); + } + } else { + adapterStarted = false; + pid.on('exit', function (code, signal) { + if (pid) { + console.log('child process terminated due to receipt of signal ' + signal); + if (cb) cb(); + pid = null; + } + }); + + pid.on('close', function (code, signal) { + if (pid) { + if (cb) cb(); + pid = null; + } + }); + + pid.kill('SIGTERM'); + } +} + +function _stopController() { + if (objects) { + objects.destroy(); + objects = null; + } + if (states) { + states.destroy(); + states = null; + } +} + +function stopController(cb) { + var timeout; + if (objects) { + console.log('Set system.adapter.' + pkg.name + '.0'); + objects.setObject('system.adapter.' + pkg.name + '.0', { + common:{ + enabled: false + } + }); + } + + stopAdapter(function () { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + + _stopController(); + + if (cb) { + cb(true); + cb = null; + } + }); + + timeout = setTimeout(function () { + timeout = null; + console.log('child process NOT terminated'); + + _stopController(); + + if (cb) { + cb(false); + cb = null; + } + pid = null; + }, 5000); +} + +// Setup the adapter +function setAdapterConfig(common, native, instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + if (common) objects[id].common = common; + if (native) objects[id].native = native; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects)); +} + +// Read config of the adapter +function getAdapterConfig(instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + return objects[id]; +} + +if (typeof module !== undefined && module.parent) { + module.exports.getAdapterConfig = getAdapterConfig; + module.exports.setAdapterConfig = setAdapterConfig; + module.exports.startController = startController; + module.exports.stopController = stopController; + module.exports.setupController = setupController; + module.exports.stopAdapter = stopAdapter; + module.exports.startAdapter = startAdapter; + module.exports.installAdapter = installAdapter; + module.exports.appName = appName; + module.exports.adapterName = adapterName; + module.exports.adapterStarted = adapterStarted; +} diff --git a/test/testAdapter.js b/test/testAdapter.js new file mode 100644 index 0000000..ae9c289 --- /dev/null +++ b/test/testAdapter.js @@ -0,0 +1,140 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +var expect = require('chai').expect; +var setup = require(__dirname + '/lib/setup'); + +var objects = null; +var states = null; +var onStateChanged = null; +var onObjectChanged = null; +var sendToID = 1; + +var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1); + +function checkConnectionOfAdapter(cb, counter) { + counter = counter || 0; + console.log('Try check #' + counter); + if (counter > 30) { + if (cb) cb('Cannot check connection'); + return; + } + + states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) { + if (err) console.error(err); + if (state && state.val) { + if (cb) cb(); + } else { + setTimeout(function () { + checkConnectionOfAdapter(cb, counter + 1); + }, 1000); + } + }); +} + +function checkValueOfState(id, value, cb, counter) { + counter = counter || 0; + if (counter > 20) { + if (cb) cb('Cannot check value Of State ' + id); + return; + } + + states.getState(id, function (err, state) { + if (err) console.error(err); + if (value === null && !state) { + if (cb) cb(); + } else + if (state && (value === undefined || state.val === value)) { + if (cb) cb(); + } else { + setTimeout(function () { + checkValueOfState(id, value, cb, counter + 1); + }, 500); + } + }); +} + +function sendTo(target, command, message, callback) { + onStateChanged = function (id, state) { + if (id === 'messagebox.system.adapter.test.0') { + callback(state.message); + } + }; + + states.pushMessage('system.adapter.' + target, { + command: command, + message: message, + from: 'system.adapter.test.0', + callback: { + message: message, + id: sendToID++, + ack: false, + time: (new Date()).getTime() + } + }); +} + +describe('Test ' + adapterShortName + ' adapter', function() { + before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) { + this.timeout(600000); // because of first install from npm + + setup.setupController(function () { + var config = setup.getAdapterConfig(); + // enable adapter + config.common.enabled = true; + config.common.loglevel = 'debug'; + + //config.native.dbtype = 'sqlite'; + + setup.setAdapterConfig(config.common, config.native); + + setup.startController(true, function(id, obj) {}, function (id, state) { + if (onStateChanged) onStateChanged(id, state); + }, + function (_objects, _states) { + objects = _objects; + states = _states; + _done(); + }); + }); + }); + +/* + ENABLE THIS WHEN ADAPTER RUNS IN DEAMON MODE TO CHECK THAT IT HAS STARTED SUCCESSFULLY +*/ + it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) { + this.timeout(60000); + checkConnectionOfAdapter(function (res) { + if (res) console.log(res); + expect(res).not.to.be.equal('Cannot check connection'); + objects.setObject('system.adapter.test.0', { + common: { + + }, + type: 'instance' + }, + function () { + states.subscribeMessage('system.adapter.test.0'); + done(); + }); + }); + }); +/**/ + +/* + PUT YOUR OWN TESTS HERE USING + it('Testname', function ( done) { + ... + }); + + You can also use "sendTo" method to send messages to the started adapter +*/ + + after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) { + this.timeout(10000); + + setup.stopController(function (normalTerminated) { + console.log('Adapter normal terminated: ' + normalTerminated); + done(); + }); + }); +}); diff --git a/test/testPackageFiles.js b/test/testPackageFiles.js new file mode 100644 index 0000000..c600a60 --- /dev/null +++ b/test/testPackageFiles.js @@ -0,0 +1,91 @@ +/* jshint -W097 */ +/* jshint strict:false */ +/* jslint node: true */ +/* jshint expr: true */ +var expect = require('chai').expect; +var fs = require('fs'); + +describe('Test package.json and io-package.json', function() { + it('Test package files', function (done) { + console.log(); + + var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8'); + var ioPackage = JSON.parse(fileContentIOPackage); + + var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8'); + var npmPackage = JSON.parse(fileContentNPMPackage); + + expect(ioPackage).to.be.an('object'); + expect(npmPackage).to.be.an('object'); + + expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; + expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; + + expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); + + if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) { + console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); + console.log(); + } + + expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist; + expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist; + + if (ioPackage.common.name.indexOf('template') !== 0) { + if (Array.isArray(ioPackage.common.authors)) { + expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); + if (ioPackage.common.authors.length === 1) { + expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); + console.log(); + } + expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; + if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') { + console.log('WARNING: titleLang is not existing in io-package.json. Please add'); + console.log(); + } + if ( + ioPackage.common.title.indexOf('iobroker') !== -1 || + ioPackage.common.title.indexOf('ioBroker') !== -1 || + ioPackage.common.title.indexOf('adapter') !== -1 || + ioPackage.common.title.indexOf('Adapter') !== -1 + ) { + console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.'); + console.log(); + } + + if (ioPackage.common.name.indexOf('vis-') !== 0) { + if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) { + console.log('WARNING: Admin3 support is missing! Please add it'); + console.log(); + } + if (ioPackage.common.materialize) { + expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; + } + } + + var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE'); + var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8'); + if (fileContentReadme.indexOf('## Changelog') === -1) { + console.log('Warning: The README.md should have a section ## Changelog'); + console.log(); + } + expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; + if (!licenseFileExists) { + console.log('Warning: The License should also exist as LICENSE file'); + console.log(); + } + if (fileContentReadme.indexOf('## License') === -1) { + console.log('Warning: The README.md should also have a section ## License to be shown in Admin3'); + console.log(); + } + done(); + }); +});