initial commit
This commit is contained in:
commit
57f4d33134
7 changed files with 432 additions and 0 deletions
86
.gitignore
vendored
Normal file
86
.gitignore
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
users.json
|
||||||
|
# Created by https://www.gitignore.io/api/node,linux
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Typescript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# Yarn Lock file
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# package.json Lock file
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/node,linux
|
3
.jshintrc
Normal file
3
.jshintrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"esnext": true
|
||||||
|
}
|
33
README.md
Normal file
33
README.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Telegram Bot that saves photos
|
||||||
|
|
||||||
|
|
||||||
|
## Things you will need to do to run it
|
||||||
|
|
||||||
|
- Get a telegram bot token from the botFather
|
||||||
|
- Just talk to [BotFather](https://telegram.me/botfather) and follow a few simple steps. Once you've created a bot and received your authorization token, copt and paste it into the config file. (botId)
|
||||||
|
- Make sure you have NodeJS and npm installed
|
||||||
|
- Install dependencies.
|
||||||
|
- ``` $ npm install```
|
||||||
|
|
||||||
|
All done :)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This bot can be configured via the config.js file.
|
||||||
|
Keep the structure of this file as is, and change the values to your needs.
|
||||||
|
|
||||||
|
## Running the bot
|
||||||
|
|
||||||
|
I would suggest to use [pm2](http://pm2.keymetrics.io) to manage this process. but its your call...
|
||||||
|
|
||||||
|
Production Run:
|
||||||
|
```
|
||||||
|
$ node index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Debug Run:
|
||||||
|
```
|
||||||
|
$ node index.js --printMessage
|
||||||
|
```
|
||||||
|
> This will print out the firs message the bot receives and will exit.<br>
|
||||||
|
So you can see user ID and other data attached to the message.
|
244
botEngine.js
Normal file
244
botEngine.js
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
// 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
|
||||||
|
}
|
42
config.js
Normal file
42
config.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Configuration
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
botId: 'xxxxxxxxxx', // Bot token/Id - Get if from botFather
|
||||||
|
slideShowFolderPath: '/full/path/to/folder', // Where to save files
|
||||||
|
DBLocation: '.', // Location of DB file. You need to have permissions to read/write in that location
|
||||||
|
permissions: {
|
||||||
|
allowAll: false, // allow any telegram user to add photos
|
||||||
|
allowAddingUsers: true, // accept contacts as authorized users?
|
||||||
|
allowNonAdminToAddUsers: false // allow non admin users to add contacts to authorized users
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
notifyActivity: true,
|
||||||
|
sendPhotoOnSave: true, // send admin the photo that somone else have saved
|
||||||
|
id: 1234 // [Number] Your Telegram UserId
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
adminNotifications:{
|
||||||
|
unauthorizedUser: 'Unauthorized user: {username}\nMessage:\n{messageObj}', // Plaseholers: {username}, {messageObj}
|
||||||
|
fileSuccessfullySaved: 'A photo was added.\n', // Plaseholers: {fileLocation}
|
||||||
|
failedToSaveFile: 'Psst...\nYou might want to know, that i have failed you. a fail was not saved.\nError:\n{error}', // Placeholders: {error}
|
||||||
|
userAdded: 'The user {username} added {username_add_rem} to the system', // Plaseholers: {username}, {username_add_rem}
|
||||||
|
userRemoved: 'The user {username} removed {username_add_rem} from the system' // Plaseholers: {username}, {username_add_rem}
|
||||||
|
},
|
||||||
|
sendieResponse: {
|
||||||
|
unauthorizedUser: 'Hi {username}, \n Who the fuck are you?!\n I have notified my creator and he will hunt you down. I would suggest that you disconnect from the Internet', // Plaseholers: {username}, {messageObj}
|
||||||
|
fileSuccessfullySaved: 'Added to your slideshow. Saved at: {fileLocation}', // Plaseholers: {fileLocation}
|
||||||
|
failedToSaveFile: 'Woops,\nsome thing happend... Can\'t save your photo.\nTry Again later', // Placeholders: {error}
|
||||||
|
unableToDoThat: 'Hi {username},\nIm unable to understand you. I\'m able only to accept photos and contacts', // Placeholders: {username}
|
||||||
|
noPermissions: 'You have no permissions to do that...',
|
||||||
|
userAdded: 'Okay, {username} was added as a trusted user.', // Placeholders: {username}
|
||||||
|
userRemoved: 'Sure, I\'ll stop supporting {username}.' // Plaseholers: {username}
|
||||||
|
},
|
||||||
|
userAdded: { // Not supported yet
|
||||||
|
added: 'Hi {username_add_rem},\n{username} added you as a trusted user. this means that you can send me photos to add to the slide show! Try it now' // Plaseholers: {username}, {username_add_rem}
|
||||||
|
},
|
||||||
|
userRemoved:{ // Not supported yet
|
||||||
|
removed: 'Hi {username_add_rem},\n{username} removed you from my system... It\'s not you, it\'s me :(' // Plaseholers: {username}, {username_add_rem}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
index.js
Normal file
10
index.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* A Telegram bot thats saves your photos locally
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Import bot engine module
|
||||||
|
const botEngine = require('./botEngine');
|
||||||
|
console.log('Initializing bot...');
|
||||||
|
//Run the bot.
|
||||||
|
botEngine.runBot(process.argv);
|
14
package.json
Normal file
14
package.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "telegram_bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"node-telegram-bot-api": "^0.27.1"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue