diff --git a/lib/plugins/loader.js b/lib/plugins/loader.js index e7865b2..f2fdf67 100644 --- a/lib/plugins/loader.js +++ b/lib/plugins/loader.js @@ -360,7 +360,6 @@ class PluginsLoader { // e.isSr = true,且命令标准化为 #星铁 开头 let srReg = /^#?(\*|星铁|星轨|穹轨|星穹|崩铁|星穹铁道|崩坏星穹铁道|铁道)+/ if (srReg.test(msg)) { - console.log('sr test true') e.isSr = true msg = msg.replace(srReg, '#星铁') } diff --git a/plugins/genshin/apps/user.js b/plugins/genshin/apps/user.js index 13ecf61..81e7739 100644 --- a/plugins/genshin/apps/user.js +++ b/plugins/genshin/apps/user.js @@ -29,7 +29,7 @@ export class user extends plugin { fnc: 'noLogin' }, { - reg: '^#?我的(ck|cookie)$', + reg: '^#?(原神|星铁)?我的(ck|cookie)$', event: 'message', fnc: 'myCk' }, @@ -38,7 +38,7 @@ export class user extends plugin { fnc: 'delCk' }, { - reg: '^#绑定(uid|UID)?[1-9][0-9]{8}$', + reg: '^#(原神|星铁)?绑定(uid|UID)?[1-9][0-9]{8}$', fnc: 'bingUid' }, { @@ -157,7 +157,8 @@ export class user extends plugin { /** 加载旧的绑定ck json */ loadOldData () { - this.User.loadOldData() + this.User.loadOldDataV2() + this.User.loadOldDataV3() } /** 检查用户CK状态 **/ diff --git a/plugins/genshin/model/db/BaseModel.js b/plugins/genshin/model/db/BaseModel.js new file mode 100644 index 0000000..45863cb --- /dev/null +++ b/plugins/genshin/model/db/BaseModel.js @@ -0,0 +1,26 @@ +import { Sequelize, DataTypes, Model } from 'sequelize' +import { Data } from '#miao' + +Data.createDir('/data/db', 'root') +let dbPath = process.cwd() + '/data/db/data.db' + +// TODO DB自定义 +const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: dbPath, + logging: false +}) + +await sequelize.authenticate() + +export default class BaseModel extends Model { + static Types = DataTypes + + static initDB (model, columns) { + let name = model.name + console.log('Model Name', name) + name = name.replace(/DB$/, 's') + model.init(columns, { sequelize, tableName: name }) + model.COLUMNS = columns + } +} diff --git a/plugins/genshin/model/db/MysUserDB.js b/plugins/genshin/model/db/MysUserDB.js new file mode 100644 index 0000000..f67a9ca --- /dev/null +++ b/plugins/genshin/model/db/MysUserDB.js @@ -0,0 +1,56 @@ +import BaseModel from './BaseModel.js' + +const { Types } = BaseModel + +const COLUMNS = { + // 用户ID,qq为数字 + ltuid: { + type: Types.INTEGER, + primaryKey: true + }, + + // MysUser类型,mys / hoyolab + type: { + type: Types.STRING, + defaultValue: 'mys', + notNull: true + }, + + // CK + ck: Types.STRING, + device: Types.STRING, + + gsUids: Types.STRING, + + srUids: Types.STRING +} + +class MysUserDB extends BaseModel { + static async find (ltuid = '', create = false) { + // DB查询 + let mys = await MysUserDB.findByPk(ltuid) + if (!mys && create) { + mys = await MysUserDB.build({ + ltuid + }) + } + return mys || false + } + + static async findByCK (ck = '') { + let ltuid = 0 + let mys = await MysUserDB.find(ltuid) + if (!mys) { + mys = await MysUserDB.build({ + ltuid, + ck + }) + } + return mys._cacheThis() + } +} + +BaseModel.initDB(MysUserDB, COLUMNS) +await MysUserDB.sync() + +export default MysUserDB diff --git a/plugins/genshin/model/db/UserDB.js b/plugins/genshin/model/db/UserDB.js new file mode 100644 index 0000000..8868625 --- /dev/null +++ b/plugins/genshin/model/db/UserDB.js @@ -0,0 +1,55 @@ +import BaseModel from './BaseModel.js' + +const { Types } = BaseModel + +const COLUMNS = { + // 用户ID,qq为数字 + id: { + type: Types.STRING, + autoIncrement: false, + primaryKey: true + }, + + type: { + type: Types.STRING, + defaultValue: 'qq', + notNull: true + }, + + // 昵称 + name: Types.STRING, + + // 头像 + face: Types.STRING, + + ltuids: Types.STRING, + + // 原神UID + gsUid: Types.STRING, + gsRegUids: Types.STRING, + + // 星铁UID + srUid: Types.STRING, + srRegUids: Types.STRING +} + +class UserDB extends BaseModel { + static async find (id, type = 'qq') { + // user_id + id = type === 'qq' ? '' + id : type + id + // DB查询 + let user = await UserDB.findByPk(id) + if (!user) { + user = await UserDB.build({ + id, + type + }) + } + return user + } +} + +BaseModel.initDB(UserDB, COLUMNS) +await UserDB.sync() + +export default UserDB \ No newline at end of file diff --git a/plugins/genshin/model/db/index.js b/plugins/genshin/model/db/index.js new file mode 100644 index 0000000..b797e7b --- /dev/null +++ b/plugins/genshin/model/db/index.js @@ -0,0 +1,12 @@ +import UserDB from './UserDB.js' +import MysUserDB from './MysUserDB.js' + + +UserDB.belongsToMany(MysUserDB, { + through: 'UserLtuids' +}) +MysUserDB.belongsToMany(UserDB, { + through: 'UserLtuids' +}) + +export { UserDB, MysUserDB } \ No newline at end of file diff --git a/plugins/genshin/model/mys/BaseModel.js b/plugins/genshin/model/mys/BaseModel.js index 23ae515..bca87f7 100644 --- a/plugins/genshin/model/mys/BaseModel.js +++ b/plugins/genshin/model/mys/BaseModel.js @@ -12,19 +12,20 @@ export default class BaseModel { // 获取缓存 _getThis (model, id = '', time = 10 * 60) { const uuid = `${model}:${id}` + this._uuid = uuid if (uuid && cacheMap[uuid]) { return cacheMap[uuid]._expire(time) } - this._uuid = uuid } // 设置缓存 - _cacheThis (time = 10 * 60) { - let id = this._uuid - if (id) { + _cacheThis (model, id, time = 10 * 60) { + const uuid = this._uuid || `${model}:${id}` + this._uuid = uuid + if (uuid) { this._expire(time) - cacheMap[id] = this - return cacheMap[id] + cacheMap[uuid] = this + return cacheMap[uuid] } return this } @@ -44,4 +45,22 @@ export default class BaseModel { return cacheMap[id] } } + + _delCache () { + let id = this._uuid + reFn[id] && clearTimeout(reFn[id]) + delete reFn[id] + delete cacheMap[id] + } + + gameKey (game = 'gs') { + if (game.user_id) { + return game.isSr ? 'sr' : 'gs' + } + return ['sr', 'star'].includes(game) ? 'sr' : 'gs' + } + + isGs (game = 'gs') { + return this.gameKey(game) === 'gs' + } } diff --git a/plugins/genshin/model/mys/DailyCache.js b/plugins/genshin/model/mys/DailyCache.js index b4b2d5f..b1b3053 100644 --- a/plugins/genshin/model/mys/DailyCache.js +++ b/plugins/genshin/model/mys/DailyCache.js @@ -4,12 +4,12 @@ import BaseModel from './BaseModel.js' const servs = ['mys', 'hoyolab'] // 超时时间不必精确,直接定24小时即可 const EX = 3600 * 24 -const redisKeyRoot = 'Yz:genshin:mys:' +const redisKeyRoot = 'Yz:cache:' export default class DailyCache extends BaseModel { - constructor (uid) { + constructor (uid, game = 'cache') { super() - const storeKey = DailyCache.getStoreKey(uid) + const storeKey = DailyCache.getStoreKey(uid, game) // 检查实例缓存 let self = this._getThis('store', storeKey) if (self) { @@ -27,33 +27,23 @@ export default class DailyCache extends BaseModel { * * 传入servKey (mys/hoyolab),会返回指定的servCache * @returns {DailyCache} */ - static create (uid) { + static create (uid, game = 'common') { return new DailyCache(uid) } - /** ---- 基础方法 ---- **/ - // 内部方法:获取redis表key键值 - getTableKey (key, sub = '') { - if (sub) { - return `${this.keyPre}:${key}-${sub}` - } else { - return `${this.keyPre}:${key}` - } - } - // 内部方法:获取server key - static getServKey (uid) { + static getServKey (uid, game = 'common') { // 不传入uid为默认cache - if (!uid || uid === 'cache') { - return 'cache' + if (!uid || game === 'common') { + return `common` } // 传入uid或sever key,判断是mys还是hoyolab - return /^[6-9]|^hoyo|^os/i.test(uid) ? servs[1] : servs[0] + return `${game}:${/^[6-9]|^hoyo|^os/i.test(uid) ? servs[1] : servs[0]}` } // 内部方法:获取redis表前缀 - static getStoreKey (uid) { - const serv = DailyCache.getServKey(uid) + static getStoreKey (uid, game = 'cache') { + const serv = DailyCache.getServKey(uid, game = 'cache') const date = moment().format('MM-DD') return `${serv}-${date}` } @@ -87,6 +77,47 @@ export default class DailyCache extends BaseModel { } } + // 内部方法,用于decode value + static decodeValue (value, decode = false) { + if (value && decode) { + try { + return JSON.parse(value) + } catch (e) { + return false + } + } + return value + } + + // 内部方法,用于encode value + static encodeValue (value) { + if (typeof (value) === 'object') { + return JSON.stringify(value) || '' + } + if (typeof (value) === 'undefined') { + return '' + } + return '' + value + } + + /** ---- 基础方法 ---- **/ + // 内部方法:获取redis表key键值 + getTableKey (key, sub = '') { + if (sub) { + return `${this.keyPre}:${key}-${sub}` + } else { + return `${this.keyPre}:${key}` + } + } + + /** + * 【基础数据结构】:Key-Value + * + * 每个key对应一个Value + * 使用redis kv存储,所有操作需要指定表名 + * + * **/ + /** * 设置指定表的过期时间 * @param table 表 @@ -110,14 +141,6 @@ export default class DailyCache extends BaseModel { await redis.del(this.getTableKey(table, 'count')) } - /** - * 【基础数据结构】:Key-Value - * - * 每个key对应一个Value - * 使用redis kv存储,所有操作需要指定表名 - * - * **/ - /** * 获取表指定key内容 * @param table 表名 @@ -176,29 +199,6 @@ export default class DailyCache extends BaseModel { return await redis.set(this.getTableKey(table), value, { EX }) } - // 内部方法,用于decode value - static decodeValue (value, decode = false) { - if (value && decode) { - try { - return JSON.parse(value) - } catch (e) { - return false - } - } - return value - } - - // 内部方法,用于encode value - static encodeValue (value) { - if (typeof (value) === 'object') { - return JSON.stringify(value) || '' - } - if (typeof (value) === 'undefined') { - return '' - } - return '' + value - } - /** * 【基础数据结构】:Key-List * diff --git a/plugins/genshin/model/mys/MysUser.js b/plugins/genshin/model/mys/MysUser.js index 8b4abd1..9121d6b 100644 --- a/plugins/genshin/model/mys/MysUser.js +++ b/plugins/genshin/model/mys/MysUser.js @@ -11,6 +11,7 @@ import NoteUser from './NoteUser.js' import MysApi from './mysApi.js' import lodash from 'lodash' import fetch from 'node-fetch' +import { MysUserDB } from '../db/index.js' const tables = { // ltuid-uid 查询表 @@ -36,9 +37,8 @@ const tables = { } export default class MysUser extends BaseModel { - constructor (data) { + constructor (ltuid) { super() - let ltuid = data.ltuid if (!ltuid) { return false } @@ -47,59 +47,33 @@ export default class MysUser extends BaseModel { if (!self) { self = this } - // 单日有效缓存,不区分服务器 - self.cache = self.cache || DailyCache.create() - self.uids = self.uids || [] - self.ltuid = data.ltuid - self.ck = self.ck || data.ck - self.qq = self.qq || data.qq || 'pub' - if (data.uid || data.uids) { - self.addUid(data.uid || data.uids) - } - if (data.ck && data.ltuid) { - self.ckData = data - } - // 单日有效缓存,使用uid区分不同服务器 - self.servCache = self.servCache || DailyCache.create(self.uids[0] || 'mys') + this.ltuid = ltuid return self._cacheThis() } // 可传入ltuid、cookie、ck对象来创建MysUser实例 // 在仅传入ltuid时,必须是之前传入过的才能被识别 - static async create (data) { + static async create (ltuid) { + ltuid = MysUser.getLtuid(ltuid) + if (!ltuid) { + return false + } + let mys = new MysUser(ltuid) + await mys.initDB() + mys.initCacheObj() + return mys + } + + static getLtuid (data) { if (!data) { return false } - if (lodash.isPlainObject(data)) { - return new MysUser(data) - } - // 传入cookie - let testRet = /ltuid=(\d{4,9})/g.exec(data) - if (testRet && testRet[1]) { - let ltuid = testRet[1] - // 尝试使用ltuid创建 - let ckUser = await MysUser.create(ltuid) - if (ckUser) { - return ckUser - } - let uids = await MysUser.getCkUid(data) - if (uids) { - return new MysUser({ - ltuid, - ck: data, - type: 'ck', - uids - }) - } - } - // 传入ltuid if (/^\d{4,9}$/.test(data)) { - // 查找ck记录 - let cache = DailyCache.create() - let ckData = await cache.kGet(tables.ck, data, true) - if (ckData && ckData.ltuid) { - return new MysUser(ckData) - } + return data + } + let testRet = /ltuid=(\d{4,9})/g.exec(data.ck || data) + if (testRet && testRet[1]) { + return testRet[1] } return false } @@ -223,42 +197,6 @@ export default class MysUser extends BaseModel { return count } - static async getGameRole (ck, serv = 'mys') { - let url = { - mys: 'https://api-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie', - hoyolab: 'https://api-os-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie?game_biz=hk4e_global' - } - - let res = await fetch(url[serv], { method: 'get', headers: { Cookie: ck } }) - if (!res.ok) return false - res = await res.json() - - return res - } - - // 获取米游社通行证id - static async getUserFullInfo (ck, serv = 'mys') { - let url = { - mys: 'https://bbs-api.mihoyo.com/user/wapi/getUserFullInfo?gids=2', - hoyolab: '' - } - let res = await fetch(url[serv], { - method: 'get', - headers: { - Cookie: ck, - Accept: 'application/json, text/plain, */*', - Connection: 'keep-alive', - Host: 'bbs-api.mihoyo.com', - Origin: 'https://m.bbs.mihoyo.com', - Referer: ' https://m.bbs.mihoyo.com/' - } - }) - if (!res.ok) return res - res = await res.json() - - return res - } - /** * 获取ck对应uid列表 * @param ck 需要获取的ck @@ -376,23 +314,126 @@ export default class MysUser extends BaseModel { } } + static getDeviceGuid () { + function S4 () { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1) + } + + return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()) + } + + async getGameRole (serv = 'mys') { + let ck = this.ck + let url = { + mys: 'https://api-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie', + hoyolab: 'https://api-os-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie' + } + + let res = await fetch(url[serv], { method: 'get', headers: { Cookie: ck } }) + if (!res.ok) return false + res = await res.json() + + return res + } + + // 获取米游社通行证id + async getUserFullInfo (serv = 'mys') { + let ck = this.ck + let url = { + mys: 'https://bbs-api.mihoyo.com/user/wapi/getUserFullInfo?gids=2', + hoyolab: '' + } + let res = await fetch(url[serv], { + method: 'get', + headers: { + Cookie: ck, + Accept: 'application/json, text/plain, */*', + Connection: 'keep-alive', + Host: 'bbs-api.mihoyo.com', + Origin: 'https://m.bbs.mihoyo.com', + Referer: ' https://m.bbs.mihoyo.com/' + } + }) + if (!res.ok) return res + res = await res.json() + + return res + } + + initCacheObj () { + if (this.cache) { + return true + } + this.cache = { + cache: DailyCache.create(), + gs: DailyCache.create(this.type, 'gs'), + sr: DailyCache.create(this.type, 'sr') + } + } + + // 初始化数据 + async initDB () { + if (this.db) { + return + } + let db = await MysUserDB.find(this.ltuid, true) + this.db = db + this.ck = db.ck || '' + this.type = db.type || 'mys' + this.gsUids = (db.gsUids || '').split(',') + this.srUids = (db.srUids || '').split(',') + this.setCkData() + } + + setCkData (data = {}) { + this.ck = data.ck || this.ck || '' + this.device = data.device || this.device || MysUser.getDeviceGuid() + this.type = data.type || this.type || 'mys' + this.gsUids = lodash.isArray(data.gsUids) ? data.gsUids : this.gsUids || [] + this.srUids = lodash.isArray(data.srUids) ? data.srUids : this.srUids || [] + } + + async saveDB () { + if (!this.ck || !this.device || !this.db) { + return false + } + let db = this.db + db.ck = this.ck + db.type = this.type + db.device = this.device + db.gsUids = this.gsUids.join(',') + db.srUids = this.srUids.join(',') + await db.save() + } + + // 为当前MysUser绑定uid - addUid (uid) { + addUid (uid, game = 'gs') { if (lodash.isArray(uid)) { for (let u of uid) { - this.addUid(u) + this.addUid(u, game) } return true } uid = '' + uid - if (/\d{9}/.test(uid) || uid === 'pub') { - if (!this.uids.includes(uid)) { - this.uids.push(uid) + if (/\d{9}/.test(uid)) { + let gameKey = this.gameKey(game) + let uids = this[`${gameKey}Uids`] + if (!uids.includes(uid)) { + uids.push(uid) } } return true } + hasUid (uid, game = 'gs') { + + } + + hasGame (game = 'gs') { + return (this.isGs(game) ? this.gsUids : this.srUids).length > 0 + } + // 初始化当前MysUser缓存记录 async initCache (user) { if (!this.ltuid || !this.servCache || !this.ck) { diff --git a/plugins/genshin/model/mys/NoteUser.js b/plugins/genshin/model/mys/NoteUser.js index 6839526..23f762a 100644 --- a/plugins/genshin/model/mys/NoteUser.js +++ b/plugins/genshin/model/mys/NoteUser.js @@ -9,6 +9,7 @@ import BaseModel from './BaseModel.js' import lodash from 'lodash' import MysUser from './MysUser.js' import gsCfg from '../gsCfg.js' +import { UserDB } from '../db/index.js' export default class NoteUser extends BaseModel { constructor (qq, data = null) { @@ -19,77 +20,22 @@ export default class NoteUser extends BaseModel { return cacheObj } this.qq = qq - if (data) { - this.ckData = this.ckData || {} - for (let uid in data) { - let ck = data[uid] - if (uid && ck.uid) { - this.ckData[uid] = ck - } - } - } else if (!this.ckData) { - this._getCkData() - } - // 缓存实例 return this._cacheThis() } - // 初始化 user - /** - * 创建NoteUser实例 - * @param qq NoterUser对应id(qq) - * * 若传入e对象则会识别e.user_id,并将user对象添加至e.user - * @param data 用户对应MysCookie数据,为空则自动读取 - * @returns {Promise} - */ - static async create (qq, data = null) { - // 兼容处理传入e - if (qq && qq.user_id) { - let e = qq - let user = await NoteUser.create(e.user_id) - e.user = user - return user - } - let user = new NoteUser(qq, data) - // 检查绑定uid (regUid) - await user.getRegUid() - // 传入data则使用,否则读取 - return user - } - - static async forEach (fn) { - // 初始化用户缓存 - let res = await gsCfg.getBingCk() - for (let qq in res.noteCk) { - let cks = res.noteCk[qq] - if (!lodash.isEmpty(cks)) { - let user = await NoteUser.create(qq, cks) - if (user && fn) { - if (await fn(user) === false) { - break - } - } - } - } - } - /** * 获取当前用户uid * 如果为绑定用户,优先获取ck对应uid,否则获取绑定uid */ get uid () { - // 如果绑定有CK,则 - if (this.hasCk) { - return this.mainCk?.uid - } - return this._regUid || '' + return this.getUid() } /** * 当前用户是否具备CK */ get hasCk () { - return this.ckData && !lodash.isEmpty(this.ckData) + return !lodash.isEmpty(this.mysUsers) } /** @@ -137,18 +83,186 @@ export default class NoteUser extends BaseModel { return cks } + /** + * 创建NoteUser实例 + * @param qq NoterUser对应id(qq) + * * 若传入e对象则会识别e.user_id,并将user对象添加至e.user + * @param data 用户对应MysCookie数据,为空则自动读取 + * @returns {Promise} + */ + static async create (qq, data = null) { + // 兼容处理传入e + if (qq && qq.user_id) { + let e = qq + let user = await NoteUser.create(e.user_id) + e.user = user + return user + } + + let user = new NoteUser(qq, data) + await user.initDB() + + // 检查绑定uid (regUid) + await user.getRegUid() + // 传入data则使用,否则读取 + return user + } + + static async forEach (fn) { + // 初始化用户缓存 + let res = await gsCfg.getBingCk() + for (let qq in res.noteCk) { + let cks = res.noteCk[qq] + if (!lodash.isEmpty(cks)) { + let user = await NoteUser.create(qq, cks) + if (user && fn) { + if (await fn(user) === false) { + break + } + } + } + } + } + + // 初始化数据 + async initDB (force = false) { + if (this.db && !force) { + return + } + // 为后续多类型用户兼容 + this.db = await UserDB.find(this.qq, 'qq') + await this.initMysUser(force) + this.initUids() + } + + // 初始化MysUser对象 + async initMysUser () { + let ltuids = this.db?.ltuids || '' + this.mysUsers = {} + for (let ltuid of ltuids.split(',')) { + let mys = await MysUser.create(ltuid) + if (mys) { + this.mysUsers[ltuid] = mys + } + } + } + + // 初始化Uid + initUids () { + let self = this + self.uids = {} + self.uidMap = {} + const { db, uids, uidMap, mysUsers } = self + lodash.forEach(['gs', 'sr'], (key) => { + // 绑定UID + uidMap[key] = {} + uids[key] = [] + // 设置CK UID + lodash.forEach(mysUsers, (mys) => { + lodash.forEach(mys[`${key}Uids`], (uid) => { + if (uid && !uidMap[key][uid]) { + uidMap[key][uid] = { uid, type: 'ck', ltuid: mys.ltuid } + uids[key].push(uid) + } + }) + }) + let regUids = db[`${key}RegUids`] || '{}' + try { + regUids = JSON.parse(regUids) + } catch (e) { + regUids = {} + } + lodash.forEach(['verify', 'reg'], (uidType) => { + lodash.forEach(regUids, (ds, uid) => { + if (uid && ds.type === uidType && !uidMap[key][uid]) { + uidMap[key][uid] = { uid, type: ds.type } + uids[key].push(uid) + } + }) + }) + self[`${key}Uid`] = self[`${key}Uid`] || db[`${key}Uid`] || uids[key]?.[0] || '' + }) + } + + async saveDB () { + let db = this.db + let ltuids = [] + lodash.forEach(this.mysUsers, (mys) => { + if (mys.ck) { + ltuids.push(mys.ltuid) + } + }) + db.ltuids = ltuids.join(',') + lodash.forEach(['gs', 'sr'], (key) => { + db[`${key}Uid`] = this[`${key}Uid`] ? this[`${key}Uid`] : this.uids[key]?.[0] || '' + db[`${key}RegUids`] = JSON.stringify(this.uidMap[key]) + console.log(this.uidMap[key]) + }) + await db.save() + } + + // 获取当前UID + getUid (game = 'gs') { + return this.isGs(game) ? this.gsUid : this.srUid + } + + // 获取UID列表 + getUidList (game = 'gs') { + let ret = [] + let gameKey = this.gameKey(game) + lodash.forEach(this.uids[gameKey], (uid) => { + ret.push(this.uidMap[gameKey][uid]) + }) + return ret + } + + // 获取当前UID数据 + getUidData (game = 'gs') { + let gameKey = this.gameKey(game) + let uid = this.getUid(game) + return this.uidMap[gameKey][uid] + } + + // 获取当前的MysUser对象 + getMysUser (game = 'gs') { + if (lodash.isEmpty(this.mysUsers)) { + return false + } + let uidData = this.getUidData(game) + let ltuid = lodash.keys(this.mysUsers)[0] + if (uidData.type === 'ck') { + ltuid = uidData.ltuid + } + return this.mysUsers[ltuid] + } + + + // 添加UID + addRegUid (uid, game = 'gs') { + let gameKey = this.gameKey(game) + if (!this.uidMap[gameKey][uid]) { + this.uidMap[gameKey][uid] = { uid, type: 'reg' } + this.uids[gameKey].push(uid) + this.setMainUid(uid, game) + } + } + + // 删除UID + delRegUid (uid, game = 'gs') { + let gameKey = this.gameKey(game) + if (this.uidMap[gameKey][uid] && this.uidMap[gameKey][uid].type !== 'ck') { + delete this.uidMap[gameKey][uid] + lodash.remove(this.uids[gameKey], (u) => u === uid) + } + } + /** * 获取当前用户的绑定UID * 主要供内部调用,建议使用 user.uid 获取用户uid * @returns {Promise<*>} */ - async getRegUid (redisKey = `Yz:genshin:mys:qq-uid:${this.qq}`) { - let uid = await redis.get(redisKey) - if (uid) { - await redis.setEx(redisKey, 3600 * 24 * 30, uid) - } - this._regUid = uid - return this._regUid + async getRegUid (game = 'gs') { + console.log('getRegUid 废弃') } /** @@ -157,101 +271,39 @@ export default class NoteUser extends BaseModel { * @param force 若已存在绑定uid关系是否强制更新 */ async setRegUid (uid = '', force = false) { - let redisKey = `Yz:genshin:mys:qq-uid:${this.qq}` - if (uid && /[1|2|5-9][0-9]{8}/.test(uid)) { - uid = String(uid) - const oldUid = await this.getRegUid() - // force true、不存在绑定UID,UID一致时存储并更新有效期 - if (force || !oldUid || oldUid === uid) { - await redis.setEx(redisKey, 3600 * 24 * 30, uid) - } - this._regUid = uid - return String(this._regUid) || '' - } - return '' + console.log('setRegUid 废弃') } - /** - * 切换绑定CK生效的UID - * @param uid 要切换的UID - */ - async setMainUid (uid = '') { + // 切换绑定CK生效的UID + setMainUid (uid = '', game = 'gs') { + let gameKey = this.gameKey(game) // 兼容传入index - if (uid * 1 <= this.ckUids.length) uid = this.ckUids[uid] - // 非法值,以及未传入时使用当前或首个有效uid - uid = (uid || this.uid) * 1 - // 设置主uid - lodash.forEach(this.ckData, (ck) => { - ck.isMain = ck.uid * 1 === uid * 1 - }) - // 保存CK数据 - this._saveCkData() - await this.setRegUid(uid, true) - } + if (uid < 100 && this.uids[gameKey][uid]) { + uid = this.uids[gameKey][uid] + } - /** - * 初始化或重置 当前用户缓存 - */ - async initCache () { - // 刷新绑定CK的缓存 - let count = 0 - let cks = this.cks - for (let ltuid in cks) { - let { ckData, uids } = cks[ltuid] - let mysUser = await MysUser.create(ckData) - for (let uid of uids) { - mysUser.addUid(uid) - } - if (mysUser && await mysUser.initCache(this)) { - count++ + if (this.uidMap[gameKey][uid]) { + if (this.isGs(game)) { + this.gsUid = uid + } else { + this.srUid = uid } } - return count } - /** - * 为当前用户增加CK - * @param cks 绑定的ck - */ - async addCk (cks) { - let ckData = this.ckData - lodash.forEach(cks, (ck, uid) => { - ckData[uid] = ck - }) - this._saveCkData() - await this.initCache() + // 添加MysUser + addMysUser (mysUser) { + this.mysUsers[mysUser.ltuid] = mysUser + this.initUids() } - /** - * 删除当前用户绑定CK - * @param ltuid 根据ltuid删除,未传入则删除当前生效uid对应ltuid - * @param needRefreshCache 是否需要刷新cache,默认刷新 - */ + // 删除当前用户绑定CK async delCk (ltuid = '', needRefreshCache = true) { - if (!ltuid) { - ltuid = this.mainCk.ltuid + if (!this.mysUsers[ltuid]) { + return false } - let ret = [] - ltuid = ltuid * 1 - let ckData = this.ckData - for (let uid in ckData) { - let ck = ckData[uid] - if (ltuid && ck.ltuid * 1 === ltuid) { - ret.push(uid) - delete ckData[uid] - } - } - // 刷新主ck并保存ckData - await this.setMainUid() - - // 刷新MysUser缓存 - if (needRefreshCache) { - let ckUser = await MysUser.create(ltuid) - if (ckUser) { - await ckUser.del(this) - } - } - return ret + delete this.mysUsers[ltuid] + this.initUids() } /** diff --git a/plugins/genshin/model/mys/mysInfo.js b/plugins/genshin/model/mys/mysInfo.js index c04e2fd..78a1491 100644 --- a/plugins/genshin/model/mys/mysInfo.js +++ b/plugins/genshin/model/mys/mysInfo.js @@ -291,7 +291,7 @@ export default class MysInfo { // 初始化用户缓存 let userCount = 0 await NoteUser.forEach(async function (user) { - userCount += await user.initCache(true) + // userCount += await user.initCache(true) }) logger.mark(`加载用户UID:${userCount}个,加入查询池`) } diff --git a/plugins/genshin/model/user.js b/plugins/genshin/model/user.js index 0c3581b..d8ff728 100644 --- a/plugins/genshin/model/user.js +++ b/plugins/genshin/model/user.js @@ -3,8 +3,12 @@ import gsCfg from './gsCfg.js' import lodash from 'lodash' import fs from 'node:fs' import common from '../../../lib/common/common.js' -import MysUser from './mys/MysUser.js' import MysInfo from './mys/mysInfo.js' +import NoteUser from './mys/NoteUser.js' +import MysUser from './mys/MysUser.js' +import { promisify } from 'node:util' +import YAML from 'yaml' +import { Data } from '#miao' export default class User extends base { constructor (e) { @@ -23,7 +27,7 @@ export default class User extends base { // 获取当前user实例 async user () { - return await MysInfo.getNoteUser(this.e) + return await NoteUser.create(this.e) } async resetCk () { @@ -41,9 +45,9 @@ export default class User extends base { return } - let ck = this.e.ck.replace(/#|'|"/g, '') + let ckStr = this.e.ck.replace(/#|'|"/g, '') let param = {} - ck.split(';').forEach((v) => { + ckStr.split(';').forEach((v) => { // 处理分割特殊cookie_token let tmp = lodash.trim(v).replace('=', '~').split('~') param[tmp[0]] = tmp[1] @@ -54,21 +58,26 @@ export default class User extends base { return } - this.ck = `ltoken=${param.ltoken};ltuid=${param.ltuid || param.login_uid};cookie_token=${param.cookie_token || param.cookie_token_v2}; account_id=${param.ltuid || param.login_uid};` + let mys = await MysUser.create(param.ltuid) + let data = {} + data.ck = `ltoken=${param.ltoken};ltuid=${param.ltuid || param.login_uid};cookie_token=${param.cookie_token || param.cookie_token_v2}; account_id=${param.ltuid || param.login_uid};` let flagV2 = false + if (param.cookie_token_v2 && (param.account_mid_v2 || param.ltmid_v2)) { // // account_mid_v2 为版本必须带的字段,不带的话会一直提示绑定cookie失败 请重新登录 flagV2 = true - this.ck = `account_mid_v2=${param.account_mid_v2};cookie_token_v2=${param.cookie_token_v2};ltoken_v2=${param.ltoken_v2};ltmid_v2=${param.ltmid_v2};` + data.ck = `account_mid_v2=${param.account_mid_v2};cookie_token_v2=${param.cookie_token_v2};ltoken_v2=${param.ltoken_v2};ltmid_v2=${param.ltmid_v2};` } /** 拼接ck */ - this.ltuid = param.ltuid || param.ltmid_v2 + data.ltuid = param.ltuid || param.ltmid_v2 /** 米游币签到字段 */ - this.login_ticket = param.login_ticket ?? '' + data.login_ticket = param.login_ticket ?? '' + + mys.setCkData(data) /** 检查ck是否失效 */ - if (!await this.checkCk(param)) { + if (!await this.checkCk(mys, param)) { logger.mark(`绑定cookie错误1:${this.checkMsg || 'cookie错误'}`) await this.e.reply(`绑定cookie失败:${this.checkMsg || 'cookie错误'}`) return @@ -76,11 +85,13 @@ export default class User extends base { if (flagV2) { // 获取米游社通行证id - let userFullInfo = await this.getUserInfo() + let userFullInfo = await mys.getUserFullInfo() if (userFullInfo?.data?.user_info) { let userInfo = userFullInfo?.data?.user_info + /* this.ltuid = userInfo.uid this.ck = `${this.ck}ltuid=${this.ltuid};` + */ } else { logger.mark(`绑定cookie错误2:${userFullInfo.message || 'cookie错误'}`) await this.e.reply(`绑定cookie失败:${userFullInfo.message || 'cookie错误'}`) @@ -88,22 +99,17 @@ export default class User extends base { } } - logger.mark(`${this.e.logFnc} 检查cookie正常 [uid:${this.uid}]`) + logger.mark(`${this.e.logFnc} 检查cookie正常 [ltuid:${mys.ltuid}]`) - await user.addCk(this.getCk()) + await user.addMysUser(mys) + await user.saveDB() - logger.mark(`${this.e.logFnc} 保存cookie成功 [uid:${this.uid}] [ltuid:${this.ltuid}]`) + logger.mark(`${this.e.logFnc} 保存cookie成功 [ltuid:${mys.ltuid}]`) - let uidMsg = [`绑定cookie成功\n${this.region_name}:${this.uid}`] - if (!lodash.isEmpty(this.allUid)) { - this.allUid.forEach(v => { - uidMsg.push(`${v.region_name}:${v.uid}`) - }) - } + let uidMsg = [`绑定cookie成功`] await this.e.reply(uidMsg.join('\n')) let msg = '' - this.region_name += lodash.map(this.allUid, 'region_name').join(',') - if (/天空岛|世界树|America Server|Europe Server|Asia Server/.test(this.region_name)) { + if (mys.hasGame('gs')) { msg += '原神模块支持:\n【#体力】查询当前树脂' msg += '\n【#签到】米游社原神自动签到' msg += '\n【#关闭签到】开启或关闭原神自动签到' @@ -115,7 +121,7 @@ export default class User extends base { msg += '\n【#我的ck】查看当前绑定ck' msg += '\n【#删除ck】删除当前绑定ck' } - if (/星穹列车/.test(this.region_name)) { + if (mys.hasGame('sr')) { msg += '\n星穹铁道支持:\n功能还在咕咕咕~' } msg += '\n 支持绑定多个ck' @@ -125,15 +131,16 @@ export default class User extends base { } /** 检查ck是否可用 */ - async checkCk (param) { + async checkCk (mys, param = {}) { let res for (let type of ['mys', 'hoyolab']) { - let roleRes = await this.getGameRoles(type) + let roleRes = await mys.getGameRole(type) if (roleRes?.retcode === 0) { res = roleRes /** 国际服的标记 */ - if (type === 'hoyolab' && typeof (param.mi18nLang) === 'string') { - this.ck += ` mi18nLang=${param.mi18nLang};` + if (type === 'hoyolab' && typeof (param.mi18nLang) === 'string' && !/mi18nLang/.test(mys.ck)) { + mys.ck += ` mi18nLang=${param.mi18nLang};` + mys.type = 'hoyolab' } break } @@ -146,41 +153,26 @@ export default class User extends base { if (!res) return false - if (!res.data.list || res.data.list.length <= 0) { + let playerList = res?.data?.list || [] + + if (!playerList || playerList.length <= 0) { this.checkMsg = '该账号尚未绑定原神或星穹角色!' return false } else { - res.data.list = res.data.list.filter(v => ['hk4e_cn', 'hkrpg_cn', 'hk4e_global'].includes(v.game_biz)) + playerList = playerList.filter(v => ['hk4e_cn', 'hkrpg_cn', 'hk4e_global', 'hkrpg_global'].includes(v.game_biz)) } //避免同时多个默认展示角色时候只绑定一个 - let is_chosen = false /** 米游社默认展示的角色 */ - for (let val of res.data.list) { - if (val.is_chosen && !is_chosen) { - this.uid = val.game_uid - this.region_name = val.region_name - is_chosen = true - } else { - this.allUid.push({ - uid: val.game_uid, - region_name: val.region_name - }) - } + for (let val of playerList) { + mys.addUid(val.game_uid, ['hk4e_cn', 'hk4e_global'].includes(val.game_biz) ? 'gs' : 'sr') } + console.log('MYS', mys) - if (!this.uid && res.data?.list?.length > 0) { - this.uid = res.data.list[0].game_uid - this.region_name = res.data.list[0].region_name - if (this.allUid[0].uid == this.uid) delete this.allUid[0] - } + await mys.saveDB() - return this.uid - } - - async getGameRoles (server = 'mys') { - return await MysUser.getGameRole(this.ck, server) + return mys } // 获取米游社通行证id @@ -204,7 +196,6 @@ export default class User extends base { ltuid: this.ltuid, login_ticket: this.login_ticket, region_name: this.region_name, - device_id: this.getGuid(), isMain: true } @@ -216,7 +207,6 @@ export default class User extends base { ck: this.ck, ltuid: this.ltuid, region_name: v.region_name, - device_id: this.getGuid(), isMain: false } }) @@ -236,57 +226,49 @@ export default class User extends base { if (!uid) return uid = uid[0] let user = await this.user() - await user.setRegUid(uid, true) - return await this.e.reply(`绑定成功uid:${uid}`, false, { at: true }) + user.addRegUid(uid, this.e) + await user.saveDB() + return await this.showUid() } /** #uid */ async showUid () { let user = await this.user() - if (!user.hasCk) { - await this.e.reply(`当前绑定uid:${user.uid || '无'}`, false, { at: true }) - return - } - let uids = user.ckUids - let ckData = user.ckData - let uid = user.uid * 1 - let msg = [`当前uid:${uid}`, '当前绑定cookie Uid列表', '通过【#uid+序号】来切换uid'] - let region_name = [] - Object.keys(ckData).forEach((v) => { - if (!region_name.includes(ckData[v].region_name)) { - region_name.push(ckData[v].region_name) + let msg = ['通过【#uid+序号】来切换uid'] + lodash.forEach({ genshin: '原神', star: '星穹铁道' }, (gameName, game) => { + let uidList = user.getUidList(game) + let currUid = user.getUid(game) + if (uidList.length === 0) { + return true } - }) - let count = 0 - for (let n of region_name) { - msg.push(n) - for (let i in uids) { - if (ckData[uids[i]].region_name == n) { - let tmp = `${++count}: ${uids[i]}` - if (uids[i] * 1 === uid) { - tmp += ' ☑' - } - msg.push(tmp) + msg.push(`【${gameName}】`) + lodash.forEach(uidList, (ds, idx) => { + let tmp = `${++idx}: ${ds.uid} (${ds.type})` + if (currUid * 1 === ds.uid * 1) { + tmp += ' ☑' } - } - } + msg.push(tmp) + }) + }) await this.e.reply(msg.join('\n')) } /** 切换uid */ async toggleUid (index) { let user = await this.user() - let uidList = user.ckUids + let game = this.e + let uidList = user.getUidList(game) if (index > uidList.length) { return await this.e.reply('uid序号输入错误') } index = Number(index) - 1 - await user.setMainUid(index) - return await this.e.reply(`切换成功,当前uid:${user.uid}`) + await user.setMainUid(index, game) + await user.saveDB() + return await this.showUid() } - /** 加载旧ck */ - async loadOldData () { + /** 加载V2ck */ + async loadOldDataV2 () { let file = [ './data/MysCookie/NoteCookie.json', './data/NoteCookie/NoteCookie.json', @@ -324,22 +306,96 @@ export default class User extends base { qq, ck: ck.cookie, ltuid, - isMain, - device_id: this.getGuid() + isMain } }) - lodash.forEach(arr, (ck, qq) => { - let saveFile = `./data/MysCookie/${qq}.yaml` - if (fs.existsSync(saveFile)) return - gsCfg.saveBingCk(qq, ck) - }) - - logger.mark(logger.green(`加载用户ck完成:${lodash.size(arr)}个`)) - + let count = await this.loadOldData(arr) + if (count > 0) { + logger.mark(logger.green(`DB导入V2用户ck${count}个`)) + } fs.unlinkSync(json) } + /** 加载V3ck */ + async loadOldDataV3 (data) { + let dir = './data/MysCookie/' + Data.createDir('./data/MysCookieBak') + let files = fs.readdirSync(dir).filter(file => file.endsWith('.yaml')) + const readFile = promisify(fs.readFile) + let promises = [] + files.forEach((v) => promises.push(readFile(`${dir}${v}`, 'utf8'))) + const res = await Promise.all(promises) + let ret = {} + for (let v of res) { + v = YAML.parse(v) + let qq + for (let k in v) { + qq = qq || v[k]?.qq + } + if (qq) { + ret[qq] = v + } + } + let count = await this.loadOldData(ret) + if (count > 0) { + logger.mark(logger.green(`DB导入V3用户ck${count}个`)) + } + } + + async loadOldData (data) { + let ltuids = {} + let count = 0 + if (!lodash.isPlainObject(data)) { + return + } + for (let u in data) { + let v = data[u] + let qq + for (let k in v) { + let data = v[k] + qq = qq || data?.qq + let { uid, ck, ltuid, region_name: region, device_id: device } = data + ltuids[ltuid] = ltuids[ltuid] || { + ck, + device, + ltuid, + star: {}, + genshin: {}, + type: /America Server|Europe Server|Asia Server/.test(region) ? 'hoyolab' : 'mys' + } + let tmp = ltuids[ltuid] + let game = region === '星穹列车' ? 'star' : 'genshin' + tmp[game][uid] = uid + } + if (!qq) { + continue + } + let user = await NoteUser.create(qq) + for (let ltuid in ltuids) { + let data = ltuids[ltuid] + let mys = await MysUser.create(data.ltuid) + if (mys) { + data.gsUids = lodash.keys(data.genshin) + data.srUids = lodash.keys(data.star) + mys.setCkData(data) + await mys.saveDB() + user.addMysUser(mys) + } + } + await user.saveDB() + if (fs.existsSync(`./data/MysCookie/${qq}.yaml`)) { + /* fs.rename(`./data/MysCookie/${qq}.yaml`, `./data/MysCookieBak/${qq}.yaml`, (err) => { + if (err) { + console.log(err) + } + }) */ + } + count++ + } + return count + } + /** 我的ck */ async myCk () { let user = await this.user() @@ -386,14 +442,6 @@ export default class User extends base { await this.e.reply(cks.join('\n----\n'), false, { at: true }) } - getGuid () { - function S4 () { - return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1) - } - - return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()) - } - async userAdmin () { this.model = 'userAdmin' await MysInfo.initCache()