feature/ts-and-profiles #2

Merged
sagi merged 4 commits from feature/ts-and-profiles into master 2020-03-23 02:10:17 +00:00
27 changed files with 681 additions and 242 deletions
Showing only changes of commit c59eec9c50 - Show all commits

View file

@ -1,2 +1,3 @@
.npmrc
lib/
node_modules/

View file

@ -5,7 +5,7 @@
1. Get a bot token ( [BotFather](https://telegram.me/botfather) )
2. `$ npm install -g node-telme`
3. `$ telme --init`
4. Do something :)
4. Need help? `$ telme --help`
## How do i get a bot token?
- Get a telegram bot token from the botFather
@ -13,25 +13,43 @@
## Configure `telme`
Simply run `$ telme --init` and follow the easy steps. You will need the bot token at this stage.
Simply run `$ telme --init` and follow 2 easy steps. You will need the bot token at this stage.
This will help you generate a `.telmeconfig` file in your home directory. You can always run `--init` again to override values or just edit the file yourself.
## Profiles
You can set multiple profiles, that will target different bots and/or different chats.
> You can use the same bot in all profiles if you like, But the target chat can be different
To initialize a new profile:
```shell
$ telme --init --profile <profile_name>
```
## Examples:
###### Simple message
```
```shell
$ telme --m "Message to send"
```
In the next example a message will be sent every time the user `user` logs in to a tty.
> Added the next lines at the bottom of `~/.profile` file
```shell
# Telme on login
telme --m "A new Login:\n\`\`\` user: $(whoami) | hostname: $(hostname) | remote ip $(who | cut -d'(' -f2 | cut -d')' -f1)\`\`\` Hope this is you!"
```
###### Task message
Task messages are a simple way to receive a message from your bot once a command has been finished. It will also let you know if you had any errors.
```
```shell
$ telme docker build . -t my-image/build/that/takes/for/ever
```
In this example, once the docker build has finished you will receive a message.
```
$ telme npm run test
```
As mentioned before - you can also specify a profile.
```shell
$ telme -p movie-club curl https://example.com/large/file/download.mp4
```
$ telme do-a-long-task
```
this will send the message to the `movie-club` profile chat. (By the `movie-club` bot)

View file

@ -1,2 +0,0 @@
#!/usr/bin/env node
require('../index');

View file

@ -1,86 +0,0 @@
'use strict';
const TelegramBot = require('node-telegram-bot-api');
const HOME = require('os').homedir();
const CONFIG_FILE_NAME = '.telmeconfig';
const FILEPATH = `${HOME}/${CONFIG_FILE_NAME}`;
const APP_NAME = 'telme';
const fs = require('fs');
class Init {
static async init() {
console.log(
'Hi, In order for telme to work you will need to create a bot. If you don\'t know how, Please take a look at README.md');
const token = await prompt('Please provide your bot token: ');
const code = generateCode();
try {
const bot = new TelegramBot(token, {polling: true});
const userId = await listenToMessage(bot, code);
console.log('Cool, Got your userId. Saving config...');
const _default = {
TOKEN: token,
USER_ID: userId,
DONE_MESSAGE:
'*Task:*\n\n```sh\n%cmd%\n```\nHas finished.\n*Errors*:\n %errors%'
};
await bot.sendMessage(
userId, `*Thanks!*\nYou are all set.\ntelme usage:\n\`\`\`\n
$ ${APP_NAME} --m "message to send"
$ ${APP_NAME} <command> <args>
\n\`\`\`\nFor more info, visit: [telme repo](https://git.sagidayan.com/sagi/telme)\n\n_Enjoy!_`,
{parse_mode: 'Markdown'});
fs.writeFileSync(FILEPATH, JSON.stringify(_default, null, 2));
console.log(`created config file at ${FILEPATH}`);
return true;
} catch (e) {
throw e;
}
}
}
function listenToMessage(bot, code) {
console.log(`Thanks! Please send '/code ${code}' to your bot from telegram`);
return new Promise((resolve, reject) => {
const now = Date.now();
bot.on('message', msg => {
const msgDate = new Date(msg.date * 1000);
if (msgDate < now) return;
const userId = msg.chat.id;
if (msg.text.indexOf('/code') === 0) {
const receivedCode = msg.text.split('/code')[1].trim();
if (code === receivedCode)
resolve(userId);
else
reject(new Error('Code does not match!'));
}
});
bot.on('polling_error', () => {
console.log('polling error');
reject(new Error('Invalid token'));
})
});
}
function generateCode(codeLength = 6) {
const allowedChars = 'aAbBcCdDEeFf0123456789';
let code = '';
for (let i = 0; i < codeLength; i++) {
code +=
allowedChars.charAt(Math.floor(Math.random() * allowedChars.length));
}
return code;
}
function prompt(question) {
return new Promise(resolve => {
var stdin = process.stdin, stdout = process.stdout;
stdin.resume();
stdout.write(question);
stdin.once('data', function(data) {
resolve(data.toString().trim());
});
});
}
module.exports = Init

View file

@ -1,11 +0,0 @@
'use strict';
const TelegramBot = require('node-telegram-bot-api');
class SendMessage {
static async send(token, userId, msg) {
const bot = new TelegramBot(token);
return await bot.sendMessage(userId, `${msg}`, {parse_mode: 'Markdown'});
}
}
module.exports = SendMessage;

View file

@ -1,56 +0,0 @@
'use strict';
const {spawn} = require('child_process');
const TelegramBot = require('node-telegram-bot-api');
const APP_NAME = 'telme';
class TaskMessage {
static async run(token, userId, doneMessage) {
const bot = new TelegramBot(token);
const command = process.argv[2];
const args = process.argv.slice(3);
const exec = spawn(command, args);
const errors = await promisifyExec(exec);
let msg = doneMessage.replace('%cmd%', ` $ ${command} ${args.join(' ')}`)
.replace('%errors%', errors);
try {
await bot.sendMessage(userId, msg, {parse_mode: 'Markdown'});
console.log(`[${APP_NAME}]: Told ya!`);
} catch (e) {
errors = e.message;
console.error(`[${APP_NAME}]: An error occurred. Error: ${e.message}`);
}
return true;
}
}
function promisifyExec(exec) {
let errors = null;
return new Promise((resolve) => {
exec.stdout.on('data', (data) => {
console.log(String(data));
});
exec.on('error', (error) => {
errors = error.message;
resolve(errors);
});
exec.stderr.on('data', (data) => {
console.error(String(data));
if (!errors)
errors = data;
else
errors += `\n${data}`;
});
exec.on('close', async (code) => {
resolve(errors);
});
});
}
module.exports = TaskMessage;

View file

@ -1,73 +0,0 @@
'use strict';
process.env.NTBA_FIX_319 = 'junk';
const TelegramBot = require('node-telegram-bot-api');
const HOME = require('os').homedir();
const CONFIG_FILE_NAME = '.telmeconfig';
const FILEPATH = `${HOME}/${CONFIG_FILE_NAME}`;
const APP_NAME = 'telme';
const fs = require('fs');
const Init = require('./flows/init');
const SendMessage = require('./flows/simple_message');
const TaskMessage = require('./flows/task_message');
function printUsage(withInit = false) {
if (withInit) console.log(`[Usage] $ ${APP_NAME} --init`);
console.log(`[Usage] $ ${APP_NAME} --m "message to send"`);
console.log(`[Usage] $ ${APP_NAME} <command> <args>`);
}
async function main() {
if (process.argv.indexOf('--init') != -1) {
try {
await Init.init();
printUsage();
process.exit(0);
} catch (e) {
console.error(`An error has occurred. Error: ${e.message}`);
process.exit(1);
}
}
try {
var file = fs.readFileSync(FILEPATH);
} catch (e) {
console.log(
`Please run '${APP_NAME} --init' first. then follow instructions`);
process.exit(1);
}
const {TOKEN, USER_ID, DONE_MESSAGE} = JSON.parse(file);
if (process.argv.indexOf('--m') === 2) {
if (!process.argv[3]) {
console.log('[ERROR] Missing message to send');
console.log(`[Usage] $ ${APP_NAME} --m "message to send"`);
process.exit(1);
}
const msg = process.argv[3];
try {
await SendMessage.send(TOKEN, USER_ID, msg);
console.log(`[${APP_NAME}]: Told ya!`);
process.exit(0);
} catch (e) {
console.error(`[${APP_NAME}]: An error occurred. Error: ${e.message}`);
process.exit(1);
}
} else {
if (process.argv.length < 3) {
printUsage(true);
process.exit(1);
}
try {
await TaskMessage.run(TOKEN, USER_ID, DONE_MESSAGE);
} catch (e) {
console.error(e);
}
}
}
main().then(() => {});

View file

@ -0,0 +1,22 @@
import { IConfig, EConfigVersions } from './config';
export default class Config004 {
static parse(conf: IConfig004): IConfig {
return {
version: EConfigVersions.V005,
profiles: {
default: {
chat_id: conf.USER_ID,
bot_token: conf.TOKEN,
task_message_template: conf.DONE_MESSAGE
}
}
}
}
}
export interface IConfig004 {
USER_ID: string;
TOKEN: string;
DONE_MESSAGE: string;
}

112
lib/config/config.ts Normal file
View file

@ -0,0 +1,112 @@
import * as fs from 'fs';
import ConfigFileMissingError from '../errors/config_file_missing.error';
import ConfigProfileError from '../errors/config_profile_missing.error';
import ConfigFileFormatError from '../errors/config_file_format.error';
import Config004 from './config.0.0.4';
const HOME = require('os').homedir();
const CONFIG_FILE_NAME = '.telmeconfig';
const FILEPATH = `${HOME}/${CONFIG_FILE_NAME}`;
export enum EConfigVersions {
V004 = '0.0.4',
V005 = '0.0.5'
}
const CURRENT_CONFIG_VERSION = EConfigVersions.V005;
export default class Config {
static APP_NAME = 'telme';
static CURRENT_CONFIG_VERSION = CURRENT_CONFIG_VERSION;
static DEFAULT_TASK_MESSAGE_TEMPLATE = '*Task:*\n\n```sh\n%cmd%\n```\nHas finished.\n*Errors*:\n %errors%';
private config: IConfig;
constructor() {
const file = this.readConfigFile();
const parsed = this.parseConfig(file);
if (parsed.originalConfigVersion != CURRENT_CONFIG_VERSION) {
this.writeConfigToFile(parsed.config);
}
this.config = parsed.config;
}
static getConfig(profile: string = 'default'): IProfileConfig {
return singleton.getConfig(profile);
}
static generateProfileTemplate(): IProfileConfig {
return {
chat_id: null,
task_message_template: Config.DEFAULT_TASK_MESSAGE_TEMPLATE,
bot_token: null
}
}
static getFullConfig(): IConfig {
return singleton.config;
}
static async writeConfigToFile(config: IConfig) {
return singleton.writeConfigToFile(config);
}
private getConfig(profile: string): IProfileConfig {
if (!this.config.profiles[profile]) {
throw new ConfigProfileError(`No profile named ${profile} found.`);
}
return this.config.profiles[profile];
}
private readConfigFile(): Buffer {
try {
const file = fs.readFileSync(FILEPATH);
return file;
} catch (e) {
throw new ConfigFileMissingError('');
}
}
private parseConfig(file: Buffer): { config: IConfig, originalConfigVersion: EConfigVersions } {
try {
const config = JSON.parse(file.toString());
const confVersion: EConfigVersions = config.version || EConfigVersions.V004;
switch (confVersion) {
case EConfigVersions.V004:
return { config: Config004.parse(config), originalConfigVersion: EConfigVersions.V004 };
// Using switch to easily add more config version. If needed...
default:
return { config, originalConfigVersion: config.version };
}
} catch (e) {
throw new ConfigFileFormatError('');
}
}
private writeConfigToFile(config: IConfig): boolean {
try {
fs.writeFileSync(FILEPATH, JSON.stringify(config, null, 2));
console.log(`created config file at ${FILEPATH}`);
return true;
} catch (e) {
return false;
}
}
}
const singleton = new Config();
export interface IProfileConfig {
chat_id: string;
bot_token: string;
task_message_template: string;
}
export interface IConfig {
version: EConfigVersions,
profiles: {
[key: string]: IProfileConfig;
}
}

10
lib/errors/base_error.ts Normal file
View file

@ -0,0 +1,10 @@
import ErrorCodes from './error_codes';
export default class BaseError extends Error {
readonly exitCode: ErrorCodes;
static readonly ErrorCodes = ErrorCodes;
constructor(message: string, code: ErrorCodes) {
super(message);
this.exitCode = code;
}
}

View file

@ -0,0 +1,7 @@
import BaseError from './base_error';
export default class ConfigFileFormatError extends BaseError {
constructor(msg: string) {
super(msg, BaseError.ErrorCodes.CONFIG_FILE_FORMAT_ERROR);
}
}

View file

@ -0,0 +1,7 @@
import BaseError from './base_error';
export default class ConfigFileMissingError extends BaseError {
constructor(msg: string) {
super(msg, BaseError.ErrorCodes.CONFIG_FILE_MISSING_ERROR);
}
}

View file

@ -0,0 +1,7 @@
import BaseError from './base_error';
export default class ConfigProfileError extends BaseError {
constructor(msg: string) {
super(msg, BaseError.ErrorCodes.CONFIG_PROFILE_ERROR);
}
}

View file

@ -0,0 +1,8 @@
enum ErrorCodes {
CONFIG_FILE_MISSING_ERROR = 1000,
CONFIG_FILE_FORMAT_ERROR = 1001,
CONFIG_PROFILE_ERROR = 1002,
INVALID_ARGS_ERROR = 1003,
INVALID_COMMAND_ERROR = 1004
}
export { ErrorCodes as default };

View file

@ -0,0 +1,7 @@
import BaseError from './base_error';
export default class InvalidArgumentsError extends BaseError {
constructor(msg: string) {
super(`Invalid Arguments: ${msg}`, BaseError.ErrorCodes.INVALID_ARGS_ERROR);
}
}

View file

@ -0,0 +1,7 @@
import BaseError from './base_error';
export default class InvalidCommandError extends BaseError {
constructor(msg: string) {
super(msg, BaseError.ErrorCodes.INVALID_COMMAND_ERROR);
}
}

105
lib/flows/init.ts Normal file
View file

@ -0,0 +1,105 @@
'use strict';
import * as TelegramBot from 'node-telegram-bot-api';
import { IInitProfileOptions } from '../utils';
import Telme from '../telme';
import Config, { IConfig } from '../config/config';
export default class Init {
static async init(options: IInitProfileOptions = null) {
const profileName = options.profileName || 'default';
let currentConfig: IConfig;
try {
currentConfig = await Config.getFullConfig();
if (currentConfig.profiles[profileName]) {
// This will override existing profile
const response = await prompt(`Do you wish to override you current '${profileName}' profile [y/n]? `);
if (response[0].toLowerCase() !== 'y') {
console.log('Aborting.');
process.exit(1);
}
} else {
currentConfig.profiles[profileName] = Config.generateProfileTemplate();
}
} catch (e) {
currentConfig = {
version: Config.CURRENT_CONFIG_VERSION,
profiles: {
[profileName]: Config.generateProfileTemplate()
}
}
}
console.log(`Initializing '${profileName}' profile...`);
console.log(
'Hi, In order for telme to work you will need to create a bot. If you don\'t know how, Please take a look at README.md');
const token = await prompt('Please provide your bot token: ');
const code = generateCode();
try {
const bot = new TelegramBot(token, { polling: true });
const chatId = await listenToMessage(bot, code);
currentConfig.profiles[profileName].bot_token = token;
currentConfig.profiles[profileName].chat_id = chatId;
console.log('Cool, Got the chat. Saving config...');
await Config.writeConfigToFile(currentConfig);
const profileFlag = profileName === 'default' ? '' : `-p ${profileName}`;
await Telme.SendMessage(currentConfig.profiles[profileName], {
message: `*Thanks!*\nYou are all set.\ntelme usage:\n\`\`\`\n
$ ${Config.APP_NAME} ${profileFlag} --m "message to send"
$ ${Config.APP_NAME} ${profileFlag} <command> <args>
\n\`\`\`\nFor more info, visit: [telme repo](https://gitlab.com/sagidayan/telme)\n\n_Enjoy!_`
})
return true;
} catch (e) {
throw e;
}
}
}
function listenToMessage(bot, code): Promise<string> {
console.log(`Thanks! Please send '/code ${code}' to your bot from telegram.
You can send a direct message to the bot OR send this message to a group that this bot is a member of.
Keep in mind that '${Config.APP_NAME}' will send messages to the chat of your choosing.`);
return new Promise((resolve, reject) => {
const now = Date.now();
bot.on('message', msg => {
const msgDate = msg.date * 1000;
if (msgDate < now) return;
const userId = msg.chat.id;
if (msg.text.indexOf('/code') === 0) {
const receivedCode = msg.text.split('/code')[1].trim();
if (code === receivedCode)
resolve(userId);
else
reject(new Error('Code does not match!'));
}
});
bot.on('polling_error', () => {
console.log('polling error');
reject(new Error('Invalid token'));
})
});
}
function generateCode(codeLength = 6) {
const allowedChars = 'aAbBcCdDEeFf0123456789';
let code = '';
for (let i = 0; i < codeLength; i++) {
code +=
allowedChars.charAt(Math.floor(Math.random() * allowedChars.length));
}
return code;
}
function prompt(question): Promise<string> {
return new Promise(resolve => {
var stdin = process.stdin, stdout = process.stdout;
stdin.resume();
stdout.write(question);
stdin.once('data', function (data) {
resolve(data.toString().trim());
});
});
}

View file

@ -0,0 +1,9 @@
'use strict';
const TelegramBot = require('node-telegram-bot-api');
import { IProfileConfig } from '../config/config';
export default class SendMessage {
static async send(config: IProfileConfig, msg: string) {
const bot = new TelegramBot(config.bot_token);
return await bot.sendMessage(config.chat_id, `${msg}`, { parse_mode: 'Markdown' });
}
}

51
lib/flows/task_message.ts Normal file
View file

@ -0,0 +1,51 @@
'use strict';
const { spawn } = require('child_process');
// import TelegramBot from 'node-telegram-bot-api';
import SendMessage from './send_message';
import { IProfileConfig } from '../config/config';
import { ITaskOptions } from '../utils';
import Config from '../config/config'
import InvalidCommandError from '../errors/invalid_command';
export default class TaskMessage {
static async run(config: IProfileConfig, options: ITaskOptions) {
const exec = spawn(options.command, options.args);
let errors = await promisifyExec(exec, options.command);
let msg = config.task_message_template.replace('%cmd%', ` $ ${options.command} ${options.args.join(' ')}`)
.replace('%errors%', errors);
try {
await SendMessage.send(config, msg);
} catch (e) {
errors = e.message;
console.error(`[${Config.APP_NAME}]: An error occurred. Error: ${e.message}`);
}
return true;
}
}
function promisifyExec(exec, command): Promise<string> {
let errors = null;
return new Promise((resolve, reject) => {
exec.stdout.on('data', (data) => {
console.log(String(data));
});
exec.on('error', (error) => {
if (error.message.indexOf('ENOENT') >= 0) {
reject(new InvalidCommandError(`Command '${command}' not found`));
}
});
exec.stderr.on('data', (data) => {
console.error(String(data));
if (!errors)
errors = data;
else
errors += `\n${data}`;
});
exec.on('close', async (code) => {
resolve(errors);
});
});
}

20
lib/telme.ts Normal file
View file

@ -0,0 +1,20 @@
process.env.NTBA_FIX_319 = 'junk';
import SendMessage from './flows/send_message';
import TaskMessage from './flows/task_message';
import { IProfileConfig } from './config/config';
import { ISimpleMessageOptions, ITaskOptions } from './utils';
export default class Telme {
static async SendMessage(config: IProfileConfig, options: ISimpleMessageOptions) {
await SendMessage.send(config, options.message);
return true;
}
static async RunTask(config: IProfileConfig, options: ITaskOptions) {
await TaskMessage.run(config, options);
}
}
export namespace Interfaces {
export interface IConfig extends IProfileConfig { };
export interface IMessageOptions extends ISimpleMessageOptions { };
export interface ITaskMessageOptions extends ITaskOptions { };
}

76
lib/telme_cli.ts Normal file
View file

@ -0,0 +1,76 @@
import Telme from './telme';
import { ArgParser, ERunMode, ISimpleMessageOptions, ITaskOptions, IInitProfileOptions } from './utils';
import Config, { IProfileConfig } from './config/config';
import Init from './flows/init'
const { version } = require('../package.json');
async function main() {
const parsed = ArgParser.parse(process.argv);
let config: IProfileConfig;
let options: any;
switch (parsed.mode) {
case ERunMode.VERSION:
console.log(`[${Config.APP_NAME}] version ${version}`);
break;
case ERunMode.HELP:
printHelp();
break;
case ERunMode.INIT:
await Init.init(parsed.mode_data);
break;
case ERunMode.SIMPLE_MESSAGE:
config = await Config.getConfig(parsed.mode_data.profileName);
options = parsed.mode_data as ISimpleMessageOptions;
await Telme.SendMessage(config, options);
console.log(`[${Config.APP_NAME}]: Told Ya!`);
break;
case ERunMode.TASK_MESSAGE:
config = await Config.getConfig(parsed.mode_data.profileName);
options = parsed.mode_data as ITaskOptions;
await Telme.RunTask(config, options);
console.log(`[${Config.APP_NAME}]: Told Ya!`);
break;
}
return true;
}
function printHelp() {
const cliFlags = ArgParser.CLI_OPTIONS;
const message =
`${Config.APP_NAME} v${version} - A CLI Telegram message tool
[Usage]: $ ${Config.APP_NAME} <telme_options> <?command> <arguments>
Options:
\t ${cliFlags.versionFlags.join(', ')} \t\t Print ${Config.APP_NAME} version.
\t ${cliFlags.helpFlags.join(', ')} \t\t This help page.
\t ${cliFlags.profileFlags.join(', ')} \t\t Specify a profile to use. This is optional. defaults to 'default' profile.
\t ${cliFlags.initFlags.join(', ')} \t\t Will generate a config file for a given profile (defaults to 'default' profile).
\t ${cliFlags.messageFlags.join(', ')} \t\t Send a simple message.
Examples:
init:
\t\t '${Config.APP_NAME} --init' - init a default profile
\t\t '${Config.APP_NAME} -i -p <profile-name>' - init a 'named' profile
tasks:
\t\t '${Config.APP_NAME} docker-compose pull' - Send a message to default profile once the command 'docker-compose pull' is done
\t\t '${Config.APP_NAME} -p <profile-name> docker-compose pull' - Send a message to <profile-name> profile once the command 'docker-compose pull' is done
messages:
\t\t '${Config.APP_NAME} -m "text to send"' - Send a message to default profile
\t\t '${Config.APP_NAME} -p <profile-name> -m "text to send"' - Send message to <profile-name> profile
`;
console.log(message);
}
main().then(_ => {
process.exit(0);
}).catch(error => {
const exitCode = error.exitCode || 1;
console.error(`[${Config.APP_NAME}] ERROR: ${error.message}`);
console.log(`[${Config.APP_NAME}] For help run '$ ${Config.APP_NAME} -h'`);
process.exit(exitCode);
});

139
lib/utils/index.ts Normal file
View file

@ -0,0 +1,139 @@
import InvalidArgumentsError from "../errors/invalid_arguments.error";
const CLI_OPTIONS = {
versionFlags: ['--version', '-v'],
helpFlags: ['--help', '-h'],
initFlags: ['--init', '-i'],
profileFlags: ['--profile', '-p'],
messageFlags: ['--message', '-m'],
};
export class ArgParser {
static CLI_OPTIONS = CLI_OPTIONS;
static parse(testArgs: string[] = null): IRunOptions {
let cliArgs = testArgs ? testArgs.slice(2) : process.argv.slice(2);
let tokens = tokenize(cliArgs);
if (tokens.version) {
return {
mode: ERunMode.VERSION
}
} else if (tokens.help) {
return {
mode: ERunMode.HELP
}
} else if (tokens.init) {
return {
mode: ERunMode.INIT,
mode_data: {
profileName: tokens.profile
}
}
} else if (tokens.simpleMessage) {
return {
mode: ERunMode.SIMPLE_MESSAGE,
mode_data: {
profileName: tokens.profile,
message: tokens.simpleMessage
}
}
} else {
// Task
return {
mode: ERunMode.TASK_MESSAGE,
mode_data: {
profileName: tokens.profile,
command: tokens.task.cmd,
args: tokens.task.args || []
}
}
}
}
}
function tokenize(args: string[]) {
let cliArgs = args;
if (!cliArgs.length) throw new InvalidArgumentsError('Missing a command to run');
const tokens: ITokenizeArgs = { init: false, help: false, version: false };
// ['node', 'execCommand', ...];
let mode: ERunMode = ERunMode.TASK_MESSAGE;
while (cliArgs.length > 0) {
let tokenCursor = 1;
const token = cliArgs[0];
if (CLI_OPTIONS.versionFlags.indexOf(token) >= 0) {
tokens.version = true;
return tokens;
} else if (CLI_OPTIONS.helpFlags.indexOf(token) >= 0) {
tokens.help = true;
return tokens;
} else if (CLI_OPTIONS.initFlags.indexOf(token) >= 0) {
tokens.init = true;
} else if (CLI_OPTIONS.profileFlags.indexOf(token) >= 0) {
if (!cliArgs[1]) {
throw new InvalidArgumentsError('Must provide a profile name');
}
tokens.profile = cliArgs[1];
tokenCursor = 2;
} else if (CLI_OPTIONS.messageFlags.indexOf(token) >= 0) {
if (!cliArgs[1]) {
throw new InvalidArgumentsError('Must provide a message to send');
}
tokens.simpleMessage = cliArgs[1];
tokenCursor = 2
} else {
// Task
if (!cliArgs[0]) {
throw new InvalidArgumentsError('Missing a command to run');
}
tokens.task = {
cmd: cliArgs[0],
args: cliArgs.slice(1)
};
cliArgs = [];
}
if (cliArgs.length) cliArgs = cliArgs.slice(tokenCursor);
}
return tokens;
}
interface ITokenizeArgs {
version: boolean;
help: boolean;
profile?: string;
init: boolean;
simpleMessage?: string;
task?: {
cmd: string;
args?: string[]
}
}
export interface IRunOptions {
mode: ERunMode;
mode_data?: IInitProfileOptions | ISimpleMessageOptions | ITaskOptions;
}
export interface IBasicOptions {
profileName?: string;
}
export interface IInitProfileOptions extends IBasicOptions { }
export interface ISimpleMessageOptions extends IBasicOptions {
message: string;
}
export interface ITaskOptions extends IBasicOptions {
command: string;
args: string[];
}
export enum ERunMode {
VERSION,
HELP,
INIT,
SIMPLE_MESSAGE,
TASK_MESSAGE
}

10
package-lock.json generated
View file

@ -1,9 +1,15 @@
{
"name": "telme",
"version": "1.0.0",
"name": "node-telme",
"version": "0.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "10.17.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz",
"integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==",
"dev": true
},
"ajv": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",

View file

@ -1,11 +1,11 @@
{
"name": "node-telme",
"version": "0.0.4",
"version": "0.1.0",
"bin": {
"telme": "bin/telme"
"telme": "dist/telme_cli.js"
},
"description": "A CLI tool that will report to you via telegram when a task is done",
"main": "index.js",
"main": "dist/telme.js",
"author": "Sagi Dayan",
"repository": {
"type": "git",
@ -14,5 +14,11 @@
"license": "MIT",
"dependencies": {
"node-telegram-bot-api": "^0.40.0"
},
"devDependencies": {
"@types/node": "^10.16.0"
},
"scripts": {
"build": "tsc"
}
}

22
tsconfig.json Normal file
View file

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
"allowJs": true,
"declaration": true,
"noImplicitAny": false,
"moduleResolution": "node",
"types": [
"node",
]
},
"include": [
"lib/**/*.ts"
],
"exclude": [
"node_modules",
"lib/**/*.test.ts"
]
}

22
tslint.json Normal file
View file

@ -0,0 +1,22 @@
{
"defaultSeverity": "error",
"extends": [
"tslint-config-airbnb"
],
"jsRules": {},
"rules": {
"max-line-length": [true, 250],
"no-console": [false],
"no-unused-variable": [
true,
{
"ignore-pattern": "^_"
}
],
"match-default-export-name": false,
"import-name": false,
"variable-name": [false]
},
"rulesDirectory": []
}

View file

@ -2,6 +2,11 @@
# yarn lockfile v1
"@types/node@^10.16.0":
version "10.17.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8"
integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==
ajv@^6.5.5:
version "6.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7"