Skip to content

Commit

Permalink
Add news also to the notification system (#2135)
Browse files Browse the repository at this point in the history
  • Loading branch information
foxriver76 authored Oct 10, 2023
1 parent 902dad0 commit 2537f32
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The icons may not be reused in other projects without the proper flaticon licens
## Changelog
### **WORK IN PROGRESS**
* (foxriver76) JSON config component `port` does no longer mark port as occupied if it is only occupied on another host
* (foxriver76) register news as notifications
* (foxriver76) if upgrade all is stopped on error, activate close button
* (bluefox) Export/import objects with its values

Expand Down
149 changes: 140 additions & 9 deletions io-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,17 @@
"wwwDontUpload": true,
"eraseOnUpload": true,
"nogit": true,
"welcomeScreenPro": {
"link": "admin/index.html",
"name": "Admin",
"img": "admin/img/admin.png",
"color": "pink",
"order": 5,
"localLinks": "_default",
"localLink": true
},
"welcomeScreenPro": [
{
"link": "admin/index.html",
"name": "Admin",
"img": "admin/img/admin.png",
"color": "pink",
"order": 5,
"localLinks": "_default",
"localLink": true
}
],
"localLinks": {
"_default": {
"link": "%protocol%://%bind%:%port%",
Expand Down Expand Up @@ -236,6 +238,135 @@
"loadingHideLogo": false,
"loadingBackgroundImage": false
},
"notifications": [
{
"scope": "news",
"name": {
"en": "News",
"de": "Nachrichten",
"ru": "Новости",
"pt": "Notícias",
"nl": "Nieuws",
"fr": "Actualités",
"it": "Notizie",
"es": "Noticias",
"pl": "News",
"uk": "Новини",
"zh-cn": "新闻"
},
"description": {
"en": "These notifications represent news regarding installed adapters or general ioBroker information.",
"de": "Diese Benachrichtigungen enthalten Neuigkeiten zu installierten Adaptern oder allgemeine ioBroker-Informationen.",
"ru": "Эти уведомления представляют новости о установленных адаптерах или общей информации ioBroker.",
"pt": "Estas notificações representam notícias sobre adaptadores instalados ou informações gerais do ioBroker.",
"nl": "Deze berichten zijn nieuws over geïnstalleerde adapters of algemene ioBroker informatie.",
"fr": "Ces notifications représentent des nouvelles concernant les adaptateurs installés ou les informations générales ioBroker.",
"it": "Queste notifiche rappresentano notizie riguardanti adattatori installati o informazioni generali su ioBroker.",
"es": "Estas notificaciones representan noticias sobre adaptadores instalados o información general ioBroker.",
"pl": "Noty te reprezentują informacje dotyczące zainstalowanych adapterów lub ogólnie dostępnych informacji ioBrokera.",
"uk": "Ці повідомлення представляють новини про встановлені адаптери або загальні відомості про ioBroker.",
"zh-cn": "这些通知是有关安装的适应器或一般的气箱信息的新闻。."
},
"categories": [
{
"category": "info",
"name": {
"en": "General news",
"de": "Allgemeine Nachrichten",
"ru": "Общие новости",
"pt": "Notícia geral",
"nl": "Generaal",
"fr": "Nouvelles générales",
"it": "Notizie generali",
"es": "Noticias generales",
"pl": "Strona oficjalna",
"uk": "Новини",
"zh-cn": "新闻"
},
"severity": "notify",
"description": {
"en": "These messages represent general news, which just have informal purpose and do not need to be read immediately.",
"de": "Diese Nachrichten stellen allgemeine Nachrichten dar, die nur informellen Zweck haben und nicht sofort gelesen werden müssen.",
"ru": "Эти сообщения представляют собой общие новости, которые просто имеют неформальную цель и не нужно читать немедленно.",
"pt": "Essas mensagens representam notícias gerais, que apenas têm um propósito informal e não precisam ser lidas imediatamente.",
"nl": "Deze berichten vertegenwoordigen algemene nieuws, wat informeel doel heeft en niet onmiddellijk hoeft te worden gelezen.",
"fr": "Ces messages représentent des nouvelles générales, qui ont juste un but informel et ne doivent pas être lus immédiatement.",
"it": "Questi messaggi rappresentano notizie generali, che hanno solo scopo informale e non devono essere letti immediatamente.",
"es": "Estos mensajes representan noticias generales, que sólo tienen un propósito informal y no necesitan ser leídos inmediatamente.",
"pl": "Wiadomości te reprezentują ogólnokrajowe wiadomości, które tylko mają nieformalny cel i nie muszą być odczytane natychmiast.",
"uk": "Ці повідомлення представляють загальні новини, які просто мають неформальне призначення і не потрібно негайно прочитати.",
"zh-cn": "这些信息是一般新闻,这只是非正式目的,不需要立即阅读。."
},
"regex": [],
"limit": 10
},
{
"category": "warning",
"name": {
"en": "Important news",
"de": "Wichtige Nachrichten",
"ru": "Важные новости",
"pt": "Notícia importante",
"nl": "Belangrijk nieuws",
"fr": "Nouvelles importantes",
"it": "Notizie importanti",
"es": "Noticias importantes",
"pl": "Important news",
"uk": "Новини",
"zh-cn": "重要的新闻"
},
"severity": "info",
"description": {
"en": "These messages represent adapter warnings and important changes in the near future.",
"de": "Diese Nachrichten stellen Adapterwarnungen und wichtige Veränderungen in der nahen Zukunft dar.",
"ru": "Эти сообщения представляют предупреждение о адаптере и важные изменения в ближайшем будущем.",
"pt": "Estas mensagens representam avisos de adaptadores e mudanças importantes no futuro próximo.",
"nl": "Deze berichten vertegenwoordigen adapter waarschuwingen en belangrijke veranderingen in de nabije toekomst.",
"fr": "Ces messages représentent des avertissements d'adaptateur et des changements importants dans un proche avenir.",
"it": "Questi messaggi rappresentano avvisi di adattatore e cambiamenti importanti nel prossimo futuro.",
"es": "Estos mensajes representan advertencias de adaptador y cambios importantes en el futuro cercano.",
"pl": "Wiadomości te reprezentują ostrzeżenia adaptatora i ważne zmiany w najbliższej przyszłości.",
"uk": "Ці повідомлення представляють попередження та важливі зміни в найближчому майбутньому.",
"zh-cn": "这些信息是适应的预警和近期的重要变化。."
},
"regex": [],
"limit": 10
},
{
"category": "danger",
"name": {
"en": "Very important news",
"de": "Sehr wichtige Nachrichten",
"ru": "Очень важные новости",
"pt": "Notícia muito importante",
"nl": "Heel belangrijk",
"fr": "Nouvelles très importantes",
"it": "Notizie molto importanti",
"es": "Noticias muy importantes",
"pl": "Ważne wiadomości",
"uk": "Останні новини",
"zh-cn": "非常重要的新闻"
},
"severity": "alert",
"description": {
"en": "These notifications are very important. They may give you a hint that an adapter upgrade is required right now to maintain functionality.",
"de": "Diese Benachrichtigungen sind sehr wichtig. Sie können Ihnen einen Hinweis geben, dass ein Adapter-Upgrade jetzt erforderlich ist, um die Funktionalität zu erhalten.",
"ru": "Эти уведомления очень важны. Они могут дать вам подсказку, что обновление адаптера требуется прямо сейчас для поддержания функциональности.",
"pt": "Estas notificações são muito importantes. Eles podem lhe dar uma dica de que uma atualização do adaptador é necessária agora para manter a funcionalidade.",
"nl": "Deze berichten zijn heel belangrijk. Ze kunnen je een hint geven dat een adapter upgrade nu nodig is om functionaliteit te behouden.",
"fr": "Ces notifications sont très importantes. Ils peuvent vous donner un indice qu'une mise à niveau d'adaptateur est nécessaire pour maintenir la fonctionnalité.",
"it": "Queste notifiche sono molto importanti. Essi possono dare un suggerimento che un aggiornamento adattatore è necessario in questo momento per mantenere la funzionalità.",
"es": "Estas notificaciones son muy importantes. Pueden darle una pista de que se requiere una actualización del adaptador ahora mismo para mantener la funcionalidad.",
"pl": "Te informacje są bardzo ważne. Mogą dać wskazówki, że ulepszanie adapteru jest niezbędne do utrzymania funkcji.",
"uk": "Ці повідомлення дуже важливі. Вони можуть надати вам підказку, що оновлення адаптера потрібно прямо зараз для підтримки функціональності.",
"zh-cn": "这些通知非常重要。 他们可以向你说明,适应人员升级现在需要保持功能。."
},
"regex": [],
"limit": 10
}
]
}
],
"objects": [],
"instanceObjects": [
{
Expand Down
160 changes: 154 additions & 6 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

const semver = require('semver');
const axios = require('axios').default;
const fs = require('fs');
const fs = require('node:fs');
const os = require('node:os');

const utils = require('@iobroker/adapter-core'); // Get common adapter utils
const getInstalledInfo = utils.commonTools.getInstalledInfo;
Expand Down Expand Up @@ -679,10 +680,10 @@ class Admin extends utils.Adapter {
return (
this.getStateAsync('info.newsETag')
.then(state => {
oldEtag = state && state.val;
oldEtag = state?.val;
return axios
.get('https://iobroker.live/repo/news-hash.json', {
timeout: 13000,
timeout: 13_000,
validateStatus: status => status < 400,
})
.then(response => response.data)
Expand All @@ -699,7 +700,7 @@ class Admin extends utils.Adapter {
newEtag = etag.hash;
return axios
.get('https://iobroker.live/repo/news.json', {
timeout: 14000,
timeout: 14_000,
validateStatus: status => status < 400,
})
.then(response => response.data)
Expand All @@ -722,7 +723,7 @@ class Admin extends utils.Adapter {
.then(state => {
try {
/** @ts-expect-error */
oldNews = state && state.val ? JSON.parse(state.val) : [];
oldNews = state?.val ? JSON.parse(state.val) : [];
} catch (e) {
oldNews = [];
}
Expand All @@ -734,7 +735,7 @@ class Admin extends utils.Adapter {
.then(lastState => {
// find time of last ID
let time = '';
if (lastState && lastState.val) {
if (lastState?.val) {
const item = oldNews.find(item => item.id === lastState.val);
if (item) {
time = item.created;
Expand All @@ -761,6 +762,7 @@ class Admin extends utils.Adapter {
}

if (originalOldNews !== JSON.stringify(oldNews)) {
this.registerNewsNotifications(oldNews, lastState?.val);
return this.setStateAsync('info.newsFeed', JSON.stringify(oldNews), true);
} else {
return Promise.resolve();
Expand All @@ -775,6 +777,152 @@ class Admin extends utils.Adapter {
);
}

/**
* Add the nes to the notification system
*
* @param {Record<string, any>[]} messages sorted news
* @param {string | undefined | null} lastMessageId lastMessageId, all after this has already been seen
*
* @return {Promise<void>}
*/
async registerNewsNotifications(messages, lastMessageId) {
const adapters = await this.getObjectViewAsync('system', 'adapter', {
startkey: 'system.adapter.\u0000',
endkey: 'system.adapter.\u9999',
});

const operatingSystem = os.platform();

const instances = await this.getObjectViewAsync('system', 'instance', {
startkey: 'system.adapter.\u0000',
endkey: 'system.adapter.\u9999',
});

const activeRepo = (await this.getForeignObjectAsync('system.config'))?.common.activeRepo;
const uuid = (await this.getForeignObjectAsync('system.meta.uuid'))?.native.uuid;
const nodeVersion = process.version;
const npmVersion = await this.getNpmVersion();

const today = Date.now();
for (const message of messages) {
if (!message) {
continue;
}

if (message.id === lastMessageId) {
break;
}
let showIt = true;

if (showIt && message['date-start'] && new Date(message['date-start']).getTime() > today) {
showIt = false;
} else if (showIt && message['date-end'] && new Date(message['date-end']).getTime() < today) {
showIt = false;
} else if (showIt && message.conditions && Object.keys(message.conditions).length > 0) {
Object.keys(message.conditions).forEach(key => {
if (showIt) {
const adapter = adapters.rows.find(adapter => adapter.id === `system.adapter.${key}`);
const condition = message.conditions[key];

if (!adapter && condition !== '!installed') {
showIt = false;
} else if (adapter && condition === '!installed') {
showIt = false;
} else if (adapter && condition === 'active') {
showIt = this.checkActive(key, instances);
} else if (adapter && condition === '!active') {
showIt = !this.checkActive(key, instances);
} else if (adapter?.value) {
showIt = this.checkConditions(condition, adapter.value.common.version);
}
}
});
}

if (showIt && message['node-version']) {
showIt = this.checkConditions(message['node-version'], nodeVersion);
}
if (showIt && message['npm-version']) {
showIt = this.checkConditions(message['npm-version'], npmVersion);
}
if (showIt && message.os) {
showIt = operatingSystem === message.os;
}
if (showIt && message.repo) {
// If multi-repo
if (Array.isArray(activeRepo)) {
showIt = activeRepo.includes(message.repo);
} else {
showIt = activeRepo === message.repo;
}
}
if (showIt && message.uuid) {
if (Array.isArray(message.uuid)) {
showIt = uuid && message.uuid.find(msgUuid => uuid === msgUuid);
} else {
showIt = !!(uuid && uuid === message.uuid);
}
}

if (showIt) {
this.log.info(`register notification ${message.class}`);
await this.registerNotification('news', message.class, message.title.en + '\n' + message.content.en);
}
}
}

/**
* Check if adapter is active
*
* @param {string} adapterName
* @param {Awaited<ioBroker.GetObjectViewPromise<ioBroker.InstanceObject>>} instances
* @return {boolean}
*/
checkActive(adapterName, instances) {
return !!Object.keys(instances)
.filter(id => id.startsWith(`adapter.system.${adapterName}.`))
.find(id => instances.rows.find(row => id === row.id)?.value?.common.enabled);
}

/**
* Check if conditions met
*
* @param {string} condition
* @param {string} installedVersion
* @return {boolean}
*/
checkConditions(condition, installedVersion) {
if (condition.startsWith('equals')) {
const vers = condition.substring(7, condition.length - 1).trim();
return installedVersion === vers;
}
if (condition.startsWith('bigger') || condition.startsWith('greater')) {
const vers = condition.substring(7, condition.length - 1).trim();
try {
return semver.gt(vers, installedVersion);
} catch (e) {
return false;
}
} else if (condition.startsWith('smaller')) {
const vers = condition.substring(8, condition.length - 1).trim();
try {
return semver.lt(installedVersion, vers);
} catch (e) {
return false;
}
} else if (condition.startsWith('between')) {
const vers1 = condition.substring(8, condition.indexOf(',')).trim();
const vers2 = condition.substring(condition.indexOf(',') + 1, condition.length - 1).trim();
try {
return semver.gte(installedVersion, vers1) && semver.lte(installedVersion, vers2);
} catch (e) {
return false;
}
} else {
return true;
}
}

/**
* Get current npm version from controller
* @returns {Promise<string>}
Expand Down
1 change: 1 addition & 0 deletions src/src/dialogs/NewsAdminDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export const checkMessages = (messages: Message[], lastMessageId: string, contex
if (!message) {
continue;
}

if (message.id === lastMessageId) {
break;
}
Expand Down

0 comments on commit 2537f32

Please sign in to comment.