mirror of
https://github.com/yoimiya-kokomi/miao-plugin.git
synced 2024-11-21 14:38:30 +00:00
角色普通立绘支持自定义,一些已知问题修复
This commit is contained in:
parent
9f75804405
commit
fa8fd9e9ec
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,4 +16,5 @@
|
||||
/config/help.js
|
||||
/config/cfg.js
|
||||
/resources/profile/super-character/*
|
||||
/resources/profile/normal-character/*
|
||||
!/resources/profile/super-character/达达利亚.webp
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,3 +1,13 @@
|
||||
# 2.1.2
|
||||
|
||||
* 角色普通立绘支持自定义,用于面板、图鉴等场景
|
||||
* 立绘可放置在 **resources/profile/normal-character/** 目录下,webp格式
|
||||
* 去除插件内自带的V2/V3兼容逻辑,使用runtime进行V2/V3兼容,如使用遇到问题请升级至最新版Yunzai
|
||||
* V3-Yunzai:官方Yunzai最新版本
|
||||
* V2-Yunzai:喵版V2-Yunzai,2.2.3版本。其余分值维护的V2-Yunzai可合并2.2.3版本
|
||||
* 较低版本的Yuznai可能无法正常使用miao-plugin
|
||||
* 一些已知问题修复
|
||||
|
||||
# 2.1.1
|
||||
|
||||
* 部分底层结构升级
|
||||
|
@ -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
|
||||
}
|
@ -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参数,生成图片类型:jpeg,png
|
||||
* @param data.quality screenshot参数,图片质量 0-100,jpeg是可传,默认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()
|
150
adapter/mys.js
150
adapter/mys.js
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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}`)
|
||||
|
@ -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) {
|
||||
|
@ -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`)
|
||||
|
@ -1,3 +0,0 @@
|
||||
import HttpsProxyAgent from 'https-proxy-agent'
|
||||
|
||||
export { HttpsProxyAgent }
|
@ -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 }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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]}`)
|
||||
}
|
||||
|
BIN
resources/profile/normal-character/埃洛伊.webp
Normal file
BIN
resources/profile/normal-character/埃洛伊.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 276 KiB |
Loading…
Reference in New Issue
Block a user