Fixes
- init/help/version does not throw an error if there is no config file - node_module usage - now working
This commit is contained in:
parent
e73b8b9bf9
commit
49c36b471f
10 changed files with 144 additions and 45 deletions
52
README.md
52
README.md
|
@ -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);
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 };
|
7
lib/errors/invalid_bot_chat_config.error.ts
Normal file
7
lib/errors/invalid_bot_chat_config.error.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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 => {
|
||||
|
|
|
@ -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`)
|
||||
}
|
|
@ -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) => {
|
||||
|
|
11
lib/telme.ts
11
lib/telme.ts
|
@ -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 { };
|
||||
}
|
|
@ -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
|
||||
|
||||
`;
|
||||
|
||||
|
|
16
package.json
16
package.json
|
@ -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"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue