├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── test-and-release.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── admin ├── admin.d.ts ├── divera247.png ├── divera247_long.png ├── index_m.html ├── style.css ├── tsconfig.json └── words.js ├── gulpfile.js ├── io-package.json ├── lib ├── adapter-config.d.ts └── tools.js ├── main.js ├── main.test.js ├── package-lock.json ├── package.json ├── test ├── integration.js ├── mocha.setup.js ├── mocharc.custom.json ├── package.js ├── tsconfig.json └── unit.js ├── tsconfig.check.json └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "es6": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended" 10 | ], 11 | "plugins": [], 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | "tab", 16 | { 17 | "SwitchCase": 1 18 | } 19 | ], 20 | "no-console": "off", 21 | "no-var": "error", 22 | "no-trailing-spaces": "error", 23 | "prefer-const": "error", 24 | "quotes": [ 25 | "error", 26 | "single", 27 | { 28 | "avoidEscape": true, 29 | "allowTemplateLiterals": true 30 | } 31 | ], 32 | "semi": [ 33 | "error", 34 | "always" 35 | ] 36 | }, 37 | "parserOptions": { 38 | "ecmaVersion": 2018 39 | } 40 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is not working as it should 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '...' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots & Logfiles** 23 | If applicable, add screenshots and logfiles to help explain your problem. 24 | 25 | **Versions:** 26 | - Adapter version: 27 | - JS-Controller version: 28 | - Node version: 29 | - Operating system: 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Test and Release 2 | 3 | # Run this job on all pushes and pull requests 4 | # as well as tags with a semantic version 5 | on: 6 | push: 7 | branches: 8 | - "*" 9 | tags: 10 | # normal versions 11 | - "v[0-9]+.[0-9]+.[0-9]+" 12 | # pre-releases 13 | - "v[0-9]+.[0-9]+.[0-9]+-**" 14 | pull_request: {} 15 | 16 | jobs: 17 | # Performs quick checks before the expensive test runs 18 | check-and-lint: 19 | if: contains(github.event.head_commit.message, '[skip ci]') == false 20 | 21 | runs-on: ubuntu-latest 22 | 23 | strategy: 24 | matrix: 25 | node-version: [14.x] 26 | 27 | steps: 28 | - name: Checkout code 29 | uses: actions/checkout@v2 30 | 31 | - name: Use Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v1 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | 36 | - name: Install Dependencies 37 | run: npm ci 38 | 39 | - name: Lint source code 40 | run: npm run lint 41 | - name: Test package files 42 | run: npm run test:package 43 | 44 | # Runs adapter tests on all supported node versions and OSes 45 | adapter-tests: 46 | if: contains(github.event.head_commit.message, '[skip ci]') == false 47 | 48 | needs: [check-and-lint] 49 | 50 | runs-on: ${{ matrix.os }} 51 | strategy: 52 | matrix: 53 | node-version: [12.x, 14.x, 16.x] 54 | os: [ubuntu-latest, windows-latest, macos-latest] 55 | 56 | steps: 57 | - name: Checkout code 58 | uses: actions/checkout@v2 59 | 60 | - name: Use Node.js ${{ matrix.node-version }} 61 | uses: actions/setup-node@v1 62 | with: 63 | node-version: ${{ matrix.node-version }} 64 | 65 | - name: Install Dependencies 66 | run: npm ci 67 | 68 | - name: Run unit tests 69 | run: npm run test:unit 70 | 71 | - name: Run integration tests (unix only) 72 | if: startsWith(runner.OS, 'windows') == false 73 | run: DEBUG=testing:* npm run test:integration 74 | 75 | - name: Run integration tests (windows only) 76 | if: startsWith(runner.OS, 'windows') 77 | run: set DEBUG=testing:* & npm run test:integration 78 | 79 | # TODO: To enable automatic npm releases, create a token on npmjs.org 80 | # Enter this token as a GitHub secret (with name NPM_TOKEN) in the repository options 81 | # Then uncomment the following block: 82 | 83 | # # Deploys the final package to NPM 84 | # deploy: 85 | # needs: [adapter-tests] 86 | # 87 | # # Trigger this step only when a commit on any branch is tagged with a version number 88 | # if: | 89 | # contains(github.event.head_commit.message, '[skip ci]') == false && 90 | # github.event_name == 'push' && 91 | # startsWith(github.ref, 'refs/tags/v') 92 | # 93 | # runs-on: ubuntu-latest 94 | # strategy: 95 | # matrix: 96 | # node-version: [14.x] 97 | # 98 | # steps: 99 | # - name: Checkout code 100 | # uses: actions/checkout@v2 101 | # 102 | # - name: Use Node.js ${{ matrix.node-version }} 103 | # uses: actions/setup-node@v1 104 | # with: 105 | # node-version: ${{ matrix.node-version }} 106 | # 107 | # - name: Extract the version and commit body from the tag 108 | # id: extract_release 109 | # # The body may be multiline, therefore newlines and % need to be escaped 110 | # run: | 111 | # VERSION="${{ github.ref }}" 112 | # VERSION=${VERSION##*/v} 113 | # echo "::set-output name=VERSION::$VERSION" 114 | # BODY=$(git show -s --format=%b) 115 | # BODY="${BODY//'%'/'%25'}" 116 | # BODY="${BODY//$'\n'/'%0A'}" 117 | # BODY="${BODY//$'\r'/'%0D'}" 118 | # echo "::set-output name=BODY::$BODY" 119 | # 120 | # - name: Publish package to npm 121 | # run: | 122 | # npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} 123 | # npm whoami 124 | # npm publish 125 | # 126 | # - name: Create Github Release 127 | # uses: actions/create-release@v1 128 | # env: 129 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 130 | # with: 131 | # tag_name: ${{ github.ref }} 132 | # release_name: Release v${{ steps.extract_release.outputs.VERSION }} 133 | # draft: false 134 | # # Prerelease versions create prereleases on Github 135 | # prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }} 136 | # body: ${{ steps.extract_release.outputs.BODY }} 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | .vscode/ 4 | *.code-workspace 5 | node_modules 6 | nbproject 7 | 8 | # npm package files 9 | iobroker.*.tgz 10 | 11 | Thumbs.db 12 | 13 | # i18n intermediate files 14 | admin/i18n/flat.txt 15 | admin/i18n/*/flat.txt -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | node_modules/ 3 | nbproject/ 4 | *.code-workspace 5 | Thumbs.db 6 | gulpfile.js 7 | 8 | # CI test files 9 | test/ 10 | travis/ 11 | appveyor.yaml 12 | 13 | # Type checking configuration 14 | tsconfig.json 15 | tsconfig.*.json 16 | 17 | # npm package files 18 | iobroker.*.tgz 19 | package-lock.json 20 | 21 | # i18n intermediate files 22 | admin/i18n 23 | 24 | # maintenance scripts 25 | maintenance/** -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | - windows 5 | language: node_js 6 | node_js: 7 | - '12' 8 | - '14' 9 | - '16' 10 | env: 11 | - CXX=g++-6 12 | addons: 13 | apt: 14 | sources: 15 | - ubuntu-toolchain-r-test 16 | packages: 17 | - g++-6 18 | before_install: 19 | - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then CC=gcc-6; fi' 20 | - 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then CC=g++-6; fi' 21 | before_script: 22 | - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) 23 | - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm; fi' 24 | - npm -v 25 | script: 26 | - 'npm run test:package' 27 | - 'npm run test:unit' 28 | - 'export DEBUG=testing:*' 29 | - 'npm run test:integration' 30 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 TKnpl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](admin/divera247_long.png) 2 | # ioBroker.divera247 3 | 4 | [![NPM version](http://img.shields.io/npm/v/iobroker.divera247.svg)](https://www.npmjs.com/package/iobroker.divera247) 5 | [![Downloads](https://img.shields.io/npm/dm/iobroker.divera247.svg)](https://www.npmjs.com/package/iobroker.divera247) 6 | ![Number of Installations (latest)](http://iobroker.live/badges/divera247-installed.svg) 7 | ![Number of Installations (stable)](http://iobroker.live/badges/divera247-stable.svg) 8 | [![Known Vulnerabilities](https://snyk.io/test/github/TKnpl/ioBroker.divera247/badge.svg)](https://snyk.io/test/github/TKnpl/ioBroker.divera247) 9 | 10 | [![NPM](https://nodei.co/npm/iobroker.divera247.png?downloads=true)](https://nodei.co/npm/iobroker.divera247/) 11 | 12 | **Tests:** ![Test and Release](https://github.com/TKnpl/ioBroker.divera247/workflows/Test%20and%20Release/badge.svg) 13 | 14 | ## divera247 adapter for ioBroker 15 | 16 | Adapter for the alerting service Divera 24/7 17 | 18 | ## Requirements 19 | For full usability of this adapter your organisation has to subscribe the "Alarm" plan of Divera 24/7 services in minimum otherwise, the adapter will not work or will not work completely. 20 | 21 | ## Configuartion of this adapter 22 | You have to enter your "Divera 24/7" login crendentials to this adapter. 23 | 24 | Furthermore you can restrict the alarms on specific users or alarm groups. 25 | For this you have to enter the Divera user IDs or alarm group numbers into the admin page of this adapter. Several user IDs and / or alarm group numbers can be specifyed seperated by comma (,). 26 | This adapter checks first the userIDs befor it checks the groups. The first hit will trigger the alarm and update all states. A combination of userID and alarm group is currently not possible. 27 | 28 | To subscribe **all alarms**, just leave the input fields empty. 29 | 30 | ## Changelog 31 | 32 | ### 0.2.0 33 | * (TKnpl) complete renewal of the adapter 34 | 35 | ### 0.1.3 36 | * (TKnpl) general revision of the adapter 37 | 38 | ### 0.1.2 39 | * (TKnpl) added alarmed vehicles datapoint 40 | 41 | ### 0.1.1 42 | * (TKnpl) small changes - wording 43 | 44 | ### 0.1.0 45 | * (TKnpl) added possibility to specify alarm groups 46 | 47 | ### 0.0.10 48 | * (TKnpl) bug in info.connection fixed and handling of user ids expanded 49 | 50 | ### 0.0.9 51 | * (TKnpl) added default values for admin page 52 | 53 | ### 0.0.8 54 | * (TKnpl) Changed API call from intervall to timeout, added states 'group' and 'foreign_id' 55 | 56 | ### 0.0.7 57 | * (TKnpl) added object 'priority' and 'alarm' object updates only in case of an new alarm or when an alarm was closed 58 | 59 | ### 0.0.6 60 | * (TKnpl) state handling while active alarm and connection check improved, fixed object types 61 | 62 | ### 0.0.5 63 | * (TKnpl) fixed io-package news issue 64 | 65 | ### 0.0.4 66 | * (TKnpl) Connection check to api improved, added timestamp of latest alert 67 | 68 | ### 0.0.3 69 | * (TKnpl) added title, text, address, latitude, longitude, general formatting 70 | 71 | ### 0.0.2 72 | * (TKnpl) adjusted translation 73 | 74 | ### 0.0.1 75 | * (TKnpl) initial commit 76 | 77 | ## License 78 | MIT License 79 | 80 | Copyright (c) 2022 TKnpl 81 | 82 | Permission is hereby granted, free of charge, to any person obtaining a copy 83 | of this software and associated documentation files (the "Software"), to deal 84 | in the Software without restriction, including without limitation the rights 85 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 86 | copies of the Software, and to permit persons to whom the Software is 87 | furnished to do so, subject to the following conditions: 88 | 89 | The above copyright notice and this permission notice shall be included in all 90 | copies or substantial portions of the Software. 91 | 92 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 93 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 94 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 95 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 96 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 97 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 98 | SOFTWARE. 99 | -------------------------------------------------------------------------------- /admin/admin.d.ts: -------------------------------------------------------------------------------- 1 | declare let systemDictionary: Record>; 2 | 3 | declare let load: (settings: Record, onChange: (hasChanges: boolean) => void) => void; 4 | declare let save: (callback: (settings: Record) => void) => void; 5 | 6 | // make load and save exist on the window object 7 | interface Window { 8 | load: typeof load; 9 | save: typeof save; 10 | } 11 | 12 | declare const instance: number; 13 | declare const adapter: string; 14 | /** Translates text */ 15 | declare function _(text: string): string; 16 | declare const socket: ioBrokerSocket; 17 | declare function sendTo( 18 | instance: any | null, 19 | command: string, 20 | message: any, 21 | callback: (result: SendToResult) => void | Promise, 22 | ): void; 23 | 24 | interface SendToResult { 25 | error?: string | Error; 26 | result?: any; 27 | } 28 | 29 | // tslint:disable-next-line:class-name 30 | interface ioBrokerSocket { 31 | emit( 32 | command: 'subscribeObjects', 33 | pattern: string, 34 | callback?: (err?: string) => void | Promise, 35 | ): void; 36 | emit( 37 | command: 'subscribeStates', 38 | pattern: string, 39 | callback?: (err?: string) => void | Promise, 40 | ): void; 41 | emit( 42 | command: 'unsubscribeObjects', 43 | pattern: string, 44 | callback?: (err?: string) => void | Promise, 45 | ): void; 46 | emit( 47 | command: 'unsubscribeStates', 48 | pattern: string, 49 | callback?: (err?: string) => void | Promise, 50 | ): void; 51 | 52 | emit( 53 | event: 'getObjectView', 54 | view: 'system', 55 | type: 'device', 56 | options: ioBroker.GetObjectViewParams, 57 | callback: ( 58 | err: string | undefined, 59 | result?: any, 60 | ) => void | Promise, 61 | ): void; 62 | emit( 63 | event: 'getStates', 64 | callback: ( 65 | err: string | undefined, 66 | result?: Record, 67 | ) => void, 68 | ): void; 69 | emit( 70 | event: 'getState', 71 | id: string, 72 | callback: (err: string | undefined, result?: ioBroker.State) => void, 73 | ): void; 74 | emit( 75 | event: 'setState', 76 | id: string, 77 | state: unknown, 78 | callback: (err: string | undefined, result?: any) => void, 79 | ): void; 80 | 81 | on(event: 'objectChange', handler: ioBroker.ObjectChangeHandler): void; 82 | on(event: 'stateChange', handler: ioBroker.StateChangeHandler): void; 83 | removeEventHandler( 84 | event: 'objectChange', 85 | handler: ioBroker.ObjectChangeHandler, 86 | ): void; 87 | removeEventHandler( 88 | event: 'stateChange', 89 | handler: ioBroker.StateChangeHandler, 90 | ): void; 91 | 92 | // TODO: other events 93 | } 94 | -------------------------------------------------------------------------------- /admin/divera247.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TKnpl/ioBroker.divera247/ffb45badf4efefe472ae9b108c4dcb9b743b89fe/admin/divera247.png -------------------------------------------------------------------------------- /admin/divera247_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TKnpl/ioBroker.divera247/ffb45badf4efefe472ae9b108c4dcb9b743b89fe/admin/divera247_long.png -------------------------------------------------------------------------------- /admin/index_m.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 63 | 64 | 65 | 66 | 67 |
68 |
69 |
70 | 71 |
72 |
73 | 74 |
75 |

Divera 24/7 Login

76 |
77 |
78 |
79 | 80 | 81 |
82 |
83 | 84 | 85 |
86 |
87 | 88 |
89 |

Filter

90 |
91 |
92 |
93 | 94 |
95 | Alarm will triggers only if my user is addressed in the alarming 96 |
97 |
98 |
99 |
100 | 101 | 102 | Alarm only for following Divera user IDs
103 | (leave blank to subscribe all alarms) 104 |
105 |
106 | 107 | 108 | Alarm only for following Divera alarm groups
109 | (leave blank to subscribe all alarms) 110 |
111 |
112 |
113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /admin/style.css: -------------------------------------------------------------------------------- 1 | /* You can delete those if you want. I just found them very helpful */ 2 | * { 3 | box-sizing: border-box 4 | } 5 | .m { 6 | /* Don't cut off dropdowns! */ 7 | overflow: initial; 8 | } 9 | .m.adapter-container, 10 | .m.adapter-container > div.App { 11 | /* Fix layout/scrolling issues with tabs */ 12 | height: 100%; 13 | width: 100%; 14 | position: relative; 15 | } 16 | .m .select-wrapper + label { 17 | /* The positioning for dropdown labels is messed up */ 18 | transform: none !important; 19 | } 20 | 21 | label > i[title] { 22 | /* Display the help cursor for the tooltip icons and fix their positioning */ 23 | cursor: help; 24 | margin-left: 0.25em; 25 | } 26 | 27 | .dropdown-content { 28 | /* Don't wrap text in dropdowns */ 29 | white-space: nowrap; 30 | } 31 | 32 | /* Add your styles here */ 33 | -------------------------------------------------------------------------------- /admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "./admin.d.ts", 5 | "./**/*.js", 6 | // include the adapter-config definition if it exists 7 | "../src/lib/adapter-config.d.ts", 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /admin/words.js: -------------------------------------------------------------------------------- 1 | /*global systemDictionary:true */ 2 | 'use strict'; 3 | 4 | systemDictionary = { 5 | "divera247 adapter settings": { 6 | "en": "divera247 adapter settings", 7 | "de": "divera247 Adaptereinstellungen", 8 | "ru": "настройки адаптера divera247", 9 | "pt": "configurações do adaptador divera247", 10 | "nl": "divera247 adapter instellingen", 11 | "fr": "Paramètres de l'adaptateur divera247", 12 | "it": "impostazioni dell'adattatore divera247", 13 | "es": "configuración del adaptador divera247", 14 | "pl": "Ustawienia adaptera divera247", 15 | "zh-cn": "divera247适配器设置" 16 | }, 17 | "Email address": { 18 | "en": "Email address", 19 | "de": "E-Mail Addresse", 20 | "ru": "Адрес электронной почты", 21 | "pt": "Endereço de e-mail", 22 | "nl": "E-mailadres", 23 | "fr": "Adresse e-mail", 24 | "it": "Indirizzo email", 25 | "es": "Dirección de correo electrónico", 26 | "pl": "Adres e-mail", 27 | "zh-cn": "电子邮件地址" 28 | }, 29 | "Divera login password": { 30 | "en": "Divera login password", 31 | "de": "Divera Login Passwort", 32 | "ru": "Пароль для входа в Divera", 33 | "pt": "Senha de login Divera", 34 | "nl": "Divera inlogwachtwoord", 35 | "fr": "Mot de passe de connexion Divera", 36 | "it": "Password di accesso Divera", 37 | "es": "Contraseña de inicio de sesión de Divera", 38 | "pl": "Hasło logowania Divera", 39 | "zh-cn": "Divera 登录密码" 40 | }, 41 | "Show only alarms for my user": { 42 | "en": "Show only alarms for my user", 43 | "de": "Nur Alarme für meinen Benutzer anzeigen", 44 | "ru": "Показывать только будильники для моего пользователя", 45 | "pt": "Mostrar apenas alarmes para meu usuário", 46 | "nl": "Toon alleen alarmen voor mijn gebruiker", 47 | "fr": "Afficher uniquement les alarmes pour mon utilisateur", 48 | "it": "Mostra solo allarmi per il mio utente", 49 | "es": "Mostrar solo alarmas para mi usuario", 50 | "pl": "Pokaż tylko alarmy dla mojego użytkownika", 51 | "zh-cn": "仅显示我的用户的警报" 52 | }, 53 | "Alarm will triggers only if my user is addressed in the alarming": { 54 | "en": "Alarm will triggers only if my user is addressed in the alarming", 55 | "de": "Alarm wird nur ausgelöst, wenn mein Benutzer in der Alarmierung enthalten ist", 56 | "ru": "Тревога будет срабатывать только в том случае, если мой пользователь адресован в тревожной", 57 | "pt": "O alarme será acionado apenas se meu usuário for abordado no alarme", 58 | "nl": "Alarm wordt alleen geactiveerd als mijn gebruiker is aangesproken in de alarmering", 59 | "fr": "L'alarme ne se déclenchera que si mon utilisateur est adressé dans l'alarme", 60 | "it": "L'allarme si attiverà solo se il mio utente è indirizzato nell'allarme", 61 | "es": "La alarma se activará solo si mi usuario está direccionado en la alarma", 62 | "pl": "Alarm uruchomi się tylko wtedy, gdy mój użytkownik zostanie zaadresowany w alarmującym", 63 | "zh-cn": "只有当我的用户在警报中得到解决时,警报才会触发" 64 | }, 65 | "Divera user IDs": { 66 | "en": "Divera user IDs", 67 | "de": "Divera Benutzer-IDs", 68 | "ru": "ID пользователя Divera", 69 | "pt": "ID de usuário Divera", 70 | "nl": "Divera gebruikers-ID", 71 | "fr": "ID utilisateur Divera", 72 | "it": "ID utente Divera", 73 | "es": "ID de usuario de Divera", 74 | "pl": "Identyfikator użytkownika Divera", 75 | "zh-cn": "Divera用户ID" 76 | }, 77 | "Alarm only for following Divera user IDs": { 78 | "en": "Alarm only for following Divera user IDs", 79 | "de": "Alarm nur für folgende Divera Benutzer-IDs", 80 | "ru": "Тревога только для следующих идентификаторов пользователя Divera", 81 | "pt": "Alarme apenas para os seguintes IDs de usuário Divera", 82 | "nl": "Alarm alleen voor volgende Divera gebruikers-ID's", 83 | "fr": "Alarme uniquement pour les ID utilisateur Divera suivants", 84 | "it": "Allarme solo per i seguenti ID utente Divera", 85 | "es": "Alarma solo para los siguientes ID de usuario de Divera", 86 | "pl": "Alarm tylko dla następujących identyfikatorów użytkowników Divera", 87 | "zh-cn": "仅针对以下Divera用户ID发出警报" 88 | }, 89 | "(leave blank to subscribe all alarms)": { 90 | "en": "(leave blank to subscribe all alarms)", 91 | "de": "(Leer lassen, um alle Alarme zu erhalten)", 92 | "ru": "(оставьте поле пустым, чтобы подписаться на все будильники)", 93 | "pt": "(deixe em branco para inscrever todos os alarmes)", 94 | "nl": "(laat dit veld leeg om alle alarmen te abonneren)", 95 | "fr": "(laissez vide pour souscrire à toutes les alarmes)", 96 | "it": "(lasciare vuoto per sottoscrivere tutti gli allarmi)", 97 | "es": "(déjelo en blanco para suscribir todas las alarmas)", 98 | "pl": "(pozostaw puste, aby zasubskrybować wszystkie alarmy)", 99 | "zh-cn": "(留空以订阅所有警报)" 100 | }, 101 | "Divera alarm groups": { 102 | "en": "Divera alarm groups", 103 | "de": "Divera Alarm-Gruppen", 104 | "ru": "Группы сигналов тревоги Divera", 105 | "pt": "Grupos de alarme Divera", 106 | "nl": "Divera alarmgroepen", 107 | "fr": "Groupes d'alarmes Divera", 108 | "it": "Divera gruppi di allarme", 109 | "es": "Grupos de alarma Divera", 110 | "pl": "Grupy alarmowe Divera", 111 | "zh-cn": "Divera警报组" 112 | }, 113 | "Alarm only for following Divera alarm groups": { 114 | "en": "Alarm only for following Divera alarm groups", 115 | "de": "Alarm nur für folgende Divera Alarm-Gruppen", 116 | "ru": "Тревога только для следующих групп тревог Divera", 117 | "pt": "Alarme apenas para os seguintes grupos de alarme Divera", 118 | "nl": "Alarm alleen voor volgende Divera alarmgroepen", 119 | "fr": "Alarme uniquement pour les groupes d'alarmes Divera suivants", 120 | "it": "Allarme solo per i seguenti gruppi di allarme Divera", 121 | "es": "Alarma solo para los siguientes grupos de alarmas de Divera", 122 | "pl": "Alarm tylko dla następujących grup alarmowych Divera", 123 | "zh-cn": "仅针对以下Divera警报组发出警报" 124 | } 125 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * ioBroker gulpfile 3 | * Date: 2019-01-28 4 | */ 5 | 'use strict'; 6 | 7 | const gulp = require('gulp'); 8 | const fs = require('fs'); 9 | const pkg = require('./package.json'); 10 | const iopackage = require('./io-package.json'); 11 | const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; 12 | const fileName = 'words.js'; 13 | const EMPTY = ''; 14 | const translate = require('./lib/tools').translateText; 15 | const languages = { 16 | en: {}, 17 | de: {}, 18 | ru: {}, 19 | pt: {}, 20 | nl: {}, 21 | fr: {}, 22 | it: {}, 23 | es: {}, 24 | pl: {}, 25 | 'zh-cn': {} 26 | }; 27 | 28 | function lang2data(lang) { 29 | let str ='{\n'; 30 | let count = 0; 31 | for (const w in lang) { 32 | if (lang.hasOwnProperty(w)) { 33 | count++; 34 | const key = ' "' + w.replace(/"/g, '\\"') + '": '; 35 | str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; 36 | } 37 | } 38 | if (!count) { 39 | return '{\n}'; 40 | } else { 41 | return str.substring(0, str.length - 2) + '\n}'; 42 | } 43 | } 44 | 45 | function readWordJs(src) { 46 | try { 47 | let words; 48 | if (fs.existsSync(src + 'js/' + fileName)) { 49 | words = fs.readFileSync(src + 'js/' + fileName).toString(); 50 | } else { 51 | words = fs.readFileSync(src + fileName).toString(); 52 | } 53 | words = words.substring(words.indexOf('{'), words.length); 54 | words = words.substring(0, words.lastIndexOf(';')); 55 | 56 | const resultFunc = new Function('return ' + words + ';'); 57 | 58 | return resultFunc(); 59 | } catch (e) { 60 | return null; 61 | } 62 | } 63 | 64 | function padRight(text, totalLength) { 65 | return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); 66 | } 67 | 68 | function writeWordJs(data, src) { 69 | let text = ''; 70 | text += '/*global systemDictionary:true */\n'; 71 | text += "'use strict';\n\n"; 72 | text += 'systemDictionary = {\n'; 73 | for (const word in data) { 74 | if (data.hasOwnProperty(word)) { 75 | text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); 76 | let line = ''; 77 | for (const lang in data[word]) { 78 | if (data[word].hasOwnProperty(lang)) { 79 | line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; 80 | } 81 | } 82 | if (line) { 83 | line = line.trim(); 84 | line = line.substring(0, line.length - 1); 85 | } 86 | text += line + '},\n'; 87 | } 88 | } 89 | text += '};'; 90 | if (fs.existsSync(src + 'js/' + fileName)) { 91 | fs.writeFileSync(src + 'js/' + fileName, text); 92 | } else { 93 | fs.writeFileSync(src + '' + fileName, text); 94 | } 95 | } 96 | 97 | function words2languages(src) { 98 | const langs = Object.assign({}, languages); 99 | const data = readWordJs(src); 100 | if (data) { 101 | for (const word in data) { 102 | if (data.hasOwnProperty(word)) { 103 | for (const lang in data[word]) { 104 | if (data[word].hasOwnProperty(lang)) { 105 | langs[lang][word] = data[word][lang]; 106 | // pre-fill all other languages 107 | for (const j in langs) { 108 | if (langs.hasOwnProperty(j)) { 109 | langs[j][word] = langs[j][word] || EMPTY; 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | if (!fs.existsSync(src + 'i18n/')) { 117 | fs.mkdirSync(src + 'i18n/'); 118 | } 119 | for (const l in langs) { 120 | if (!langs.hasOwnProperty(l)) 121 | continue; 122 | const keys = Object.keys(langs[l]); 123 | keys.sort(); 124 | const obj = {}; 125 | for (let k = 0; k < keys.length; k++) { 126 | obj[keys[k]] = langs[l][keys[k]]; 127 | } 128 | if (!fs.existsSync(src + 'i18n/' + l)) { 129 | fs.mkdirSync(src + 'i18n/' + l); 130 | } 131 | 132 | fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); 133 | } 134 | } else { 135 | console.error('Cannot read or parse ' + fileName); 136 | } 137 | } 138 | 139 | function languages2words(src) { 140 | const dirs = fs.readdirSync(src + 'i18n/'); 141 | const langs = {}; 142 | const bigOne = {}; 143 | const order = Object.keys(languages); 144 | dirs.sort(function (a, b) { 145 | const posA = order.indexOf(a); 146 | const posB = order.indexOf(b); 147 | if (posA === -1 && posB === -1) { 148 | if (a > b) 149 | return 1; 150 | if (a < b) 151 | return -1; 152 | return 0; 153 | } else if (posA === -1) { 154 | return -1; 155 | } else if (posB === -1) { 156 | return 1; 157 | } else { 158 | if (posA > posB) 159 | return 1; 160 | if (posA < posB) 161 | return -1; 162 | return 0; 163 | } 164 | }); 165 | for (const lang of dirs) { 166 | if (lang === 'flat.txt') 167 | continue; 168 | langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); 169 | langs[lang] = JSON.parse(langs[lang]); 170 | const words = langs[lang]; 171 | for (const word in words) { 172 | if (words.hasOwnProperty(word)) { 173 | bigOne[word] = bigOne[word] || {}; 174 | if (words[word] !== EMPTY) { 175 | bigOne[word][lang] = words[word]; 176 | } 177 | } 178 | } 179 | } 180 | // read actual words.js 181 | const aWords = readWordJs(); 182 | 183 | const temporaryIgnore = ['flat.txt']; 184 | if (aWords) { 185 | // Merge words together 186 | for (const w in aWords) { 187 | if (aWords.hasOwnProperty(w)) { 188 | if (!bigOne[w]) { 189 | console.warn('Take from actual words.js: ' + w); 190 | bigOne[w] = aWords[w]; 191 | } 192 | dirs.forEach(function (lang) { 193 | if (temporaryIgnore.indexOf(lang) !== -1) 194 | return; 195 | if (!bigOne[w][lang]) { 196 | console.warn('Missing "' + lang + '": ' + w); 197 | } 198 | }); 199 | } 200 | } 201 | 202 | } 203 | 204 | writeWordJs(bigOne, src); 205 | } 206 | 207 | async function translateNotExisting(obj, baseText, yandex) { 208 | let t = obj['en']; 209 | if (!t) { 210 | t = baseText; 211 | } 212 | 213 | if (t) { 214 | for (let l in languages) { 215 | if (!obj[l]) { 216 | const time = new Date().getTime(); 217 | obj[l] = await translate(t, l, yandex); 218 | console.log('en -> ' + l + ' ' + (new Date().getTime() - time) + ' ms'); 219 | } 220 | } 221 | } 222 | } 223 | 224 | //TASKS 225 | 226 | gulp.task('adminWords2languages', function (done) { 227 | words2languages('./admin/'); 228 | done(); 229 | }); 230 | 231 | gulp.task('adminLanguages2words', function (done) { 232 | languages2words('./admin/'); 233 | done(); 234 | }); 235 | 236 | gulp.task('updatePackages', function (done) { 237 | iopackage.common.version = pkg.version; 238 | iopackage.common.news = iopackage.common.news || {}; 239 | if (!iopackage.common.news[pkg.version]) { 240 | const news = iopackage.common.news; 241 | const newNews = {}; 242 | 243 | newNews[pkg.version] = { 244 | en: 'news', 245 | de: 'neues', 246 | ru: 'новое', 247 | pt: 'novidades', 248 | nl: 'nieuws', 249 | fr: 'nouvelles', 250 | it: 'notizie', 251 | es: 'noticias', 252 | pl: 'nowości', 253 | 'zh-cn': '新' 254 | }; 255 | iopackage.common.news = Object.assign(newNews, news); 256 | } 257 | fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); 258 | done(); 259 | }); 260 | 261 | gulp.task('updateReadme', function (done) { 262 | const readme = fs.readFileSync('README.md').toString(); 263 | const pos = readme.indexOf('## Changelog\n'); 264 | if (pos !== -1) { 265 | const readmeStart = readme.substring(0, pos + '## Changelog\n'.length); 266 | const readmeEnd = readme.substring(pos + '## Changelog\n'.length); 267 | 268 | if (readme.indexOf(version) === -1) { 269 | const timestamp = new Date(); 270 | const date = timestamp.getFullYear() + '-' + 271 | ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + 272 | ('0' + (timestamp.getDate()).toString(10)).slice(-2); 273 | 274 | let news = ''; 275 | if (iopackage.common.news && iopackage.common.news[pkg.version]) { 276 | news += '* ' + iopackage.common.news[pkg.version].en; 277 | } 278 | 279 | fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); 280 | } 281 | } 282 | done(); 283 | }); 284 | 285 | gulp.task('translate', async function (done) { 286 | 287 | let yandex; 288 | const i = process.argv.indexOf('--yandex'); 289 | if (i > -1) { 290 | yandex = process.argv[i + 1]; 291 | } 292 | 293 | if (iopackage && iopackage.common) { 294 | if (iopackage.common.news) { 295 | console.log('Translate News'); 296 | for (let k in iopackage.common.news) { 297 | console.log('News: ' + k); 298 | let nw = iopackage.common.news[k]; 299 | await translateNotExisting(nw, null, yandex); 300 | } 301 | } 302 | if (iopackage.common.titleLang) { 303 | console.log('Translate Title'); 304 | await translateNotExisting(iopackage.common.titleLang, iopackage.common.title, yandex); 305 | } 306 | if (iopackage.common.desc) { 307 | console.log('Translate Description'); 308 | await translateNotExisting(iopackage.common.desc, null, yandex); 309 | } 310 | 311 | if (fs.existsSync('./admin/i18n/en/translations.json')) { 312 | let enTranslations = require('./admin/i18n/en/translations.json'); 313 | for (let l in languages) { 314 | console.log('Translate Text: ' + l); 315 | let existing = {}; 316 | if (fs.existsSync('./admin/i18n/' + l + '/translations.json')) { 317 | existing = require('./admin/i18n/' + l + '/translations.json'); 318 | } 319 | for (let t in enTranslations) { 320 | if (!existing[t]) { 321 | existing[t] = await translate(enTranslations[t], l, yandex); 322 | } 323 | } 324 | if (!fs.existsSync('./admin/i18n/' + l + '/')) { 325 | fs.mkdirSync('./admin/i18n/' + l + '/'); 326 | } 327 | fs.writeFileSync('./admin/i18n/' + l + '/translations.json', JSON.stringify(existing, null, 4)); 328 | } 329 | } 330 | 331 | } 332 | fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); 333 | }); 334 | 335 | gulp.task('translateAndUpdateWordsJS', gulp.series('translate', 'adminLanguages2words', 'adminWords2languages')); 336 | 337 | gulp.task('default', gulp.series('updatePackages', 'updateReadme')); -------------------------------------------------------------------------------- /io-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "name": "divera247", 4 | "version": "0.2.0", 5 | "news": { 6 | "0.2.0": { 7 | "en": "Complete renewal of the adapter", 8 | "de": "Generelle Erneuerung des Adapters" 9 | }, 10 | "0.1.3": { 11 | "en": "general revision of the adapter", 12 | "de": "Generelle Überarbeitung des Adapters" 13 | }, 14 | "0.1.2": { 15 | "en": "added alarmed vehicles datapointg", 16 | "de": "Datenpunkt für alarmierte Fahrzeuge hinzugefügt" 17 | }, 18 | "0.1.1": { 19 | "en": "small changes - wording", 20 | "de": "Kleine Änderungen in Bezeichnungen" 21 | }, 22 | "0.1.0": { 23 | "en": "added possibility to specify alarm groups", 24 | "de": "Möglichkeit hinzugefügt, nur auf Alarmierungen bestimmter Alarm-Gruppe zu lauschen" 25 | }, 26 | "0.0.10": { 27 | "en": "bug in info.connection fixed and handling of user ids expanded", 28 | "de": "Fehler in Verbindungsanzeige behoben und Handling von UserIDs erweitert" 29 | }, 30 | "0.0.9": { 31 | "en": "added default values for admin page", 32 | "de": "Standardwerte für Admin-Seite hinzugefügt" 33 | }, 34 | "0.0.8": { 35 | "en": "Changed API call from intervall to timeout, added states 'group' and 'foreign_id'", 36 | "de": "Apfrage der API von Intervall auf Timeout geändert, States 'Gruppe' und 'Einsatznummer' hinzugefügt" 37 | }, 38 | "0.0.7": { 39 | "en": "added object priority and alarm object updates only in case of an new alarm or when an alarm was closed", 40 | "de": "Objekt Priorität/Sonderrechte hinzugefügt und Alarm wird nur noch bei neuem oder Schließung eines Alarms aktualisiert" 41 | }, 42 | "0.0.6": { 43 | "en": "state handling while active alarm and connection check improved, fixed object types", 44 | "de": "state handling bei aktivem Alarm und connection check verbessert, Fehler bei Objekttypen behoben" 45 | }, 46 | "0.0.5": { 47 | "en": "fixed io-package news issue", 48 | "de": "io-package news Anforderungen nachgepflegt" 49 | }, 50 | "0.0.4": { 51 | "en": "Connection check to api improved, added timestamp of latest alert", 52 | "de": "API-Verbindungs-Check verbesserunt, Alarmieringszeitstempel hinzugefügt" 53 | }, 54 | "0.0.3": { 55 | "en": "added title, text, address, latitude, longitude, general formatting", 56 | "de": "Einsatzstichwort, Meldungstext, Adresse, Längengrad und Breitengrad hinzugefügt" 57 | }, 58 | "0.0.2": { 59 | "en": "adjusted translation", 60 | "de": "Übersetzung verbessert" 61 | }, 62 | "0.0.1": { 63 | "en": "initial release", 64 | "de": "Erstveröffentlichung" 65 | } 66 | }, 67 | "title": "Divera 24/7", 68 | "titleLang": { 69 | "en": "Divera 24/7", 70 | "de": "Divera 24/7" 71 | }, 72 | "desc": { 73 | "en": "Adapter for the alerting software Divera 24/7", 74 | "de": "Adapter zur Alarmierungssoftware Divera 24/7" 75 | }, 76 | "authors": [ 77 | "tknpl " 78 | ], 79 | "author": { 80 | "name": "TKnpl", 81 | "email": "dev@t-concepts.de" 82 | }, 83 | "keywords": [ 84 | "divera", 85 | "alarm" 86 | ], 87 | "license": "MIT", 88 | "platform": "Javascript/Node.js", 89 | "main": "main.js", 90 | "icon": "divera247.png", 91 | "enabled": true, 92 | "extIcon": "https://raw.githubusercontent.com/TKnpl/ioBroker.divera247/master/admin/divera247.png", 93 | "readme": "https://github.com/TKnpl/ioBroker.divera247/blob/master/README.md", 94 | "loglevel": "info", 95 | "mode": "daemon", 96 | "type": "alarm", 97 | "compact": true, 98 | "connectionType": "cloud", 99 | "dataSource": "poll", 100 | "materialize": true, 101 | "dependencies": [ 102 | { 103 | "js-controller": ">=2.0.0" 104 | } 105 | ] 106 | }, 107 | "native": { 108 | "diveraAccessKey": "", 109 | "diveraUserId": "", 110 | "diveraAlarmGroup": "", 111 | "pollIntervall": 30 112 | }, 113 | "objects": [], 114 | "instanceObjects": [ 115 | { 116 | "_id": "info", 117 | "type": "channel", 118 | "common": { 119 | "name": "Information" 120 | }, 121 | "native": {} 122 | }, 123 | { 124 | "_id": "info.connection", 125 | "type": "state", 126 | "common": { 127 | "role": "indicator.connected", 128 | "name": "Device or service connected", 129 | "type": "boolean", 130 | "read": true, 131 | "write": false, 132 | "def": false 133 | }, 134 | "native": {} 135 | } 136 | ] 137 | } -------------------------------------------------------------------------------- /lib/adapter-config.d.ts: -------------------------------------------------------------------------------- 1 | // This file extends the AdapterConfig type from "@types/iobroker" 2 | // using the actual properties present in io-package.json 3 | // in order to provide typings for adapter.config properties 4 | 5 | import { native } from '../io-package.json'; 6 | 7 | type _AdapterConfig = typeof native; 8 | 9 | // Augment the globally declared type ioBroker.AdapterConfig 10 | declare global { 11 | namespace ioBroker { 12 | interface AdapterConfig extends _AdapterConfig { 13 | // Do not enter anything here! 14 | } 15 | } 16 | } 17 | 18 | // this is required so the above AdapterConfig is found by TypeScript / type checking 19 | export {}; -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios').default; 2 | 3 | /** 4 | * Tests whether the given variable is a real object and not an Array 5 | * @param {any} it The variable to test 6 | * @returns {it is Record} 7 | */ 8 | function isObject(it) { 9 | // This is necessary because: 10 | // typeof null === 'object' 11 | // typeof [] === 'object' 12 | // [] instanceof Object === true 13 | return Object.prototype.toString.call(it) === '[object Object]'; 14 | } 15 | 16 | /** 17 | * Tests whether the given variable is really an Array 18 | * @param {any} it The variable to test 19 | * @returns {it is any[]} 20 | */ 21 | function isArray(it) { 22 | if (typeof Array.isArray === 'function') return Array.isArray(it); 23 | return Object.prototype.toString.call(it) === '[object Array]'; 24 | } 25 | 26 | /** 27 | * Translates text to the target language. Automatically chooses the right translation API. 28 | * @param {string} text The text to translate 29 | * @param {string} targetLang The target languate 30 | * @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers 31 | * @returns {Promise} 32 | */ 33 | async function translateText(text, targetLang, yandexApiKey) { 34 | if (targetLang === 'en') { 35 | return text; 36 | } else if (!text) { 37 | return ''; 38 | } 39 | if (yandexApiKey) { 40 | return translateYandex(text, targetLang, yandexApiKey); 41 | } else { 42 | return translateGoogle(text, targetLang); 43 | } 44 | } 45 | 46 | /** 47 | * Translates text with Yandex API 48 | * @param {string} text The text to translate 49 | * @param {string} targetLang The target languate 50 | * @param {string} apiKey The yandex API key. You can create one for free at https://translate.yandex.com/developers 51 | * @returns {Promise} 52 | */ 53 | async function translateYandex(text, targetLang, apiKey) { 54 | if (targetLang === 'zh-cn') { 55 | targetLang = 'zh'; 56 | } 57 | try { 58 | const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; 59 | const response = await axios({url, timeout: 15000}); 60 | if (response.data && response.data.text && isArray(response.data.text)) { 61 | return response.data.text[0]; 62 | } 63 | throw new Error('Invalid response for translate request'); 64 | } catch (e) { 65 | throw new Error(`Could not translate to "${targetLang}": ${e}`); 66 | } 67 | } 68 | 69 | /** 70 | * Translates text with Google API 71 | * @param {string} text The text to translate 72 | * @param {string} targetLang The target languate 73 | * @returns {Promise} 74 | */ 75 | async function translateGoogle(text, targetLang) { 76 | try { 77 | const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; 78 | const response = await axios({url, timeout: 15000}); 79 | if (isArray(response.data)) { 80 | // we got a valid response 81 | return response.data[0][0][0]; 82 | } 83 | throw new Error('Invalid response for translate request'); 84 | } catch (e) { 85 | if (e.response && e.response.status === 429) { 86 | throw new Error( 87 | `Could not translate to "${targetLang}": Rate-limited by Google Translate` 88 | ); 89 | } else { 90 | throw new Error(`Could not translate to "${targetLang}": ${e}`); 91 | } 92 | } 93 | } 94 | 95 | module.exports = { 96 | isArray, 97 | isObject, 98 | translateText 99 | }; 100 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('@iobroker/adapter-core'); 4 | const axios = require('axios'); 5 | const adapterName = require('./package.json').name.split('.').pop(); 6 | 7 | const userData = []; 8 | userData['diveraAPIToken'] = ''; 9 | userData['diveraMemberships'] = []; 10 | 11 | const internalAlarmData = []; 12 | internalAlarmData['alarmID'] = 0; 13 | internalAlarmData['alarmClosed'] = true; 14 | internalAlarmData['lastAlarmUpdate'] = 0; 15 | 16 | 17 | const pollIntervallSeconds = 15; 18 | 19 | const dataPoints = [{ 20 | 'id': 'alarm', 21 | 'name': 'Alarm', 22 | 'type': 'boolean', 23 | 'role': 'indicator', 24 | 'read': true, 25 | 'write': false 26 | }, 27 | { 28 | 'id': 'title', 29 | 'name': 'Einsatzstichwort', 30 | 'type': 'string', 31 | 'role': 'text', 32 | 'read': true, 33 | 'write': false 34 | }, 35 | { 36 | 'id': 'text', 37 | 'name': 'Meldungstext', 38 | 'type': 'string', 39 | 'role': 'text', 40 | 'read': true, 41 | 'write': false 42 | }, 43 | { 44 | 'id': 'foreign_id', 45 | 'name': 'Einsatznummer', 46 | 'type': 'number', 47 | 'role': 'text', 48 | 'read': true, 49 | 'write': false 50 | }, 51 | { 52 | 'id': 'divera_id', 53 | 'name': 'Einsatz ID', 54 | 'type': 'number', 55 | 'role': 'text', 56 | 'read': true, 57 | 'write': false 58 | }, 59 | { 60 | 'id': 'address', 61 | 'name': 'Adresse', 62 | 'type': 'string', 63 | 'role': 'text', 64 | 'read': true, 65 | 'write': false 66 | }, 67 | { 68 | 'id': 'lat', 69 | 'name': 'Längengrad', 70 | 'type': 'number', 71 | 'role': 'text', 72 | 'read': true, 73 | 'write': false 74 | }, 75 | { 76 | 'id': 'lng', 77 | 'name': 'Breitengrad', 78 | 'type': 'number', 79 | 'role': 'text', 80 | 'read': true, 81 | 'write': false 82 | }, 83 | { 84 | 'id': 'date', 85 | 'name': 'Alarmierungszeit', 86 | 'type': 'number', 87 | 'role': 'date', 88 | 'read': true, 89 | 'write': false 90 | }, 91 | { 92 | 'id': 'priority', 93 | 'name': 'Priorität/Sonderrechte', 94 | 'type': 'boolean', 95 | 'role': 'indicator', 96 | 'read': true, 97 | 'write': false 98 | }, 99 | { 100 | 'id': 'addressed_users', 101 | 'name': 'Alarmierte Benutzer', 102 | 'type': 'string', 103 | 'role': 'text', 104 | 'read': true, 105 | 'write': false 106 | }, 107 | { 108 | 'id': 'addressed_groups', 109 | 'name': 'Alarmierte Gruppen', 110 | 'type': 'string', 111 | 'role': 'text', 112 | 'read': true, 113 | 'write': false 114 | }, 115 | { 116 | 'id': 'addressed_vehicle', 117 | 'name': 'Alarmierte Fahrzeuge', 118 | 'type': 'string', 119 | 'role': 'text', 120 | 'read': true, 121 | 'write': false 122 | }, 123 | { 124 | 'id': 'lastUpdate', 125 | 'name': 'Letzte Aktualisierung', 126 | 'type': 'number', 127 | 'role': 'date', 128 | 'read': true, 129 | 'write': false 130 | }]; 131 | 132 | class Divera247 extends utils.Adapter { 133 | 134 | constructor(options) { 135 | super({ 136 | ...options, 137 | name: adapterName, 138 | }); 139 | 140 | this.refreshStateTimeout = null; 141 | 142 | this.on('ready', this.onReady.bind(this)); 143 | this.on('unload', this.onUnload.bind(this)); 144 | } 145 | 146 | async onReady() { 147 | this.setState('info.connection', false, true); 148 | 149 | // Generating DataPoints for this adapter 150 | dataPoints.forEach( (elm) => { 151 | this.setObjectNotExistsAsync(elm.id, { 152 | type: 'state', 153 | common: { 154 | name: elm.name, 155 | type: elm.type, 156 | role: elm.role, 157 | read: elm.read, 158 | write: elm.write 159 | }, 160 | native: {}, 161 | }); 162 | }); 163 | 164 | ////////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 165 | const diveraLoginName = this.config.diveraUserLogin; 166 | const diveraLoginPassword = this.config.diveraLoginPassword; 167 | const diveraFilterOnlyAlarmsForMyUser = this.config.explizitUserAlarms; 168 | const diveraUserIdInput = this.config.diveraUserId; 169 | const diveraUserGroupInput = this.config.diveraAlarmGroup; 170 | 171 | const diveraUserIDs = diveraUserIdInput.replace(/\s/g, '').split(','); 172 | const diveraUserGroups = diveraUserGroupInput.replace(/\s/g, '').split(','); 173 | 174 | // Check if all values of diveraUserIDs are valid 175 | const userIDInputIsValid = this.uiFilterIsValid(diveraUserIDs)[0]; 176 | 177 | // Check if all values of diveraUserGroups are valid 178 | const userGroupInputIsValid = this.uiFilterIsValid(diveraUserGroups)[0]; 179 | 180 | // Startup logic from here. Login and API calls 181 | if (diveraLoginName && diveraLoginPassword && pollIntervallSeconds && userIDInputIsValid && userGroupInputIsValid) { 182 | if (await this.checkConnectionToApi(diveraLoginName, diveraLoginPassword)) { 183 | // Connected to API 184 | this.setState('info.connection', true, true); 185 | 186 | this.log.debug('Login passed'); 187 | 188 | // Start repeating Call of the API 189 | this.getDataFromApiAndSetObjects(userData.diveraAPIToken, diveraFilterOnlyAlarmsForMyUser, diveraUserIDs, diveraUserGroups); 190 | } else { 191 | this.log.error('Login to API failed'); 192 | } 193 | } else { 194 | this.log.warn('Adapter configuration is invalid'); 195 | } 196 | } 197 | 198 | uiFilterIsValid(obj) { 199 | const valuesGiven = obj.length > 0; 200 | let valuesGivenAndValid = false; 201 | if (valuesGiven) { 202 | let allInputsValid = true; 203 | obj.forEach( (elm) => { 204 | isNaN(Number(elm)) ? allInputsValid = false : ''; 205 | }); 206 | valuesGivenAndValid = allInputsValid; 207 | } 208 | return [valuesGiven ? valuesGivenAndValid : true, valuesGiven]; 209 | } 210 | 211 | /** 212 | * Function to login to the API 213 | * returns true / false 214 | * If successful, it is setting userData.diveraAPIToken and userData.diveraMemberships 215 | * 216 | * @param {string} diveraLoginName 217 | * @param {string} diveraLoginPassword 218 | */ 219 | checkConnectionToApi(diveraLoginName, diveraLoginPassword) { 220 | // Calling and loggin in into the API V2 221 | // @ts-ignore 222 | return axios({ 223 | method: 'post', 224 | baseURL: 'https://www.divera247.com/', 225 | url: '/api/v2/auth/login', 226 | data: { 227 | Login: { 228 | username: diveraLoginName, 229 | password: diveraLoginPassword, 230 | jwt: false 231 | } 232 | }, 233 | responseType: 'json' 234 | }).then( 235 | (response) => { 236 | const responseBody = response.data; 237 | 238 | if (response.status == 200 && responseBody.success) { 239 | this.log.debug('Connected to API'); 240 | userData.diveraAPIToken = responseBody.data.user.access_token; 241 | userData.diveraMemberships = responseBody.data.ucr; 242 | this.log.debug('Divera Memberships: ' + JSON.stringify(userData.diveraMemberships)); 243 | return true; 244 | } else { 245 | return false; 246 | } 247 | } 248 | ).catch( 249 | (error) => { 250 | if (error.response) { 251 | // The request was made and the server responded with a error status code 252 | this.log.error('received error ' + error.response.status + ' response with content: ' + JSON.stringify(error.response.data)); 253 | return false; 254 | } else if (error.request) { 255 | // The request was made but no response was received 256 | this.log.error(error.message); 257 | return false; 258 | } else { 259 | // Something happened in setting up the request that triggered an Error 260 | this.log.error(error.message); 261 | return false; 262 | } 263 | } 264 | ); 265 | } 266 | 267 | /** 268 | * Function that calls the API and set the Object States 269 | * 270 | * @param {string} diveraAccessKey 271 | * @param {boolean} diveraFilterOnlyAlarmsForMyUser 272 | * @param {string[]} diveraUserIDs 273 | * @param {string[]} diveraUserGroups 274 | */ 275 | async getDataFromApiAndSetObjects(diveraAccessKey, diveraFilterOnlyAlarmsForMyUser, diveraUserIDs, diveraUserGroups) { 276 | // Calling the alerting-server api 277 | // @ts-ignore 278 | await axios({ 279 | method: 'get', 280 | baseURL: 'https://www.divera247.com/', 281 | url: '/api/v2/alarms?accesskey=' + diveraAccessKey, 282 | responseType: 'json' 283 | }).then( 284 | (response) => { 285 | const content = response.data; 286 | 287 | // If last request failed set info.connection true again 288 | // @ts-ignore 289 | this.getState('info.connection', (err, state) => { 290 | // @ts-ignore 291 | if (!state.val) { 292 | this.setState('info.connection', true, true); 293 | this.log.debug('Reconnected to API'); 294 | } 295 | }); 296 | 297 | // Setting the update state 298 | this.setState('lastUpdate', { val: Date.now(), ack: true }); 299 | 300 | // Setting the alarm specific states when a new alarm is active and addressed to the configured divera user id 301 | if (content.success && Object.keys(content.data.items).length > 0) { 302 | const alarmContent = content.data.items[content.data.sorting[0]]; 303 | if ((internalAlarmData.alarmID != alarmContent.id && !alarmContent.closed) || (internalAlarmData.alarmID == alarmContent.id && internalAlarmData.lastAlarmUpdate < alarmContent.ts_update && !alarmContent.closed)) { 304 | this.log.debug('New or updated alarm!'); 305 | this.log.debug('Received data from Divera-API: ' + JSON.stringify(content)); 306 | 307 | // Setting internal variables for later checkes 308 | internalAlarmData.alarmID = alarmContent.id; 309 | internalAlarmData.alarmClosed = alarmContent.closed; 310 | internalAlarmData.lastAlarmUpdate = alarmContent.ts_update; 311 | 312 | // Checking UI Input filter and trigger update the states 313 | if (diveraFilterOnlyAlarmsForMyUser) { 314 | for (const elm of userData.diveraMemberships) { 315 | this.log.debug('checking if my user-id \'' + elm.id + '\' for \'' + elm.name + '\' is alarmed'); 316 | if (alarmContent.ucr_addressed.includes(parseInt(elm.id, 10))) { 317 | this.setAdapterStates(alarmContent); 318 | this.log.debug('my user is alarmed - states refreshed for the current alarm'); 319 | break; 320 | } else { 321 | this.log.debug('user is not alarmed'); 322 | } 323 | } 324 | } else if (diveraUserIDs.length > 0 && diveraUserIDs[0] != '') { 325 | for (const elm of diveraUserIDs) { 326 | this.log.debug('checking if user \'' + elm + '\' is alarmed'); 327 | if (alarmContent.ucr_addressed.includes(parseInt(elm, 10))) { 328 | this.setAdapterStates(alarmContent); 329 | this.log.debug('user is alarmed - states refreshed for the current alarm'); 330 | break; 331 | } else { 332 | this.log.debug('user is not alarmed'); 333 | } 334 | } 335 | } else if (diveraUserGroups.length > 0 && diveraUserGroups[0] != '') { 336 | for (const elm of diveraUserGroups) { 337 | this.log.debug('checking if group \'' + elm + '\' is alarmed'); 338 | if (alarmContent.group.includes(parseInt(elm, 10))) { 339 | this.setAdapterStates(alarmContent); 340 | this.log.debug('group is alarmed - states refreshed for the current alarm'); 341 | break; 342 | } else { 343 | this.log.debug('group is not alarmed'); 344 | } 345 | } 346 | } else { 347 | this.log.debug('userID and group check skipped as of no userID or group is specified or my user was already alarmed'); 348 | this.setAdapterStates(alarmContent); 349 | this.log.debug('states refreshed for the current alarm'); 350 | } 351 | } else if (internalAlarmData.alarmID == alarmContent.id && alarmContent.closed && !internalAlarmData.alarmClosed) { 352 | this.setState('alarm', { val: !alarmContent.closed, ack: true }); 353 | this.log.debug('alarm is closed'); 354 | internalAlarmData.alarmClosed = alarmContent.closed; 355 | } 356 | } 357 | } 358 | ).catch( 359 | (error) => { 360 | if (error.response) { 361 | // The request was made and the server responded with a error status code 362 | if (error.response.status == 403) { 363 | this.log.error('Login not possible'); 364 | this.setState('info.connection', false, true); 365 | } else { 366 | this.log.warn('received error ' + error.response.status + ' response with content: ' + JSON.stringify(error.response.data)); 367 | this.setState('info.connection', false, true); 368 | } 369 | } else if (error.request) { 370 | // The request was made but no response was received 371 | this.log.error(error.message); 372 | this.setState('info.connection', false, true); 373 | } else { 374 | // Something happened in setting up the request that triggered an Error 375 | this.log.error(error.message); 376 | this.setState('info.connection', false, true); 377 | } 378 | } 379 | ); 380 | 381 | // Timeout and self call handling 382 | this.refreshStateTimeout = this.refreshStateTimeout || setTimeout(() => { 383 | this.refreshStateTimeout = null; 384 | this.getDataFromApiAndSetObjects(diveraAccessKey, diveraFilterOnlyAlarmsForMyUser, diveraUserIDs, diveraUserGroups); 385 | }, pollIntervallSeconds * 1000); 386 | } 387 | 388 | // Function to set satates 389 | /** 390 | * @param {{ title: string; text: string; foreign_id: number; id: number; address: string; lat: number; lng: number; date: number; priority: boolean; ucr_addressed: string[]; group: string[]; vehicle: string[]; }} alarmData 391 | */ 392 | setAdapterStates(alarmData) { 393 | this.setState('title', { val: alarmData.title, ack: true }); 394 | this.setState('text', { val: alarmData.text, ack: true }); 395 | this.setState('foreign_id', { val: Number(alarmData.foreign_id), ack: true }); 396 | this.setState('divera_id', { val: Number(alarmData.id), ack: true }); 397 | this.setState('address', { val: alarmData.address, ack: true }); 398 | this.setState('lat', { val: Number(alarmData.lat), ack: true }); 399 | this.setState('lng', { val: Number(alarmData.lng), ack: true }); 400 | this.setState('date', { val: Number(alarmData.date)*1000, ack: true }); 401 | this.setState('priority', { val: alarmData.priority, ack: true }); 402 | this.setState('addressed_users', { val: alarmData.ucr_addressed.join(), ack: true }); 403 | this.setState('addressed_groups', { val: alarmData.group.join(), ack: true }); 404 | this.setState('addressed_vehicle', { val: alarmData.vehicle.join(), ack: true }); 405 | this.setState('alarm', { val: true, ack: true }); 406 | } 407 | 408 | // Is called when adapter shuts down 409 | onUnload(callback) { 410 | try { 411 | if (this.refreshStateTimeout) { 412 | this.log.debug('clearing refreshStateTimeout'); 413 | clearTimeout(this.refreshStateTimeout); 414 | } 415 | this.log.debug('cleaned everything up'); 416 | callback(); 417 | } catch (e) { 418 | callback(); 419 | } 420 | } 421 | } 422 | 423 | // @ts-ignore parent is a valid property on module 424 | if (module.parent) { 425 | // Export the constructor in compact mode 426 | /** 427 | * @param {Partial} [options={}] 428 | */ 429 | module.exports = (options) => new Divera247(options); 430 | } else { 431 | // otherwise start the instance directly 432 | new Divera247(); 433 | } -------------------------------------------------------------------------------- /main.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This is a dummy TypeScript test file using chai and mocha 5 | * 6 | * It's automatically excluded from npm and its build output is excluded from both git and npm. 7 | * It is advised to test all your modules with accompanying *.test.js-files 8 | */ 9 | 10 | // tslint:disable:no-unused-expression 11 | 12 | const { expect } = require('chai'); 13 | // import { functionToTest } from "./moduleToTest"; 14 | 15 | describe('module to test => function to test', () => { 16 | // initializing logic 17 | const expected = 5; 18 | 19 | it(`should return ${expected}`, () => { 20 | const result = 5; 21 | // assign result a value from functionToTest 22 | expect(result).to.equal(expected); 23 | // or using the should() syntax 24 | result.should.equal(expected); 25 | }); 26 | // ... more tests => it 27 | 28 | }); 29 | 30 | // ... more test suites => describe 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "iobroker.divera247@0.2.0", 3 | "_id": "iobroker.divera247@0.2.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-nFW/x7r8xVvA4N8In5YjTBIbOen+4d03Kvd0SlHZ0ERm9Llae8hi2zFkwTPOJIrTSfA4mHE9d1fw/t7xr1/IQg==", 6 | "_location": "/iobroker.divera247", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "version", 10 | "registry": true, 11 | "raw": "iobroker.divera247@0.2.0", 12 | "name": "iobroker.divera247", 13 | "escapedName": "iobroker.divera247", 14 | "rawSpec": "0.2.0", 15 | "saveSpec": null, 16 | "fetchSpec": "0.2.0" 17 | }, 18 | "_requiredBy": [ 19 | "#USER", 20 | "/" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/iobroker.divera247/-/iobroker.divera247-0.2.0.tgz", 23 | "_shasum": "1d91da0d747552853eadacdf740dc33818456a43", 24 | "_spec": "iobroker.divera247@0.2.0", 25 | "_where": "/opt/iobroker", 26 | "author": { 27 | "name": "tknpl", 28 | "email": "dev@t-concepts.de" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/TKnpl/ioBroker.divera247/issues" 32 | }, 33 | "bundleDependencies": false, 34 | "dependencies": { 35 | "@iobroker/adapter-core": "^2.4.0", 36 | "axios": "^0.21.0" 37 | }, 38 | "deprecated": false, 39 | "description": "Adapter zur Alarmierungssoftware Divera 24/7", 40 | "devDependencies": { 41 | "@iobroker/testing": "^2.5.4", 42 | "@types/chai": "^4.2.14", 43 | "@types/chai-as-promised": "^7.1.3", 44 | "@types/gulp": "^4.0.7", 45 | "@types/mocha": "^8.2.0", 46 | "@types/node": "^14.14.19", 47 | "@types/proxyquire": "^1.3.28", 48 | "@types/sinon": "^9.0.10", 49 | "@types/sinon-chai": "^3.2.5", 50 | "axios": "^0.21.1", 51 | "chai": "^4.2.0", 52 | "chai-as-promised": "^7.1.1", 53 | "eslint": "^7.17.0", 54 | "gulp": "^4.0.2", 55 | "mocha": "^8.2.1", 56 | "proxyquire": "^2.1.3", 57 | "sinon": "^9.2.2", 58 | "sinon-chai": "^3.5.0", 59 | "typescript": "^4.1.3" 60 | }, 61 | "homepage": "https://github.com/TKnpl/ioBroker.divera247", 62 | "keywords": [ 63 | "divera", 64 | "alarm" 65 | ], 66 | "license": "MIT", 67 | "main": "main.js", 68 | "name": "iobroker.divera247", 69 | "repository": { 70 | "type": "git", 71 | "url": "git+https://github.com/TKnpl/ioBroker.divera247.git" 72 | }, 73 | "scripts": { 74 | "check": "tsc --noEmit -p tsconfig.check.json", 75 | "lint": "eslint", 76 | "test": "npm run test:js && npm run test:package", 77 | "test:integration": "mocha test/integration --exit", 78 | "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"", 79 | "test:package": "mocha test/package --exit", 80 | "test:unit": "mocha test/unit --exit" 81 | }, 82 | "version": "0.2.0" 83 | } 84 | -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run integration tests - See https://github.com/ioBroker/testing for a detailed explanation and further options 5 | tests.integration(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /test/mocha.setup.js: -------------------------------------------------------------------------------- 1 | // Don't silently swallow unhandled rejections 2 | process.on('unhandledRejection', (e) => { 3 | throw e; 4 | }); 5 | 6 | // enable the should interface with sinon 7 | // and load chai-as-promised and sinon-chai by default 8 | const sinonChai = require('sinon-chai'); 9 | const chaiAsPromised = require('chai-as-promised'); 10 | const { should, use } = require('chai'); 11 | 12 | should(); 13 | use(sinonChai); 14 | use(chaiAsPromised); -------------------------------------------------------------------------------- /test/mocharc.custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": [ 3 | "test/mocha.setup.js" 4 | ], 5 | "watch-files": [ 6 | "!(node_modules|test)/**/*.test.js", 7 | "*.test.js", 8 | "test/**/test!(PackageFiles|Startup).js" 9 | ] 10 | } -------------------------------------------------------------------------------- /test/package.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Validate the package files 5 | tests.packageFiles(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noImplicitAny": false 5 | }, 6 | "include": [ 7 | "./**/*.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/unit.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tests } = require('@iobroker/testing'); 3 | 4 | // Run unit tests - See https://github.com/ioBroker/testing for a detailed explanation and further options 5 | tests.unit(path.join(__dirname, '..')); 6 | -------------------------------------------------------------------------------- /tsconfig.check.json: -------------------------------------------------------------------------------- 1 | // Specialized tsconfig for type-checking js files 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": [ 6 | "**/*.js", 7 | "**/*.d.ts" 8 | ], 9 | "exclude": [ 10 | "**/build", 11 | "node_modules/", 12 | "widgets/", 13 | "gulpfile.js" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Root tsconfig to set the settings and power editor support for all TS files 2 | { 3 | "compileOnSave": true, 4 | "compilerOptions": { 5 | // do not compile anything, this file is just to configure type checking 6 | "noEmit": true, 7 | 8 | // check JS files 9 | "allowJs": true, 10 | "checkJs": true, 11 | 12 | "module": "commonjs", 13 | "moduleResolution": "node", 14 | "esModuleInterop": true, 15 | // this is necessary for the automatic typing of the adapter config 16 | "resolveJsonModule": true, 17 | 18 | // Set this to false if you want to disable the very strict rules (not recommended) 19 | "strict": true, 20 | // Or enable some of those features for more fine-grained control 21 | // "strictNullChecks": true, 22 | // "strictPropertyInitialization": true, 23 | // "strictBindCallApply": true, 24 | "noImplicitAny": false, 25 | // "noUnusedLocals": true, 26 | // "noUnusedParameters": true, 27 | 28 | // Consider targetting es2019 or higher if you only support Node.js 12+ 29 | "target": "es2018", 30 | 31 | }, 32 | "include": [ 33 | "**/*.js", 34 | "**/*.d.ts" 35 | ], 36 | "exclude": [ 37 | "node_modules/**" 38 | ] 39 | } --------------------------------------------------------------------------------