import os from 'os' // @ts-ignore import LRU from 'lru-cache' import path from 'path' import fs from 'fs-extra' import { FlashStore } from 'flash-store' import PuppetMini from '../puppet-mini.js' import type { MiniContactPayload, MiniMessagePayload } from '../help/struct' import { log } from '../config.js' import { FileBox, type FileBoxInterface } from 'file-box' import Hashids from 'hashids' const PRE = 'CacheManager' class CacheManager { /** * ************************************************************************ * Instance Methods * ************************************************************************ */ protected cacheMessageRawPayload?: LRU protected cacheTokenRawPayload?: LRU protected cacheContactRawPayload?: FlashStore protected cacheFileRawPayload?: FlashStore /** * ************************************************************************ * Static Methods * ************************************************************************ */ async init () { log.verbose(PRE, 'init()') if (this.cacheMessageRawPayload) { throw new Error('PayloadStore should be stop() before start() again.') } const baseDir = path.join( os.homedir(), path.sep, '.wechaty', path.sep, 'puppet-mini-cache', path.sep, PuppetMini.token, path.sep, ) const fileDir = path.join( os.homedir(), path.sep, '.wechaty', path.sep, 'puppet-mini-cache', path.sep, PuppetMini.token, path.sep, 'file', path.sep, ) if (!fs.existsSync(baseDir)) { fs.mkdirSync(baseDir, { recursive: true }) } if (!fs.existsSync(fileDir)) { fs.mkdirSync(fileDir, { recursive: true }) } this.cacheContactRawPayload = new FlashStore(path.join(baseDir, 'contactRawPayload')) this.cacheFileRawPayload = new FlashStore(path.join(baseDir, 'fileRawPayload')) const lruOptions: LRU.Options = { // @ts-ignore dispose (key: string, val: any) { log.silly('PayloadStore', `constructor() lruOptions.dispose(${key}, ${JSON.stringify(val)})`) }, max : 2000, maxAge : 2000 * 60 * 60 * 12, } this.cacheMessageRawPayload = new LRU(lruOptions) const tokenLruOptions: LRU.Options = { // @ts-ignore dispose (key: string, val: any) { log.silly('PayloadStore', `constructor() lruOptions.dispose(${key}, ${JSON.stringify(val)})`) }, max : 2000, maxAge : 1000 * 60 * 60 * 12, } this.cacheTokenRawPayload = new LRU(tokenLruOptions) } async stop () { log.verbose('PayloadStore', 'stop()') if (this.cacheMessageRawPayload) { this.cacheMessageRawPayload = undefined } if (this.cacheTokenRawPayload) { this.cacheTokenRawPayload = undefined } if (this.cacheContactRawPayload) { await this.cacheContactRawPayload.close() this.cacheContactRawPayload = undefined } if (this.cacheFileRawPayload) { await this.cacheFileRawPayload.close() this.cacheFileRawPayload = undefined } } /** * ------------------------------- * Message Section * -------------------------------- */ public async getMessage (messageId: string): Promise { if (!this.cacheMessageRawPayload) { throw new Error(`${PRE} getMessage() has no cache.`) } log.verbose(PRE, `getMessage(${messageId})`) return this.cacheMessageRawPayload.get(messageId) } public async setMessage (messageId: string, payload: MiniMessagePayload): Promise { if (!this.cacheMessageRawPayload || !messageId) { throw new Error(`${PRE} setMessage() has no cache.`) } log.verbose(PRE, `setMessage(${messageId}): ${JSON.stringify(payload)}`) await this.cacheMessageRawPayload.set(messageId, payload) } /** * ------------------------------- * Token Section * -------------------------------- */ public async getUserToken (token: string): Promise { if (!this.cacheTokenRawPayload) { throw new Error(`${PRE} getUserToken() has no cache.`) } log.verbose(PRE, `getUserToken(${token})`) return this.cacheTokenRawPayload.get(token) } public async setUserToken (token: string, userId: string): Promise { if (!this.cacheTokenRawPayload || !token) { throw new Error(`${PRE} setUserToken() has no cache.`) } log.verbose(PRE, `setUserToken(${token}): ${userId}`) await this.cacheTokenRawPayload.set(token, userId) } /** * ------------------------------- * Contact Section * -------------------------------- */ public async getContact (contactId: string): Promise { if (!this.cacheContactRawPayload) { throw new Error(`${PRE} getContact() has no cache.`) } log.verbose(PRE, `getContact(${contactId})`) if (!await this.cacheContactRawPayload.has(contactId)) { const hashids = new Hashids(PuppetMini.serviceSalt) const payload = { alias: 'notWlcUser', avatar: '', hashId: hashids.encode(contactId.toString().split('')), id: contactId, name: contactId } await this.cacheContactRawPayload.set(contactId, payload) return payload } return await this.cacheContactRawPayload.get(contactId) } public async getContactList (selfId: string): Promise { if (!this.cacheContactRawPayload) { throw new Error(`${PRE} getContactList(${selfId}) has no cache.`) } const result: string[] = [] for await (const contactId of this.cacheContactRawPayload.keys()) { if (contactId !== selfId) { result.push(contactId) } } return result } public async setContact (contactId: string, payload: MiniContactPayload): Promise { if (!this.cacheContactRawPayload || !contactId) { throw new Error(`${PRE} setContact() has no cache.`) } log.verbose(PRE, `setContact(${contactId}): ${JSON.stringify(payload)}`) await this.cacheContactRawPayload.set(contactId, payload) } public async setContactAlias (contactId: string, alias: string): Promise { if (!this.cacheContactRawPayload || !contactId) { throw new Error(`${PRE} setContact() has no cache.`) } log.verbose(PRE, `setContactAlias(${contactId}): ${alias}`) const payload = await this.cacheContactRawPayload.get(contactId) payload!.name = alias await this.cacheContactRawPayload.set(contactId, payload!) } /** * ------------------------------- * file Section * -------------------------------- */ public async getFile (fileId: string): Promise { if (!this.cacheFileRawPayload) { throw new Error(`${PRE} getFile() has no cache.`) } log.verbose(PRE, `getFile(${fileId})`) const filePath = await this.cacheFileRawPayload.get(fileId) if (!filePath) { throw new Error(`${PRE} getFile() has no cache.`) } return FileBox.fromFile(filePath) } public async setFile (fileId: string, payload: FileBoxInterface | undefined): Promise { if (!this.cacheFileRawPayload || !fileId || !payload) { throw new Error(`${PRE} setFile() has no cache.`) } log.verbose(PRE, `setFile(${fileId}): ${JSON.stringify(payload)}`) const baseDir = path.join( os.homedir(), path.sep, '.wechaty', path.sep, 'puppet-mini-cache', path.sep, PuppetMini.token, path.sep, 'file', path.sep, `${fileId}_${payload.name}`, ) await payload.toFile(baseDir, true) await this.cacheFileRawPayload.set(fileId, baseDir) } } export { CacheManager }