// Node imports const fs = require('fs'); const path = require('path'); // Telegram Module const TelegramBot = require('node-telegram-bot-api'); // Congifuration const config = require('./config'); let admin_chatId; let authorizedUsers; let telegram; const SLIDESHOW_LOCATION = config.slideShowFolderPath; /** * Runs the bot. * @param {Array} args CLI arguments * @return {void} */ let runBot = (args) => { loadDB(); telegram = new TelegramBot(config.botId, { polling: true }); telegram.on('message', (msg) => { const chatId = msg.chat.id; if (args.indexOf('--printMessage') > 0) { console.log(JSON.stringify(msg, null, 2)); process.exit(0); } if (userAllowd(msg)) { handleMessage(msg); } else { let message = config.messages.sendieResponse.unauthorizedUser; message = message.replace('{username}', msg.from.username); message = message.replace('{messageObj}', JSON.stringify(msg, null, 2)); telegram.sendMessage(chatId, message); message = config.messages.adminNotifications.unauthorizedUser; message = message.replace('{username}', msg.from.username); message = message.replace('{messageObj}', JSON.stringify(msg, null, 2)); console.warn(message); notifyAdmin(message); } }); console.log('Telegram bot is alive and listening!'); } /** * Save a photo to config.slideShowFolderPath * @param {string} fileId fileId (received from telegram) * @param {Function} callback post save function - args: {Error} error, {string} filePath * @return {void} */ function storeFile(fileId, callback) { telegram.downloadFile(fileId, SLIDESHOW_LOCATION) .then((filePath) => { let message = config.messages.sendieResponse.fileSuccessfullySaved; message = message.replace('{fileLocation}', filePath); callback(null, filePath); }) .catch((err) => { callback(err); }); } let storeFileCallback = (err, filePath, chatId, photoId) => { let response; if (err) { console.error('Failed to save file...[ERROR]\n', err); response = config.messages.sendieResponse.failedToSaveFile; response = response.replace('{error}', err); telegram.sendMessage(chatId, response); response = config.messages.sendieResponse.failedToSaveFile; response = response.replace('{error}', err); if (admin_chatId !== chatId) notifyAdmin(response, photoId); } else { response = config.messages.sendieResponse.fileSuccessfullySaved; response = response.replace('{fileLocation}', filePath); telegram.sendMessage(chatId, response); response = config.messages.adminNotifications.fileSuccessfullySaved; response = response.replace('{fileLocation}', filePath); if (admin_chatId !== chatId) notifyAdmin(response, photoId); } }; /** * This function is called on every message from an authorized user. * @param {JSON} msg telegram message object * @return {void} */ let handleMessage = (msg) => { const chatId = msg.chat.id; if (msg.photo || (msg.document && msg.document.mime_type.indexOf('image/') === 0)) { // Handle Images if (msg.photo) { storeFile(msg.photo[msg.photo.length - 1].file_id, (err, filePath) => { storeFileCallback(err, filePath, chatId, msg.photo[msg.photo.length - 1].file_id); }); } else { // document - uncompressed photo storeFile(msg.document.file_id, (err, filePath) => { storeFileCallback(err, filePath, chatId, msg.document.file_id); }); } } else if (msg.contact) { // Handle Contact addRemoveUser(msg.contact, msg.from); } else { // send a message to the chat acknowledging receipt of their message let message = config.messages.sendieResponse.unableToDoThat; message = message.replace('{username}', msg.from.username); telegram.sendMessage(chatId, message); } }; /** * Check if a user is authorized to interact with this bot * @param {JSON} msg telegram message object * @return {boolean} true/false */ let userAllowd = (msg) => { if (msg.from.id === config.admin.id) { admin_chatId = msg.chat.id; return true; } if (config.permissions.allowAll) return true; if (authorizedUsers.indexOf(msg.from.id) >= 0) return true; return false; }; /** * Sends a message to admin. Only if config.admin.notifyActivity is true * @param {string} msg message to send * @param {string} photo_id the photo to send to admin incase config.notifyActivity.sendPhoto is true * @return {void} */ let notifyAdmin = (msg, photo_id) => { if (admin_chatId != null && config.admin.notifyActivity) { telegram.sendMessage(admin_chatId, msg); if (photo_id && config.admin.sendPhotoOnSave) telegram.sendPhoto(admin_chatId, photo_id); } }; /** * Will add/remove a user from authorized users list. (Saved in a file) * If a user is not in the list, it will add him. Else remove him. * Will notify admin if wanted (config.admin.notifyActivity) and send the initiating user on fail/success * @param {JSON} contact Telegram contact object. The user to add/remove * @param {JSON} from Telegram user object. Who wanted to add/remove the contact */ let addRemoveUser = (contact, from) => { if (!config.permissions.allowAddingUsers) { // Ignore... console.warn('Attempt to add/remove a user. this functionality is disabled.'); return; } else { if (from.id != config.admin.id && !config.permissions.allowNonAdminToAddUsers) { telegram.sendMessage(from.id, config.messages.sendieResponse.noPermissions); console.warn('Attempt to add/remove a user from an unpermited user. id: ', from.id); return; } } let toAdd = true; if (authorizedUsers.indexOf(contact.user_id) < 0) { // Add user authorizedUsers.push(contact.user_id); } else { // remove user toAdd = false; authorizedUsers.splice(authorizedUsers.indexOf(contact.user_id), 1); } if (saveToDB()) { let message; if (toAdd) message = config.messages.sendieResponse.userAdded; else message = config.messages.sendieResponse.userRemoved; message = message.replace('{username}', contact.first_name); telegram.sendMessage(from.id, message); // response to the user that added the new one // Not supported yet - a bot can't start a chat with a new user... so can't do this. // if(toAdd) message = config.messages.userAdded.added; // else message = config.messages.userRemoved.removed; // message = message.replace('{username_add_rem}', contact.first_name); // message = message.replace('{username}', from.first_name); // telegram.sendMessage(contact.user_id, message); // Notify the new/old user if (toAdd) message = config.messages.adminNotifications.userAdded; else message = config.messages.adminNotifications.userRemoved; message = message.replace('{username_add_rem}', contact.first_name); message = message.replace('{username}', from.first_name); if (from.id != config.admin.id) notifyAdmin(message); // notifyAdmin } else { console.log('Failed to save DB!!!'); } } /** * Load authorized users list from disk (Reading a JSON file). If not exists (Usually on first run), will create the file. * Incase the file is not in valid JSON format. will kill process with err code 1. * @return {void} */ let loadDB = () => { let dbLocation = config.DBLocation; if (dbLocation[dbLocation.length - 1] != '/') dbLocation += '/users.json'; else dbLocation += 'users.json'; try { data = fs.readFileSync(path.resolve(dbLocation)); try { authorizedUsers = JSON.parse(data.toString()); console.log('DB loaded. AuthorizedUsers: ', authorizedUsers); } catch (err) { console.error('DB file is corrupted. Please delete the file', dbLocation, 'And rerun this service'); process.exit(1); } } catch (err) { authorizedUsers = []; if (saveToDB()) { console.log('DB was created. Everything is Okay.'); } else { console.error('Unable to read from DB. Error:\n', err); process.exit(1); } } } /** * Saves the authorized users list to disk. (config.DBLocation) * @return {boolean} true=success, false=fail */ let saveToDB = () => { let dbLocation = config.DBLocation; if (dbLocation[dbLocation.length - 1] != '/') dbLocation += '/users.json'; else dbLocation += 'users.json'; try { fs.writeFileSync(dbLocation, JSON.stringify(authorizedUsers)); return true; } catch (err) { console.error('Failed to save to DB... ERROR:\n', err); return false; } } /** * Bot engine exported interface * @type {Object} */ module.exports = { runBot: runBot }