角色普通立绘支持自定义,一些已知问题修复

This commit is contained in:
Kokomi 2022-11-25 16:14:25 +08:00
parent 9f75804405
commit fa8fd9e9ec
15 changed files with 34 additions and 423 deletions

1
.gitignore vendored
View File

@ -16,4 +16,5 @@
/config/help.js
/config/cfg.js
/resources/profile/super-character/*
/resources/profile/normal-character/*
!/resources/profile/super-character/达达利亚.webp

View File

@ -1,3 +1,13 @@
# 2.1.2
* 角色普通立绘支持自定义,用于面板、图鉴等场景
* 立绘可放置在 **resources/profile/normal-character/** 目录下webp格式
* 去除插件内自带的V2/V3兼容逻辑使用runtime进行V2/V3兼容如使用遇到问题请升级至最新版Yunzai
* V3-Yunzai官方Yunzai最新版本
* V2-Yunzai喵版V2-Yunzai2.2.3版本。其余分值维护的V2-Yunzai可合并2.2.3版本
* 较低版本的Yuznai可能无法正常使用miao-plugin
* 一些已知问题修复
# 2.1.1
* 部分底层结构升级

View File

@ -1,20 +0,0 @@
import fs from 'fs'
import _puppeteer from './lib/puppeteer.js'
import _plugin from './lib/plugin.js'
const importV3 = async function (file, def, key = 'default') {
if (fs.existsSync(process.cwd() + file)) {
let obj = await import(`file://${process.cwd()}/${file}`)
return obj[key] || def
}
return def
}
let MysInfo = await importV3('/plugins/genshin/model/mys/mysInfo.js', {})
let plugin = await importV3('lib/plugins/plugin.js', _plugin)
let puppeteer = await importV3('lib/plugins/plugin.js', _puppeteer)
export {
plugin,
MysInfo,
puppeteer
}

View File

@ -1,226 +0,0 @@
/*
* V3版Yunzai puppeteer
* */
import template from 'art-template'
import fs from 'fs'
import lodash from 'lodash'
import { segment } from 'oicq'
const _path = process.cwd()
let puppeteer = {}
let logger = global.logger || global.Bot?.logger || {}
logger.green = logger.green || ((t) => t)
class Puppeteer {
constructor () {
this.browser = false
this.lock = false
this.shoting = []
/** 截图数达到时重启浏览器 避免生成速度越来越慢 */
this.restartNum = 400
/** 截图次数 */
this.renderNum = 0
this.config = {
headless: true,
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-sandbox',
'--no-zygote',
'--single-process'
]
}
this.html = {}
this.watcher = {}
this.createDir('./data/html')
}
async initXlsx () {
if (!lodash.isEmpty(puppeteer)) return puppeteer
puppeteer = (await import('puppeteer')).default
}
createDir (dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
}
/**
* 初始化chromium
*/
async browserInit () {
await this.initXlsx()
if (this.browser) return this.browser
if (this.lock) return false
this.lock = true
logger.mark('puppeteer Chromium 启动中。。')
/** 初始化puppeteer */
this.browser = await puppeteer.launch(this.config).catch((err) => {
logger.error(err.toString())
if (String(err).includes('correct Chromium')) {
logger.error('没有正确安装Chromium可以尝试执行安装命令node ./node_modules/puppeteer/install.js')
}
})
this.lock = false
if (!this.browser) {
logger.error('puppeteer Chromium 启动失败')
return false
}
logger.mark('puppeteer Chromium 启动成功')
/** 监听Chromium实例是否断开 */
this.browser.on('disconnected', (e) => {
logger.error('Chromium实例关闭或崩溃')
this.browser = false
})
return this.browser
}
/**
* `chromium` 截图
* @param data 模板参数
* @param data.tplFile 模板路径必传
* @param data.saveId 生成html名称为空name代替
* @param data.imgType screenshot参数生成图片类型jpegpng
* @param data.quality screenshot参数图片质量 0-100jpeg是可传默认90
* @param data.omitBackground screenshot参数隐藏默认的白色背景背景透明默认不透明
* @param data.path screenshot参数截图保存路径截图图片类型将从文件扩展名推断出来如果是相对路径则从当前路径解析如果没有指定路径图片将不会保存到硬盘
* @return oicq img
*/
async screenshot (name, data = {}) {
if (!await this.browserInit()) {
return false
}
let savePath = this.dealTpl(name, data)
if (!savePath) return false
let buff = ''
let start = Date.now()
try {
this.shoting.push(name)
const page = await this.browser.newPage()
page.setCacheEnabled(true)
await page.goto(`file://${_path}${lodash.trim(savePath, '.')}`, data.pageGotoParams || {})
let body = await page.$('#container') || await page.$('body')
await page.waitForSelector('#container')
let randData = {
// encoding: 'base64',
type: data.imgType || 'jpeg',
omitBackground: data.omitBackground || false,
quality: data.quality || 90,
path: data.path || ''
}
if (data.imgType == 'png') delete randData.quality
buff = await body.screenshot(randData)
page.close().catch((err) => logger.error(err))
this.shoting.pop()
} catch (error) {
logger.error(`图片生成失败:${name}:${error}`)
/** 关闭浏览器 */
if (this.browser) {
await this.browser.close().catch((err) => logger.error(err))
}
this.browser = false
buff = ''
return false
}
if (!buff) {
logger.error(`图片生成为空:${name}`)
return false
}
this.renderNum++
/** 计算图片大小 */
let kb = (buff.length / 1024).toFixed(2) + 'kb'
logger.mark(`[图片生成][${name}][${this.renderNum}次] ${kb} ${logger.green(`${Date.now() - start}ms`)}`)
this.restart()
return segment.image(buff)
}
/** 模板 */
dealTpl (name, data) {
let { tplFile, saveId = name } = data
let savePath = `./data/html/${name}/${saveId}.html`
/** 读取html模板 */
if (!this.html[tplFile]) {
this.createDir(`./data/html/${name}`)
try {
this.html[tplFile] = fs.readFileSync(tplFile, 'utf8')
} catch (error) {
logger.error(`加载html错误${tplFile}`)
return false
}
this.watch(tplFile)
}
data.resPath = `${_path}/resources/`
/** 替换模板 */
let tmpHtml = template.render(this.html[tplFile], data)
/** 保存模板 */
fs.writeFileSync(savePath, tmpHtml)
logger.debug(`[图片生成][使用模板] ${savePath}`)
return savePath
}
/** 监听配置文件 */
watch (tplFile) {
/*
if (this.watcher[tplFile]) return
const watcher = chokidar.watch(tplFile)
watcher.on('change', path => {
delete this.html[tplFile]
logger.mark(`[修改html模板] ${tplFile}`)
})
this.watcher[tplFile] = watcher
*/
}
/** 重启 */
restart () {
/** 截图超过重启数时,自动关闭重启浏览器,避免生成速度越来越慢 */
if (this.renderNum % this.restartNum == 0) {
if (this.shoting.length <= 0) {
setTimeout(async () => {
this.browser.removeAllListeners('disconnected')
await this.browser.close().catch((err) => logger.error(err))
this.browser = false
logger.mark('puppeteer 关闭重启')
}, 100)
}
}
}
}
export default new Puppeteer()

View File

@ -1,150 +0,0 @@
import lodash from 'lodash'
import { MysInfo } from './index.js'
class User {
constructor (cfg) {
this.id = cfg.id
this.uid = cfg.uid
this.cookie = ''
}
// 保存用户配置
async setCfg (path, value) {
let userCfg = await redis.get(`miao:user-cfg:${this.id}`) || await redis.get(`genshin:user-cfg:${this.id}`)
userCfg = userCfg ? JSON.parse(userCfg) : {}
lodash.set(userCfg, path, value)
await redis.set(`miao:user-cfg:${this.id}`, JSON.stringify(userCfg))
}
/* 获取用户配置 */
async getCfg (path, defaultValue) {
let userCfg = await redis.get(`miao:user-cfg:${this.id}`) || await redis.get(`genshin:user-cfg:${this.id}`)
userCfg = userCfg ? JSON.parse(userCfg) : {}
return lodash.get(userCfg, path, defaultValue)
}
async getMysUser () {
return {
uid: this.uid
}
}
}
class Mys {
constructor (e, uid, MysApi) {
let ckUid = MysApi.ckInfo?.uid
this.selfUser = new User({ id: e.user_id, uid })
this.targetUser = this.selfUser
this.e = e
this.MysApi = MysApi
e.targetUser = this.targetUser
e.selfUser = this.selfUser
e.isSelfCookie = uid * 1 === ckUid * 1
}
async getData (api, data) {
if (!this.MysApi) {
return false
}
let e = this.e
// 暂时先在plugin侧阻止错误防止刷屏
e._original_reply = e._original_reply || e.reply
e._reqCount = e._reqCount || 0
e.reply = function (msg) {
if (!e._isReplyed) {
e._isReplyed = true
return e._original_reply(msg)
} else {
// console.log('请求错误')
}
}
e._reqCount++
let ret = await MysInfo.get(this.e, api, data)
e._reqCount--
if (e._reqCount === 0) {
e.reply = e._original_reply
}
if (!ret) {
return false
}
return ret.data || ret
}
// 获取角色信息
async getCharacter () {
return await this.getData('character')
}
// 获取角色详情
async getAvatar (id) {
return await this.getData('detail', { avatar_id: id })
}
// 首页宝箱信息
async getIndex () {
return await this.getData('index')
}
// 获取深渊信息
async getSpiralAbyss (type = 1) {
return await this.getData('spiralAbyss', { schedule_type: type })
}
async getDetail (id) {
return await this.getData('detail', { avatar_id: id })
}
async getCompute (data) {
return await this.getData('compute', data)
}
async getAvatarSkill (id) {
return await this.getData('avatarSkill', { avatar_id: id })
}
get isSelfCookie () {
return this.e.isSelfCookie
}
}
export async function getMysApi (e, cfg) {
let { auth = 'all' } = cfg
let MysApi = await MysInfo.init(e, 'roleIndex')
if (!MysApi) {
return false
}
let uid = MysApi.uid
let ckUid = MysApi.ckInfo?.uid
/* 检查user ck */
if (auth === 'cookie') {
let isCookieUser = await MysInfo.checkUidBing(uid)
if (!isCookieUser || uid !== ckUid) {
e.reply('尚未绑定Cookie...')
return false
}
}
return new Mys(e, uid, MysApi)
}
export async function checkAuth (e, cfg) {
let { auth = 'all' } = cfg
let uid = await MysInfo.getUid(e)
if (!uid) return false
if (auth === 'master' && !e.isMaster) {
return false
}
/* 检查user ck */
if (auth === 'cookie') {
let isCookieUser = await MysInfo.checkUidBing(uid)
if (!isCookieUser) {
e.reply('尚未绑定Cookie...')
return false
}
}
e.selfUser = new User({ id: e.user_id, uid })
return e.selfUser
}

View File

@ -51,12 +51,12 @@ export async function getTargetUid (e) {
}
try {
let user = await MysApi.initUser(e)
let mys = await MysApi.init(e)
if (!user || !user.uid) {
if (!mys || !mys.uid) {
return false
}
uid = user.uid
uid = mys.uid
if (!uid || !uidReg.test(uid)) {
e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
return false

View File

@ -388,7 +388,7 @@ async function uploadData (e) {
if (!Cfg.get('uploadAbyssData', false) && !isMatch) {
return false
}
let mys = await MysApi.init(e, { auth: 'all' })
let mys = await MysApi.init(e, 'all')
if (!mys || !mys.uid) {
if (isMatch) {
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)

View File

@ -1,5 +1,5 @@
import lodash from 'lodash'
import { plugin } from '../adapter/index.js'
import plugin from './common-lib/plugin.js'
class App {
constructor (cfg) {

View File

@ -96,10 +96,6 @@ let Data = {
return ret.default || {}
},
async import (name) {
return await Data.importModule(`components/optional-lib/${name}.js`)
},
async importCfg (key) {
let sysCfg = await Data.importModule(`config/system/${key}_system.js`)
let diyCfg = await Data.importModule(`config/${key}.js`)

View File

@ -1,3 +0,0 @@
import HttpsProxyAgent from 'https-proxy-agent'
export { HttpsProxyAgent }

View File

@ -14,7 +14,7 @@ export default new ProfileServ({
}
let proxy = this.getCfg('proxyAgent')
if (proxy) {
let { HttpsProxyAgent } = await Data.import('https-proxy-agent')
let HttpsProxyAgent = await import('https-proxy-agent')
params.agent = new HttpsProxyAgent(proxy)
}
return { api, params }

View File

@ -12,17 +12,12 @@ export default class MysApi {
e.isSelfCookie = this.isSelfCookie
}
static async init (e, cfg = 'all') {
static async init (e, auth = 'all') {
if (!e.runtime) {
Version.runtime()
return false
}
if (typeof (cfg) === 'string') {
cfg = { auth: cfg }
}
let { auth = 'all' } = cfg
let mys = false
mys = await e.runtime.getMysInfo(auth)
let mys = await e.runtime.getMysInfo(auth)
if (!mys) {
return false
}
@ -31,17 +26,22 @@ export default class MysApi {
return e._mys
}
static async initUser (e, cfg = 'all') {
static async initUser (e, auth = 'all') {
if (!e.runtime) {
Version.runtime()
return false
}
if (typeof (cfg) === 'string') {
cfg = { auth: cfg }
let uid = e.runtime.uid
if (e.at) {
// 暂时使用MysApi.init替代
let mys = await MysApi.init(e, auth)
if (!mys) {
return false
}
uid = mys.uid || uid
}
let uid = e.runtime?.uid
if (uid) {
return new User({ id: e.user_id, uid: uid })
return new User({ id: e.user_id, uid })
}
return false
}

View File

@ -1,6 +1,6 @@
/**
* 角色照片及角色图像资源相关
* */
* 角色照片及角色图像资源相关
* */
import fs from 'fs'
import lodash from 'lodash'
import sizeOf from 'image-size'
@ -78,10 +78,13 @@ const CharImg = {
add('face', 'imgs/face', `imgs/face${costumeCfg[0]}`)
add('side', 'imgs/side', `imgs/side${costumeCfg[0]}`)
add('gacha', 'imgs/gacha')
// 检查彩蛋自定义
if (costumeCfg[1] === 'super' && fs.existsSync(`${rPath}/profile/super-character/${name}.webp`)) {
imgs.splash = `profile/super-character/${name}.webp`
} else if (costumeCfg[1] === 'super' && fs.existsSync(`${rPath}/${nPath}/imgs/splash0.webp`)) {
imgs.splash = `${nPath}imgs/splash0.webp`
} else if (fs.existsSync(`${rPath}/profile/normal-character/${name}.webp`)) {
imgs.splash = `profile/normal-character/${name}.webp`
} else {
add('splash', 'imgs/splash', `imgs/splash${costumeCfg[0]}`)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB