- init/help/version does not throw an error if there is no config file
 - node_module usage - now working
This commit is contained in:
Sagi Dayan 2020-03-25 18:40:18 -04:00
parent e73b8b9bf9
commit 49c36b471f
10 changed files with 144 additions and 45 deletions

View file

@ -8,14 +8,33 @@
4. Need help? `$ telme --help`
## How do i get a bot token?
- 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, copy it for later
- Get a telegram bot token from the BotFather, It takes less than a minute
- 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, copy it for later
## Configure `telme`
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.
#### Config file structure
The config file `.telmeconfig` should be located in your home folder and contain a valid JSON.
Example config (`~/.telmeconfig`):
```json
{
"version": "0.1.0",
"profiles": {
"profile_name": {
"chat_id": 000000,
"bot_token": "<bot_token>",
"task_message_template": "*Task:*\n\n```sh\n%cmd%\n```\nHas finished.\n*Errors*:\n %errors%"
},
...
}
}
```
> `task_message_template` allows the following optional placeholders `%cmd%`, `%errors%`. These will be replaced with the actual command and errors.
## Profiles
You can set multiple profiles, that will target different bots and/or different chats.
@ -53,3 +72,32 @@ $ telme -p movie-club curl https://example.com/large/file/download.mp4
```
this will send the message to the `movie-club` profile chat. (By the `movie-club` bot)
## Using as a `node_module`
> Typescript users will have definitions
```javascript
import Telme from 'node-telme' // OR const Telme = require('node-telme').default
const config = {
chat_id: 'somechatid',
bot_token: 'bot-token'
}
Telme.sendMessage(config, 'Hi there!').then(_=>{
...
}).catch(console.error);
// %cmd% and %errors% will be replaced buy actual values.
config.task_message_template = 'Task: %cmd% is done!. Errors: %errors%';
const options = {
command: 'ls',
args: ['-lah'] // If no args pass in an empty array
};
Telme.runTask(config, options).then(_=>{
...
}).catch(console.error);
```

View file

@ -9,6 +9,8 @@ const HOME = require('os').homedir();
const CONFIG_FILE_NAME = '.telmeconfig';
const FILEPATH = `${HOME}/${CONFIG_FILE_NAME}`;
let singleton = null;
export enum EConfigVersions {
V004 = '0.0.4',
V010 = '0.1.0'
@ -21,6 +23,9 @@ export default class Config {
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;
static cliInit() {
singleton = new Config();
}
constructor() {
const file = this.readConfigFile();
const parsed = this.parseConfig(file);
@ -29,11 +34,11 @@ export default class Config {
}
this.config = parsed.config;
}
static getConfig(profile: string = 'default'): IProfileConfig {
static getConfig(profile: string = 'default'): ITaskConfig {
return singleton.getConfig(profile);
}
static generateProfileTemplate(): IProfileConfig {
static generateProfileTemplate(): ITaskConfig {
return {
chat_id: null,
task_message_template: Config.DEFAULT_TASK_MESSAGE_TEMPLATE,
@ -49,7 +54,7 @@ export default class Config {
return singleton.writeConfigToFile(config);
}
private getConfig(profile: string): IProfileConfig {
private getConfig(profile: string): ITaskConfig {
if (!this.config.profiles[profile]) {
throw new ConfigProfileError(`No profile named ${profile} found.`);
}
@ -61,7 +66,7 @@ export default class Config {
const file = fs.readFileSync(FILEPATH);
return file;
} catch (e) {
throw new ConfigFileMissingError('');
return Buffer.from(JSON.stringify(EMPTY_CONFIG));
}
}
@ -77,14 +82,14 @@ export default class Config {
return { config, originalConfigVersion: config.version };
}
} catch (e) {
throw new ConfigFileFormatError('');
throw new ConfigFileFormatError('Invalid JSON format in config file. If you modified the file yourself, please double check your modifications');
}
}
private writeConfigToFile(config: IConfig): boolean {
try {
fs.writeFileSync(FILEPATH, JSON.stringify(config, null, 2));
console.log(`created config file at ${FILEPATH}`);
console.log(`created config file at ${FILEPATH}`);
return true;
} catch (e) {
return false;
@ -92,21 +97,21 @@ export default class Config {
}
}
const singleton = new Config();
export interface IProfileConfig {
const EMPTY_CONFIG: IConfig = {
version: CURRENT_CONFIG_VERSION,
profiles: {}
}
export interface IMessageConfig {
chat_id: string;
bot_token: string;
}
export interface ITaskConfig extends IMessageConfig {
task_message_template: string;
}
export interface IConfig {
version: EConfigVersions,
profiles: {
[key: string]: IProfileConfig;
[key: string]: ITaskConfig;
}
}

View file

@ -3,6 +3,8 @@ enum ErrorCodes {
CONFIG_FILE_FORMAT_ERROR = 1001,
CONFIG_PROFILE_ERROR = 1002,
INVALID_ARGS_ERROR = 1003,
INVALID_COMMAND_ERROR = 1004
INVALID_COMMAND_ERROR = 1004,
INVALID_BOT_CHAT_CONFIG = 1005,
}
export { ErrorCodes as default };

View file

@ -0,0 +1,7 @@
import BaseError from './base_error';
export default class InvalidBotOrChatConfig extends BaseError {
constructor() {
super(`bot_token OR chat_id are invalid`, BaseError.ErrorCodes.INVALID_BOT_CHAT_CONFIG);
}
}

View file

@ -41,12 +41,12 @@ export default class Init {
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
await Telme.sendMessage(currentConfig.profiles[profileName],
`*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;
@ -57,9 +57,12 @@ $ ${Config.APP_NAME} ${profileFlag} <command> <args>
}
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.`);
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 => {

View file

@ -1,9 +1,22 @@
'use strict';
const TelegramBot = require('node-telegram-bot-api');
import { IProfileConfig } from '../config/config';
import { IMessageConfig } from '../config/config';
import InvalidBotOrChatConfig from '../errors/invalid_bot_chat_config.error';
import InvalidArgumentsError from '../errors/invalid_arguments.error';
export default class SendMessage {
static async send(config: IProfileConfig, msg: string) {
static async send(config: IMessageConfig, msg: string) {
validate(config, msg);
const bot = new TelegramBot(config.bot_token);
return await bot.sendMessage(config.chat_id, `${msg}`, { parse_mode: 'Markdown' });
try {
await bot.sendMessage(config.chat_id, `${msg}`, { parse_mode: 'Markdown' });
return true;
} catch (e) {
throw new InvalidBotOrChatConfig();
}
}
}
function validate(config: IMessageConfig, msg: string) {
if (!config.bot_token || !config.chat_id) throw new InvalidArgumentsError(`Config object must have bot_token<string>, chat_id<string>`);
if (typeof msg !== 'string') throw new InvalidArgumentsError(`message must be of type string`)
}

View file

@ -3,12 +3,14 @@
const { spawn } = require('child_process');
// import TelegramBot from 'node-telegram-bot-api';
import SendMessage from './send_message';
import { IProfileConfig } from '../config/config';
import { ITaskConfig } from '../config/config';
import { ITaskOptions } from '../utils';
import Config from '../config/config'
import InvalidCommandError from '../errors/invalid_command';
import InvalidArgumentsError from '../errors/invalid_arguments.error';
export default class TaskMessage {
static async run(config: IProfileConfig, options: ITaskOptions) {
static async run(config: ITaskConfig, options: ITaskOptions) {
validate(config, options);
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(' ')}`)
@ -23,6 +25,11 @@ export default class TaskMessage {
}
}
function validate(config: ITaskConfig, options: ITaskOptions) {
if (!config.bot_token || !config.chat_id || !config.task_message_template) throw new InvalidArgumentsError(`Config object must have bot_token<string>, chat_id<string>, task_message_template<string>`);
if (!options.command || !options.args || !Array.isArray(options.args) || typeof options.command !== 'string') throw new InvalidArgumentsError(`Option object must have command<string>, and args<string[]>`)
}
function promisifyExec(exec, command): Promise<string> {
let errors = null;
return new Promise((resolve, reject) => {

View file

@ -1,20 +1,21 @@
process.env.NTBA_FIX_319 = 'junk';
import SendMessage from './flows/send_message';
import TaskMessage from './flows/task_message';
import { IProfileConfig } from './config/config';
import { ITaskConfig as TaskConfig, IMessageConfig as MessageConfig } 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);
static async sendMessage(config: MessageConfig, msg: string) {
await SendMessage.send(config, msg);
return true;
}
static async RunTask(config: IProfileConfig, options: ITaskOptions) {
static async runTask(config: TaskConfig, options: ITaskOptions) {
await TaskMessage.run(config, options);
}
}
export namespace Interfaces {
export interface IConfig extends IProfileConfig { };
export interface IMessageConfig extends MessageConfig { };
export interface ITaskConfig extends TaskConfig { };
export interface IMessageOptions extends ISimpleMessageOptions { };
export interface ITaskMessageOptions extends ITaskOptions { };
}

View file

@ -1,13 +1,14 @@
#!/usr/bin/env node
import Telme from './telme';
import { ArgParser, ERunMode, ISimpleMessageOptions, ITaskOptions, IInitProfileOptions } from './utils';
import Config, { IProfileConfig } from './config/config';
import Config, { ITaskConfig } 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;
Config.cliInit();
let config: ITaskConfig;
let options: any;
switch (parsed.mode) {
case ERunMode.VERSION:
@ -22,13 +23,13 @@ async function main() {
case ERunMode.SIMPLE_MESSAGE:
config = await Config.getConfig(parsed.mode_data.profileName);
options = parsed.mode_data as ISimpleMessageOptions;
await Telme.SendMessage(config, options);
await Telme.sendMessage(config, options.message);
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);
await Telme.runTask(config, options);
console.log(`[${Config.APP_NAME}]: Told Ya!`);
break;
}
@ -52,15 +53,15 @@ Options:
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
\t '${Config.APP_NAME} --init' - init a default profile
\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
\t '${Config.APP_NAME} docker-compose pull' - Send a message to default profile once the command 'docker-compose pull' is done
\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
\t '${Config.APP_NAME} -m "text to send"' - Send a message to default profile
\t '${Config.APP_NAME} -p <profile-name> -m "text to send"' - Send message to <profile-name> profile
`;

View file

@ -1,6 +1,6 @@
{
"name": "node-telme",
"version": "0.1.1",
"version": "0.1.2",
"bin": {
"telme": "dist/telme_cli.js"
},
@ -11,6 +11,9 @@
"type": "git",
"url": "https://gitlab.com/sagidayan/telme"
},
"bugs": {
"url": "https://gitlab.com/sagidayan/telme/-/issues"
},
"license": "MIT",
"dependencies": {
"node-telegram-bot-api": "^0.40.0"
@ -20,5 +23,14 @@
},
"scripts": {
"build": "tsc"
}
},
"keywords": [
"telegram",
"bot",
"cli",
"telme",
"notifications",
"notification",
"automation"
]
}