JSONSession.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. const { generateRandomLong, getRandomInt } = require('../Helpers')
  2. const fs = require('fs').promises
  3. const { existsSync, readFileSync } = require('fs')
  4. const AuthKey = require('../crypto/AuthKey')
  5. const { TLObject } = require('../tl/tlobject')
  6. const utils = require('../Utils')
  7. const types = require('../tl/types')
  8. BigInt.toJSON = function() {
  9. return { fool: this.fool.toString('hex') }
  10. }
  11. BigInt.parseJson = function() {
  12. return { fool: BigInt('0x' + this.fool) }
  13. }
  14. class Session {
  15. constructor(sessionUserId) {
  16. this.sessionUserId = sessionUserId
  17. this._serverAddress = null
  18. this._dcId = 0
  19. this._port = null
  20. // this.serverAddress = "localhost";
  21. // this.port = 21;
  22. this.authKey = undefined
  23. this.id = generateRandomLong(false)
  24. this.sequence = 0
  25. this.salt = 0n // Unsigned long
  26. this.timeOffset = 0n
  27. this.lastMessageId = 0n
  28. this.user = undefined
  29. this._files = {}
  30. this._entities = new Set()
  31. this._updateStates = {}
  32. }
  33. /**
  34. * Saves the current session object as session_user_id.session
  35. */
  36. async save() {
  37. if (this.sessionUserId) {
  38. const str = JSON.stringify(this, function(key, value) {
  39. if (typeof value === 'bigint') {
  40. return value.toString() + 'n'
  41. } else {
  42. return value
  43. }
  44. })
  45. await fs.writeFile(`${this.sessionUserId}.session`, str)
  46. }
  47. }
  48. setDC(dcId, serverAddress, port) {
  49. this._dcId = dcId | 0
  50. this._serverAddress = serverAddress
  51. this._port = port
  52. }
  53. get serverAddress() {
  54. return this._serverAddress
  55. }
  56. get port() {
  57. return this._port
  58. }
  59. get dcId() {
  60. return this._dcId
  61. }
  62. getUpdateState(entityId) {
  63. return this._updateStates[entityId]
  64. }
  65. setUpdateState(entityId, state) {
  66. return this._updateStates[entityId] = state
  67. }
  68. close() {
  69. }
  70. delete() {
  71. }
  72. _entityValuesToRow(id, hash, username, phone, name) {
  73. // While this is a simple implementation it might be overrode by,
  74. // other classes so they don't need to implement the plural form
  75. // of the method. Don't remove.
  76. return [id, hash, username, phone, name]
  77. }
  78. _entityToRow(e) {
  79. if (!(e instanceof TLObject)) {
  80. return
  81. }
  82. let p
  83. let markedId
  84. try {
  85. p = utils.getInputPeer(e, false)
  86. markedId = utils.getPeerId(p)
  87. } catch (e) {
  88. // Note: `get_input_peer` already checks for non-zero `access_hash`.
  89. // See issues #354 and #392. It also checks that the entity
  90. // is not `min`, because its `access_hash` cannot be used
  91. // anywhere (since layer 102, there are two access hashes).
  92. return
  93. }
  94. let pHash
  95. if (p instanceof types.InputPeerUser || p instanceof types.InputPeerChannel) {
  96. pHash = p.accessHash
  97. } else if (p instanceof types.InputPeerChat) {
  98. pHash = 0
  99. } else {
  100. return
  101. }
  102. let username = e.username
  103. if (username) {
  104. username = username.toLowerCase()
  105. }
  106. const phone = e.phone
  107. const name = utils.getDisplayName(e)
  108. return this._entityValuesToRow(markedId, pHash, username, phone, name)
  109. }
  110. _entitiesToRows(tlo) {
  111. let entities = []
  112. if (tlo instanceof TLObject && utils.isListLike(tlo)) {
  113. // This may be a list of users already for instance
  114. entities = tlo
  115. } else {
  116. if ('user' in tlo) {
  117. entities.push(tlo.user)
  118. }
  119. if ('chats' in tlo && utils.isListLike(tlo.chats)) {
  120. entities.concat(tlo.chats)
  121. }
  122. if ('users' in tlo && utils.isListLike(tlo.users)) {
  123. entities.concat(tlo.users)
  124. }
  125. }
  126. const rows = [] // Rows to add (id, hash, username, phone, name)
  127. for (const e of entities) {
  128. const row = this._entityToRow(e)
  129. if (row) {
  130. rows.push(row)
  131. }
  132. }
  133. return rows
  134. }
  135. static tryLoadOrCreateNew(sessionUserId) {
  136. if (sessionUserId === undefined) {
  137. return new Session()
  138. }
  139. const filepath = `${sessionUserId}.session`
  140. if (existsSync(filepath)) {
  141. try {
  142. const ob = JSON.parse(readFileSync(filepath, 'utf-8'), function(key, value) {
  143. if (typeof value == 'string' && value.match(/(\d+)n/)) {
  144. return BigInt(value.slice(0, -1))
  145. } else {
  146. return value
  147. }
  148. })
  149. const authKey = new AuthKey(Buffer.from(ob.authKey._key.data))
  150. const session = new Session(ob.sessionUserId)
  151. session._serverAddress = ob._serverAddress
  152. session._port = ob._port
  153. session._dcId = ob._dcId
  154. // this.serverAddress = "localhost";
  155. // this.port = 21;
  156. session.authKey = authKey
  157. session.id = ob.id
  158. session.sequence = ob.sequence
  159. session.salt = ob.salt // Unsigned long
  160. session.timeOffset = ob.timeOffset
  161. session.lastMessageId = ob.lastMessageId
  162. session.user = ob.user
  163. return session
  164. } catch (e) {
  165. return new Session(sessionUserId)
  166. }
  167. } else {
  168. return new Session(sessionUserId)
  169. }
  170. }
  171. getNewMsgId() {
  172. const msTime = new Date().getTime()
  173. let newMessageId =
  174. (BigInt(BigInt(Math.floor(msTime / 1000)) + this.timeOffset) << 32n) |
  175. (BigInt(msTime % 1000) << 22n) |
  176. (BigInt(getRandomInt(0, 524288)) << 2n) // 2^19
  177. if (this.lastMessageId >= newMessageId) {
  178. newMessageId = this.lastMessageId + 4n
  179. }
  180. this.lastMessageId = newMessageId
  181. return newMessageId
  182. }
  183. processEntities(tlo) {
  184. const entitiesSet = this._entitiesToRows(tlo)
  185. for (const e of entitiesSet) {
  186. this._entities.add(e)
  187. }
  188. }
  189. getEntityRowsByPhone(phone) {
  190. for (const e of this._entities) { // id, hash, username, phone, name
  191. if (e[3] === phone) {
  192. return [e[0], e[1]]
  193. }
  194. }
  195. }
  196. getEntityRowsByName(name) {
  197. for (const e of this._entities) { // id, hash, username, phone, name
  198. if (e[4] === name) {
  199. return [e[0], e[1]]
  200. }
  201. }
  202. }
  203. getEntityRowsByUsername(username) {
  204. for (const e of this._entities) { // id, hash, username, phone, name
  205. if (e[2] === username) {
  206. return [e[0], e[1]]
  207. }
  208. }
  209. }
  210. getEntityRowsById(id, exact = true) {
  211. if (exact) {
  212. for (const e of this._entities) { // id, hash, username, phone, name
  213. if (e[0] === id) {
  214. return [e[0], e[1]]
  215. }
  216. }
  217. } else {
  218. const ids = [utils.getPeerId(new types.PeerUser({ userId: id })),
  219. utils.getPeerId(new types.PeerChat({ chatId: id })),
  220. utils.getPeerId(new types.PeerChannel({ channelId: id })),
  221. ]
  222. for (const e of this._entities) { // id, hash, username, phone, name
  223. if (ids.includes(e[0])) {
  224. return [e[0], e[1]]
  225. }
  226. }
  227. }
  228. }
  229. getInputEntity(key) {
  230. let exact
  231. if (key.SUBCLASS_OF_ID !== undefined) {
  232. if ([0xc91c90b6, 0xe669bf46, 0x40f202fd].includes(key.SUBCLASS_OF_ID)) {
  233. // hex(crc32(b'InputPeer', b'InputUser' and b'InputChannel'))
  234. // We already have an Input version, so nothing else required
  235. return key
  236. }
  237. // Try to early return if this key can be casted as input peer
  238. return utils.getInputPeer(key)
  239. } else {
  240. // Not a TLObject or can't be cast into InputPeer
  241. if (key instanceof TLObject) {
  242. key = utils.getPeerId(key)
  243. exact = true
  244. } else {
  245. exact = !(typeof key == 'number') || key < 0
  246. }
  247. }
  248. let result = null
  249. if (typeof key === 'string') {
  250. const phone = utils.parsePhone(key)
  251. if (phone) {
  252. result = this.getEntityRowsByPhone(phone)
  253. } else {
  254. const { username, isInvite } = utils.parseUsername(key)
  255. if (username && !isInvite) {
  256. result = this.getEntityRowsByUsername(username)
  257. } else {
  258. const tup = utils.resolveInviteLink(key)[1]
  259. if (tup) {
  260. result = this.getEntityRowsById(tup, false)
  261. }
  262. }
  263. }
  264. } else if (typeof key === 'number') {
  265. result = this.getEntityRowsById(key, exact)
  266. }
  267. if (!result && typeof key === 'string') {
  268. result = this.getEntityRowsByName(key)
  269. }
  270. if (result) {
  271. let entityId = result[0] // unpack resulting tuple
  272. const entityHash = result[1]
  273. const resolved = utils.resolveId(entityId)
  274. entityId = resolved[0]
  275. const kind = resolved[1]
  276. // removes the mark and returns type of entity
  277. if (kind === types.PeerUser) {
  278. return new types.InputPeerUser({ userId: entityId, accessHash: entityHash })
  279. } else if (kind === types.PeerChat) {
  280. return new types.InputPeerChat({ chatId: entityId })
  281. } else if (kind === types.PeerChannel) {
  282. return new types.InputPeerChannel({ channelId: entityId, accessHash: entityHash })
  283. }
  284. } else {
  285. throw new Error('Could not find input entity with key ' + key)
  286. }
  287. }
  288. }
  289. module.exports = Session