1
0

SQLiteSession.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. const EXTENSION = '.session'
  2. const CURRENT_VERSION = 1
  3. const AuthKey = require('../crypto/AuthKey')
  4. const Database = require('better-sqlite3')
  5. const utils = require('../Utils')
  6. const { PeerUser, PeerChannel, PeerChat } = require('../tl/types')
  7. const types = require('../tl/types')
  8. const fs = require('fs')
  9. const MemorySession = require('./Memory')
  10. class SQLiteSession extends MemorySession {
  11. /**
  12. * This session contains the required information to login into your
  13. * Telegram account. NEVER give the saved session file to anyone, since
  14. * they would gain instant access to all your messages and contacts.
  15. * If you think the session has been compromised, close all the sessions
  16. * through an official Telegram client to revoke the authorization.
  17. */
  18. constructor(sessionId = null) {
  19. super()
  20. this.filename = ':memory:'
  21. this.saveEntities = true
  22. if (sessionId) {
  23. this.filename = sessionId
  24. if (!this.filename.endsWith(EXTENSION)) {
  25. this.filename += EXTENSION
  26. }
  27. }
  28. this.db = new Database(this.filename)
  29. let stmt = this.db.prepare('SELECT name FROM sqlite_master where type=\'table\' and name=\'version\'')
  30. if (stmt.get()) {
  31. // Tables already exist, check for the version
  32. stmt = this.db.prepare('select version from version')
  33. const version = stmt.get().version
  34. if (version < CURRENT_VERSION) {
  35. this._upgradeDatabase(version)
  36. this.db.exec('delete from version')
  37. stmt = this.db.prepare('insert into version values (?)')
  38. stmt.run(CURRENT_VERSION)
  39. this.save()
  40. }
  41. // These values will be saved
  42. stmt = this.db.prepare('select * from sessions')
  43. const res = stmt.get()
  44. if (res) {
  45. this._dcId = res['dcId']
  46. this._serverAddress = res['serverAddress']
  47. this._port = res['port']
  48. this._authKey = new AuthKey(res['authKey'])
  49. this._takeoutId = res['takeoutId']
  50. }
  51. } else {
  52. // Tables don't exist, create new ones
  53. this._createTable(
  54. 'version (version integer primary key)'
  55. ,
  56. `sessions (
  57. dcId integer primary key,
  58. serverAddress text,
  59. port integer,
  60. authKey blob,
  61. takeoutId integer
  62. )`,
  63. `entities (
  64. id integer primary key,
  65. hash integer not null,
  66. username text,
  67. phone integer,
  68. name text
  69. )`,
  70. `sent_files (
  71. md5Digest blob,
  72. fileSize integer,
  73. type integer,
  74. id integer,
  75. hash integer,
  76. primary key(md5Digest, fileSize, type)
  77. )`,
  78. `updateState (
  79. id integer primary key,
  80. pts integer,
  81. qts integer,
  82. date integer,
  83. seq integer
  84. )`,
  85. )
  86. stmt = this.db.prepare('insert into version values (?)')
  87. stmt.run(CURRENT_VERSION)
  88. this._updateSessionTable()
  89. this.save()
  90. }
  91. }
  92. load() {
  93. }
  94. get authKey() {
  95. return super.authKey
  96. }
  97. _upgradeDatabase(old) {
  98. // nothing so far
  99. }
  100. _createTable(...definitions) {
  101. for (const definition of definitions) {
  102. this.db.exec(`create table ${definition}`)
  103. }
  104. }
  105. // Data from sessions should be kept as properties
  106. // not to fetch the database every time we need it
  107. setDC(dcId, serverAddress, port) {
  108. super.setDC(dcId, serverAddress, port)
  109. this._updateSessionTable()
  110. // Fetch the authKey corresponding to this data center
  111. const row = this.db.prepare('select authKey from sessions').get()
  112. if (row && row.authKey) {
  113. this._authKey = new AuthKey(row.authKey)
  114. } else {
  115. this._authKey = null
  116. }
  117. }
  118. set authKey(value) {
  119. this._authKey = value
  120. this._updateSessionTable()
  121. }
  122. set takeoutId(value) {
  123. this._takeoutId = value
  124. this._updateSessionTable()
  125. }
  126. _updateSessionTable() {
  127. // While we can save multiple rows into the sessions table
  128. // currently we only want to keep ONE as the tables don't
  129. // tell us which auth_key's are usable and will work. Needs
  130. // some more work before being able to save auth_key's for
  131. // multiple DCs. Probably done differently.
  132. this.db.exec('delete from sessions')
  133. const stmt = this.db.prepare('insert or replace into sessions values (?,?,?,?,?)')
  134. stmt.run(this._dcId, this._serverAddress,
  135. this._port, this._authKey ? this._authKey.key : Buffer.alloc(0), this._takeoutId)
  136. }
  137. getUpdateState(entityId) {
  138. const row = this.db.prepare('select pts, qts, date, seq from updateState where id=?').get(entityId)
  139. if (row) {
  140. return new types.update.State({
  141. pts: row.pts,
  142. qts: row.qts, date: new Date(row.date), seq: row.seq, unreadCount: 0,
  143. })
  144. }
  145. }
  146. setUpdateState(entityId, state) {
  147. const stmt = this.db.prepare('insert or replace into updateState values (?,?,?,?,?)')
  148. stmt.run(entityId, state.pts, state.qts,
  149. state.date.getTime(), state.seq)
  150. }
  151. save() {
  152. // currently nothing needs to be done
  153. }
  154. /**
  155. * Deletes the current session file
  156. */
  157. delete() {
  158. if (this.db.name === ':memory:') {
  159. return true
  160. }
  161. try {
  162. fs.unlinkSync(this.db.name)
  163. return true
  164. } catch (e) {
  165. return false
  166. }
  167. }
  168. /**
  169. * Lists all the sessions of the users who have ever connected
  170. * using this client and never logged out
  171. */
  172. listSessions() {
  173. // ???
  174. }
  175. // Entity processing
  176. /**
  177. * Processes all the found entities on the given TLObject,
  178. * unless .enabled is False.
  179. *
  180. * Returns True if new input entities were added.
  181. * @param tlo
  182. */
  183. processEntities(tlo) {
  184. if (!this.saveEntities) {
  185. return
  186. }
  187. const rows = this._entitiesToRows(tlo)
  188. if (!rows) {
  189. return
  190. }
  191. for (const row of rows) {
  192. row[1] = Database.Integer(row[1].toString())
  193. const stmt = this.db.prepare('insert or replace into entities values (?,?,?,?,?)')
  194. stmt.run(...row)
  195. }
  196. }
  197. getEntityRowsByPhone(phone) {
  198. return this.db.prepare('select id, hash from entities where phone=?').get(phone)
  199. }
  200. getEntityRowsByUsername(username) {
  201. return this.db.prepare('select id, hash from entities where username=?').get(username)
  202. }
  203. getEntityRowsByName(name) {
  204. return this.db.prepare('select id, hash from entities where name=?').get(name)
  205. }
  206. getEntityRowsById(id, exact = true) {
  207. if (exact) {
  208. return this.db.prepare('select id, hash from entities where id=?').get(id)
  209. } else {
  210. return this.db.prepare('select id, hash from entities where id in (?,?,?)').get(
  211. utils.getPeerId(new PeerUser(id)),
  212. utils.getPeerId(new PeerChat(id)),
  213. utils.getPeerId(new PeerChannel(id)),
  214. )
  215. }
  216. }
  217. // File processing
  218. getFile(md5Digest, fileSize, cls) {
  219. // nope
  220. }
  221. cacheFile(md5Digest, fileSize, instance) {
  222. // nope
  223. }
  224. }
  225. module.exports = SQLiteSession