Skip to content

Commit

Permalink
perf: 推送模式3 细化推送开关
Browse files Browse the repository at this point in the history
  • Loading branch information
XasYer committed Jan 6, 2025
1 parent f97bea6 commit 6ab1edc
Show file tree
Hide file tree
Showing 20 changed files with 627 additions and 454 deletions.
12 changes: 3 additions & 9 deletions apps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,13 @@ const apps = {}
for (const i of files) {
if (i === 'index.js') continue
try {
const startTime = Date.now()
const exp = await import(`file://${join(path, i)}`)
const id = i.replace('.js', '')
// const app = new App(exp.app || {
// id: i.replace('.js', ''),
// name: i.replace('.js', '')
// })
// for (const key in exp.rule) {
// const rule = exp.rule[key]
// app.rule(key, rule.reg, rule.fnc, rule.cfg)
// }
apps[id] = exp.app
logger.debug(`加载js: apps/${i}成功 耗时: ${Date.now() - startTime}ms`)
} catch (error) {
logger.error('error', `[${Version.pluginName}]加载js: apps/${i}错误\n`, error)
logger.error('error', `加载js: apps/${i}错误\n`, error)
}
}

Expand Down
4 changes: 4 additions & 0 deletions components/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ class Config {
* 获取推送配置
* @returns {{
* enable: boolean,
* playStart: boolean,
* playEnd: boolean,
* stateChange: boolean,
* stateOnline: boolean,
* stateOffline: boolean,
* pushApi: number,
* pushMode: number,
* time: number,
Expand Down
312 changes: 17 additions & 295 deletions components/Render.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
import fs from 'fs'
import _ from 'lodash'
import { join } from 'path'
import { canvas } from '#models'
import template from 'art-template'
import { execSync } from 'child_process'
import { logger, puppeteer, segment } from '#lib'
import { Version, Config } from '#components'
import { utils } from '#models'

const canvasPKG = await (async () => {
try {
const pkg = await import('@napi-rs/canvas')
const { GlobalFonts } = pkg
const fontPath = join(Version.pluginPath, 'resources', 'common', 'font', 'MiSans-Normal.ttf')
GlobalFonts.registerFromPath(fontPath, 'MiSans')
return pkg
} catch (error) {
return null
}
})()
import { logger, puppeteer, segment } from '#lib'

function scale (pct = 1) {
const scale = Math.min(2, Math.max(0.5, Config.other.renderScale / 100))
Expand Down Expand Up @@ -64,10 +52,14 @@ const Render = {
data.style = `<style>\n#container,.games{\nwidth: ${len * 370}px;\n}\n</style>`
// 暂时只支持inventory/index
if (Config.other.renderType == 2) {
if (!canvasPKG) {
throw new Error('请先pnpm i 安装依赖')
}
return renderCanvas(params.data, minLength)
return canvas.inventory.render(params.data, minLength)
}
} else if (path === 'game/game') {
params.data = params.data.map(i => _.sortBy(i.games, 'name')).flat()
if (Config.other.renderType == 2) {
return canvas.game.render(params.data)
} else {
return this.simpleRender(path, params)
}
}
const img = await puppeteer.screenshot(`${Version.pluginName}/${path}`, data)
Expand All @@ -85,11 +77,16 @@ const Render = {
saveId: path.split('/').pop(),
imgType: 'jpeg',
pageGotoParams: {
waitUntil: 'networkidle0' // +0.5s
waitUntil: 'load'
},
...params
}
return await puppeteer.screenshot(`${Version.pluginName}/${path}`, data)
const img = await puppeteer.screenshot(`${Version.pluginName}/${path}`, data)
if (img) {
return img
} else {
return '制作图片出错辣!再试一次吧'
}
},
/**
* 渲染gif 需要传入tempName参数 用于存放临时文件 建议使用唯一id 比如steamId
Expand Down Expand Up @@ -195,279 +192,4 @@ function tplFile (path, params, tempPath) {
return tplPath.replace(/\\/g, '/')
}

function drawBackgroundColor (ctx, color, x, y, width, height, radius) {
ctx.beginPath()
const backgroundX = x - 5
const backgroundY = y - 20
const backgroundWidth = width + 2
const backgroundHeight = height + 5
ctx.fillStyle = color
ctx.moveTo(backgroundX + radius, backgroundY)
ctx.arcTo(backgroundX + backgroundWidth, backgroundY, backgroundX + backgroundWidth, backgroundY + backgroundHeight, radius)
ctx.arcTo(backgroundX + backgroundWidth, backgroundY + backgroundHeight, backgroundX, backgroundY + backgroundHeight, radius)
ctx.arcTo(backgroundX, backgroundY + backgroundHeight, backgroundX, backgroundY, radius)
ctx.arcTo(backgroundX, backgroundY, backgroundX + backgroundWidth, backgroundY, radius)
ctx.closePath()
ctx.fill()
}

async function renderCanvas (data, minLength) {
const { createCanvas, loadImage } = canvasPKG
const start = Date.now()

// 每一项的宽高间距
const gameWidth = 468
const gameHeight = 93
const spacing = 10

// 每行显示的游戏数量
const lineItemCount = minLength

// 总行数
const lineTotal = _.sum(data.map(i => Math.ceil(i.games.length / lineItemCount)))
// 额外高度
const extraHeight = _.sumBy(data, i => i.desc.length * 30 + 50) + 30

// 创建画布
// 宽度为 (每项的宽+间距)*每行个数 + 左间距
const canvasWidth = (gameWidth + spacing) * lineItemCount + spacing
// 高度为 (每项的高+间距)*行数 + 额外高度
const canvasHeight = (gameHeight + spacing) * lineTotal + extraHeight

// 计算居中坐标
const centerX = canvasWidth / 2

const canvas = createCanvas(canvasWidth, canvasHeight)
const ctx = canvas.getContext('2d')

// 设置背景颜色
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, canvasWidth, canvasHeight)

// 设置字体和颜色
ctx.font = '20px MiSans'
ctx.fillStyle = '#000000'

// 异步加载图片
const imgs = await Promise.all(data.map(i => i.games).flat().map(async i => {
if (i.noImg) {
return {}
}
const Image = await loadImage(i.image || utils.steam.getHeaderImgUrlByAppid(i.appid)).catch(() => null)
return {
...i,
Image
}
})).then(imgs => imgs.reduce((acc, cur) => {
if (cur?.Image) {
acc[`${cur.name}${cur.appid}${cur.desc}`] = cur.Image
}
return acc
}, {}))

let startX = 0
let startY = 0

for (const g of data) {
startY += 40
// title
ctx.save()
ctx.font = 'bold 24px MiSans'
ctx.textAlign = 'center'
ctx.fillText(g.title, centerX, startY)
ctx.restore()

// desc
if (g.desc.length) {
for (const desc of g.desc) {
startY += 30
ctx.save()
ctx.font = 'bold 20px MiSans'
ctx.textAlign = 'center'
ctx.fillText(desc, centerX, startY)
ctx.restore()
}
}

let x = 10 - gameWidth + startX
let y = startY + 10
let index = 1

for (const items of _.chunk(g.games, lineItemCount)) {
const remainingItems = items.length

// 如果是最后一行且元素数量不足,则居中
if (remainingItems < lineItemCount) {
const totalWidth = gameWidth * remainingItems + spacing * (remainingItems - 1)
// 计算居中偏移量
x = (canvasWidth - totalWidth) / 2
} else {
x = 10
}

// 绘制当前行的元素
for (const i of items) {
ctx.save()

const nameY = y + 24
const appidY = y + 52
const descY = y + 79

let currentX = x

// 边框
ctx.strokeStyle = '#ccc'
ctx.lineWidth = 1
ctx.beginPath()
ctx.roundRect(currentX, y, gameWidth, gameHeight, 10)
ctx.stroke()

// 内边距10
currentX += 10

// 最大内容宽度 20内间距
let maxContentWidth = gameWidth - 20

// 图片
if (!i.noImg) {
const img = imgs[`${i.name}${i.appid}${i.desc}`]
const imgY = y + 10
const imgWidth = i.isAvatar ? 72 : 156
const imgHeight = 72
const radius = 10
if (img) {
ctx.save()

// 圆角矩形路径
ctx.beginPath()
ctx.moveTo(currentX + radius, imgY)
ctx.arcTo(currentX + imgWidth, imgY, currentX + imgWidth, imgY + imgHeight, radius)
ctx.arcTo(currentX + imgWidth, imgY + imgHeight, currentX, imgY + imgHeight, radius)
ctx.arcTo(currentX, imgY + imgHeight, currentX, imgY, radius)
ctx.arcTo(currentX, imgY, currentX + imgWidth, imgY, radius)
ctx.closePath()

// 设置裁剪区域
ctx.clip()

// 绘制图片
ctx.drawImage(img, currentX, imgY, imgWidth, imgHeight)

// 恢复绘图状态
ctx.restore()
}
// 图片右边距5
currentX += imgWidth + 5
maxContentWidth -= (imgWidth + 5)
}

// 价格
if (i.price) {
const priceXOffset = 385 + x
const maxPriceWidth = 70
maxContentWidth -= maxPriceWidth
ctx.font = '20px MiSans'
if (i.price.discount) {
// 打折的话原价格加删除线
const originalWidth = ctx.measureText(i.price.original).width
ctx.fillStyle = '#999'
ctx.fillText(i.price.original, priceXOffset, nameY)
// 删除线
ctx.strokeStyle = '#999'
// 删除线宽度
ctx.lineWidth = 1
ctx.beginPath()
const lineOffset = -7
ctx.moveTo(priceXOffset, nameY + lineOffset)
ctx.lineTo(priceXOffset + originalWidth, nameY + lineOffset)
ctx.stroke()

// 折扣率的背景色
drawBackgroundColor(ctx, '#beee11', priceXOffset, appidY, maxPriceWidth, 20, 10)

// 折扣率
ctx.fillStyle = '#333'
ctx.fillText(`-${i.price.discount}%`, priceXOffset, appidY)
ctx.font = 'blob 20px MiSans'
ctx.fillText(i.price.current, priceXOffset, descY)
} else {
ctx.fillText(i.price.original, priceXOffset, nameY)
}
}

// name
ctx.font = 'bold 20px MiSans'
while (ctx.measureText(i.name).width > maxContentWidth) {
i.name = i.name.slice(0, -1)
}
ctx.fillText(i.name, currentX, nameY)

// appid
ctx.font = '20px MiSans'
i.appid = i.appid ? String(i.appid) : ''
while (ctx.measureText(i.appid).width > maxContentWidth) {
i.appid = i.appid.slice(0, -1)
}
if (i.appidPercent) {
ctx.fillStyle = '#999999'
ctx.fillRect(currentX, appidY - 18, maxContentWidth * (i.appidPercent / 100), 20)
}

ctx.fillStyle = '#666'
ctx.fillText(String(i.appid), currentX, appidY)

// desc
i.desc = i.desc || ''
while (ctx.measureText(i.desc).width > maxContentWidth) {
i.desc = i.desc.slice(0, -1)
}

if (i.descBgColor) {
drawBackgroundColor(ctx, i.descBgColor, currentX, descY, ctx.measureText(i.desc).width + 10, 20, 10)
ctx.fillStyle = '#ffffff'
} else {
ctx.fillStyle = '#999'
}

ctx.fillText(i.desc, currentX, descY)

// 序号
ctx.font = '12px MiSans'
const indexText = `No. ${index}`
const indexWidth = ctx.measureText(indexText).width
drawBackgroundColor(ctx, '#ffffff', x + 30, y + 10, indexWidth + 8, 11, 0)
ctx.fillStyle = 'black'
ctx.fillText(indexText, x + 30, y + 5)
index++

ctx.restore()

// 更新 x 坐标
x += gameWidth + spacing
}

// 更新 y 坐标
y += gameHeight + spacing
}
// 减去最后一行的间距
y -= spacing
startX = x
startY = y
}

// 底部文字
ctx.font = 'bold 20px MiSans'
ctx.textAlign = 'center'
ctx.fillText(`Created By ${Version.BotName} v${Version.BotVersion} & ${Version.pluginName} v${Version.pluginVersion}`, centerX, startY + 30)

const buffer = canvas.toBuffer('image/jpeg')
const end = Date.now()
logger.info(`[图片生成][inventory/index] ${(buffer.length / 1024).toFixed(2)}KB ${end - start}ms`)
if (Version.BotName === 'Karin') {
return segment.image(`base64://${buffer.toString('base64')}`)
} else {
return segment.image(buffer)
}
}

export default Render
Loading

0 comments on commit 6ab1edc

Please sign in to comment.