├── .gitignore ├── README.md ├── config.example.js ├── example.js ├── lib ├── models │ ├── APIConnection.js │ ├── Message.js │ └── Peer.js ├── telegram-api.js ├── telegram-cli-wrapper.js └── utils │ ├── line-parser.js │ └── socket-wrapper.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | tg 2 | sandbox -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tg-cli-node 2 | Node.js wrapper for [telegram-cli](https://github.com/vysheng/tg) 3 | 4 | [Docs](https://github.com/Perkovec/tg-cli-node/wiki/Documentation) 5 | 6 | ## How to use 7 | Install package from npm: 8 | ```bash 9 | npm install tg-cli-node 10 | ``` 11 | 12 | Create `config.js` with: 13 | ```javascript 14 | const path = require('path'); 15 | const os = require('os'); 16 | 17 | module.exports = { 18 | telegram_cli_path: path.join(__dirname, 'tg/bin/telegram-cli'), //path to tg-cli (see https://github.com/vysheng/tg) 19 | telegram_cli_socket_path: path.join( os.tmpdir(), 'socket'), // path for socket file 20 | server_publickey_path: path.join(__dirname, 'tg/tg-server.pub'), // path to server key (traditionally, in %tg_cli_path%/tg-server.pub) 21 | } 22 | ``` 23 | 24 | Open your app script and use this example: 25 | ```javascript 26 | const TelegramAPI = require('tg-cli-node'); 27 | const config = require('./config'); 28 | 29 | const Client = new TelegramAPI(config); 30 | 31 | Client.connect(connection => { 32 | connection.on('message', message => { 33 | console.log('message:', message); 34 | }); 35 | 36 | connection.on('error', e => { 37 | console.log('Error from Telegram API:', e); 38 | }); 39 | 40 | connection.on('disconnect', () => { 41 | console.log('Disconnected from Telegram API'); 42 | }); 43 | }); 44 | ``` -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | telegram_cli_path: path.join(__dirname, 'tg/bin/telegram-cli'), 5 | telegram_cli_socket_path: path.join(__dirname, 'socket'), 6 | server_publickey_path: path.join(__dirname, 'tg/tg-server.pub'), 7 | } -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const TelegramAPI = require('./lib/telegram-api.js'); 2 | const config = require('./config'); 3 | 4 | const Client = new TelegramAPI(config); 5 | 6 | Client.connect(connection => { 7 | connection.on('message', message => { 8 | console.log('message:', message); 9 | }); 10 | 11 | connection.on('error', e => { 12 | console.log('Error from Telegram API:', e); 13 | }); 14 | 15 | connection.on('disconnect', () => { 16 | console.log('Disconnected from Telegram API'); 17 | }); 18 | }); -------------------------------------------------------------------------------- /lib/models/APIConnection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const events = require('events'); 3 | const Parser = require('../utils/line-parser'); 4 | const Peer = require('./Peer'); 5 | const Message = require('./Message'); 6 | class APIConnection extends events.EventEmitter { 7 | constructor(socket) { 8 | super(); 9 | this.socket = socket; 10 | this.lineParser = new Parser(this); 11 | this._registerEvents(); 12 | this.open = true; 13 | this._cb_receiver = null; 14 | } 15 | 16 | sendTyping(peer_id) { 17 | this._executeCommand('send_typing', peer_id); 18 | } 19 | 20 | userInfo(peer_id) { 21 | return new Promise((resolve, reject) => { 22 | this._executeCommand('user_info', peer_id); 23 | this._cb_receiver = data => { 24 | resolve(new Peer(this, data)); 25 | }; 26 | }); 27 | } 28 | 29 | forward(peer, msg_id) { 30 | this._executeCommand('fwd', peer, msg_id); 31 | } 32 | 33 | history(peer, limit) { 34 | return new Promise((resolve, reject) => { 35 | this._executeCommand('history', peer, limit); 36 | this._cb_receiver = data => { 37 | const messageArr = []; 38 | for (let i = 0; i < data.length; ++i) { 39 | messageArr.push(new Message(this, data[i])); 40 | } 41 | resolve(messageArr); 42 | }; 43 | }); 44 | 45 | } 46 | 47 | contactList() { 48 | return new Promise((resolve, reject) => { 49 | this._executeCommand('contact_list'); 50 | this._cb_receiver = data => { 51 | const contactArr = []; 52 | for (let i = 0; i < data.length; ++i) { 53 | contactArr.push(new Peer(this, data[i])); 54 | } 55 | resolve(contactArr); 56 | }; 57 | }); 58 | } 59 | 60 | dialogList() { 61 | return new Promise((resolve, reject) => { 62 | this._executeCommand('dialog_list'); 63 | this._cb_receiver = data => { 64 | const dialogArr = []; 65 | for (let i = 0; i < data.length; ++i) { 66 | dialogArr.push(new Peer(this, data[i])); 67 | } 68 | resolve(dialogArr); 69 | }; 70 | }); 71 | } 72 | 73 | send(peer, message) { 74 | this._executeCommand('msg', peer, `"${message}"`); 75 | } 76 | 77 | reply(id, message) { 78 | this._executeCommand('reply', id, `"${message}"`); 79 | } 80 | 81 | deleteMsg(id) { 82 | this._executeCommand('delete_msg', id); 83 | } 84 | 85 | sendImage(peer, path) { 86 | this._executeCommand('send_photo', peer, '"' + path + '"'); 87 | } 88 | 89 | sendDocument(peer, path) { 90 | this._executeCommand('send_document', peer, '"' + path + '"'); 91 | } 92 | 93 | _executeCommand() { 94 | if(this.open){ 95 | var args = Array.prototype.slice.call(arguments); 96 | var cmd = args.join(' '); 97 | console.log('executing command:', cmd); 98 | this.socket.writeLine(cmd); 99 | }else{ 100 | throw new Error('Api connection closed'); 101 | } 102 | } 103 | 104 | close() { 105 | if(this.open){ 106 | this.open = false; 107 | this.socket.end(); 108 | } 109 | } 110 | 111 | _registerEvents() { 112 | this.socket.on('line', line => { 113 | const parsedLine = this.lineParser.parse(line); 114 | if (parsedLine) { 115 | if (parsedLine.type === 'callback' && this._cb_receiver !== null) { 116 | this._cb_receiver(parsedLine.data); 117 | this._cb_receiver = null; 118 | } else { 119 | this.emit(parsedLine.type, parsedLine.data); 120 | } 121 | } 122 | }); 123 | 124 | this.socket.on('error', e => { 125 | this.emit('error', e); 126 | }); 127 | 128 | this.socket.on('close', () => { 129 | this.emit('disconnect'); 130 | }); 131 | }; 132 | } 133 | 134 | 135 | module.exports = APIConnection; -------------------------------------------------------------------------------- /lib/models/Message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Peer = require('./Peer'); 3 | 4 | class Message { 5 | constructor(connection, data) { 6 | this.service = data.service; 7 | this.event = data.event; 8 | this.reply_id = data.reply_id || null; 9 | this.id = data.id; 10 | this.from = new Peer(connection, data.from); 11 | this.to = new Peer(connection, data.to); 12 | this.flags = data.flags; 13 | this.out = data.out; 14 | this.unread = data.unread; 15 | this.date = data.date; 16 | this.text = data.text || null; 17 | 18 | this._registerEvents(connection); 19 | } 20 | 21 | _registerEvents(connection) { 22 | this.forward = peer => { 23 | connection.forward(peer, this.id); 24 | } 25 | 26 | this.send = msg => { 27 | this.to.send(msg); 28 | } 29 | 30 | this.reply = msg => { 31 | connection.reply(this.id, msg); 32 | } 33 | 34 | this.sendImage = path => { 35 | this.to.sendImage(path); 36 | } 37 | 38 | this.sendDocument = path => { 39 | this.to.sendDocument(path); 40 | } 41 | 42 | this.deleteMsg = () => { 43 | connection.deleteMsg(this.id); 44 | } 45 | 46 | this.sendTyping = () => { 47 | this.from.sendTyping(); 48 | } 49 | } 50 | 51 | } 52 | 53 | module.exports = Message; -------------------------------------------------------------------------------- /lib/models/Peer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Peer { 4 | constructor(connection, data) { 5 | this.peer_id = data.peer_id; 6 | this.id = data.id; 7 | this.phone = data.phone || null; 8 | this.peer_type = data.peer_type; 9 | this.flags = data.flags; 10 | this.first_name = data.first_name; 11 | this.last_name = data.last_name; 12 | this.print_name = data.print_name; 13 | this.username = data.username; 14 | this.title = data.title || null; 15 | this.members_num = data.members_num || null; 16 | this.kicked_count = data.kicked_count || null; 17 | this.participants_count = data.participants_count || null; 18 | this.admins_count = data.admins_count || null; 19 | 20 | this._registerEvents(connection); 21 | } 22 | 23 | _registerEvents(connection) { 24 | this.forward = msg_id => { 25 | connection.forward(this.print_name, msg_id); 26 | } 27 | 28 | this.send = msg => { 29 | connection.send(this.print_name, msg); 30 | } 31 | 32 | this.sendImage = path => { 33 | connection.sendImage(this.print_name, path); 34 | } 35 | 36 | this.sendDocument = path => { 37 | connection.sendDocument(this.print_name, path); 38 | } 39 | 40 | this.sendTyping = () => { 41 | connection.sendTyping(this.id); 42 | } 43 | } 44 | 45 | } 46 | 47 | module.exports = Peer; -------------------------------------------------------------------------------- /lib/telegram-api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const TelegramCliWrapper = require('./telegram-cli-wrapper.js'); 3 | const APIConnection = require('./models/APIConnection.js'); 4 | 5 | class TelegramAPI { 6 | constructor(config) { 7 | this.socket = null; 8 | this.connection = null; 9 | this.tgcli = new TelegramCliWrapper(config); 10 | } 11 | 12 | connect(callback) { 13 | if(this.tgcli.isRunning()){ 14 | throw new Error('TelegramAPI is already running'); 15 | } 16 | 17 | this.tgcli.start(socket => { 18 | this.connection = new APIConnection(socket); 19 | this.socket = socket; 20 | 21 | this.socket.writeLine('main_session'); 22 | 23 | callback(this.connection); 24 | }); 25 | } 26 | 27 | disconnect() { 28 | this.tgcli.stop(); 29 | if(this.connection){ 30 | this.connection.close(); 31 | this.connection = null; 32 | } 33 | } 34 | } 35 | 36 | module.exports = TelegramAPI; -------------------------------------------------------------------------------- /lib/telegram-cli-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const spawn = require('child_process').spawn; 3 | const SocketWrapper = require('./utils/socket-wrapper.js'); 4 | const net = require('net'); 5 | 6 | class TelegramCliWrapper { 7 | constructor(config) { 8 | this.childInstance = null; 9 | this.socket = null; 10 | this.config = config; 11 | } 12 | 13 | start(callback) { 14 | if (this.childInstance) { 15 | throw new Error('Process already started'); 16 | } 17 | 18 | const cli_path = this.config.telegram_cli_path; 19 | const publickey_path = this.config.server_publickey_path; 20 | const socket_path = generateSocketName(this.config.telegram_cli_socket_path); 21 | 22 | console.log('starting child process'); 23 | this.childInstance = spawn(cli_path, ['-k', publickey_path, '-S', socket_path, '--json']); 24 | bindChildEvents.call(this, this.childInstance); 25 | // this.childInstance.stdout.pipe(process.stdout); 26 | this.childInstance.stdout.setEncoding('utf8'); 27 | this.childInstance.stdout.once('data', data => { 28 | data = data.replace(/\r|\n/g, '').trim(); 29 | 30 | if (/^Telegram-cli version/.test(data)) { 31 | this.childInstance.stdout.once('data', () => { 32 | this.socket = net.createConnection(socket_path, () => { 33 | callback(new SocketWrapper(this.socket)); 34 | }); 35 | }); 36 | } 37 | }); 38 | } 39 | 40 | stop() { 41 | if(this.socket && this.socket.writable){ 42 | this.socket.write('quit\n'); 43 | } 44 | 45 | if(this.childInstance){ 46 | this.childInstance = null; 47 | } 48 | } 49 | 50 | isRunning() { 51 | return !!this.childInstance; 52 | } 53 | } 54 | 55 | //Generate unique socket name 56 | function generateSocketName(path){ 57 | return path + +new Date; 58 | } 59 | 60 | function bindChildEvents(childProcess){ 61 | childProcess.on('error', e => { 62 | console.log(e); 63 | }); 64 | 65 | childProcess.on('exit', (() => { 66 | this.stop(); 67 | }).bind(this)); 68 | } 69 | 70 | module.exports = TelegramCliWrapper; -------------------------------------------------------------------------------- /lib/utils/line-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Message = require('../models/Message.js'); 3 | 4 | class Parser { 5 | constructor(connection) { 6 | this.connection = connection; 7 | } 8 | 9 | parse(line) { 10 | try { 11 | const scheme = JSON.parse(line); 12 | if (scheme.event === 'message') { 13 | return { type: 'message', data: new Message(this.connection, scheme) }; 14 | } else { 15 | return { type: 'callback', data: scheme }; 16 | } 17 | } catch(e) { 18 | } 19 | } 20 | } 21 | 22 | module.exports = Parser; -------------------------------------------------------------------------------- /lib/utils/socket-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const events = require('events'); 3 | 4 | class SocketWrapper extends events.EventEmitter { 5 | constructor(socket, options) { 6 | super(); 7 | this.socket = socket; 8 | options = options || {}; 9 | this.lineDelimiter = options.lineDelimiter || '\n'; 10 | this.encoding = options.encoding || 'utf8'; 11 | this.init(); 12 | } 13 | 14 | init() { 15 | this.buffer = ''; 16 | this.socket.setEncoding(this.encoding); 17 | this.socket.on('data', data => { 18 | this.buffer += data; 19 | let i; 20 | while ((i = this.buffer.indexOf(this.lineDelimiter)) !== -1) { 21 | const line = this.buffer.substr(0, i); 22 | //printBuffer(this.buffer); 23 | if (line) { 24 | this.emit('line', line); 25 | } 26 | this.buffer = this.buffer.substr(i + 1); 27 | } 28 | }); 29 | 30 | this.socket.on('error', e => { 31 | this.emit('error', e); 32 | }); 33 | 34 | this.socket.on('close', () => { 35 | this.emit('close'); 36 | }); 37 | } 38 | 39 | end() { 40 | if(this.socket){ 41 | this.socket.end(); 42 | } 43 | } 44 | 45 | writeLine(line) { 46 | this.socket.write(line + '\n'); 47 | } 48 | } 49 | 50 | function printBuffer(buffer){ 51 | console.log('buffer:', buffer.replace(/\n/g, '\\n')); 52 | } 53 | 54 | module.exports = SocketWrapper; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tg-cli-node", 3 | "version": "0.0.8", 4 | "description": "Nodejs wrapper for telegram-cli.", 5 | "main": "./lib/telegram-api.js", 6 | "scripts": { 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/Perkovec/tg-cli-node.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/Perkovec/tg-cli-node/issues" 14 | }, 15 | "author": "Perkovec", 16 | "license": "GNU GPLv3", 17 | "dependencies": {} 18 | } 19 | --------------------------------------------------------------------------------