koishi-plugin-p-draw 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +61 -48
- package/lib/media.js +22 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -150,6 +150,7 @@ const {
|
|
|
150
150
|
animaOotdI2IWorkflow, buildI2IWorkflow, customWorkflow,
|
|
151
151
|
} = require('./lib/workflows')
|
|
152
152
|
const { outputImages, waitComfyResult } = require('./lib/comfy')
|
|
153
|
+
const { materializeImageSource } = require('./lib/media')
|
|
153
154
|
const {
|
|
154
155
|
MULTI_PERSON_NEGATIVE_TAGS, buildMultiPersonPlanPrompt, parseMultiPersonPlan,
|
|
155
156
|
renderMultiPersonCharacter, multiPersonAutoSize,
|
|
@@ -460,40 +461,46 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
460
461
|
return { ok: true, tasks, firstPosition }
|
|
461
462
|
}
|
|
462
463
|
|
|
463
|
-
//
|
|
464
|
-
function feedbackBase(session, { firstPosition, count, totalPrice, isAdmin }) {
|
|
465
|
-
const
|
|
466
|
-
if (cfg.queueEnabled) {
|
|
467
|
-
|
|
468
|
-
if (count > 1)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
464
|
+
// 即时反馈的公共部分。扣费提醒单独返回,随后以引用消息发送。
|
|
465
|
+
function feedbackBase(session, { firstPosition, count, totalPrice, isAdmin }) {
|
|
466
|
+
const notices = []
|
|
467
|
+
if (cfg.queueEnabled) {
|
|
468
|
+
notices.push(session.text('.queued', [firstPosition, cfg.queueMaxRequests || '∞']))
|
|
469
|
+
if (count > 1) notices.push(session.text('.batch-count', [count]))
|
|
470
|
+
} else {
|
|
471
|
+
notices.push(session.text('.generating'))
|
|
472
|
+
if (count > 1) notices.push(session.text('.batch-count', [count]))
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
notices,
|
|
476
|
+
chargeNotice: isAdmin ? '' : session.text('.charged', [totalPrice]),
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function sendNotices(session, notices, opts = {}) {
|
|
481
|
+
const content = notices.filter(Boolean).join('\n')
|
|
482
|
+
if (!content) return
|
|
483
|
+
try {
|
|
484
|
+
const message = opts.quote && session.messageId ? h.quote(session.messageId) + content : content
|
|
485
|
+
await session.send(message)
|
|
486
|
+
} catch (e) {
|
|
487
|
+
logger.warn(`发送反馈消息失败:${e.message}`)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// 生成图片使用合并转发;不附原消息引用。发送失败时回退普通图片消息。
|
|
492
|
+
async function sendImagesAsForward(session, outputs) {
|
|
493
|
+
const imageElements = await Promise.all(outputs.map(async (src) => {
|
|
494
|
+
const materialized = await materializeImageSource(src)
|
|
495
|
+
return Buffer.isBuffer(materialized) ? h.image(materialized) : h.image(src)
|
|
496
|
+
}))
|
|
497
|
+
try {
|
|
498
|
+
await session.send(h('figure', imageElements))
|
|
499
|
+
} catch (e) {
|
|
500
|
+
logger.warn(`发送转发图片失败:${e.message}`)
|
|
501
|
+
await session.send(imageElements)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
497
504
|
|
|
498
505
|
// P 点读改写按用户串行化,避免并发指令互相覆盖余额
|
|
499
506
|
const userLocks = new Map()
|
|
@@ -1445,8 +1452,10 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
1445
1452
|
// 即时反馈
|
|
1446
1453
|
const notice = []
|
|
1447
1454
|
if (parsedBatch.clamped) notice.push(session.text('.batch-limit', [count]))
|
|
1448
|
-
|
|
1449
|
-
|
|
1455
|
+
const feedback = feedbackBase(session, { firstPosition, count, totalPrice: count * price, isAdmin })
|
|
1456
|
+
notice.push(...feedback.notices)
|
|
1457
|
+
await sendNotices(session, notice)
|
|
1458
|
+
await sendNotices(session, [feedback.chargeNotice], { quote: true })
|
|
1450
1459
|
|
|
1451
1460
|
// 单张生成 +(可选)视觉校验
|
|
1452
1461
|
const runOne = async (i) => {
|
|
@@ -1489,8 +1498,8 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
1489
1498
|
|
|
1490
1499
|
if (cfg.outputLogs) logger.success(`${USERID} 多人生成成功 ${successCount}/${count} 张`)
|
|
1491
1500
|
|
|
1492
|
-
//
|
|
1493
|
-
await
|
|
1501
|
+
// 发图:合并转发,不引用原指令
|
|
1502
|
+
await sendImagesAsForward(session, allOutputs)
|
|
1494
1503
|
|
|
1495
1504
|
const reply = []
|
|
1496
1505
|
if (count > 1) {
|
|
@@ -1621,8 +1630,10 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
1621
1630
|
if (identity && fixedChars[identity]) notice.push(`已固定角色「${identity}」的身份 tags,各阶段外观将保持一致。`)
|
|
1622
1631
|
if (!useLLM) notice.push(session.text('.series-no-llm'))
|
|
1623
1632
|
if (degradedStages) notice.push(session.text('.prompt-degraded', ['(连续图阶段优化失败,已使用原始描述)']))
|
|
1624
|
-
|
|
1625
|
-
|
|
1633
|
+
const feedback = feedbackBase(session, { firstPosition, count, totalPrice: count * price, isAdmin })
|
|
1634
|
+
notice.push(...feedback.notices)
|
|
1635
|
+
await sendNotices(session, notice)
|
|
1636
|
+
await sendNotices(session, [feedback.chargeNotice], { quote: true })
|
|
1626
1637
|
|
|
1627
1638
|
// 单阶段生成(共用 seed)
|
|
1628
1639
|
const runOne = async (i) => {
|
|
@@ -1657,8 +1668,8 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
1657
1668
|
|
|
1658
1669
|
if (cfg.outputLogs) logger.success(`${USERID} 连续图生成成功 ${successCount}/${count} 阶段(seed=${seed})`)
|
|
1659
1670
|
|
|
1660
|
-
//
|
|
1661
|
-
await
|
|
1671
|
+
// 发图:合并转发,不引用原指令
|
|
1672
|
+
await sendImagesAsForward(session, allOutputs)
|
|
1662
1673
|
|
|
1663
1674
|
const reply = []
|
|
1664
1675
|
reply.push(session.text('.series-ok', [count * price, successCount, seed]))
|
|
@@ -2326,8 +2337,10 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
2326
2337
|
notice.push(session.text('.no-optimize', [reasons[noOptimizeReason] || noOptimizeReason]))
|
|
2327
2338
|
}
|
|
2328
2339
|
if (parsedBatch.clamped) notice.push(session.text('.batch-limit', [count]))
|
|
2329
|
-
|
|
2330
|
-
|
|
2340
|
+
const feedback = feedbackBase(session, { firstPosition, count, totalPrice: count * cfg.price, isAdmin })
|
|
2341
|
+
notice.push(...feedback.notices)
|
|
2342
|
+
await sendNotices(session, notice)
|
|
2343
|
+
await sendNotices(session, [feedback.chargeNotice], { quote: true })
|
|
2331
2344
|
|
|
2332
2345
|
// 单张生成
|
|
2333
2346
|
const runOne = async (i) => {
|
|
@@ -2367,8 +2380,8 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
2367
2380
|
|
|
2368
2381
|
if (cfg.outputLogs) logger.success(`${USERID} 生成成功 ${successCount}/${count} 张`)
|
|
2369
2382
|
|
|
2370
|
-
//
|
|
2371
|
-
await
|
|
2383
|
+
// 发图:合并转发,不引用原指令
|
|
2384
|
+
await sendImagesAsForward(session, allOutputs)
|
|
2372
2385
|
|
|
2373
2386
|
const reply = []
|
|
2374
2387
|
if (count > 1) {
|
|
@@ -2521,5 +2534,5 @@ exports.apply = async function apply(ctx, cfg) {
|
|
|
2521
2534
|
})
|
|
2522
2535
|
|
|
2523
2536
|
// 暴露内部接口供自动化测试调用(Koishi 忽略 apply 返回值,不影响生产行为)
|
|
2524
|
-
return { couponConfirmFlow, buyCouponsAndConsume, normalizeConfirm, resolveCouponPrice, handleGenerateI2I, extractImageFromSession, uploadImageToComfyui, taggerImage, animaI2IWorkflow, animaStyleI2IWorkflow, animaOotdI2IWorkflow, buildI2IWorkflow, detectI2ICapabilities, parseDenoise }
|
|
2525
|
-
}
|
|
2537
|
+
return { couponConfirmFlow, buyCouponsAndConsume, normalizeConfirm, resolveCouponPrice, handleGenerateI2I, extractImageFromSession, uploadImageToComfyui, taggerImage, animaI2IWorkflow, animaStyleI2IWorkflow, animaOotdI2IWorkflow, buildI2IWorkflow, detectI2ICapabilities, parseDenoise, sendNotices, sendImagesAsForward }
|
|
2538
|
+
}
|
package/lib/media.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// 消息媒体来源处理。
|
|
2
|
+
// OneBot 不一定能读取机器人进程本地的 file:// 路径,因此发送前转换为 Buffer。
|
|
3
|
+
|
|
4
|
+
const fsp = require('fs/promises')
|
|
5
|
+
const { fileURLToPath } = require('url')
|
|
6
|
+
|
|
7
|
+
async function materializeImageSource(source, readFile = fsp.readFile) {
|
|
8
|
+
if (typeof source !== 'string' || !/^file:\/\//i.test(source)) return source
|
|
9
|
+
let filePath
|
|
10
|
+
try {
|
|
11
|
+
filePath = fileURLToPath(source)
|
|
12
|
+
} catch (e) {
|
|
13
|
+
return source
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
return await readFile(filePath)
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return source
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { materializeImageSource }
|