const os = require('os');

const fs = require('fs');

const path = require('path');

require('../config/config');

require('../models/subscribers');

const printf = require('util').format;

const Site = require('../models/site');

const CryptoJS = require('crypto-js');

const Url = require('url-parse');

const {ObjectID} = require('mongodb');

const shared_consts = require('./shared_nodejs');

const mongoose = require('mongoose').set('debug', false);
const Subscription = mongoose.model('subscribers');

const server_constants = require('./server_constants');

const download = require('image-downloader');

// SETTINGS WebPush Configuration
const webpush = require('web-push');

const FILELOG = 'filelog.txt';
const FILEEVENTS = 'logevents.txt';
const FILEMANAGERS = 'logmanagers.txt';
const FILEERRLOGIN = 'errlogin.txt';
const FILESOSTITUZIONI = 'log_sostituzioni.txt';
const FILEIP_TO_BAN = 'log_iptoban.txt';
const FILENAVE = 'logNave.txt';
const subject = process.env.VAPI_KEY_SUBJECT;
const publicVapidKey = process.env.PUBLIC_VAPI_KEY;
const privateVapidKey = process.env.PRIVATE_VAPI_KEY;

const FIELDS_REGEX = ['username', 'name', 'surname'];

// Code goes here
const keySize = 256;
const ivSize = 128;
const iterations = 100;

if (!!process.env.GCM_API_KEY && process.env.GCM_API_KEY !== '') {
  webpush.setGCMAPIKey(process.env.GCM_API_KEY);

  webpush.setVapidDetails(subject, publicVapidKey, privateVapidKey);
}
// console.log('setVapidDetails... config...');

// To Translate!
const textlang = {
  it: {
    'L\'Email è già stata Verificata': 'L\'Email è già stata Verificata',
    'Nuova Registrazione': 'Nuova Registrazione',
    'Effettuata una Nuova Registrazione': 'Effettuata una Nuova Registrazione',
    'partecipanti': 'partecipanti all\'Evento',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'partecipanti a Cena Condivisa': 'partecipanti a Cena Condivisa',
    'TESTO_ASSISTENZA': '<strong><a href="%s">👉 Per entrare nel Sito</a></strong>\n\n' +
        '👉 <strong><a href="https://freeplanet.app/requestresetpwd">Hai dimenticato la password?</a></strong>\n\n',
    'BENVENUTO': 'Benvenut@',
    'TUE_NAVI': 'Ecco le tue Navi programmate',
    'HAI_I_7_REQUISITI': 'PRIMI PASSI OK!\nHai i Primi Requisiti per Entrare nella Lista !',
    'NON_HAI_I_7_REQUISITI': 'Attenzione!\nAncora non hai i 7 Requisiti per Entrare nella Lista !',
    'INFO_LA_MIA_LAVAGNA': '✨ Lista dei Passi: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Per completare tutti i requisiti vai sul sito:\n%s\n👉🏻 <a href="%s">Entra nella tua Lavagna</a>.\n',
    'INFO_LAVAGNA_SITO': 'Per vedere in dettaglio lo <strong>STATO</strong>, sul sito \n 👉🏻 <a href="%s">Entra nella tua Lavagna</a>.\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Link da condividere ai tuoi invitati per farli registrare al sito:\n\n%s',
    'INFO_LINK_ZOOM': 'Link da condividere per partecipare allo Zoom (Conferenza OnLine):\n%s',
    'ZOOM_CONFERENCE': 'Ecco il programma delle Conferenze (Zoom) aperti a TUTTI:',
    'NON_VERIF': 'Non Verificata',
    'VERIF': 'Verificata',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Email Non Verificata\nleggi la tua casella email e trova **"Confermare la Registrazione"**\ne clicca sul bottone **"Verifica Registrazione"**',
    'TELEGRAM_NOT_CONNECTED': 'Telegram non associato al tuo account!',
    'NESSUN_INVITATO': 'Non hai invitato nessuno',
    'ZOOM_PARTECIPATO': 'Partecipazione ad almeno 1 Conferenza (Zoom)',
    'LINEE_GUIDA': 'Accettato le Linee Guida',
    'VIDEO_INTRO': 'Visto il Video',
    'SCRITTO_SOGNO': 'Hai scritto il tuo Sogno',
    'PAYMENTS': 'Modalità di Pagamento',
    'INVITATI': 'persone registrate che hai invitato',
    'INVITATI_ATTIVI': 'Invitati con i 7 Requisiti',
    'NONREG': 'Invitati non Registrati',
    'CLICCA_PER_ZOOM': 'AL GIORNO E ORA INDICATA, PER ENTRARE NELLA VIDEO-CONFERENZA, CLICCA SUL LINK INDICATO',
    'CLICCA_ENTRA': 'CLICCA QUI PER ENTRARE !',
    'ZOOM_INIZIATO': 'QUESTA CONFERENZA E\' INIZIATA!',
    'SCEGLI_VOCE': 'scegli una voce:',
    'INVITATI_LISTA': 'I Tuoi Invitati (in verde con almeno i primi 7 Requisiti)',
    'CIAO': 'Ciao',
    'ADDED_TOLISTAINGRESSO': '<strong>Complimenti! 🎊</strong>\nSei stato aggiunto alla <strong>Lista D\'Imbarco</strong> !\nNei prossimi giorni riceverai un messaggio qui quando la tua Nave sarà pronta a partire.\nRicorda che per salire di graduatoria, puoi condividere il Movimento con i tuoi amici ❤️.',
    'REGALATO_INVITANTE': 'da fare.... 😍🎊 Hai ricevuto in Regalo un Invitato {invitato} da parte di {mittente} !',
    'NO_PROG': 'Attualmente non sei ancora dentro alla Lista d\'Imbarco!',
    'SEND_LINK_CHAT_DONATORI': 'Ciao %s!\nLa tua NAVE sta finalmente Salpando!\nEntra nella Gift Chat della nave {%s} cliccando qui: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Ciao %s!\nOggi Diventi Sognatore !!! 🎊🎊🎊 Entra nella Gift Chat della nave {%s} cliccando qui: %s',
    'ENTRA_GIFT_CHAT': 'Entra nella Gift Chat',
    'DATA_PART_NAVE': 'data di Partenza della Nave:',
    'SOGNATORE': 'SOGNATORE',
    'INTERMEDIO': 'Intermedio',
    'MEDIATORE': 'MEDIATORE',
    'DONATORI': 'DONATORI',
    'RITESSITURA': 'RITESSITURA',
    'NAVE COMPLETATA': 'NAVE COMPLETATA',
    'Doni Effettuati': 'Doni Effettuati',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore che affianchèra il Mediatore',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Apertura GIFT CHAT\n%s Chiusura GIFT CHAT\nNota Bene: Hai tempo 3 giorni dall\'apertura per entrare in Chat ed effettuare il tuo Dono, dopodichè effettueremo la sostituzione.',
    'Giorno di Apertura GIFT CHAT': 'Giorno di Apertura GIFT CHAT',
    'Giorno in cui Inviare il DONO': 'Hai 3 giorni di tempo per inviare il tuo dono.\nGiorno di Chiusura',
    'SPOSTATO': 'Sei stato Spostato in una Nuova Nave !',
    'Note': 'Note',
    'TEMPORANEA': 'TEMPORANEA',
    'NAVE': 'NAVE',
    'MSG_SEND_FROM': 'Msg Inviato da',
    'ZOOM_CONFERMATO': 'Sei stato confermato ad aver visto la Video Conferenza di Benvenuto!',
    'RICHIESTA_AMICIZIA': '🌈 Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': '✅ Sei stato accettato da %s a far parte del Gruppo %s',
    'CREATO_NUOVO_GRUPPO': '✅ Hai appena creato un nuovo Gruppo chiamato %s',
    'ACCETTATO_NOTIFICA_ADMINS': '✅ l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_GRUPPO': 'Richiesta di entrare nel Gruppo %s da parte di %s',
    'GRUPPO_ELIMINATO': 'Il gruppo %s è stato eliminato da parte di %s',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  si: {},
  es: {
    'L\'Email è già stata Verificata': 'El correo electrónico ya ha sido verificado',
    'Nuova Registrazione': 'Nuevo Registro',
    'Effettuata una Nuova Registrazione': 'Se ha realizado un nuevo registro',
    'partecipanti': 'participantes',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'Para entrar en el sitio:\n%s\n\n' +
        '¿Olvidó su contraseña para acceder al sitio?\nhttps://freeplanet.app/requestresetpwd\n\n',
    'BENVENUTO': 'Bienvenido',
    'TUE_NAVI': 'Aquí están sus naves programadas',
    'HAI_I_7_REQUISITI': '¡LOS PRIMEROS PASOS ESTÁN BIEN!\nTiene los primeros 7 requisitos para entrar en la lista de embarque!',
    'NON_HAI_I_7_REQUISITI': '¡Atención!\nTodavía no tienes los 7 requisitos para entrar en la lista de embarque!',
    'HAI_I_9_REQUISITI': '¡FELICITACIONES!\n¡Has completado los 9 pasos de la Guía! Gracias por ayudar a expandirse!',
    'NON_HAI_I_9_REQUISITI': 'Recuerda que puedes ayudar a que el Movimiento crezca y se expanda compartiendo nuestro viaje con todos.!',
    'INFO_LA_MIA_LAVAGNA': '✨ Lista de pasos: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Para completar todos los requisitos vaya al sitio:\n%s\nPara ver el estado de su nave y sus invitados,👉🏻 <a href="%s">Pizarra</a>.\n',
    'INFO_LAVAGNA_SITO': 'Para ver en detalle el estado de su nave, en el sitio web, haga clic en las 3 líneas de la parte superior izquierda y vaya a "Pizarra"..\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Enlaces para compartir con sus invitados para que se registren en el sitio web:\n\n%s',
    'INFO_LINK_ZOOM': 'Enlaces para compartir para participar en el Zoom (Conferencia en línea):\n%s',
    'ZOOM_CONFERENCE': 'Aquí puedes encontrar las fechas de programación en el Zoom:',
    'NON_VERIF': 'No verificado',
    'VERIF': 'Verificado',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Correo electrónico no verificado\nlea su buzón y encuentre **"Confirmar Registro "** <marca>y haga clic en el botón **"Verificar Registro "**.',
    'TELEGRAM_NOT_CONNECTED': 'Telegram no asociado a su cuenta!',
    'NESSUN_INVITATO': 'No invitaste a nadie',
    'ZOOM_PARTECIPATO': 'Participación en al menos 1 Conferencia (Zoom)',
    'LINEE_GUIDA': 'Directrices aceptadas',
    'VIDEO_INTRO': 'Ver el video',
    'SCRITTO_SOGNO': 'Escribiste tu sueño',
    'PAYMENTS': 'Métodos de pago',
    'INVITATI': 'las personas registradas que usted invitó',
    'INVITATI_ATTIVI': 'Invitado con los 7 requisitos',
    'NONREG': 'Invitados no registrados',
    'CLICCA_PER_ZOOM': 'AL DÍA Y HORA INDICADOS, PARA ENTRAR EN LA VIDEOCONFERENCIA, PULSE AQUÍ',
    'CLICCA_ENTRA': 'HAGA CLIC AQUÍ PARA ENTRAR !',
    'ZOOM_INIZIATO': 'ESTA CONFERENCIA HA COMENZADO!',
    'SCEGLI_VOCE': 'elegir una voz:',
    'INVITATI_LISTA': 'Sus invitados (en verde con al menos los 7 primeros requisitos)',
    'CIAO': 'Hola',
    'ADDED_TOLISTAINGRESSO': 'Has sido añadido a la lista de personas que entrarán en Lista de embarque!',
    'NO_PROG': 'Actualmente no está en la lista de embarque.!',
    'SEND_LINK_CHAT_DONATORI': 'Hola %s!\n¡Tu barco por fin está navegando!\nEntra en el Gift Chat haciendo clic aquí: %s',
    'SEND_LINK_CHAT_SOGNATORE': '¡Hola!\n¡Hoy te conviertes en un soñador! 🎊🎊🎊 Entre en el Chat de Regalos de la nave {%s} haciendo clic aquí: %s',
    'ENTRA_GIFT_CHAT': 'Entre en el Chat de Regalos',
    'DATA_PART_NAVE': 'fecha de salida de la nave:',
    'SOGNATORE': 'SOÑADOR',
    'INTERMEDIO': 'Intermedio',
    'MEDIATORE': 'MEDIATOR',
    'DONATORI': 'DONANTES',
    'RITESSITURA': 'RETEJIDO',
    'NAVE COMPLETATA': 'NAVE COMPLETADA',
    'Doni Effettuati': 'Regalos Realizados',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore de apoyo al Donante',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Apertura del CHAT DE REGALOS\n%s Cierre del CHAT DE REGALOS\nNota: Tienes 3 días desde la apertura para entrar en el Chat y hacer tu Regalo, después de lo cual haremos el reemplazo',
    'SPOSTATO': 'Has sido trasladado a una nueva nave !',
    'Note': 'Notas',
    'TEMPORANEA': 'TEMPORAL',
    'NAVE': 'NAVE',
    'MSG_SEND_FROM': 'Mensaje enviado por',
    'ZOOM_CONFERMATO': '¡Se ha confirmado que ha visto la Video Conferencia de Bienvenida!',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_GRUPPO': 'Richiesta di entrare nel Gruppo %s da parte di %s',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  enUs: {
    'partecipanti': 'participants',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'To enter the Site:\n%s\n\nForgot your password to access the site?\n' +
        'https://freeplanet.app/requestresetpwd\nChat AYNI BOT (this one):\nhttps://t.me/notevoleaynibot\n\n',
    'BENVENUTO': 'Welcome',
    'TUE_NAVI': 'Here are your programmed ships',
    'HAI_I_7_REQUISITI': 'FIRST STEPS OK!\nYou have the First 7 Requirements to Enter the Boarding List!',
    'NON_HAI_I_7_REQUISITI': 'Attention!\nYou still do not have the 7 requirements to enter the boarding list!',
    'INFO_LA_MIA_LAVAGNA': '✨ Step List: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'To complete all the requirements go to the site:%s\nTo see the status of your Ship and your guests\n👉🏻 <a href="%s">Dashboard</a>\n',
    'INFO_LAVAGNA_SITO': 'To see in detail the status of your ship, on the website, click on the 3 lines at the top left and go to "Blackboard".\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Links to share with your guests to have them register on website:\n\n%s',
    'INFO_LINK_ZOOM': 'Links to share to participate in Zoom (Online Conference):\n%s',
    'ZOOM_CONFERENCE': 'Here you can find the Zoom Conference Planning:',
    'NON_VERIF': 'Not Verified',
    'VERIF': 'Verified',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Email Not Verified\nread your mailbox and find **"Confirm Registration "**<br>and click on the button **"Verify Registration "**.',
    'TELEGRAM_NOT_CONNECTED': 'Telegram not associated with your account!',
    'NESSUN_INVITATO': 'You didn\'t invite anyone',
    'ZOOM_PARTECIPATO': 'Participation in at least 1 Conference (Zoom)',
    'LINEE_GUIDA': 'Guidelines Accepted',
    'VIDEO_INTRO': 'Seen the Video',
    'SCRITTO_SOGNO': 'You wrote your Dream',
    'PAYMENTS': 'Methods of Payment',
    'INVITATI': 'registered people you invited',
    'INVITATI_ATTIVI': 'Guests with the 7 Requirements',
    'NONREG': 'Non-registered Guests',
    'CLICCA_PER_ZOOM': 'TO THE DAY AND TIME INDICATED, TO ENTER THE VIDEO-CONFERENCE, CLICK HERE',
    'CLICCA_ENTRA': 'CLICK HERE TO ENTER !',
    'ZOOM_INIZIATO': 'THIS CONFERENCE HAS BEGUN!',
    'SCEGLI_VOCE': 'choose a menu item:',
    'INVITATI_LISTA': 'Your Guests (in green with at least the first 7 requirements)',
    'CIAO': 'Hi',
    'ADDED_TOLISTAINGRESSO': 'You have been added to the List of people who will enter the Boarding List!',
    'NO_PROG': 'You are not currently on the boarding list.!',
    'SEND_LINK_CHAT_DONATORI': 'Hi %s!\nYour ship is finally sailing!\nEnter the Gift Chat by clicking here: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Hello %s!\nToday you become a Dreamer! 🎊🎊🎊 Enter the ship\'s Gift Chat {%s} by clicking here: %s',
    'ENTRA_GIFT_CHAT': 'Enter the ship\'s Gift Chat',
    'DATA_PART_NAVE': 'date of departure of the ship:',
    'SOGNATORE': 'DREAMER',
    'INTERMEDIO': 'Intermediate',
    'MEDIATORE': 'MEDIATOR',
    'DONATORI': 'DONOR',
    'RITESSITURA': 'RE-ENTER',
    'NAVE COMPLETATA': 'SHIP COMPLETED',
    'Doni Effettuati': 'Gifts Made',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore supporting the Mediator',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Opening GIFT CHAT\n%s Closing GIFT CHAT\nNote: You have 3 days from the opening to enter the Chat and make your Gift, after which we will make the replacement.',
    'SPOSTATO': 'You\'ve been moved to a New Ship !',
    'Note': 'Note',
    'TEMPORANEA': 'TEMPORARY',
    'NAVE': 'SHIP',
    'MSG_SEND_FROM': 'Msg sent by',
    'ZOOM_CONFERMATO': 'You have been confirmed to have seen the Welcome Video Conference!',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_GRUPPO': 'Richiesta di entrare nel Gruppo %s da parte di %s',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  fr: {
    'L\'Email è già stata Verificata': 'Le courrier électronique a déjà été vérifié',
    'Nuova Registrazione': 'Nouvelle inscription',
    'Effettuata una Nuova Registrazione': 'Un nouvel enregistrement a été effectué',
    'partecipanti': 'participants',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'Pour entrer sur le site:\n%s\n\n' +
        'Vous avez oublié votre mot de passe pour accéder au site ?\nhttps://ayni.gifteconomy.app/requestresetpwd\n\n',
    'BENVENUTO': 'Bienvenue',
    'TUE_NAVI': 'Voici vos navires programmés',
    'HAI_I_7_REQUISITI': 'PREMIÈRES ÉTAPES OK!\nvous avez les 7 premiers Requis pour Entrer dans la liste d\'embarquement!',
    'NON_HAI_I_7_REQUISITI': '\'Attention!\nVous ne remplissez pas encore les 7 conditions pour figurer sur la liste d\'embarquement!',
    'HAI_I_9_REQUISITI': 'FÉLICITATIONS!\n!Vous avez franchi les 9 étapes de la conduite ! Merci d\'avoir aidé à se développer!',
    'NON_HAI_I_9_REQUISITI': 'N\'oubliez pas que vous pouvez aider le Mouvement à grandir et à se développer en partageant notre voyage avec tout le monde.!',
    'INFO_LA_MIA_LAVAGNA': '✨ Liste des étapes: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Pour remplir toutes les conditions, rendez-vous sur le site:\n%s\nPour voir le statut de votre navire et de vos invités\n 👉🏻 <a href="%s">Tableau</a>.\n',
    'INFO_LAVAGNA_SITO': 'Pour voir en détail le statut de votre navire, sur le site, cliquez sur les 3 lignes en haut à gauche et allez sur "Tableau".\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Liens à partager avec vos invités pour qu\'ils s\'inscrivent sur le site web:\n\n%s',
    'INFO_LINK_ZOOM': 'Liens à partager pour participer à Zoom (Conférence en ligne):\n%s',
    'ZOOM_CONFERENCE': 'Vous trouverez ici les dates de programmation sur Zoom:',
    'NON_VERIF': 'Non vérifié',
    'VERIF': 'Vérifié',
    'EMAIL': 'Courriel',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Courriel non vérifié\nlisez votre boîte aux lettres et trouvez **"Confirmer l\'inscription "**<br>et cliquez sur le bouton **"Vérifier l\'inscription "**.',
    'TELEGRAM_NOT_CONNECTED': 'Télégramme non associé à votre compte!',
    'NESSUN_INVITATO': 'Vous n\'avez invité personne',
    'ZOOM_PARTECIPATO': 'Participation à au moins 1 conférence (Zoom)',
    'LINEE_GUIDA': 'Lignes directrices acceptées',
    'VIDEO_INTRO': 'Voir la vidéo',
    'SCRITTO_SOGNO': 'Vous avez écrit votre rêve',
    'PAYMENTS': 'Modes de paiement',
    'INVITATI': 'personnes inscrites que vous avez invitées',
    'INVITATI_ATTIVI': 'Invité avec les 7 exigences',
    'NONREG': 'Invités non enregistrés',
    'CLICCA_PER_ZOOM': 'AU JOUR ET À L\'HEURE INDIQUÉS, POUR ENTRER DANS LA VIDÉOCONFÉRENCE, CLIQUEZ ICI',
    'CLICCA_ENTRA': 'CLIQUEZ ICI POUR ENTRER !',
    'ZOOM_INIZIATO': 'CETTE CONFÉRENCE A COMMENCÉ!',
    'SCEGLI_VOCE': 'choisir une voix:',
    'INVITATI_LISTA': 'Vos invités (en vert avec au moins les 7 premières exigences)',
    'CIAO': 'Salut',
    'ADDED_TOLISTAINGRESSO': 'Vous avez été ajouté à la liste d\'embarquement !',
    'NO_PROG': 'Vous n\'êtes pas encore sur la liste d\'embarquement.!',
    'SEND_LINK_CHAT_DONATORI': 'Salut %s!\nVotre SHIP prend enfin la mer.!\nEntrez dans le Chat Cadeau en cliquant ici: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Bonjour %s !\nAujourd\'hui, vous devenez un Rêveur ! 🎊🎊🎊 Entrez dans le chat des cadeaux du navire {%s} en cliquant ici : %s',
    'ENTRA_GIFT_CHAT': 'Entrez dans le chat des cadeaux',
    'DATA_PART_NAVE': 'la date de départ du navire:',
    'SOGNATORE': 'Rêveur',
    'INTERMEDIO': 'Intermediaire',
    'MEDIATORE': 'Médiateur',
    'DONATORI': 'DONATEUR',
    'RITESSITURA': 'RETESSITURA',
    'NAVE COMPLETATA': 'NAVIRE COMPLÉTÉ',
    'Doni Effettuati': 'Don effectués',
    'Facilitatore che affianchèra il Mediatore': 'Le tuteur qui soutient le Mediateur',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s : Chat d\'ouverture\n%s Clôture du GIFT CHAT\nNote: Vous avez 3 jours à partir de l\'ouverture pour entrer dans le Chat et faire votre Cadeau, après quoi nous ferons le remplacement',
    'SPOSTATO': 'Vous avez été transféré sur un nouveau navire !',
    'Note': 'Notes',
    'TEMPORANEA': 'TEMPORAIRE',
    'NAVE': 'NAVIRE',
    'MSG_SEND_FROM': 'Message envoyé par',
    'ZOOM_CONFERMATO': 'Vous avez été confirmé comme ayant vu la vidéoconférence de bienvenue !',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_GRUPPO': 'Richiesta di entrare nel Gruppo %s da parte di %s',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
  pt: {
    'L\'Email è già stata Verificata': '',
    'Nuova Registrazione': 'Novo Registo',
    'Effettuata una Nuova Registrazione': 'Foi efectuado um novo registo',
    'partecipanti': 'participantes',
    'partecipanti a Pranzo': 'partecipanti a Pranzo',
    'partecipanti a Cena': 'partecipanti a Cena',
    'TESTO_ASSISTENZA': 'Para entrar no site:\n%s\n\n' +
        'Esqueceu sua senha para acessar o site?\nhttps://freeplanet.app/requestresetpwd\n\n',
    'BENVENUTO': 'Bem-vindo',
    'TUE_NAVI': 'Aqui estão os seus navios programados',
    'HAI_I_7_REQUISITI': 'PRIMEIROS PASSOS OK!\nVocê tem os 7 primeiros requisitos para entrar na lista de embarque!',
    'NON_HAI_I_7_REQUISITI': 'Atenção!\nVocê ainda não tem os 7 requisitos para entrar na lista de embarque!',
    'INFO_LA_MIA_LAVAGNA': '✨ Lista de etapas: ✨ \n',
    'INFO_LAVAGNA_SITO_COMPLETARE': 'Para completar todos os requisitos ir para o sitio:\n%s\nPara ver o estado do seu navio e dos seus convidados:\n 👉🏻 <a href="%s">Entre no seu Tablero</a>.\n',
    'INFO_LAVAGNA_SITO': 'Para ver em detalhe o estado do seu navio, no site, clique nas 3 linhas no canto superior esquerdo e vá para "DashBoard"..\n',
    'INFO_LINK_DA_CONDIVIDERE': 'Links para partilhar com os seus convidados para que se registem no website:\n\n%s',
    'INFO_LINK_ZOOM': 'Links para partilhar para participar na Zoom (Conferência Online):\n%s',
    'ZOOM_CONFERENCE': 'Aqui está o programa das Conferências (Zoom) aberto a TODOS:',
    'NON_VERIF': 'Não verificado',
    'VERIF': 'Verificado',
    'EMAIL': 'Email',
    'BOT': 'BOT',
    'EMAIL_NON_VERIF': 'Email Não verificado\nleia a sua caixa de correio e encontre **"Confirmar o registo"**<br>e clique no botão **"Verificação do registo"**',
    'TELEGRAM_NOT_CONNECTED': 'Telegrama não associado à sua conta!',
    'NESSUN_INVITATO': 'Você não convidou ninguém',
    'ZOOM_PARTECIPATO': 'Participação em pelo menos 1 Conferência (Zoom)',
    'LINEE_GUIDA': 'Directrizes Aceites',
    'VIDEO_INTRO': 'Ver o vídeo',
    'SCRITTO_SOGNO': 'Você escreveu o seu sonho',
    'PAYMENTS': 'Formas de pagamento',
    'INVITATI': 'pessoas registadas que convidou',
    'INVITATI_ATTIVI': 'Convidado com os 7 Requisitos',
    'NONREG': 'Convidados não registados',
    'CLICCA_PER_ZOOM': 'PARA O DIA E HORA INDICADOS, PARA ENTRAR NA VÍDEO-CONFERÊNCIA, CLIQUE AQUI',
    'CLICCA_ENTRA': 'CLIQUE AQUI PARA ENTRAR !',
    'ZOOM_INIZIATO': 'ESTA CONFERÊNCIA JÁ COMEÇOU!',
    'SCEGLI_VOCE': 'escolha uma voz:',
    'INVITATI_LISTA': 'Os seus convidados (em verde com, pelo menos, os 7 primeiros requisitos)',
    'CIAO': 'Olá',
    'ADDED_TOLISTAINGRESSO': 'Foi acrescentado à Lista de Embarque !',
    'NO_PROG': 'Actualmente, ainda não consta da lista de embarque.!',
    'SEND_LINK_CHAT_DONATORI': 'Olà %s!\nO seu SHIP está finalmente a zarpar.!\nEntre no Gift Chat, clicando aqui: %s',
    'SEND_LINK_CHAT_SOGNATORE': 'Olá %s!\nHoje você se torna um sonhador! 🎊🎊🎊 Entre no Gift Chat do navio {%s} clicando aqui: %s',
    'ENTRA_GIFT_CHAT': 'Entre no Gift Chat do navio',
    'DATA_PART_NAVE': 'data de partida do navio:',
    'SOGNATORE': 'SONHEIROS',
    'INTERMEDIO': 'Intermediar',
    'MEDIATORE': 'MEDIATOR',
    'DONATORI': 'DONATORES',
    'RITESSITURA': 'ENTRE EM',
    'NAVE COMPLETATA': 'NAVIO COMPLETADO',
    'Doni Effettuati': 'Regalo Feitos',
    'Facilitatore che affianchèra il Mediatore': 'Facilitatore que apoia o Mediator',
    'APERTURA_CHIUSURA_GIFT_CHAT': '%s: Abertura do GIFT CHAT\n%s Fechamento GIFT CHAT\nNota: Você tem 3 dias desde a abertura para entrar no Chat e fazer o seu Presente, depois do qual faremos o substituição.',
    'SPOSTATO': 'Você foi transferido para um novo navio !',
    'Note': 'Notas',
    'TEMPORANEA': 'TEMPORÁRIO',
    'NAVE': 'NAVE',
    'MSG_SEND_FROM': 'Mensagem enviada por',
    'ZOOM_CONFERMATO': 'Foi-lhe confirmado que assistiu à Conferência de Vídeo de Boas-vindas!',
    'RICHIESTA_AMICIZIA': 'Richiesta d\'Amicizia da parte di %s',
    'ACCETTATO_SUL_GRUPPO': 'Sei stato accettato da %s a far parte del Gruppo %s',
    'ACCETTATO_NOTIFICA_ADMINS': 'l\'utente %s è stato accettato a far parte del Gruppo %s (da parte di %s)',
    'RICHIESTA_GRUPPO': 'Richiesta di entrare nel Gruppo %s da parte di %s',
    'RICHIESTA_BLOCCO_GRUPPO': 'Richiesta di bloccare il Gruppo %s da parte di %s',
  },
};

module.exports = {
  MYAPPS: [],
  INITDB_FIRSTIME: true,
  ACAPO: '\n',

  ENABLE_CRONTAB: 'CRONTAB',
  UPDATE_GRADUATORIA: 'UPDATE_GRAD',

  LANGADMIN: 'it',

  FREEPLANET: '1',
  AYNI: '7',
  CNM: '10',
  PDNM: '12',
  RISO: '13',

  HELP_CHAT: '',
  TYPECONF_ZOOM: 'zoom',
  TYPECONF_JITSI: 'jitsi',

  APORTADOR_NONE: '------',

  SEP: '|',

  TYPE_PROJECT: 1,
  TYPE_TODO: 2,

  TipoMsg: {
    SEND_LINK_CHAT_DONATORI: 1,
    SEND_MSG: 2,
    SEND_MSG_SINGOLO: 3,
    SEND_TO_ALL: 10,
    SEND_MSG_EFFETTUA_IL_DONO: 1000,
    SEND_MSG_SOLLECITO_DONATORI_NO_DONO: 1005,
    SEND_MSG_A_MEDIATORI: 1010,
    SEND_MSG_A_SOGNATORE: 1020,
    SEND_MSG_A_UTENTE_SOSTITUITO: 1030,
    SEND_MSG_DONO_RICEVUTO_CORRETTAMENTE: 1040,
    SEND_MSG_DONO_NON_RICEVUTO: 1050,
  },

  Perm: {
    PERM_NONE: 0,
    PERM_FRIEND: 1,
    PERM_ALL: 10,
  },

  Placca: {
    DONATORI: 1,
    TUTTI: 2,

    STR_SOGNATORE: '<strong>Sognatore</strong>',
    STR_MEDIATORE: '<strong>Mediatore</strong>',
    STR_MEDIATORI: '<strong>Mediatori</strong>',
    STR_DONATORI: '<strong>Donatori</strong>',
    SOGNATORE: 'E1 🌈 ',
    MEDIATORE: 'A1 💦 ',
    SONOFUOCO: 0,
    SONOACQUA: 3,
    SONOTERRA: 2,
    SONOARIA: 1,
  },

  FieldType: {
    boolean: 1,
    date: 2,
    string: 4,
    binary: 8,
    html: 16,
    select: 32,
    number: 64,
    typeinrec: 128,
    multiselect: 256,
    password: 512,
    listimages: 1024,
    exact: 2048,
    image: 3000,
    nationality: 4096,
    intcode: 5000,
    multioption: 6000,
    onlydate: 7000,
    hours: 8000,
    crypted: 9000,
  },

  MAX_PHASES: 5,
  FIRST_PROJ: '__PROJECTS',
  EXECUTE_CALCPROJ: true,

  gettranslate(text, lang) {
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getlangbyres(res) {
    // ++Todo: res estrarre la lingua
    let lang = '';
    try {
      lang = res.lang;
    } catch (e) {

    }
    if (!lang) {
      lang = 'it';
    }
    return lang;
  },

  get__(text, lang) {
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getres__(text, res) {
    let lang = this.getlangbyres(res);
    try {
      return textlang[lang][text];
    } catch (e) {
      return textlang['it'][text];
    }
  },

  getHostname: function() {
    return os.hostname();
  },
  testing: function() {
    return (process.env.TESTING_ON === '1');
  },

  mylog: function(...args) {
    if (!this.testing())
      console.log(args);
  },

  mylogoff: function(...args) {
    // doing nothing
  },

  mylogshow: function(...args) {
    console.log(args);
  },

  mylogserr: function(...args) {
    console.error(args);
  },

  allfieldSendMsg: function() {
    return [
      'userId',
      'source',
      'dest',
      'message',
      'datemsg',
      'read',
      'deleted',
      'origin',
      'idapp',
      'status',
      'options'];
  },

  allfieldSendNotif: function() {
    return [
      'type',
      'userId',
      'sender',
      'dest',
      'descr',
      'datenotif',
      'read',
      'deleted',
      'idapp',
      'status'];
  },

  allfieldTodo: function() {
    return [
      'userId',
      'pos',
      'category',
      'descr',
      'note',
      'priority',
      'statustodo',
      'assignedToUsers',
      'created_at',
      'groupId',
      'modify_at',
      'completed_at',
      'expiring_at',
      'enableExpiring',
      'progress',
      'modified',
      'phase',
      'assigned_to_userId',
      'assignedToUsers',
      'hoursplanned',
      'hoursworked',
      'start_date',
      'themecolor',
      'themebgcolor'];
  },

  allfieldMyEvent: function() {
    return ['userId'];
  },

  allfieldTodoWithId: function() {
    return ['_id', ...this.allfieldTodo()];
  },

  // #TODO Projects++ Add fields ...
  allfieldProject: function() {
    return [
      'idapp',
      'userId',
      'respUsername',
      'viceRespUsername',
      'pos',
      'typeproj',
      'id_main_project',
      'id_parent',
      'descr',
      'longdescr',
      'groupId',
      'hoursplanned',
      'hoursleft',
      'themecolor',
      'themebgcolor',
      'hoursworked',
      'priority',
      'statusproj',
      'created_at',
      'modify_at',
      'completed_at',
      'expiring_at',
      'enableExpiring',
      'progressCalc',
      'modified',
      'live_url',
      'test_url',
      'begin_development',
      'begin_test',
      'totalphases',
      'actualphase',
      'hoursweeky_plannedtowork',
      'endwork_estimate'
      ,
      'privacyread',
      'privacywrite',
      'tipovisu',
      'view'];
  },

  allfieldBooking: function() {
    return [
      'idapp',
      'userId',
      'id_bookedevent',
      'numpeople',
      'numpeopleLunch',
      'numpeopleDinner',
      'numpeopleDinnerShared',
      'msgbooking',
      'modified',
      'infoevent',
      'datebooked',
      'booked'];
  },

  allfieldBookingChange: function() {
    return [
      'numpeople',
      'numpeopleLunch',
      'numpeopleDinner',
      'numpeopleDinnerShared',
      'msgbooking',
      'modified',
      'infoevent',
      'datebooked',
      'booked'];
  },

  allfieldProjectWithId: function() {
    return ['_id', ...this.allfieldProject()];
  },

  jsonCopy(src) {
    return JSON.parse(JSON.stringify(src));
  },

  CloneRecordToNew(src) {
    const myrec = Object.assign({}, src);
    delete myrec._doc['_id'];
    myrec._id = new ObjectID();

    return myrec._doc;
  },

  sendBackNotif: function(subscription, payload) {

    // console.log('sendBackNotif:', subscription, payload);

    const pushOptions = {
      vapidDetails: {
        subject: process.env.VAPI_KEY_SUBJECT,
        privateKey: process.env.PRIVATE_VAPI_KEY,
        publicKey: process.env.PUBLIC_VAPI_KEY,
      },
      TTL: payload.ttl,
      headers: {},
    };

    // Pass object into sendNotification
    return webpush.sendNotification(subscription, JSON.stringify(payload),
        pushOptions).
        catch(err => {
          if (err.statusCode === 410) {
            // Gone: is not valid anymore (Expired probably!), so I have to delete from my db
            return Subscription.findOneAndRemove({_id: subscription._id});
          } else {
            console.log('Subscription is no longer valid: ', err);
          }
        }).
        then(ris => {
          // console.log('ris', ris)
        }).
        catch(err => {
          console.error(err);
        });
  },

  sendNotificationToUser: function(userId, title, content, openUrl, openUrl2, tag, actions) {

    let payload = {
      actions,
      title: title,
      content,
      url: openUrl,
      url2: openUrl2,
      tag,
      // ttl: req.body.ttl,
      // icon: req.body.icon,
      // image: req.body.image,
      // badge: req.body.badge,
      // tag: req.body.tag
    };

    return Subscription.find({userId}, (err, subscriptions) => {
      if (err) {
        console.error(`Error occurred while getting subscriptions`);
        // res.status(500).json({
        //   error: 'Technical error occurred'
        // });

        //++ Rimuovi questo record !?

        return false;
      } else {
        let conta = 0;
        let parallelSubscriptionCalls = subscriptions.map((subscription) => {
          const trovati = subscriptions.length;
          return new Promise((resolve, reject) => {
            const pushSubscription = {
              endpoint: subscription.endpoint,
              keys: {
                p256dh: subscription.keys.p256dh,
                auth: subscription.keys.auth,
              },
            };

            conta++;

            const parse = require('url-parse');
            const parsedUrl = parse(subscription.endpoint);
            const audience = parsedUrl.protocol + '//' + parsedUrl.hostname;

            const vapidHeaders = webpush.getVapidHeaders(
                audience,
                process.env.VAPI_KEY_SUBJECT,
                process.env.PUBLIC_VAPI_KEY,
                process.env.PRIVATE_VAPI_KEY,
                'aes128gcm',
            );

            const pushOptions = {
              vapidDetails: {
                subject: process.env.VAPI_KEY_SUBJECT,
                privateKey: process.env.PRIVATE_VAPI_KEY,
                publicKey: process.env.PUBLIC_VAPI_KEY,
              },
              TTL: payload.ttl,
              headers: vapidHeaders,
            };

            console.log('************  INVIO WEBPUSH.SENDNOTIFICATION N° ',
                conta, '/', trovati, 'A', subscription.browser);

            const pushPayload = JSON.stringify(payload);

            webpush.sendNotification(
                pushSubscription,
                pushPayload,
                pushOptions,
            ).then((value) => {
              // console.log('Invio Push', value);
              resolve({
                status: true,
                endpoint: subscription.endpoint,
                data: value,
              });
            }).catch(async (err) => {
              console.error('err Push', err.body);

              // Cancella dal DB la notifica Push, visto che da errore! (sarà scaduto)
              const ris = await Subscription.deleteOne({_id: subscription._id});

              reject({
                status: false,
                endpoint: subscription.endpoint,
                data: err,
              });
            });
          }).catch(error => {
            console.log('ERROR: sendNotificationToUser', error.data.body ? error.data.body : error);
          });
        });
        // q.allSettled(parallelSubscriptionCalls).then((pushResults) => {
        //   console.info(pushResults);
        // });
        // res.json({
        //   data: 'Push triggered'
        // });
        return true;
      }
    });

  },

  sendNotificationByUsername: async function(idapp, username, cmd, telegram, usernameOrig) {

    var {User} = require('../models/user');

    const telegrambot = require('../telegram/telegrambot');

    const user = await User.findOne({idapp, username}, {_id: 1, username: 1, lang: 1}).lean();
    if (!user)
      return;

    let userId = user._id;
    let lang = user.lang;

    let sendnotif = true;

    let title = this.getNomeAppByIdApp(idapp);
    let descr = '';
    let openUrl = '/';
    let tag = '';
    let actions = [];
    if (cmd) {
      if (cmd === shared_consts.FRIENDSCMD.REQFRIEND) {
        descr = printf(this.get__('RICHIESTA_AMICIZIA', lang), usernameOrig);
        openUrl = '/my/' + usernameOrig;
        tag = 'reqfriends';

        const userrecDest = await User.getUserShortDataByUsername(idapp, usernameOrig);

        if (userrecDest) {
          sendnotif = false;  // non lo rimandare 2 volte !
          telegrambot.askConfirmationUserFriend(idapp, shared_consts.CallFunz.RICHIESTA_AMICIZIA, userrecDest, username, usernameOrig);
        }
      }
    }

    if (userId) {
      this.sendNotificationToUser(userId, title, descr, openUrl, '', tag,
          actions);
    }

    if (telegram && sendnotif) {

      const idtelegram = await User.TelegIdByUsername(idapp, username);

      await telegrambot.sendMsgTelegramByIdTelegram(idapp, idtelegram, descr);
    }

  },

  sendNotifAndMsgTelegram: async function(idapp, userId, objmsg, telegram, msgextrateleg) {

    if (userId) {
      this.sendNotificationToUser(userId, objmsg.title, objmsg.descr, objmsg.openUrl, '', objmsg.tag, objmsg.actions);
    }

    if (telegram) {
      var {User} = require('../models/user');
      const telegrambot = require('../telegram/telegrambot');

      const idtelegram = await User.TelegIdById(idapp, userId);

      const msgteleg = objmsg.descr + (msgextrateleg ? '\n' + msgextrateleg : '');
      await telegrambot.sendMsgTelegramByIdTelegram(idapp, idtelegram, msgteleg);
    }
  },

  sendNotificationByGroupname: async function(idapp, usernameOrig, groupname, cmd, telegram, username_action) {

    var {User} = require('../models/user');
    var {MyGroup} = require('../models/mygroup');

    const telegrambot = require('../telegram/telegrambot');

    const group = await MyGroup.findOne({idapp, groupname},
        {_id: 1, admins: 1});
    if (!group)
      return;

    // Send msg to Admins

    const arrusernameAdmins = group.admins;

    const myuser = await User.findOne({idapp, username: usernameOrig}).lean();

    let struserinfomsg = this.getUserInfoMsg(idapp, myuser);

    let objmsg = {
      title: this.getNomeAppByIdApp(idapp),
      descr: '',
      openUrl: '/',
      tag: '',
      actions: [],
    };

    let creazionegruppo = false;

    for (const singleadmin of arrusernameAdmins) {
      try {
        if (singleadmin.username) {

          creazionegruppo = arrusernameAdmins.length === 1 && usernameOrig === singleadmin.username && cmd ===
              shared_consts.GROUPSCMD.SETGROUP;

          const user = await User.findOne({idapp, username: singleadmin.username},
              {_id: 1, lang: 1});
          if (user) {

            let userId = user._id;
            let lang = user.lang;

            let sendnotif = true;

            if (cmd) {
              if (cmd === shared_consts.GROUPSCMD.SETGROUP) {
                if (creazionegruppo) {
                  objmsg.descr = printf(this.get__('CREATO_NUOVO_GRUPPO', lang), groupname);
                } else {
                  objmsg.descr = printf(this.get__('ACCETTATO_NOTIFICA_ADMINS', lang), usernameOrig, groupname, username_action);
                }
                objmsg.openUrl = '/grp/' + groupname;
                objmsg.tag = 'addgroups';

              } else if (cmd === shared_consts.GROUPSCMD.REQGROUP) {
                objmsg.descr = printf(this.get__('RICHIESTA_GRUPPO', lang), groupname,
                    singleadmin.username);
                objmsg.openUrl = '/grp/' + groupname;
                objmsg.tag = 'reqgroups';

                sendnotif = false;  // non lo rimandare 2 volte !
                telegrambot.askConfirmationUser(idapp, shared_consts.CallFunz.RICHIESTA_GRUPPO, myuser, singleadmin.username, groupname,
                    group._id);

              } else if (cmd === shared_consts.GROUPSCMD.BLOCK_USER) {
                objmsg.descr = printf(this.get__('RICHIESTA_BLOCCO_GRUPPO', lang),
                    groupname, singleadmin.username);
                objmsg.openUrl = '/grp/' + groupname;
                objmsg.tag = 'blockgroups';
              } else if (cmd === shared_consts.GROUPSCMD.DELETE_GROUP) {
                objmsg.descr = printf(this.get__('GRUPPO_ELIMINATO', lang), groupname, username_action);
                objmsg.openUrl = '/grp/' + groupname;
                objmsg.tag = 'deletegroup';
              }

            }

            if (sendnotif && objmsg.descr && !creazionegruppo) {
              await this.sendNotifAndMsgTelegram(idapp, userId, objmsg, telegram, struserinfomsg);
            }

          }
        }
      } catch (e) {
        console.error('sendNotificationByGroupname', e);
      }
    }

    if (cmd === shared_consts.GROUPSCMD.SETGROUP) {
      const reclang = await User.getRecLangAndIdByUsername(idapp, usernameOrig);
      objmsg.openUrl = '/grp/' + groupname;
      objmsg.tag = 'addgroups';
      if (creazionegruppo) {
        objmsg.descr = printf(this.get__('CREATO_NUOVO_GRUPPO', reclang.lang), groupname);
      } else {
        objmsg.descr = printf(this.get__('ACCETTATO_SUL_GRUPPO', reclang.lang), username_action, groupname);
      }
      // Send to User:
      await this.sendNotifAndMsgTelegram(idapp, reclang._id, objmsg, telegram);
    }

  },

  // **********************
  // SORT WITH PREV_ID
  // **********************
  /* mapSort: function (linkedList) {
    let sortedList = [];
    let remainingList = [];
    var map = new Map();
    var currentId = null;

    // console.log('linkedList', linkedList);

    // index the linked list by previous_item_id
    for (var i = 0; i < linkedList.length; i++) {
      var item = linkedList[i];
      if (item.id_prev === server_constants.LIST_START) {
        // first item
        currentId = item._id.toString();
        // console.log('currentId', currentId);
        sortedList.push(item);
      } else {
        map.set(item.id_prev.toString(), i);
      }
    }

    let conta = 0;
    while (conta < linkedList.length) {
      // get the item with a previous item ID referencing the current item
      var nextItem = linkedList[map.get(currentId)];
      if (nextItem === undefined) {

      } else {
        sortedList.push(nextItem);
        currentId = nextItem._id.toString();
      }
      conta++;
    }

    if (linkedList.length > sortedList.length) {
      // If are not in the list, I'll put at the bottom of the list
      // console.log('ATTENZIONE !!! ', sortedList.length, linkedList.length);
      for (const itemlinked of linkedList) {
        const elemtrov = sortedList.find((item) => item._id === itemlinked._id);
        if (elemtrov === undefined) {
          sortedList.push(itemlinked);
        }
      }
    }

    // console.log('DOPO sortedList', sortedList);

    return sortedList;
  },
  */

  sendNotifToAdmin(title, msg, tag = '') {
    const {User} = require('../models/user');

    // console.log('nomeapp 1: ' , this.getNomeAppByIdApp(1));
    // console.log('nomeapp 2: ' , this.getNomeAppByIdApp(2));

    User.find({username: 'paoloar77', idapp: '1'}).then((arrusers) => {
      if (arrusers !== null) {
        for (const user of arrusers) {
          this.sendNotificationToUser(user._id, title, msg, '/', '', tag, []).
              then(ris => {
                if (ris) {

                } else {
                  // already sent the error on calling sendNotificationToUser
                }
              });
        }
      }
    });

  },

  checkUserOk(userpassed, userauth, res) {
    this.mylog('checkUserOk', userpassed, userauth);
    if (String(userpassed) !== String(userauth)) {
      // I'm trying to write something not mine!
      this.mylog('userId = ', userpassed, 'req.user._id', userauth);
      return {
        exit: true,
        ret: res.status(404).
            send({code: server_constants.RIS_CODE_TODO_CREATING_NOTMYUSER}),
      };
    } else {
      return {exit: false, ret: false};
    }

  },

  convertHTMLtoText(myhtml) {
    let msg = myhtml;
    msg = msg.replace('&quot;', '"');
    msg = msg.replace('&gt;', '>');
    msg = msg.replace('&lt;', '<');
    msg = msg.replace('&amp;', '&');
    msg = msg.replace('<br>', '\n');

    return msg;
  },

  convertTexttoHtml(myhtml) {
    // let msg = myhtml;
    // msg = msg.replace('\n', '<br>');

    // return msg

    return myhtml;
  },

  removeSpecialCharForEmail(myhtml) {
    let msg = myhtml;
    msg = msg.replace(/"/g, '\'');

    return msg;
  },

  getNomeAppByIdApp: function(idapp) {

    const myapp =
        this.getApps().find(item => item.idapp === idapp);
    if (myapp)
      return ((process.env.NODE_ENV === 'test') ? 'Test: ' : '') + myapp.name;
    else
      return '';
  },

  getLinkRegByIdAppAndMsgStrutt: function(idapp, msg, rec) {

    let myapp = this.getHostByIdApp(idapp);
    if (myapp) {
      myapp += '/signup/' + rec.aportador_solidario + '/' + msg.from.username + '/' + msg.from.id;
    }

    return myapp;
  },

  getHostByIdApp: function(idapp) {

    const myapp = this.getApps().find(item => item.idapp === idapp);
    if (myapp) {
      let siteport = (myapp.portapp && myapp.portapp !== '0') ? (':' +
          myapp.portapp) : '';

      if (process.env.NODE_ENV === 'test')
        return myapp.host_test + siteport;
      else
        return myapp.host + siteport;

    } else
      return '';
  },

  getLinkUserProfile: function(idapp, username) {
    return tools.getHostByIdApp(idapp) + '/my/' + username;
  },

  getLinkEditUserProfile: function(idapp) {
    return tools.getHostByIdApp(idapp) + '/editprofile';
  },

  getConfSiteOptionEnabledByIdApp: function(idapp, option) {

    const myapp = this.getApps().find(item => item.idapp === idapp);
    if (myapp) {

      if (myapp.hasOwnProperty('confsite')) {
        if (myapp.confsite.hasOwnProperty('options')) {
          return this.isBitActive(myapp.confsite.options, option);
        }
      }
    }

    return false;
  },

  getConfParamSiteByIdApp: function(idapp, field) {

    const myapp = this.getApps().find(item => item.idapp === idapp);
    if (myapp) {

      if (myapp.hasOwnProperty('confsite')) {
        if (myapp.confsite.hasOwnProperty(field)) {
          return myapp.confsite[field];
        }
      }
    }

    return false;
  },

  isAbilitaNave: function(idapp) {
    const myapp = this.getApps().find(item => item.idapp === idapp);
    return myapp.abilitanave;
  },

  getdirByIdApp: function(idapp, dirmain = false) {

    let mypath = '';
    const myapp =
        this.getApps().find(item => item.idapp === idapp);
    if (myapp) {
      if (process.env.NODE_ENV === 'test')
        mypath = (myapp) ? myapp.dir_test : '';
      else
        mypath = (myapp) ? myapp.dir : '';

      if (dirmain) {
        if (!this.sulServer()) {
          mypath += '/public';
        }
      }
    }

    return mypath;
  },

  getAdminEmailByIdApp: function(idapp) {
    const myapp = this.getApps().find((item) => item.idapp === idapp);
    if (myapp)
      return myapp.adminemail;
    else
      return '';
  },

  getreplyToEmailByIdApp: function(idapp) {
    const myapp = this.getApps().find((item) => item.idapp === idapp);
    if (myapp)
      return myapp.replyTo;
    else
      return '';
  },

  getpathregByIdApp: function(idapp, lang) {
    const myapp = this.getApps().find((item) => item.idapp === idapp);
    if (myapp) {
      const addstr = myapp.pathreg_add ? myapp.pathreg_add : '';

      return 'registration' + addstr + '/' + lang;
    } else {
      return 'registration' + '/' + lang;
    }
  },

  getAskToVerifyReg: function(idapp) {
    return this.getConfSiteOptionEnabledByIdApp(idapp, shared_consts.ConfSite.Need_Aportador_On_DataReg_To_Verify_Reg);
  },

  isManagAndAdminDifferent(idapp) {
    const manag = this.getManagerEmailByIdApp(idapp);
    return (manag !== this.getAdminEmailByIdApp(idapp)) && (manag !== '');
  },

  getManagerEmailByIdApp: function(idapp) {
    const myapp = this.getApps().find((item) => item.idapp === idapp);
    if (myapp)
      return !!myapp.manageremail ? myapp.manageremail : '';
    else
      return '';
  },

  getEmailByIdApp: function(idapp) {
    const myapp = this.getApps().find((item) => item.idapp === idapp);
    return (myapp) ? myapp.email_from : '';
  },

  getPwdByIdApp: function(idapp) {
    const myapp = this.getApps().find((item) => item.idapp === idapp);
    return (myapp) ? this.decryptdata(myapp.email_pwd) : '';
  },

  getTelegramBotNameByIdApp: function(idapp) {
    const myapp = this.getApps().find((item) => item.idapp === idapp);
    if (process.env.NODE_ENV === 'test')
      return (myapp) ? myapp.telegram_bot_name_test : '';
    else
      return (myapp) ? myapp.telegram_bot_name : '';
  },

  getTelegramSupportChat: function(idapp) {
    try {
      const myapp = this.getApps().find((item) => item.idapp === idapp);
      return (myapp && myapp.telegram_support_chat)
          ? myapp.telegram_support_chat
          : '';
    } catch (e) {
      return '';
    }
  },

  getTelegramKeyByIdApp: function(idapp) {
    const myarr = this.getApps();
    const myapp = myarr.find((item) => item.idapp === idapp);
    if (process.env.NODE_ENV === 'test')
      return (myapp) ? myapp.telegram_key_test : '';
    else
      return (myapp) ? myapp.telegram_key : '';
  },

  getLookup: function(params, num, pass_proj) {
    const query = [];

    if (!params)
      return;

    let mylkLF = params.lk_LF;
    if (params.af_objId_tab) {
      const myobj = {};
      myobj['myId' + num] = {'$toObjectId': '$' + params.lk_LF};
      query.push(
          {'$addFields': myobj},
      );
      mylkLF = 'myId' + num;
    } else {

    }

    if (params.lk_tab) {
      let proj = params.lk_proj;
      if (!!pass_proj) {
        proj = pass_proj;
      }
      query.push(
          {
            $lookup: {
              from: params.lk_tab,
              localField: mylkLF,    // field in my collection
              foreignField: params.lk_FF,  // field in the 'from' collection
              as: params.lk_as,
            },
          },
          {
            $replaceRoot: {
              newRoot: {
                $mergeObjects: [
                  {
                    $arrayElemAt: [
                      '$' + params.lk_as, 0],
                  }, '$$ROOT'],
              },
            },
          },
          {$project: proj},
      );
    }

    return query;
  },

  getFilterParam: function(filter, fieldsearch) {
    let myregexp = {};
    myregexp = new RegExp(filter.trim().replace(' ', '|'), 'ig');

    let query = [];

    const myfilters = [];
    for (const rec of fieldsearch) {
      let data = {};
      if (rec.type === this.FieldType.exact) {
        data[rec.field] = filter.trim();
      } else if (rec.type === this.FieldType.string) {
        data[rec.field] = myregexp;
      } else if ((rec.type === this.FieldType.number) ||
          (rec.type === this.FieldType.hours)) {
        data[rec.field] = parseFloat(filter.trim());
      }
      if (data)
        myfilters.push(data);
    }

    if (myfilters.length > 0) {
      query = [
        {$match: {$or: myfilters}},
      ];
    }

    return query;
  },

  getQueryTable: function(idapp, params, user) {

    if (typeof params.startRow !== 'number') {
      throw new Error('startRow must be number');
    } else if (typeof params.endRow !== 'number') {
      throw new Error('endRow must be number');
    }

    let newvers = !!params.lookup1;

    let query = [];

    if (params.filter && params.fieldsearch) {
      const querytemp = this.getFilterParam(params.filter, params.fieldsearch);
      if (querytemp) {
        query = [...query, ...querytemp];
      }
    }

    let filtriadded = [];

    // if (params.table === 'extralist') {
    //   if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_DELETED))
    //     filtriadded.push({ deleted: true });
    //   else
    //     filtriadded.push({ deleted: { $exists: false } });
    // }

    if (params.filterand) {

      if (params.filterand.includes(
          shared_consts.FILTER_EXTRALIST_NOT_REGISTERED))
        filtriadded.push({registered: false});

      if (params.filterand.includes(
          shared_consts.FILTER_EXTRALIST_NOT_CONTACTED)) {
        filtriadded.push({contacted: {$exists: false}});
      }
      if (params.filterand.includes(shared_consts.FILTER_EXTRALIST_WITH_NOTE))
        filtriadded.push({
          'note': {$exists: true},
          '$expr': {'$gt': [{'$strLenCP': '$note'}, 1]},
        });
      if (params.filterand.includes(shared_consts.FILTER_QUALIFIED))
        filtriadded.push({'profile.qualified': true});
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_ZOOM))
        filtriadded.push({'profile.saw_zoom_presentation': false});
      if (params.filterand.includes(shared_consts.FILTER_ASK_ZOOM_VISTO))
        filtriadded.push({'profile.ask_zoom_partecipato': false});
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_INVITANTE))
        filtriadded.push({
          aportador_solidario: {$exists: false},
        });
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_TELEGRAM_ID))
        filtriadded.push({'profile.teleg_id': {$lt: 1}});
      if (params.filterand.includes(shared_consts.FILTER_USER_SI_TELEGRAM_ID))
        filtriadded.push({'profile.teleg_id': {$gt: 1}});
      if (params.filterand.includes(
          shared_consts.FILTER_USER_CODICE_AUTH_TELEGRAM))
        filtriadded.push({'profile.teleg_checkcode': {$gt: 1}});
      if (params.filterand.includes(
          shared_consts.FILTER_USER_NO_EMAIL_VERIFICATA))
        filtriadded.push({verified_email: false});
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_VERIFIED_APORTADOR))
        filtriadded.push({
          verified_by_aportador: {$exists: false},
        });
      if (params.filterand.includes(shared_consts.FILTER_USER_WITHOUT_USERNAME_TELEGRAM))
        filtriadded.push({
          $or: [
            {'profile.username_telegram': {$exists: false}},
            {'profile.username_telegram': {$exists: true, $eq: ''}}],

        });
      if (params.filterand.includes(shared_consts.FILTER_USER_NO_DREAM))
        filtriadded.push({
          'profile.my_dream': {
            $exists: false,
          },
        });
      if (params.filterand.includes(shared_consts.FILTER_USER_TELEGRAM_BLOCKED))
        filtriadded.push({'profile.teleg_id_old': {$gt: 1}});
      if (params.filterand.includes(shared_consts.FILTER_ATTIVI))
        filtriadded.push({
          $or: [
            {deleted: {$exists: false}},
            {deleted: {$exists: true, $eq: false}}],
        });

      // Hours
      if (params.filterand.includes(shared_consts.FILTER_HOURS_ALL))
        filtriadded.push(
            {
              idapp,
              todoId: params.codeId,
              $or: [
                {deleted: {$exists: false}},
                {deleted: {$exists: true, $eq: false}}],
            },
        );
      else if (params.filterand.includes(shared_consts.FILTER_HOURS_MYLIST))
        filtriadded.push(
            {
              idapp,
              userId: params.userId,
              todoId: params.codeId,
              $or: [
                {deleted: {$exists: false}},
                {deleted: {$exists: true, $eq: false}}],
            },
        );

      if (params.filterand.includes(shared_consts.FILTER_NASCOSTI))
        filtriadded.push({
          deleted: {$exists: true, $eq: true},
        });
      if (params.filterand.includes(shared_consts.FILTER_NAVI_NON_PRESENTI))
        filtriadded.push({
          navinonpresenti: {$exists: true, $eq: true},
          $or: [
            {subaccount: {$exists: false}},
            {subaccount: {$exists: true, $eq: false}}],
        });

      // Iscritti Conacreis
      if (params.filterand.includes(shared_consts.FILTER_MISSING_PAYMENT))
        filtriadded.push({
          $or: [
            {ha_pagato: {$exists: false}},
            {ha_pagato: {$exists: true, $lt: 1}}],
        });

      if (params.filterand.includes(
          shared_consts.FILTER_TO_MAKE_MEMBERSHIP_CARD))
        filtriadded.push({
          $or: [
            {codiceConacreis: {$exists: false}},
            {codiceConacreis: {$exists: true, $eq: ''}}],
        });

      if (params.filterand.includes(shared_consts.FILTER_MEMBERSHIP_CARD_OK))
        filtriadded.push({
          codiceConacreis: {$exists: true},
          '$expr': {'$gt': [{'$strLenCP': '$codiceConacreis'}, 0]},
        });

    }

    if (params.filtercustom) {
      let condition = {};
      for (const myfilter of params.filtercustom) {
        if (myfilter['userId']) {
          myfilter['userId'] = ObjectID(myfilter['userId']);
        }
        if (myfilter['_idOBJ']) {
          filtriadded.push({_id: ObjectID(myfilter['_idOBJ'])});
        } else if (myfilter['dateTimeStart']) {
          const gte = myfilter['dateTimeStart'].$gte;
          const lte = myfilter['dateTimeStart'].$lte;
          condition = {
            dateTimeStart: {
              $gte: new Date(gte),
              $lte: new Date(lte),
            },
          };
          filtriadded.push(condition);
        } else if (myfilter.hasOwnProperty('pub_to_share')) {
          // non aggiungere niente
        } else {
          filtriadded.push(myfilter);
        }
      }
    }

    if (params.filter_gte) {
      for (let ind = 0; ind < params.filter_gte.length; ind++) {
        for (const [key, value] of Object.entries(params.filter_gte[ind])) {
          if (value > 0) {
            let condition = {};
            condition[key] = {$gte: value};
            filtriadded.push(condition);
          }
        }
      }
    }

    if (params.filtersearch) {
      filtriadded.push(...params.filtersearch);
    }

    if (params.options) {
      if (this.isBitActive(params.options,
          shared_consts.OPTIONS_SEARCH_ONLY_FULL_WORDS)) {

      }
    }

    if (filtriadded) {
      if (filtriadded.length > 0)
        query.push({$match: {$and: filtriadded}});
    }

    if (idapp > 0) {
      query.push({$match: {idapp}});
    }

    // console.log('QUERYMATCH', query[0].$match.or);
    // console.log('filter', params.filter);

    if (params.sortBy) {
      // maybe we want to sort by blog title or something
      const mysort = {$sort: params.sortBy};
      // console.log('sortBy', params.sortBy);
      // console.table(mysort);
      query.push(mysort);
    }

    let numrowend = params.endRow - params.startRow;
    if (numrowend < 0)
      numrowend = 1;

    if (params.querytype === shared_consts.QUERYTYPE_MYGROUP) {
      // query.push({'$addFields': {'myid': {'$toObjectId': params.myid}}});
      /*query.push({
        $match: {
          $and: [
            {idapp},
            {'_id': '$myid'},
          ],
        },
      });

       */
      query.push({
        $match: {
          $expr: {
            $eq: ['$_id', params.myid],
          },
        },
      });
      query.push({$project: {req_users: 1}});
      const qa1 = this.getLookup(
          {
            lk_tab: 'users',
            lk_LF: 'req_users.username',
            lk_FF: 'username',
            lk_as: 'user',
          }, 0, {
            'user.idapp': 1,
            'user.username': 1,
            'user.profile.img': 1,
            'user.profile.qualifica': 1,
          });
      if (qa1) query = [...query, ...qa1];
      query.push({$unwind: '$user'});
      query.push({
        $match: {
          $and: [
            {'user.idapp': idapp},
          ],
        },
      });

      query.push({
            $replaceRoot: {
              newRoot: '$user',
            },
          },
      );

    }

    if (newvers) {
      // NUOVA VERSIONE
      let proj = params.lookup2 ? params.lookup2.lk_proj : null;

      if (params.proj0) {
        query.push({$project: params.proj0});
      }

      if (params.unwind0) {
        query.push({$unwind: params.unwind0});
      }

      const q1 = this.getLookup(params.lookup1, 1, proj);
      if (q1) query = [...query, ...q1];

      if (params.unwind1) {
        query.push({$unwind: params.unwind1});
      }

      const q2 = this.getLookup(params.lookup2, 2, proj);
      if (q2) query = [...query, ...q2];

      const q3 = this.getLookup(params.lookup3, 3, proj);
      if (q3) query = [...query, ...q3];

      const q4 = this.getLookup(params.lookup4, 4, proj);
      if (q4) query = [...query, ...q4];

      const q5 = this.getLookup(params.lookup5, 5, proj);
      if (q5) query = [...query, ...q5];

      if (params.filtersearch2) {
        if (params.filtersearch2.length > 0) {
          query.push({$match: {$and: params.filtersearch2}});
        }
      }

      if (params.filtercustom) {
        let condition = {};
        for (const myfilter of params.filtercustom) {

          if (myfilter['pub_to_share'] === shared_consts.PUBTOSHARE.ONLY_GROUPS_FOLLOW) {

            let arraygroups = [];

            if (user && user.profile.mygroups) {
              arraygroups = user.profile.mygroups.map(rec => rec.groupname);
            }
            // prendere i gruppi dell'utente

            // Cerca tra i gruppi di ogni record, se combaciano almeno 1
            condition = {
              'profile.mygroups':
                  {
                    $elemMatch: {
                      groupname: {$in: arraygroups},
                    },

                  },
            };

            query.push({$match: {$and: [condition]}});
          } else if (shared_consts.TABLES_ENABLE_GETREC_BYID.includes(params.table)) {
            // Rimuovi dalla query quelli non visibili

            let arraygroups = [];

            if (user && user.profile.mygroups) {
              arraygroups = user.profile.mygroups.map(rec => rec.groupname);
            }
            // prendere i gruppi dell'utente

            // Cerca tra i gruppi di ogni record, se combaciano almeno 1
            condition = {
              'profile.mygroups':
                  {
                    $elemMatch: {
                      groupname: {$in: arraygroups},
                    },

                  },
            };

            query.push(
                {
                  $match:
                      {
                        $or: [
                          {
                            $and: [condition, {pub_to_share: 1}],
                          },
                          {$or: [{pub_to_share: {$exists: false}}, {pub_to_share: {$exists: true, $eq: shared_consts.PUBTOSHARE.ALL}}]},
                        ],
                      },
                },
            );

          }

        }
      }

      if (params.filtersearch3or) {
        if (params.filtersearch3or.length > 0) {
          query.push({$match: {$or: params.filtersearch3or}});
        }
      }

      if (params.filtersearch3and) {
        if (params.filtersearch3and.length > 0) {
          query.push({$match: {$and: params.filtersearch3and}});
        }
      }

      if (params.filter && params.fieldsearch_last) {
        const querytemp = this.getFilterParam(params.filter,
            params.fieldsearch_last);
        if (querytemp) {
          query = [...query, ...querytemp];
        }
      }

    } else {
      // VECCHIA VERSIONE
      const q1 = this.getLookup(params, 1);
      if (q1) query = [...query, ...q1];
    }

    query.push(
        {
          $group: {
            _id: null,
            // get a count of every result that matches until now
            count: {$sum: 1},
            // keep our results for the next operation
            results: {$push: '$$ROOT'},
          },
        },
        // and finally trim the results to within the range given by start/endRow
        {
          $project: {
            count: 1,
            rows: {$slice: ['$results', params.startRow, numrowend]},
          },
        },
    );

    // console.log('query', query);

    return query;

  },

  async executeQueryTable(mythistable, idapp, params, user) {
    let query = this.getQueryTable(idapp, params, user);

    try {
      // console.log('query', query);
      const [ris] = await mythistable.aggregate(query);

      if (ris) {
        // console.table(ris.rows);
        // console.log('ROW ', ris.count);
        return ({count: ris.count, rows: ris.rows});
      } else {
        return ({count: 0, rows: []});
      }
    } catch (e) {
      console.log('error', e);
    }

  },
  async DuplicateAllRecords(mythistable, idapporig, idappdest) {
    console.log(mythistable.name);

    const numrec = await mythistable.findAllIdApp(idappdest).then((arrrec) => {
      if (arrrec && arrrec.length > 1) {
        return arrrec.length; // Has Already set
      } else {
        return 0;
      }
    });

    // Before check if exist more than 1 record in the destination,
    // if Yes don't copy
    if (numrec <= 0) {
      return mythistable.findAllIdApp(idapporig).then(async (arrrec) => {
        let num = 0;

        for (let ind = 0; ind < arrrec.length; ind++) {
          let newrec = new mythistable(arrrec[ind]);
          newrec._id = new ObjectID();
          newrec.idapp = idappdest;

          try {
            await newrec.save((err, rec) => {
              if (rec) {
                num++;
              }

            });
          } catch (e) {
            console.error('e', e);
          }

        }
        return num;
      });
    }
  },

  isBitActive(bit, whattofind) {
    return ((bit & whattofind) === whattofind);
  },

  isBitAttivoESelez(bit, whattofind, loSono) {
    const abil = this.isBitActive(bit, whattofind);

    return ((abil && loSono) || !abil);
  },

  SetBit(myval, bit) {
    myval = myval & bit;
    return myval;
  },

  async snooze(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  },

  IncDateNow(secs) {
    let mydate = new Date(new Date().getTime() + secs);
    // console.log('mydate', mydate);
    return mydate;
  },

  isdiffSecDateLess(mydatediffstr, secs) {
    let mydate = new Date();
    // console.log('mydate', mydate);
    let mydata2 = new Date(mydatediffstr);
    let ris = ((mydate.getTime() - mydata2.getTime()) / 1000);
    return (ris < secs);
  },

  AddDate(mydate, days) {
    let date = new Date(mydate);
    date.setTime(date.getTime() + days * 86400000);
    return date;
  },

  isMonToSat() {
    const dayOfWeek = new Date(new Date()).getDay();
    return dayOfWeek >= 1 && dayOfWeek <= 6;
  },

  isMonToFri() {
    const dayOfWeek = new Date(new Date()).getDay();
    return dayOfWeek >= 1 && dayOfWeek <= 5;
  },

  // var startTime = '15:10:10';
  // var endTime = '22:30:00';
  isBetweenTwoTime(startTime, endTime) {

    currentDate = new Date();

    startDate = new Date(currentDate.getTime());
    startDate.setHours(startTime.split(':')[0]);
    startDate.setMinutes(startTime.split(':')[1]);
    startDate.setSeconds(startTime.split(':')[2]);

    endDate = new Date(currentDate.getTime());
    endDate.setHours(endTime.split(':')[0]);
    endDate.setMinutes(endTime.split(':')[1]);
    endDate.setSeconds(endTime.split(':')[2]);

    valid = startDate < currentDate && endDate > currentDate;

    return valid;
  },

  getlimiti(mypos) {

    if (mypos.col < 0) {
      mypos.col = 0;
    }

    if (mypos.riga < 0) {
      mypos.riga = 0;
      mypos.col = 0;
    }

    return mypos;
  },

  getRigaColByPosUp(mypos) {
    if (mypos.numup > 0) {
      mypos.riga = mypos.riga - mypos.numup;
      mypos.col = Math.ceil(mypos.col / (Math.pow(2, mypos.numup)));
    }
    if (mypos.numup < 0) {
      mypos.riga = mypos.riga - mypos.numup;
      mypos.col = Math.ceil(mypos.col * (Math.pow(2, -mypos.numup)));
    }
    if (mypos.riga < 1) {
      mypos.riga = 1;
    }
    if (mypos.col < 1) {
      mypos.col = 1;
    }
  },

  getRigaColGenerica(idapp, riga, col, numup) {
    mypos = {
      idapp,
      riga,
      col,
      numup,
    };

    if (idapp === this.AYNI) {
      this.getRigaColByPosUp(mypos);
      ris = this.getlimiti(mypos);
    }

    return ris;
  },

  getRigaColSognatoreByDonatore(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 6);
  },

  getRigaColMediatoreByFuoco(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 3);
  },

  getRigaColSognatoreByMediatore(idapp, riga, col) {
    return this.getRigaColGenerica(idapp, riga, col, 3);
  },

  appendLeadingZeroes(n) {
    if (n <= 9) {
      return '0' + n;
    }
    return n;
  },

  getWeekDayByLangByNumCar(date, lang, num) {
    if (!lang)
      lang = 'it';

    const dayOfWeek = new Date(date).getDay();

    const myday = {
      it: [
        'Domenica',
        'Lunedì',
        'Martedì',
        'Mercoledì',
        'Giovedì',
        'Venerdì',
        'Sabato'],
      enUs: [
        'Sunday',
        'Monday',
        'Tuesday',
        'Wednesday',
        'Thursday',
        'Friday',
        'Saturday'],
      fr: [
        'Dimanche',
        'Lundi',
        'Mardi',
        'Mercredi',
        'Jeudi',
        'Vendredi',
        'Samedi'],
      es: [
        'Domingo',
        'Lunes',
        'Martes',
        'Miércoles',
        'Jueves',
        'iernes',
        'Sábado'],
      pt: [
        'Domingo',
        'Segunda',
        'Terça',
        'Quarta',
        'Quinta',
        'Sexta',
        'Sábado'],
      de: [
        'Sonntag',
        'Montag',
        'Dienstag',
        'Mittwoch',
        'Donnerstag',
        'Freitag',
        'Samstag'],
      si: [
        'Nedelja',
        'Ponedeljek',
        'Torek',
        'Sreda',
        'četrtek',
        'Petek',
        'Sobota'],
    };

    if (num > 0) {
      return isNaN(dayOfWeek) ? '' : myday[lang][dayOfWeek].substring(0, num);
    } else {
      return isNaN(dayOfWeek) ? '' : myday[lang][dayOfWeek];
    }
  },

  isSunday(mydate) {
    const dayOfWeek = new Date(mydate).getDay();
    return dayOfWeek === 0;
  },

  isMonday(mydate) {
    const dayOfWeek = new Date(mydate).getDay();
    return dayOfWeek === 1;
  },

  isSundayDate(mydate) {
    const dayOfWeek = mydate.getDay();
    return dayOfWeek === 0;
  },

  isMondayDate(mydate) {
    const dayOfWeek = mydate.getDay();
    return dayOfWeek === 1;
  },

  getWeekDayByLang(date, lang) {
    return this.getWeekDayByLangByNumCar(date, lang, 3);
  },

  getWeekDayByLangTot(date, lang) {
    return this.getWeekDayByLangByNumCar(date, lang, 0);
  },

  getWeekDay(date) {
    //Create an array containing each day, starting with Sunday.
    const weekdays = [
      'Domenica',
      'Lunedì',
      'Martedì',
      'Mercoledì',
      'Giovedí',
      'Venerdì',
      'Sabato',
    ];
    //Use the getDay() method to get the day.
    const day = date.getDay();
    //Return the element that corresponds to that index.
    return weekdays[day];
  },

  getstrDateTimeShort(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLang(mydate, lang) + ' ' +
          this.appendLeadingZeroes(mydate.getDate()) + '/' +
          this.appendLeadingZeroes(mydate.getMonth() + 1) + ' ORE ' +
          this.appendLeadingZeroes(mydate.getHours()) + ':' +
          this.appendLeadingZeroes(mydate.getMinutes());
    } else {
      return '';
    }
  },

  getstrDateShort(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLang(mydate, lang).substring(0, 3) + ' ' +
          this.appendLeadingZeroes(mydate.getDate()) + '/' +
          this.appendLeadingZeroes(mydate.getMonth() + 1);
    } else {
      return '';
    }
  },

  getstrDate_DD_MM_YYYY(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.appendLeadingZeroes(mydate.getDate()) + '/' +
          this.appendLeadingZeroes(mydate.getMonth() + 1) + '/' +
          this.appendLeadingZeroes(mydate.getFullYear());
    } else {
      return '';
    }
  },

  getstrDateYYYY_MM_DD(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return mydate.getFullYear() + '-' +
          this.appendLeadingZeroes(mydate.getMonth() + 1) + '-' +
          this.appendLeadingZeroes(mydate.getDate());
    } else {
      return '';
    }
  },

  getstrUTCDateYYYY_MM_DD(mydate) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return mydate.getUTCFullYear() + '-' +
          this.appendLeadingZeroes(mydate.getUTCMonth() + 1) + '-' +
          this.appendLeadingZeroes(mydate.getUTCDate());
    } else {
      return '';
    }
  },

  getstrDateLong(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLang(mydate, lang) + ' ' +
          this.appendLeadingZeroes(mydate.getDate()) + '/' +
          this.appendLeadingZeroes(mydate.getMonth() + 1) + '/' +
          mydate.getFullYear();
    } else {
      return '';
    }
  },

  getstrDateLongTot(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return this.getWeekDayByLangTot(mydate, lang) + ' ' +
          this.appendLeadingZeroes(mydate.getDate()) + '/' +
          this.appendLeadingZeroes(mydate.getMonth() + 1) + '/' +
          mydate.getFullYear();
    } else {
      return '';
    }
  },

  getstrDateLongFile(mydate, lang) {
    if (mydate) {
      // console.log('getstrDate', mytimestamp)
      return mydate.getFullYear() + '-' +
          this.appendLeadingZeroes(mydate.getMonth() + 1) + '-' +
          this.appendLeadingZeroes(mydate.getDate()) + ' (' +
          this.getWeekDayByLang(mydate, lang) + ')';
    } else {
      return '';
    }
  },

  getlinkzoom(rec) {
    if (rec === null) {
      rec = {
        typeconf: this.TYPECONF_ZOOM,
        id_conf_zoom: '',
      };
    }
    let typeconf = rec.typeconf;
    if (typeconf === '')
      typeconf = this.TYPECONF_ZOOM;

    let mylink = 'https://zoom.us/j/';
    if (typeconf === this.TYPECONF_JITSI)
      mylink = 'https://meet.jit.si/';

    if (rec.id_conf_zoom === '') {
      rec.id_conf_zoom = '6668882000';
    }

    return mylink + rec.id_conf_zoom;

  },

  getmd5(mystr) {
    return CryptoJS.MD5(mystr.toLowerCase()).toString();
  },

  getHash(mystr) {
    return CryptoJS.SHA512(mystr, {outputLength: 256}).toString();
  },

  encrypt(msg, pass) {
    var salt = CryptoJS.lib.WordArray.random(128 / 8);

    var key = CryptoJS.PBKDF2(pass, salt, {
      keySize: keySize / 32,
      iterations: iterations,
    });

    var iv = CryptoJS.lib.WordArray.random(128 / 8);

    var encrypted = CryptoJS.AES.encrypt(msg, key, {
      iv: iv,
      padding: CryptoJS.pad.Pkcs7,
      mode: CryptoJS.mode.CBC,

    });

    // salt, iv will be hex 32 in length
    // append them to the ciphertext for use  in decryption
    var transitmessage = salt.toString() + iv.toString() + encrypted.toString();
    return transitmessage;
  },

  decrypt(transitmessage, pass) {
    var salt = CryptoJS.enc.Hex.parse(transitmessage.substr(0, 32));
    var iv = CryptoJS.enc.Hex.parse(transitmessage.substr(32, 32));
    var encrypted = transitmessage.substring(64);

    var key = CryptoJS.PBKDF2(pass, salt, {
      keySize: keySize / 32,
      iterations: iterations,
    });

    var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
      iv: iv,
      padding: CryptoJS.pad.Pkcs7,
      mode: CryptoJS.mode.CBC,

    });
    return decrypted;
  },

  cryptdata(mydata) {
    if (mydata === '')
      return '';
    // Encrypt
    //return CryptoJS.AES.encrypt(JSON.stringify(mydata), process.env.SECRK);
    return this.encrypt(mydata, process.env.SECRK);
  },

  decryptdata(mydatacrypted) {
    if (mydatacrypted === '')
      return '';
    // Decrypt
    // const bytes = CryptoJS.AES.decrypt(mydatacrypted.toString(), process.env.SECRK);
    // return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
    return this.decrypt(mydatacrypted, process.env.SECRK).
        toString(CryptoJS.enc.Utf8);
  },

  BoolToInt(mybool) {
    return (mybool) ? -1 : 0;
  },

  StrToBool(mystr) {
    return (mystr === '-1') ? true : false;
  },

  writelogfile(mystr, filename) {
    //   fs.appendFile(FILELOG, mystr, function (err) {
    //     if (err) throw err;
    //     console.log('Saved!');
    //   });
    try {
      mystr = this.getstrDateTimeShort(new Date(), 'it') + ': ' + mystr;

      const stream = fs.createWriteStream(filename, {flags: 'a'});
      stream.write('\n' + mystr);
      stream.end();
    } catch (e) {

    }

  },

  readlogfile(idapp, filename) {

    try {
      return fs.readFileSync(idapp + '/' + filename, 'utf8');
    } catch (e) {
      return '';
    }
  },

  writelog(mystr) {
    this.writelogfile(mystr, FILELOG);
  },

  writeEventsLog(mystr) {
    this.writelogfile(mystr, FILEEVENTS);
  },

  writeManagersLog(mystr) {
    this.writelogfile(mystr, FILEMANAGERS);
  },
  writeErrorLog(mystr) {
    this.writelogfile(mystr, FILEERRLOGIN);
  },

  writeSostituzioniLog(mystr) {
    this.writelogfile(mystr, FILESOSTITUZIONI);
  },

  writeIPToBan(mystr) {
    this.writelogfile(mystr, FILEIP_TO_BAN);
  },

  writeFlottaLog(idapp, mystr, riga, col) {
    this.mkdirpath(idapp + '/');
    this.writelogfile(mystr, idapp + '/' + riga + '_' + col + '.txt');
  },

  readFlottaLog(idapp, riga, col) {
    const nomefile = riga + '_' + col + '.txt';
    return this.readlogfile(idapp, nomefile);
  },

  writeNaveLog(mystr) {
    this.writelogfile(mystr, FILENAVE);
  },

  move(oldPath, newPath, callback) {

    fs.rename(oldPath, newPath, function(err) {
      if (err) {
        if (err.code === 'EXDEV') {
          copy();
        } else {
          callback(err);
        }
        return;
      } else {
        console.log(' ... File Rinominato', oldPath, 'in:', newPath);
      }

      callback();
    });

    function copy() {
      const readStream = fs.createReadStream(oldPath);
      const writeStream = fs.createWriteStream(newPath);

      readStream.on('error', callback);
      writeStream.on('error', callback);

      readStream.on('close', function() {
        console.log('cancella file già copiato', oldPath);
        fs.unlink(oldPath, callback);
      });

      readStream.pipe(writeStream);
    }
  },

  copy(oldPath, newPath, callback) {

    fs.copyFile(oldPath, newPath, (err) => {
      if (err) throw err;
      // console.log('source.txt was copied to destination.txt');
    });
  },

  extractFileName: function(filename) {
    return path.basename(filename);
  },

  extractFilePath: function(filename) {
    return path.dirname(filename);
  },

  delete(mypath, alsothumb, callback) {

    fs.unlink(mypath, function(err) {
      if (alsothumb) {
        try {
          let img_small = path.dirname(mypath) + '/' +
              server_constants.PREFIX_IMG_SMALL + path.basename(mypath);
          fs.unlink(img_small, function(err) {
            if (err)
              console.log('Errore durante la Cancellazione del file', mypath);
            else
              console.log('deleted file', mypath);
          });

        } catch (e) {
          console.error(err);
        }
      }
      if (err) {
        console.error(err);
        callback(err);
        return;
      }
      callback();
    });
  },

  mkdirpath(dirPath) {
    if (!fs.existsSync(dirPath)) {
      try {
        fs.mkdirSync(dirPath, {recursive: true});
      } catch (e) {
        if (dirPath !== path.dirname(dirPath)) {
          const myname = path.dirname(dirPath);
          this.mkdirpath(myname);
          // this.mkdirpath(dirPath);
        }
      }
    }
  },

  extractNameAndSurnameByComplete(name_complete) {
    const suffissoCognome = ['Del', 'La', 'De', 'Lo'];

    let campi = name_complete.split(' ');
    let namesurname = {
      name: '',
      surname: '',
    };

    if (campi.length === 2) {
      namesurname.name = campi[0];
      namesurname.surname = campi[1];
    } else if (campi.length === 3) {
      if (suffissoCognome.includes(campi[1])) {
        namesurname.name = campi[0];
        namesurname.surname = campi[1] + ' ' + campi[2];
      } else {
        namesurname.name = campi[0] + ' ' + campi[1];
        namesurname.surname = campi[2];
      }
    } else if (campi.length === 4) {
      namesurname.name = campi[0] + ' ' + campi[1];
      namesurname.surname = campi[2] + ' ' + campi[3];
    } else if (campi.length > 4) {
      namesurname.name = campi[0] + ' ' + campi[1];
      namesurname.surname = ' ' + campi[2];
      for (let ind = 3; ind < campi.length; ind++) {
        namesurname.surname += ' ' + campi[ind];
      }
    }

    return namesurname;
  },

  isFileExists(filename) {
    return fs.existsSync(filename);
  },

  getiPAddressUser(req) {
    try {
      const striniziale = '::ffff:';
      if (req.ip.indexOf(striniziale) >= 0) {
        return req.ip.substring(striniziale.length);
      } else {
        return req.ip;  // Express
      }

    } catch (e) {
      return '';
    }
  },
  removespaces(mystr) {
    return mystr.replace(/\s+/g, '');
  },

  ModificheConsentite(table, fieldsvalue, idrec, user) {
    if (table === 'sharewithus') {
      return true;
    }
    if (table === 'hours') {
      return true;
    }
    if (table === 'users') {
      if ('aportador_solidario' in fieldsvalue) {
        return true;
      }
      if ('ALL' in fieldsvalue) {
        //++Todo: Cancellalo solo se sono io il creatore dell'utente ... o se posso!
        return true;
      }
    }

    if (shared_consts.TABLES_PERM_CHANGE_FOR_USERS.includes(table)) {
      return true;
    }
    if (shared_consts.TABLES_PERM_NEWREC.includes(table)) {
      return true;
    }

    return false;
  },

  NotifyIfDelRecord(table) {
    if ((table === 'users') || (table === 'extralist') ||
        (table === 'groups') || (table === 'graduatorias')) {
      return true;
    }

    return false;
  },

  getFirstWord(mystr) {
    const myarr = mystr.split(' ');
    if (myarr.length > 0)
      return myarr[0];
    else
      return mystr;
  },

  getFirst2Car(mystr) {
    if (!!mystr)
      return mystr.substring(0, 2) + '.';
    else
      return '';
  },

  getmaxcol(riga) {
    return Math.pow(2, riga - 1);
  },

  getPrimoFuocoByIndCol(col) {
    // let ris = Math.ceil(col - (col % 8)) + 1;
    let ris = ((Math.ceil(col / 8) - 1) * 8) + 1;
    if (ris <= 0)
      ris = 1;
    return ris;
  },

  getPrimaColFlotta(col) {
    // let ris = Math.ceil(col - (col % 8)) + 1;
    let ris = ((Math.ceil(col / 64) - 1) * 64) + 1;
    if (ris <= 0)
      ris = 1;
    return ris;
  },

  getStrMsgByTipoMsg(tipomsg) {

    let mystr = '';

    if (tipomsg === this.TipoMsg.SEND_MSG_EFFETTUA_IL_DONO)
      mystr = 'Inviato Messaggio per Effettuare il Dono a Tutta la Flotta';
    else if (tipomsg === this.TipoMsg.SEND_MSG_SOLLECITO_DONATORI_NO_DONO)
      mystr = 'Inviato Messaggio per RICORDARE di Effettuare il Dono a chi ancora non l\'ha fatto';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_MEDIATORI)
      mystr = 'Inviato Messaggio ai Mediatori';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_SOGNATORE)
      mystr = 'Inviato Messaggio al Sognatore';
    else if (tipomsg === this.TipoMsg.SEND_MSG_A_UTENTE_SOSTITUITO)
      mystr = 'Inviato Messaggio all\'utente Sostituito';
    else if (tipomsg === this.TipoMsg.SEND_MSG_DONO_RICEVUTO_CORRETTAMENTE)
      mystr = 'Inviato Messaggio Dono Ricevuto Correttamente';

    return mystr;
  },

  getflagtelegrambyLang(lang) {
    if (lang === 'it')
      return '🇮🇹';
    else if (lang === 'si')
      return '🇸🇮';
    else if (lang === 'es')
      return '🇪🇸';
    else if (lang === 'enUs')
      return '🇬🇧';
    else if (lang === 'uk')
      return '🇬🇧';
    else if (lang === 'fr')
      return '🇫🇷';
  },

  blockwords(mystr) {
    const arrwords = [
      'http',
      'Http',
      'Asasfasfas',
      '://',
      'mrbit',
      'admin',
      'superuser'];
    try {
      for (const myword of arrwords) {
        if (mystr) {
          if (mystr.includes(myword)) {
            return true;
          }
        }
      }
    } catch (e) {

    }

    return false;
  },

  isAlphaNumeric(str) {
    let code, i, len;

    for (i = 0, len = str.length; i < len; i++) {
      code = str.charCodeAt(i);
      if (!(code > 47 && code < 58) && // numeric (0-9)
          !(code > 64 && code < 91) && // upper alpha (A-Z)
          !(code > 96 && code < 123)) { // lower alpha (a-z)
        return false;
      }
    }
    return true;
  },

  isAlphaNumericAndSpecialCharacter(str) {
    let code, i, len;

    for (i = 0, len = str.length; i < len; i++) {
      code = str.charCodeAt(i);
      if (!(code > 47 && code < 58) && // numeric (0-9)
          !(code > 63 && code < 91) && // upper alpha (A-Z)  // e @
          (code !== 46) && // . (punto)
          (code !== 95) && // _
          (code !== 45) && // -
          !(code > 96 && code < 123)) { // lower alpha (a-z)
        return false;
      }
    }
    return true;
  },

  categorizeQueryString(queryObj) {
    let query = {};
    let order = {};
    //extract query, order, filter value
    for (const i in queryObj) {
      if (queryObj[i]) {
        // extract order
        if (i === 'order') {
          order['sort'] = queryObj[i];
          continue;
        }
        // extract range
        if (i === 'range') {
          let range_arr = [];
          let query_arr = [];
          // multi ranges
          if (queryObj[i].constructor === Array) {
            for (const r of queryObj[i]) {
              range_arr = r.split('-');
              query_arr.push({
                price: {$gt: range_arr[0], $lt: range_arr[1]},
              });
            }
          }
          // one range
          if (queryObj[i].constructor === String) {
            range_arr = queryObj[i].split('-');
            query_arr.push({
              price: {$gt: range_arr[0], $lt: range_arr[1]},
            });
          }
          Object.assign(query, {$or: query_arr});
          delete query[i];
          continue;
        }
        query[i] = queryObj[i];
      }
    }
    return {query, order};
  },

  dateToEpoch(thedate) {
    const time = new Date(thedate).getTime();
    return time - (time % 86400000);
  },

  dateToEpochStr(thedate) {
    const time = new Date(thedate).getTime();
    return new Date(time - (time % 86400000));
  },

  async loadApps() {
    this.MYAPPS = await Site.findAll(0);
    // console.log('this.MYAPPS', this.MYAPPS);
  },
  getApps() {
    if (this.MYAPPS.length <= 0)
      this.loadApps();

    return this.MYAPPS;
  },

  getPaymentTypesById(idmetodo) {
    return shared_consts.PaymentTypes[idmetodo];
  },

  getVersionint(versionstr) {
    let versionarr = versionstr.split('.');
    let version = 0;

    versionarr = versionarr.reverse();

    for (let i = 0; i < versionarr.length; i++) {
      version += versionarr[i] * Math.pow(10, i * 2);
    }

    return parseInt(version);
  },

  invertescapeslash(mystr) {
    return mystr.replace(/-/g, '/');
  },

  isNumber(n) {
    return typeof (n) != 'boolean' && !isNaN(n) && n;
  },
  getNumObj(obj) {
    let count = 0;
    for (let properties in obj) {
      count = count + 1;
    }
    return count;
  },

  getNomeCognomeEUserNameByUser(user) {
    let nome = `${user.name} ${user.surname} (${user.username})`;
    if (!user.name) {
      nome = user.username;
    }

    return nome;
  },

  sulServer() {
    return process.env.LOCALE !== '1';
  },

  refreshAllTablesInMem(idapp, table, updatebot, username) {
    const telegrambot = require('../telegram/telegrambot');

    if (table === shared_consts.TAB_BOTS || updatebot) {
      telegrambot.reloadMenuBot(idapp);
    }

    if (table === shared_consts.TAB_USERS) {
      telegrambot.deleteRecInMemByUsername(idapp, username);
    } else if (table === shared_consts.TAB_SITES) {
      telegrambot.reloadSites();
    }

  },

  downloadImage(url, filepath) {
    try {
      return download.image({
        url,
        dest: filepath,
      });
    } catch (e) {
      console.error('Err download image', e);
      return false;
    }

  },

  convertSpecialTags(user, msg) {
    try {
      if (!!user) {
        msg = msg.replace('{appname}', this.getNomeAppByIdApp(user.idapp));
        msg = msg.replace('{username}', user.username);
        msg = msg.replace('{name}', user.name ? user.name : user.username);
        msg = msg.replace('{surname}', user.surname ? user.surname : '');
        msg = msg.replace('{aportador_solidario}', user.aportador_solidario ? user.aportador_solidario : '');
        if (!!user.profile.link_payment)
          msg = msg.replace('{link_paypalme}', user.profile.link_payment);
        if (!!user.profile.revolut)
          msg = msg.replace('{revolut}', user.profile.revolut);
        if (!!user.profile.payeer_id)
          msg = msg.replace('{payeer_id}', user.profile.payeer_id);
        if (!!user.profile.advcash_id)
          msg = msg.replace('{advcash_id}', user.profile.advcash_id);
        if (!!user.profile.email_paypal)
          msg = msg.replace('{email_paypal}', user.profile.email_paypal);
        if (!!user.profile.note_payment)
          msg = msg.replace('{note_payment}', user.profile.note_payment);
      }

      // const cl = getclTelegByidapp(user.idapp);
      msg = msg.replace('{link_chathelp}', this.HELP_CHAT);

    } catch (e) {
      console.log(e);
    }

    return msg;
  },

  getUserInfoMsg: function(idapp, myuser, usernametelegram = '', name_telegram = '',
      surname_telegram = '') {

    let msg = '';
    try {
      const name = myuser.username +
          (myuser.name ? ` (${myuser.name} ${myuser.surname})` : '');
      const linkuserprof = this.getHostByIdApp(idapp) + '/my/' +
          myuser.username;

      msg = `<br>Username: <b>${name}</b><br>Profilo su APP: ${linkuserprof}<br>Email: ${myuser.email}`;

      let u_tg = myuser.profile && myuser.profile.hasOwnProperty('username_telegram') ? myuser.profile.username_telegram : usernametelegram;
      let name_tg = myuser.profile && myuser.profile.hasOwnProperty('firstname_telegram')
          ? myuser.profile.firstname_telegram
          : name_telegram;
      let surname_tg = myuser.profile && myuser.profile.hasOwnProperty('lastname_telegram')
          ? myuser.profile.lastname_telegram
          : surname_telegram;

      if (u_tg) {
        msg += `<br>Profilo su Telegram [${name_tg} ${surname_tg}]:<br>https://t.me/${u_tg}`;
      }
    } catch (e) {
      console.error('getUserInfoMsg', e);
    }

    return msg;
  },

  getlinkRequestNewPassword: function(idapp, email, tokenforgot) {
    const strlinkreg = this.getHostByIdApp(idapp) + process.env.LINK_UPDATE_PASSWORD +
        `?idapp=${idapp}&email=${email}&tokenforgot=${tokenforgot}`;
    return strlinkreg;
  },

  execScript: function(idapp, msg, script, testo) {
    const {exec} = require('child_process');
    const telegrambot = require('../telegram/telegrambot');

    const idchat = msg.chat.id;

    console.log(testo + ' (' + script + ')');
    telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, testo);

    exec(script, (error, stdout, stderr) => {
      if (error) {
        telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, 'ERROR: ' + error.message);
        console.log(`error: ${error.message}`);
        return;
      }
      if (stderr) {
        telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, 'ERROR: ' + stderr);
        console.log(`stderr: ${stderr}`);
        return;
      }

      // console.log(`  ... stdout: ${stdout}`);
      telegrambot.local_sendMsgTelegramByIdTelegram(idapp, idchat, stdout);
    });
  },

  removeAtChar(mystr) {
    if (mystr && mystr[0] === '@'){
      return mystr = mystr.substring(1);
    }

    return mystr;
  },

};
