Skip to content

Commit

Permalink
version 1.3
Browse files Browse the repository at this point in the history
  • Loading branch information
nodegin committed Jun 3, 2018
1 parent 69bd742 commit 2662996
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 69 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Changelog

-----

#### tglib v1.3

- Now support multiple session login by storing files in separate directories. (#16, #19)
- Ability to login as Bots.
```diff
const client = new Client({
apiId: 'YOUR_API_ID',
apiHash: 'YOUR_API_HASH',
- phoneNumber: 'YOUR_PHONE_NUMBER',
+ auth: {
+ type: 'user',
+ value: 'YOUR_PHONE_NUMBER',
+ },
})
```
- Error events for `client.fetch` are now handled. `ClientFetchError` will be thrown if `client.fetch` fails.
- `client.tg.sendMessageTypeText` renamed to `client.tg.sendTextMessage`.
- `client.connect()` changed to `client.ready`.
95 changes: 57 additions & 38 deletions Client.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
const uuidv4 = require('uuid/v4')
const { crc32 } = require('crc')
const ffi = require('ffi-napi')
const ref = require('ref-napi')
const fs = require('fs-extra')
const path = require('path')
const TG = require('./TG')
const { InvalidEventError, ClientCreateError, ClientNotCreatedError } = require('./Errors')

const TG = require('./TG')
const { version: appVersion } = require('./package.json')
const { InvalidEventError, ClientCreateError, ClientNotCreatedError, ClientFetchError } = require('./Errors')
const { buildQuery, getInput, emptyFunction } = require('./utils')

class Client {
constructor(options = {}) {
const defaultOptions = {
apiId: null,
apiHash: null,
auth: {},
binaryPath: 'libtdjson',
verbosityLevel: 2,
tdlibParameters: {
Expand All @@ -20,16 +23,18 @@ class Client {
'system_language_code': 'en',
'application_version': '1.0',
'device_model': 'tglib',
'system_version': 'node',
'system_version': appVersion,
'enable_storage_optimizer': true,
},
}
this.options = {
...defaultOptions,
...options,
}
const connectPromise = new Promise((resolve) => { this.resolver = resolve })
this.connect = () => connectPromise
this.ready = new Promise((resolve) => {
// Add some delay to allow telegram get ready. (Issue #20)
this.resolver = () => setTimeout(resolve, 500)
})
this.client = null
this.tg = null
this.fetching = {}
Expand All @@ -42,8 +47,17 @@ class Client {

async init() {
try {
const {
auth: { type, value },
binaryPath,
verbosityLevel,
} = this.options

this.appDir = path.resolve(process.cwd(), '__tglib__', crc32(`${type}${value}`).toString())
await fs.ensureDir(this.appDir)

this.tdlib = ffi.Library(
path.resolve(process.cwd(), this.options.binaryPath),
path.resolve(process.cwd(), binaryPath),
{
'td_json_client_create' : ['pointer', []],
'td_json_client_send' : ['void' , ['pointer', 'string']],
Expand All @@ -55,11 +69,12 @@ class Client {
'td_set_log_fatal_error_callback': ['void' , ['pointer']],
}
)
this.tdlib.td_set_log_file_path(path.resolve(process.cwd(), '_td_logs.txt'))
this.tdlib.td_set_log_verbosity_level(this.options.verbosityLevel)
this.tdlib.td_set_log_file_path(path.resolve(this.appDir, 'logs.txt'))
this.tdlib.td_set_log_verbosity_level(verbosityLevel)
this.tdlib.td_set_log_fatal_error_callback(ffi.Callback('void', ['string'], (message) => {
console.error('TDLib Fatal Error:', message)
}))

this.client = await this._create()
this.tg = new TG(this)
this.loop()
Expand Down Expand Up @@ -100,55 +115,54 @@ class Client {
async handleAuth(update) {
switch (update['authorization_state']['@type']) {
case 'authorizationStateWaitTdlibParameters': {
await this._send({
return this._send({
'@type': 'setTdlibParameters',
'parameters': {
...this.options.tdlibParameters,
'@type': 'tdlibParameters',
'database_directory': path.resolve(process.cwd(), '_td_database'),
'files_directory': path.resolve(process.cwd(), '_td_files'),
'database_directory': path.resolve(this.appDir, 'database'),
'files_directory': path.resolve(this.appDir, 'files'),
'api_id': this.options.apiId,
'api_hash': this.options.apiHash,
},
})
break
}
case 'authorizationStateWaitEncryptionKey': {
await this._send({
return this._send({
'@type': 'checkDatabaseEncryptionKey',
})
break
}
case 'authorizationStateWaitPhoneNumber': {
await this._send({
'@type': 'setAuthenticationPhoneNumber',
'phone_number': this.options.phoneNumber,
})
break
const { auth: { type, value } } = this.options
return type === 'user' ? (
this._send({
'@type': 'setAuthenticationPhoneNumber',
'phone_number': value,
})
) : (
this._send({
'@type': 'checkAuthenticationBotToken',
'token': value,
})
)
}
case 'authorizationStateWaitCode': {
const code = await getInput('input', 'Please enter auth code: ')
await this._send({
return this._send({
'@type': 'checkAuthenticationCode',
'code': code,
})
break
}
case 'authorizationStateWaitPassword': {
const passwordHint = update['authorization_state']['password_hint']
const password = await getInput('password', `Please enter password (${passwordHint}): `)
await this._send({
return this._send({
'@type': 'checkAuthenticationPassword',
'password': password,
})
break
}
case 'authorizationStateReady': {
this.resolver()
break
}
default:
break
case 'authorizationStateReady':
return this.resolver()
}
}

Expand All @@ -161,27 +175,32 @@ class Client {
'@type': 'checkAuthenticationCode',
'code': code,
})
break
return
}
case 'PASSWORD_HASH_INVALID': {
const password = await getInput('password', `Wrong password, please re-enter: `)
await this._send({
'@type': 'checkAuthenticationPassword',
'password': password,
})
break
return
}
default:
this.listeners['_error'].call(null, update)
break
}
const id = update['@extra']
if (this.fetching[id]) {
delete update['@extra']
this.fetching[id].reject(new ClientFetchError(update))
delete this.fetching[id]
return
}
this.listeners['_error'].call(null, update)
}

async handleUpdate(update) {
const id = update['@extra']
if (this.fetching[id]) {
delete update['@extra']
this.fetching[id](update)
this.fetching[id].resolve(update)
delete this.fetching[id]
return
}
Expand All @@ -199,10 +218,10 @@ class Client {
}

async fetch(query) {
const id = uuidv4()
const id = crc32(Math.random().toString()).toString()
query['@extra'] = id
const receiveUpdate = new Promise((resolve, reject) => {
this.fetching[id] = resolve
this.fetching[id] = { resolve, reject }
// timeout after 29 seconds
setTimeout(() => {
delete this.fetching[id]
Expand Down
14 changes: 11 additions & 3 deletions Errors.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
class InvalidEventError extends Error {
constructor (eventName) {
constructor(eventName) {
super()
this.message = `"${eventName}" is not a valid event name.`
}
}

class ClientCreateError extends Error {
constructor (error) {
constructor(error) {
super(error)
this.message = `Unable to create client: ${error.message}`
}
}

class ClientNotCreatedError extends Error {
constructor (error) {
constructor(error) {
super(error)
this.message = `Client is not created.`
}
}

class ClientFetchError extends Error {
constructor(update) {
super()
this.message = JSON.stringify(update)
}
}

module.exports = {
InvalidEventError,
ClientCreateError,
ClientNotCreatedError,
ClientFetchError,
}
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

TDLib (Telegram Database library) bindings for Node.js

[![npm](https://img.shields.io/npm/v/tglib.svg)](https://www.npmjs.com/package/tglib)

-----

### Getting started
Expand All @@ -15,23 +17,40 @@ TDLib (Telegram Database library) bindings for Node.js

tglib provide some useful methods that makes your Telegram app development easier.

#### Authorizing an user

```js
const client = new Client({
apiId: 'YOUR_API_ID',
apiHash: 'YOUR_API_HASH',
phoneNumber: 'YOUR_PHONE_NUMBER',
credentials: {
type: 'user',
value: 'YOUR_PHONE_NUMBER',
},
})
```

#### Authorizing a bot

```js
const client = new Client({
apiId: 'YOUR_API_ID',
apiHash: 'YOUR_API_HASH',
credentials: {
type: 'bot',
value: 'YOUR_BOT_TOKEN',
},
})
```

#### ![](https://placehold.it/12/efcf39/000?text=+) Low Level APIs

##### `client.connect()` -> Promise -> Void
##### `client.ready` -> Promise

This API is provided by tglib, you can use this API to initialize and connect your client with Telegram.
This promise is used for initializing tglib client and connect with Telegram.

```js
await client.connect()
await client.ready
```

##### `client.on(event, callback)` -> Void
Expand Down
7 changes: 5 additions & 2 deletions TG.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ class TG {
this.client = client
}

async sendMessageTypeText(chatId, text, options = {}) {
/*
* Sends text message to a existing chat.
*/
async sendTextMessage(chatId, text, options = {}) {
let formattedText
switch (options.parse_mode) {
case 'html':
Expand Down Expand Up @@ -54,7 +57,7 @@ class TG {
},
}
combine(payload, options)
await this.client._send(payload)
return this.client.fetch(payload)
}
}

Expand Down
31 changes: 31 additions & 0 deletions examples/botCommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { Client } = require('tglib')

void async function() {
const client = new Client({
apiId: 'YOUR_API_ID',
apiHash: 'YOUR_API_HASH',
auth: {
type: 'bot',
value: 'YOUR_BOT_TOKEN',
},
})

await client.ready

const { id: myId } = await client.fetch({ '@type': 'getMe' })

client.on('_update', async (update) => {
if (update['@type'] === 'updateNewMessage') {
// check if message is sent from self
const sender = update['message']['sender_user_id']
if (sender !== myId) {
const { text: { text } } = update['message']['content']
if (text.startsWith('/')) {
await client.tg.sendTextMessage(sender, `Are you requested "${text}"?`)
} else {
await client.tg.sendTextMessage(sender, `Sorry I do not understand "${text}".`)
}
}
}
})
}()
Loading

0 comments on commit 2662996

Please sign in to comment.