Bläddra i källkod

Add sqlite session
Fix reconnecting
Add node socket
Fix disconnect bug

painor 5 år sedan
förälder
incheckning
d301c5c5d0

+ 4 - 4
gramjs/client/TelegramClient.js

@@ -6,7 +6,7 @@ const { addKey } = require('../crypto/RSA')
 const { TLRequest } = require('../tl/tlobject')
 const utils = require('../Utils')
 const Session = require('../sessions/Abstract')
-const JSONSession = require('../sessions/JSONSession')
+const SQLiteSession = require('../sessions/SQLiteSession')
 const os = require('os')
 const { GetConfigRequest } = require('../tl/functions/help')
 const { LAYER } = require('../tl/AllTLObjects')
@@ -58,7 +58,7 @@ class TelegramClient {
         // Determine what session we will use
         if (typeof session === 'string' || !session) {
             try {
-                session = JSONSession.tryLoadOrCreateNew(session)
+                session = new SQLiteSession(session)
             } catch (e) {
                 console.log(e)
                 session = new MemorySession()
@@ -253,10 +253,10 @@ class TelegramClient {
                     e instanceof errors.UserMigrateError) {
                     this._log.info(`Phone migrated to ${e.newDc}`)
                     const shouldRaise = e instanceof errors.PhoneMigrateError || e instanceof errors.NetworkMigrateError
+                    console.log('should I raise ? ', shouldRaise)
                     if (shouldRaise && await this.isUserAuthorized()) {
                         throw e
                     }
-                    await sleep(1000)
                     await this._switchDC(e.newDc)
                 } else {
                     throw e
@@ -392,7 +392,7 @@ class TelegramClient {
 
         }
         const name = utils.getDisplayName(me)
-        console.log('Signed in successfully as'+ name)
+        console.log('Signed in successfully as' + name)
         return this
     }
 

+ 2 - 0
gramjs/crypto/AuthKey.js

@@ -8,6 +8,7 @@ class AuthKey {
     }
 
     set key(value) {
+
         if (!value) {
             this._key = this.auxHash = this.keyId = null
             return
@@ -18,6 +19,7 @@ class AuthKey {
             this.keyId = value.keyId
             return
         }
+
         this._key = value
         const reader = new BinaryReader(sha1(this._key))
         this.auxHash = reader.readLong(false)

+ 12 - 0
gramjs/crypto/index.js

@@ -0,0 +1,12 @@
+const AES = require('./AES')
+const AESCTR = require('./AESCTR')
+const AuthKey = require('./AuthKey')
+const Factorizator = require('./Factorizator')
+const RSA = require('./RSA')
+module.exports = {
+    AES,
+    AESCTR,
+    AuthKey,
+    Factorizator,
+    RSA,
+}

+ 3 - 2
gramjs/errors/Common.js

@@ -45,14 +45,15 @@ class InvalidChecksumError extends Error {
  */
 class InvalidBufferError extends Error {
     constructor(payload) {
-        const code = -(struct.unpack('<i', payload)[0])
+        console.log('payload is ', payload)
         if (payload.length === 4) {
+            const code = -(struct.unpack('<i', payload)[0])
             super(`Invalid response buffer (HTTP code ${code})`)
+            this.code = code
         } else {
             super(`Invalid response buffer (too short ${payload})`)
             this.code = null
         }
-        this.code = code
         this.payload = payload
     }
 }

+ 10 - 7
gramjs/extensions/PromisedNetSockets.js

@@ -1,16 +1,13 @@
 const Socket = require('net').Socket
 
-const closeError = new Error('WebSocket was closed')
+const closeError = new Error('NetSocket was closed')
 
 class PromisedNetSockets {
     constructor() {
-        this.stream = Buffer.alloc(0)
         this.client = null
 
-        this.canRead = new Promise((resolve) => {
-            this.resolveRead = resolve
-        })
-        this.closed = false
+
+        this.closed = true
     }
 
     async read(number) {
@@ -50,7 +47,13 @@ class PromisedNetSockets {
      * @returns {Promise<void>}
      */
     async connect(port, ip) {
+        this.stream = Buffer.alloc(0)
+
         this.client = new Socket()
+        this.canRead = new Promise((resolve) => {
+            this.resolveRead = resolve
+        })
+        this.closed = false
         return new Promise(function(resolve, reject) {
             this.client.connect(port, ip, function() {
                 this.receive()
@@ -77,7 +80,7 @@ class PromisedNetSockets {
 
     async close() {
         await this.client.destroy()
-        this.resolveRead(false)
+        this.client.unref()
         this.closed = true
     }
 

+ 10 - 8
gramjs/extensions/PromisedWebSockets.js

@@ -8,13 +8,7 @@ class PromisedWebSockets {
             process.type === 'renderer' ||
             process.browser === true ||
             process.__nwjs
-        this.stream = Buffer.alloc(0)
-        this.client = null
-
-        this.canRead = new Promise((resolve) => {
-            this.resolveRead = resolve
-        })
-        this.closed = false
+        this.closed = true
     }
 
     async read(number) {
@@ -57,6 +51,15 @@ class PromisedWebSockets {
 
     async connect(port, ip) {
         console.log('trying to connect')
+
+        this.stream = Buffer.alloc(0)
+        this.client = null
+
+        this.canRead = new Promise((resolve) => {
+            this.resolveRead = resolve
+        })
+
+        this.closed = false
         this.website = this.getWebSocketLink(ip, port)
         this.client = new WebSocketClient(this.website, 'binary')
         return new Promise(function(resolve, reject) {
@@ -86,7 +89,6 @@ class PromisedWebSockets {
     async close() {
         console.log('something happened. closing')
         await this.client.close()
-        this.resolveRead(false)
         this.closed = true
     }
 

+ 2 - 2
gramjs/network/Authenticator.js

@@ -29,10 +29,10 @@ async function doAuthentication(sender, log) {
     let bytes = Helpers.generateRandomBytes(16)
 
     const nonce = Helpers.readBigIntFromBuffer(bytes, false, true)
-
+    console.log('our nonce is ', nonce)
     const resPQ = await sender.send(new ReqPqMultiRequest({ nonce: nonce }))
     log.debug('Starting authKey generation step 1')
-
+    console.log('got result is ', resPQ)
     if (!(resPQ instanceof ResPQ)) {
         throw new Error(`Step 1 answer was ${resPQ}`)
     }

+ 1 - 2
gramjs/network/MTProtoPlainSender.js

@@ -28,14 +28,13 @@ class MTProtoPlainSender {
      * @param request
      */
     async send(request) {
-
         let body = request.bytes
         let msgId = this._state._getNewMsgId()
         const res = Buffer.concat([struct.pack('<qqi', [0, msgId.toString(), body.length]), body])
 
         await this._connection.send(res)
         body = await this._connection.recv()
-        if (body.length < 9) {
+        if (body.length < 8) {
             throw new InvalidBufferError(body)
         }
         const reader = new BinaryReader(body)

+ 9 - 2
gramjs/network/MTProtoSender.js

@@ -218,6 +218,7 @@ class MTProtoSender {
      */
     async _connect() {
         this._log.info('Connecting to {0}...'.replace('{0}', this._connection))
+
         await this._connection.connect()
         this._log.debug('Connection success!')
         if (!this.authKey._key) {
@@ -342,7 +343,8 @@ class MTProtoSender {
                     this._log.info('Broken authorization key; resetting')
                     this.authKey.key = null
                     if (this._authKeyCallback) {
-                        this._authKeyCallback(null)
+                        console.log('called auth callback')
+                        await this._authKeyCallback(null)
                     }
                     this._startReconnect(e)
 
@@ -717,10 +719,11 @@ class MTProtoSender {
         await this._connection.disconnect()
         this._reconnecting = false
         this._state.reset()
-        const retries = this._retries
+        const retries = 1 //this._retries
         for (let attempt = 0; attempt < retries; attempt++) {
             try {
                 await this._connect()
+                console.log('fiinsihed connecting')
                 this._send_queue.extend(Object.values(this._pending_state))
                 this._pending_state = {}
                 if (this._autoReconnectCallback) {
@@ -728,10 +731,14 @@ class MTProtoSender {
                 }
                 break
             } catch (e) {
+                console.log(e.stack)
+
+                console.log('ok why did i get this error ?')
                 this._log.error(e)
                 await Helpers.sleep(this._delay)
             }
         }
+        process.exit()
     }
 }
 

+ 4 - 0
gramjs/sessions/Memory.js

@@ -66,6 +66,9 @@ class MemorySession extends Session {
     save() {
     }
 
+    load() {
+    }
+
     delete() {
     }
 
@@ -128,6 +131,7 @@ class MemorySession extends Session {
         }
         const rows = [] // Rows to add (id, hash, username, phone, name)
         for (const e of entities) {
+            console.log('enttiy is ', e)
             const row = this._entityToRow(e)
             if (row) {
                 rows.push(row)

+ 0 - 2
gramjs/sessions/PouchSession.js

@@ -1,2 +0,0 @@
-const EXTENSION = '.session'
-const CURRENT_VERSION = 1

+ 262 - 0
gramjs/sessions/SQLiteSession.js

@@ -0,0 +1,262 @@
+const EXTENSION = '.session'
+const CURRENT_VERSION = 1
+const AuthKey = require('../crypto/AuthKey')
+const Database = require('better-sqlite3')
+const utils = require('../Utils')
+const { PeerUser, PeerChannel, PeerChat } = require('../tl/types')
+const types = require('../tl/types')
+const fs = require('fs')
+const MemorySession = require('./Memory')
+
+class SQLiteSession extends MemorySession {
+    /**
+     * This session contains the required information to login into your
+     * Telegram account. NEVER give the saved session file to anyone, since
+     * they would gain instant access to all your messages and contacts.
+
+     * If you think the session has been compromised, close all the sessions
+     * through an official Telegram client to revoke the authorization.
+     */
+    constructor(sessionId = null) {
+        super()
+        this.filename = ':memory:'
+        this.saveEntities = true
+
+        if (sessionId) {
+            this.filename = sessionId
+            if (!this.filename.endsWith(EXTENSION)) {
+                this.filename += EXTENSION
+            }
+        }
+
+        this.db = new Database(this.filename)
+        let stmt = this.db.prepare('SELECT name FROM sqlite_master where type=\'table\' and name=\'version\'')
+        if (stmt.get()) {
+            // Tables already exist, check for the version
+            stmt = this.db.prepare('select version from version')
+            const version = stmt.get().version
+            if (version < CURRENT_VERSION) {
+                this._upgradeDatabase(version)
+                this.db.exec('delete from version')
+                stmt = this.db.prepare('insert into version values (?)')
+                stmt.run(CURRENT_VERSION)
+                this.save()
+            }
+
+            // These values will be saved
+            stmt = this.db.prepare('select * from sessions')
+            const res = stmt.get()
+
+
+            if (res) {
+
+                this._dcId = res['dcId']
+                this._serverAddress = res['serverAddress']
+                this._port = res['port']
+                this._authKey = new AuthKey(res['authKey'])
+                this._takeoutId = res['takeoutId']
+            }
+        } else {
+            // Tables don't exist, create new ones
+            this._createTable(
+                'version (version integer primary key)'
+                ,
+                `sessions (
+                dcId integer primary key,
+                    serverAddress text,
+                    port integer,
+                    authKey blob,
+                    takeoutId integer
+                )`,
+                `entities (
+                id integer primary key,
+                    hash integer not null,
+                    username text,
+                    phone integer,
+                    name text
+                )`,
+                `sent_files (
+                md5Digest blob,
+                    fileSize integer,
+                    type integer,
+                    id integer,
+                    hash integer,
+                    primary key(md5Digest, fileSize, type)
+                )`,
+                `updateState (
+                id integer primary key,
+                    pts integer,
+                    qts integer,
+                    date integer,
+                    seq integer
+                )`,
+            )
+            stmt = this.db.prepare('insert into version values (?)')
+            stmt.run(CURRENT_VERSION)
+            this._updateSessionTable()
+            this.save()
+        }
+
+    }
+
+    load() {
+
+    }
+
+    get authKey() {
+        return super.authKey
+    }
+
+    _upgradeDatabase(old) {
+        // nothing so far
+    }
+
+    _createTable(...definitions) {
+        for (const definition of definitions) {
+            this.db.exec(`create table ${definition}`)
+        }
+    }
+
+    // Data from sessions should be kept as properties
+    // not to fetch the database every time we need it
+    setDC(dcId, serverAddress, port) {
+        super.setDC(dcId, serverAddress, port)
+        this._updateSessionTable()
+
+        // Fetch the authKey corresponding to this data center
+        const row = this.db.prepare('select authKey from sessions').get()
+        if (row && row.authKey) {
+            this._authKey = new AuthKey(row.authKey)
+        } else {
+            this._authKey = null
+        }
+    }
+
+    set authKey(value) {
+        this._authKey = value
+        this._updateSessionTable()
+    }
+
+    set takeoutId(value) {
+        this._takeoutId = value
+        this._updateSessionTable()
+    }
+
+    _updateSessionTable() {
+        // While we can save multiple rows into the sessions table
+        // currently we only want to keep ONE as the tables don't
+        // tell us which auth_key's are usable and will work. Needs
+        // some more work before being able to save auth_key's for
+        // multiple DCs. Probably done differently.
+        this.db.exec('delete from sessions')
+        const stmt = this.db.prepare('insert or replace into sessions values (?,?,?,?,?)')
+        stmt.run(this._dcId, this._serverAddress,
+            this._port, this._authKey ? this._authKey.key : Buffer.alloc(0), this._takeoutId)
+    }
+
+    getUpdateState(entityId) {
+        const row = this.db.prepare('select pts, qts, date, seq from updateState where id=?').get(entityId)
+        if (row) {
+            return new types.update.State({
+                pts: row.pts,
+                qts: row.qts, date: new Date(row.date), seq: row.seq, unreadCount: 0,
+            })
+        }
+    }
+
+    setUpdateState(entityId, state) {
+        const stmt = this.db.prepare('insert or replace into updateState values (?,?,?,?,?)')
+        stmt.run(entityId, state.pts, state.qts,
+            state.date.getTime(), state.seq)
+    }
+
+    save() {
+        // currently nothing needs to be done
+    }
+
+    /**
+     * Deletes the current session file
+     */
+    delete() {
+        if (this.db.name === ':memory:') {
+            return true
+        }
+        try {
+            fs.unlinkSync(this.db.name)
+            return true
+        } catch (e) {
+            return false
+        }
+    }
+
+    /**
+     * Lists all the sessions of the users who have ever connected
+     * using this client and never logged out
+     */
+    listSessions() {
+        // ???
+    }
+
+    // Entity processing
+    /**
+     * Processes all the found entities on the given TLObject,
+     * unless .enabled is False.
+     *
+     * Returns True if new input entities were added.
+     * @param tlo
+     */
+    processEntities(tlo) {
+        if (!this.saveEntities) {
+            return
+        }
+        console.log(tlo)
+        const rows = this._entitiesToRows(tlo)
+        console.log(rows)
+        if (!rows) {
+            return
+        }
+        for (const row of rows) {
+            row[1] = Database.Integer(row[1].toString())
+            console.log('row to be added', row)
+            const stmt = this.db.prepare('insert or replace into entities values (?,?,?,?,?)')
+            stmt.run(...row)
+            console.log('row added :D', ...row)
+        }
+    }
+
+    getEntityRowsByPhone(phone) {
+        return this.db.prepare('select id, hash from entities where phone=?').get(phone)
+    }
+
+    getEntityRowsByUsername(username) {
+        return this.db.prepare('select id, hash from entities where username=?').get(username)
+    }
+
+    getEntityRowsByName(name) {
+        return this.db.prepare('select id, hash from entities where name=?').get(name)
+    }
+
+    getEntityRowsById(id, exact = true) {
+        if (exact) {
+            return this.db.prepare('select id, hash from entities where id=?').get(id)
+        } else {
+            return this.db.prepare('select id, hash from entities where id in (?,?,?)').get(
+                utils.getPeerId(new PeerUser(id)),
+                utils.getPeerId(new PeerChat(id)),
+                utils.getPeerId(new PeerChannel(id)),
+            )
+        }
+    }
+
+    // File processing
+
+    getFile(md5Digest, fileSize, cls) {
+        // nope
+    }
+
+    cacheFile(md5Digest, fileSize, instance) {
+        // nope
+    }
+}
+
+module.exports = SQLiteSession

+ 14 - 0
package-lock.json

@@ -1499,6 +1499,15 @@
         "tweetnacl": "^0.14.3"
       }
     },
+    "better-sqlite3": {
+      "version": "5.4.3",
+      "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-5.4.3.tgz",
+      "integrity": "sha512-fPp+8f363qQIhuhLyjI4bu657J/FfMtgiiHKfaTsj3RWDkHlWC1yT7c6kHZDnBxzQVoAINuzg553qKmZ4F1rEw==",
+      "requires": {
+        "integer": "^2.1.0",
+        "tar": "^4.4.10"
+      }
+    },
     "big.js": {
       "version": "5.2.2",
       "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -3922,6 +3931,11 @@
         }
       }
     },
+    "integer": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/integer/-/integer-2.1.0.tgz",
+      "integrity": "sha512-vBtiSgrEiNocWvvZX1RVfeOKa2mCHLZQ2p9nkQkQZ/BvEiY+6CcUz0eyjvIiewjJoeNidzg2I+tpPJvpyspL1w=="
+    },
     "interpret": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "aes-js": "^3.1.2",
     "babel-loader": "^8.0.6",
     "babel-register": "^6.26.0",
+    "better-sqlite3": "^5.4.3",
     "chalk": "^2.4.2",
     "crc": "^3.8.0",
     "csv-parse": "^4.4.6",