From 77a2978318bb6d1a0817cf16e4735230b8c2e6b1 Mon Sep 17 00:00:00 2001 From: Muthu Kumar Date: Mon, 16 Jul 2018 14:05:08 +0530 Subject: [PATCH] [mvc] Proof of concept --- app.js | 94 +++++++++++++++++++++++++++++++++++++++++++++----- lib/process.js | 3 ++ lib/responseHandler.js | 34 ++++++++++++++++++ lib/validator.js | 10 ++++++ package.json | 2 +- 5 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 lib/process.js create mode 100644 lib/responseHandler.js create mode 100644 lib/validator.js diff --git a/app.js b/app.js index 01cd689..a5cba87 100644 --- a/app.js +++ b/app.js @@ -1,14 +1,92 @@ +const { spawn } = require('child_process'); +const { EOL } = require('os'); + const Telegraf = require('telegraf'); -const config = require('./config') +const { path } = require('./util/index.js'); + +const config = require('./config.js'); +const validator = require('./lib/validator'); +const responder = require('./lib/responseHandler.js'); + +const dateOptions = { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', +}; + +const bot = new Telegraf(config.botApiKey); +const sessions = []; +sessions.history = []; + +bot.use((ctx, next) => + validator(ctx) + .then(next) + .catch(responder.fail( + `Username Not authenticated!` + ))); + +bot.command('start', + ctx => { + ctx.replyWithHTML('Welcome to tsh -- Telegram Shell!'); + const newProc = spawn('bash', { + cwd: '/home' + }); + newProc.stdout.setEncoding('utf8'); + sessions.push(newProc); + sessions.currentSession = newProc; + }); + +bot.command('ls', + ctx => ctx.reply( + sessions.reduce((acc, _, index) => + acc ? `${acc}\n${index}` : `${index}`, '') + || `No sessions found. Start one with /start.` + )); + +bot.command('attach', + ctx => { + const text = path(['update', 'message', 'text'], ctx); + const sessionIndex = parseInt(text.replace('/attach ', '').trim()); + if(Number.isNaN(sessionIndex) || !sessions[sessionIndex]) return responder.fail('Session not found. /ls for list of sessions')(ctx); + sessions.currentSession = sessions[sessionIndex]; + return responder.success(`Reattached to shell ${sessionIndex}`)(ctx); + }); -const bot = new Telegraf(config.apiKey) +bot.command('detach', + ctx => { + const text = path(['update', 'message', 'text'], ctx); + const sessionIndex = parseInt(text.replace('/detach ', '').trim()); + const currentSession = text.trim() === '/detach' ? sessions.currentSession : sessions[sessionIndex]; + if(!currentSession) return responder.fail('Session not found. /ls for list of sessions.')(ctx); + sessions.currentSession = undefined; + return responder.success(`Detached from shell ${sessionIndex}`)(ctx); + }); -bot.command('start', ctx => { - return ctx.reply('Bot succesfully started!'); -}) +bot.command('kill', + ctx => { + const text = path(['update', 'message', 'text'], ctx); + const sessionIndex = parseInt(text.replace('/kill ', '').trim()); + if(Number.isNaN(sessionIndex) || !sessions[sessionIndex]) return responder.fail('Session not found. /ls for list of sessions.')(ctx); + const disconnect = sessions[sessionIndex]; + delete sessions[sessionIndex]; + if(disconnect === sessions.currentSession) sessions.currentSession = undefined; + disconnect.kill(); + ctx.reply('Session killed. /ls for list of sessions.') + }) -bot.hears('hi', ctx => { - return ctx.reply('Hey!, How are you?'); -}) +bot.use(ctx => { + if(!sessions.currentSession) return responder.fail('No active session. Start one with /start or view list of sessions by sending /ls.')(ctx); + const cmd = ctx.update.message.text; + const history = `${new Date().toLocaleDateString('en-IN', dateOptions)}: ${cmd}`; + sessions.history.push(history); + console.log(history); + sessions.currentSession.stdin.write(cmd + EOL); + sessions.currentSession.stdout.on('data', d => responder.success(d)(ctx)); + sessions.currentSession.stdout.on('error', e => responder.success(e)(ctx)); +}); bot.startPolling(); +console.log(`Polling for updates.`); diff --git a/lib/process.js b/lib/process.js new file mode 100644 index 0000000..e6aadd8 --- /dev/null +++ b/lib/process.js @@ -0,0 +1,3 @@ +const process = ctx => { + const { text } = ctx.update.message; +} \ No newline at end of file diff --git a/lib/responseHandler.js b/lib/responseHandler.js new file mode 100644 index 0000000..eeb4ba1 --- /dev/null +++ b/lib/responseHandler.js @@ -0,0 +1,34 @@ +const success = response => ctx => response ? ctx.reply(response) : null; +const { EOL } = require('os'); +const { path } = require('../util/index.js'); + +const convertCtx = ctx => { + const message = path(['update', 'message'], ctx); + const { id, first_name, username, language_code } = path(['from'], message); + const { text } = message; + return JSON.stringify({ + id, + first_name, + username, + language_code, + text, + }, null, 2); +}; + +const fail = response => ctx => { + if(!response + || !path(['update', 'message'], ctx) + ) return; + console.log( + EOL, + response, + EOL, + `With context: `, + convertCtx(ctx), + ); + return ctx.reply(response); +}; + +module.exports = { + success, fail, +}; diff --git a/lib/validator.js b/lib/validator.js new file mode 100644 index 0000000..3fa2007 --- /dev/null +++ b/lib/validator.js @@ -0,0 +1,10 @@ +const { path } = require('../util/index.js'); +const config = require('../config.js'); + +const validate = + ctx => { + if(!path(['update', 'message', 'from', 'id'], ctx)) return Promise.reject(ctx); + return (ctx.update.message.from.id === config.masterID) ? Promise.resolve(ctx) : Promise.reject(ctx); + } + +module.exports = validate; diff --git a/package.json b/package.json index ada536d..31bc148 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codefeathers/tsh", - "version": "0.0.1", + "version": "0.0.2", "description": "Telegram Shell -- complete remote shell access over Telegram bot API", "main": "app.js", "dependencies": {