import base from './base.js' import cfg from '../../../lib/config/config.js' import common from '../../../lib/common/common.js' import fs from 'node:fs' import moment from 'moment' import GachaLog from './gachaLog.js' import lodash from 'lodash' let xlsx = {} export default class ExportLog extends base { constructor (e) { super(e) this.model = 'gachaLog' this.urlKey = `${this.prefix}url:` /** 绑定的uid */ this.uidKey = `Yz:genshin:mys:qq-uid:${this.userId}` this.path = `./data/gachaJson/${this.e.user_id}/` this.pool = [ { type: 301, typeName: '角色活动' }, { type: 302, typeName: '武器活动' }, { type: 200, typeName: '常驻' } ] this.typeName = { 301: '角色', 302: '武器', 200: '常驻' } } async initXlsx () { if (!lodash.isEmpty(xlsx)) return xlsx xlsx = await import('node-xlsx') } async exportJson () { await this.getUid() if (!this.uid) return false let list = this.getAllList().list let data = { info: { uid: this.uid, lang: list[0].lang, export_time: moment().format('YYYY-MM-DD HH:mm:ss'), export_timestamp: moment().format('X'), export_app: 'Miao-Yunzai', export_app_version: cfg.package.version, uigf_version: 'v2.2' }, list } let saveFile = `${this.path}${this.uid}/${this.uid}.json` fs.writeFileSync(saveFile, JSON.stringify(data, '', '\t')) logger.mark(`${this.e.logFnc} 导出成功${this.uid}.json`) this.e.reply(`导出成功:${this.uid}.json,共${list.length}条 \n请接收文件`) await this.e.friend.sendFile(saveFile).catch((err) => { logger.error(`${this.e.logFnc} 发送文件失败 ${JSON.stringify(err)}`) }) /** 删除文件 */ fs.unlink(saveFile, () => {}) } async exportXlsx () { await this.getUid() if (!this.uid) return false await this.initXlsx() logger.mark(`${this.e.logFnc} 开始导出${this.uid}.xlsx`) let res = this.getAllList() /** 处理卡池数据 */ let xlsxData = this.xlsxDataPool(res) /** 处理所有数据 */ xlsxData.push(this.xlsxDataAll(res)) /** node-xlsx导出的buffer有点大.. */ let buffer = xlsx.build(xlsxData) let saveFile = `${this.path}${this.uid}/${this.uid}.xlsx` fs.writeFileSync(saveFile, Buffer.from(buffer)) logger.mark(`${this.e.logFnc} 导出成功${this.uid}.xlsx`) this.e.reply(`记录文件${this.uid}.xlsx上传中,请耐心等待...`) res = await this.e.friend.sendFile(saveFile).catch((err) => { this.e.reply(`发送文件${this.uid}.xlsx失败,请稍后再试`) logger.error(`${this.e.logFnc} 发送文件失败 ${JSON.stringify(err)}`) }) let line = xlsxData[xlsxData.length - 1].data.length - 1 if (res) this.e.reply(`${this.uid}.xlsx上传成功,共${line}条\n请接收文件`) /** 删除文件 */ fs.unlink(saveFile, () => {}) } async getUid () { let gachLog = new GachaLog(this.e) let uid = await gachLog.getUid() if (!uid) return false this.uid = uid return this.uid } getAllList () { let res = { list: [] } let tmpId = {} for (let v of this.pool) { let json = `${this.path}${this.uid}/${v.type}.json` if (fs.existsSync(json)) { json = JSON.parse(fs.readFileSync(json, 'utf8')) json = json.reverse() } else { json = [] } res[v.type] = json for (let v of json) { if (v.gacha_type == 301 || v.gacha_type == 400) { v.uigf_gacha_type = '301' } else { v.uigf_gacha_type = v.gacha_type } let id = v.id if (!id) { id = moment(v.time).format('x') + '000000' v.id = id } else { if (id.length == 13) { v.id = `${id}000000` } } if (tmpId[id]) { let newId = `${id}00000${tmpId[id].length}` tmpId[id].push(newId) v.id = newId } else { tmpId[id] = [id] } res.list.push(v) } } res.list = lodash.orderBy(res.list, ['id', 'asc']) return res } loadJson (json) { if (!fs.existsSync(json)) return [] return JSON.parse(fs.readFileSync(json, 'utf8')) } xlsxDataPool (data) { let xlsxData = [] for (let v of this.pool) { let poolData = [ [ '时间', '名称', '物品类型', '星级', '祈愿类型' ] ] for (let log of data[v.type]) { poolData.push([ log.time, log.name, log.item_type, log.rank_type, log.gacha_type ]) } let sheetOptions = { '!cols': [{ wch: 20 }, { wch: 20 }, { wch: 10 }, { wch: 10 }, { wch: 10 }] } xlsxData.push({ name: `${v.typeName}祈愿`, data: poolData, options: sheetOptions }) } return xlsxData } xlsxDataAll (data) { let list = [ [ 'count', 'gacha_type', 'id', 'item_id', 'item_type', 'lang', 'name', 'rank_type', 'time', 'uid', 'uigf_gacha_type' ] ] for (let v of data.list) { let tmp = [] for (let i of list[0]) { if (i == 'id' || i == 'uigf_gacha_type') v[i] = String(v[i]) tmp.push(v[i]) } list.push(tmp) } let sheetOptions = { '!cols': [{ wch: 10 }, { wch: 10 }, { wch: 20 }, { wch: 10 }, { wch: 10 }, { wch: 10 }, { wch: 20 }, { wch: 10 }, { wch: 20 }, { wch: 10 }, { wch: 10 }] } return { name: '原始数据', data: list, options: sheetOptions } } /** xlsx导入抽卡记录 */ async logXlsx () { await this.initXlsx() let uid = /[1-9][0-9]{8}/g.exec(this.e.file.name)[0] let textPath = `${this.path}${this.e.file.name}` /** 获取文件下载链接 */ let fileUrl = await this.e.friend.getFileUrl(this.e.file.fid) let ret = await common.downFile(fileUrl, textPath) if (!ret) { this.e.reply('下载xlsx文件错误') return false } let list = xlsx.parse(textPath) list = lodash.keyBy(list, 'name') if (!list['原始数据']) { this.e.reply('xlsx文件内容错误:非统一祈愿记录标准') return false } /** 处理xlsx数据 */ let data = this.dealXlsx(list['原始数据'].data) if (!data) return false /** 保存json */ let msg = [] for (let type in data) { if (!this.typeName[type]) continue let gachLog = new GachaLog(this.e) gachLog.uid = uid gachLog.type = type gachLog.writeJson(data[type]) msg.push(`${this.typeName[type]}记录:${data[type].length}条`) } /** 删除文件 */ fs.unlink(textPath, () => {}) await this.e.reply(`${this.e.file.name},导入成功\n${msg.join('\n')}`) } dealXlsx (list) { /** 必要字段 */ let reqField = ['uigf_gacha_type', 'gacha_type', 'item_type', 'name', 'time'] /** 不是必要字段 */ let noReqField = ['id', 'uid', 'count', 'item_id', 'lang', 'rank_type'] let field = {} for (let i in list[0]) { field[list[0][i]] = i } /** 判断字段 */ for (let v of reqField) { if (!field[v]) { this.e.reply(`xlsx文件内容错误:缺少必要字段${v}`) return } } /** 倒序 */ if (moment(list[1][field.time]).format('x') < moment(list[list.length - 1][field.time]).format('x')) { list = list.reverse() } let data = {} for (let v of list) { if (v[field.name] == 'name') continue if (!data[v[field.uigf_gacha_type]]) data[v[field.uigf_gacha_type]] = [] let tmp = {} /** 加入必要字段 */ for (let re of reqField) { tmp[re] = v[field[re]] } /** 加入非必要字段 */ for (let noRe of noReqField) { if (field[noRe]) { tmp[noRe] = v[field[noRe]] } else { tmp[noRe] = '' } } data[v[field.uigf_gacha_type]].push(tmp) } return data } /** json导入抽卡记录 */ async logJson () { let uid = /[1-9][0-9]{8}/g.exec(this.e.file.name)[0] let textPath = `${this.path}${this.e.file.name}` /** 获取文件下载链接 */ let fileUrl = await this.e.friend.getFileUrl(this.e.file.fid) let ret = await common.downFile(fileUrl, textPath) if (!ret) { this.e.reply('下载json文件错误') return false } let json = {} try { json = JSON.parse(fs.readFileSync(textPath, 'utf8')) } catch (error) { this.e.reply(`${this.e.file.name},json格式错误`) return false } if (lodash.isEmpty(json) || !json.list) { this.e.reply('json文件内容错误:非统一祈愿记录标准') return false } let data = this.dealJson(json.list) if (!data) return false /** 保存json */ let msg = [] for (let type in data) { if (!this.typeName[type]) continue let gachLog = new GachaLog(this.e) gachLog.uid = uid gachLog.type = type gachLog.writeJson(data[type]) msg.push(`${this.typeName[type]}记录:${data[type].length}条`) } /** 删除文件 */ fs.unlink(textPath, () => {}) await this.e.reply(`${this.e.file.name},导入成功\n${msg.join('\n')}`) } dealJson (list) { let data = {} /** 必要字段 */ let reqField = ['gacha_type', 'item_type', 'name', 'time'] for (let v of reqField) { if (!list[0][v]) { this.e.reply(`json文件内容错误:缺少必要字段${v}`) return false } } /** 倒序 */ if (moment(list[0].time).format('x') < moment(list[list.length - 1].time).format('x')) { list = list.reverse() } for (let v of list) { if (!data[v.uigf_gacha_type]) data[v.uigf_gacha_type] = [] data[v.uigf_gacha_type].push(v) } return data } }