diff --git a/.gitignore b/.gitignore index 14a43878..de7c53ad 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /components/setting.json /config.js *.css.map +/resources/character-img/*/upload/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c87e60d..7de14427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.8.1 + +* 增加`#添加刻晴图像`命令,感谢 **@叶** + * 可通过命令上传添加指定角色图片,上传至 **resources/character-img/刻晴/upload** + * 请将图像与命令一同发送,后续会支持at图像及命令后发送图像 +* 部分功能的页面文案及功能优化 + # 1.8.0 * `#角色面板`、`#圣遗物列表` 使用新的圣遗物评分逻辑计算评分 @@ -22,7 +29,6 @@ * 角色图像增加小清新开关,默认关闭 * 对增量包内的角色图像进行分级,较为清凉的图像独立管理 * 勇士们可使用 `#喵喵设置小清新开启` 启用 - * 自行添加请到 **resources/character-img/** 的对应角色目录下添加 * 伤害计算增加扩散、感电的计算逻辑,感谢 **@49631073**的逻辑梳理 * `#角色面板` 伤害计算增加部分角色,目前支持 * 长柄武器:雷神、胡桃、魈、钟离、香菱 diff --git a/apps/character.js b/apps/character.js index 3aa83a83..e3e737ff 100644 --- a/apps/character.js +++ b/apps/character.js @@ -8,6 +8,9 @@ import { renderProfile } from "./character/profile-detail.js"; export { enemyLv, getOriginalPicture } from "./character/utils.js"; +// 角色图像上传 +export { uploadCharacterImg } from "./character/character-img-upload.js"; + // export { getProfileAll, getProfile, profileHelp }; diff --git a/apps/uploadCharacterImage.js b/apps/character/character-img-upload.js similarity index 57% rename from apps/uploadCharacterImage.js rename to apps/character/character-img-upload.js index ddef57ab..9ef2c5bb 100644 --- a/apps/uploadCharacterImage.js +++ b/apps/character/character-img-upload.js @@ -1,15 +1,16 @@ -import { segment } from "oicq"; -import fetch from "node-fetch"; import fs from "fs"; -import Data from "../components/Data.js"; -import { Character } from "../components/models.js"; -import lodash from "lodash"; import { promisify } from "util"; import { pipeline } from "stream"; +import { segment } from "oicq"; +import MD5 from "md5"; +import fetch from "node-fetch"; +import lodash from "lodash"; +import Data from "../../components/Data.js"; +import { Character } from "../../components/models.js"; -const rootPath = process.cwd() + "/plugins/miao-plugin/"; -let regex = /^#*喵喵(上传|添加)(.+)写真.*$/; +const _res_path = process.cwd() + "/plugins/miao-plugin/resources/"; +let regex = /^#?\s*(?:喵喵)?(?:上传|添加)(.+)(?:照片|写真|图片|图像)\s*$/; export const rule = { uploadCharacterImage: { @@ -19,47 +20,48 @@ export const rule = { }, }; -export async function uploadCharacterImage(e) { +export async function uploadCharacterImg(e) { let promise = await isAllowedToUploadCharacterImage(e); if (!promise) { - return true; + return; } let imageMessages = []; let msg = e.msg; - let regexResult = regex.exec(msg); + let regRet = regex.exec(msg); //通过解析正则获取消息中的角色名 - let characterName = regexResult[2]; - //将消息中的角色名转换为官方名称 - let officialName = Character.get(characterName).name; - if (officialName === undefined) { - e.reply("未查询到该角色。请输入有效的角色名或别名。"); - return true; + if (!regRet || !regRet[1]) { + return; } - console.log("本次要上传的角色是: ", officialName); + let char = Character.get(regRet[1]); + if (!char || !char.name) { + return; + } + let name = char.name; for (let val of e.message) { if ("image" === val.type) { imageMessages.push(val); } } if (imageMessages.length <= 0) { - e.reply("消息中未找到图片,无法添加。"); + // TODO 支持at图片添加,以及支持后发送 + e.reply("消息中未找到图片,请将要发送的图片与消息一同发送.."); return true; } - await saveImages(e, officialName, imageMessages); + await saveImages(e, name, imageMessages); return true; } -async function saveImages(e, officialName, imageMessages) { +async function saveImages(e, name, imageMessages) { let imgMaxSize = e.groupConfig.imgMaxSize || 1; - let pathSuffix = "resources/miao-res-plus/character-img/" + officialName; - let path = rootPath + pathSuffix; + let pathSuffix = `character-img/${name}/upload`; + let path = _res_path + pathSuffix; if (!fs.existsSync(path)) { - console.log("路径不存在,创建目录: ", path); - Data.createDir(rootPath, pathSuffix); + Data.createDir(_res_path, pathSuffix); } let senderName = lodash.truncate(e.sender.card, { length: 8 }); + let imgCount = 0; for (let val of imageMessages) { const response = await fetch(val.url); if (!response.ok) { @@ -75,12 +77,27 @@ async function saveImages(e, officialName, imageMessages) { if (response.headers.get("content-type") === "image/gif") { fileType = "gif"; } - + let imgPath = `${path}/${fileName}.${fileType}`; const streamPipeline = promisify(pipeline); - await streamPipeline(response.body, fs.createWriteStream(`${path}/${fileName}.${fileType}`)); + await streamPipeline(response.body, fs.createWriteStream(imgPath)); + + // 使用md5作为文件名 + let buffers = fs.readFileSync(imgPath); + let base64 = new Buffer.from(buffers, 'base64').toString(); + let md5 = MD5(base64); + let newImgPath = `${path}/${md5}.${fileType}` + if (fs.existsSync(newImgPath)) { + fs.unlink(newImgPath, (err) => { + console.log('unlink', err); + }); + } + fs.rename(imgPath, newImgPath, (err) => { + console.log('rename', err); + }) + imgCount++; Bot.logger.mark(`添加成功: ${path}/${fileName}`); } - e.reply([segment.at(e.user_id, senderName), `\n添加${officialName}信息成功。`]); + e.reply([segment.at(e.user_id, senderName), `\n成功添加${imgCount}张${name}图片。`]); return true; } @@ -91,6 +108,11 @@ async function isAllowedToUploadCharacterImage(e) { if (!e.msg) { return false; } + if (!e.isMaster) { + return false; + } + + // 由于添加角色图是全局,暂时屏蔽非管理员的添加 if (e.isPrivate) { if (!e.isMaster) { e.reply(`只有主人才能添加。`); diff --git a/apps/character/profile-artis.js b/apps/character/profile-artis.js index 77422849..ab0991d6 100644 --- a/apps/character/profile-artis.js +++ b/apps/character/profile-artis.js @@ -4,7 +4,7 @@ * */ import lodash from "lodash"; import { Profile, Common, Models, Format } from "../../components/index.js"; -import { getTargetUid, profileHelp } from "./profile-common.js"; +import { autoRefresh, getTargetUid, profileHelp, autoGetProfile } from "./profile-common.js"; import { Character, Artifact } from "../../components/models.js"; /* @@ -13,11 +13,22 @@ import { Character, Artifact } from "../../components/models.js"; export async function profileArtis(e, { render }) { let { uid, avatar } = e; - let profile = await Profile.get(uid, avatar); - let char = Character.get(profile.name); + let { profile, char, err } = await autoGetProfile(e, uid, avatar, async () => { + await profileArtis(e, { render }); + }); + + if (err) { + return; + } let charCfg = Artifact.getCharCfg(profile.name); let { artis, totalMark, totalMarkClass, usefulMark } = getArtis(profile.name, profile.artis); + + if (!profile.artis || profile.artis.length === 0) { + e.reply("未能获得圣遗物详情,请重新获取面板信息后查看") + return true; + } + let { attrMap } = Artifact.getMeta(); //渲染图像 @@ -52,7 +63,6 @@ export async function profileArtisList(e, { render }) { return true; } - lodash.forEach(profiles || [], (ds) => { let name = ds.name; if (!name || name === "空" || name === "荧") { diff --git a/apps/character/profile-common.js b/apps/character/profile-common.js index 192ea42d..94f6d090 100644 --- a/apps/character/profile-common.js +++ b/apps/character/profile-common.js @@ -95,7 +95,7 @@ export async function autoRefresh(e) { } if (!data.chars) { - e.reply("请确认角色已在【游戏内】橱窗展示并开放了查看详情。设置完毕后请5分钟后使用 #面板更新 重新获取"); + e.reply("请确认角色已在【游戏内】橱窗展示并开放了查看详情。请在设置完毕5分钟后使用 #面板更新 重新获取"); return false; } else { let ret = []; @@ -106,7 +106,7 @@ export async function autoRefresh(e) { } }) if (ret.length === 0) { - e.reply("请确认角色已在【游戏内】橱窗展示并开放了查看详情。设置完毕后请5分钟后使用 #面板更新 重新获取") + e.reply("请确认角色已在【游戏内】橱窗展示并开放了查看详情。请在设置完毕5分钟后使用 #面板更新 重新获取") return false; } else { // e.reply(`本次获取成功角色: ${ret.join(", ")} `) @@ -116,6 +116,40 @@ export async function autoRefresh(e) { return true; } +export async function autoGetProfile(e, uid, avatar, callback) { + + let refresh = async () => { + let refreshRet = await autoRefresh(e); + if (refreshRet) { + await callback(); + } + return refreshRet; + } + + let char = Character.get(avatar); + if (!char) { + return { err: true }; + } + + let profile = await Profile.get(uid, char.id); + if (!profile) { + if (await refresh()) { + return { err: true }; + } else { + e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`); + } + return { err: true }; + } else if (!['enka', 'input2', 'miao', 'miao-pre'].includes(profile.dataSource)) { + if (!await refresh()) { + e.reply(`由于数据格式升级,请重新获取面板信息后查看`); + } + return { err: true }; + } + + return { profile, char, refresh } + +} + /* * 面板数据更新 * */ diff --git a/apps/wiki.js b/apps/wiki.js index b48940ca..b29b4dd8 100644 --- a/apps/wiki.js +++ b/apps/wiki.js @@ -19,7 +19,7 @@ export async function wiki(e, { render }) { return false; } - let reg = /#?(.+)(命座|命之座|天赋|技能|资料|照片|写真|图片|插画)$/, msg = e.msg; + let reg = /#?(.+)(命座|命之座|天赋|技能|资料|照片|写真|图片|图像)$/, msg = e.msg; let ret = reg.exec(msg); if (!ret || !ret[1] || !ret[2]) { diff --git a/components/Profile.js b/components/Profile.js index 0119a20a..cb98f01d 100644 --- a/components/Profile.js +++ b/components/Profile.js @@ -28,6 +28,9 @@ function sleep(ms) { } function getServ(uid) { + if (config.profileApi) { + return config.profileApi({ Enka, Miao }) + } if ((uid + '')[0] === '5') { return Miao; } diff --git a/components/models/Character.js b/components/models/Character.js index e55fd906..228868b0 100644 --- a/components/models/Character.js +++ b/components/models/Character.js @@ -54,6 +54,7 @@ class Character extends Base { }); } addImg(`character-img/${name}`); + addImg(`character-img/${name}/upload`); addImg(`character-img/${name}/se`, !se) const plusPath = `./plugins/miao-plugin/resources/miao-res-plus/`; diff --git a/components/profile-data/enka.js b/components/profile-data/enka.js index fd923fd3..ab3e31a6 100644 --- a/components/profile-data/enka.js +++ b/components/profile-data/enka.js @@ -4,26 +4,21 @@ import Data from "./enka-data.js"; let Enka = { key: "enka", cd: 5, - async request({ e, uid, config }) { - let profileApi = config.profileApi || function (uid) { + async request({ e, uid, avatar, config }) { + let profileApi = config.enkaApi || function ({ uid }) { return `https://enka.shinshin.moe/u/${uid}/__data.json` }; - let api = profileApi(uid); + let api = profileApi({ uid, avatar }); let req = await fetch(api); let data = await req.json(); if (!data.playerInfo) { - if ((uid + '')[0] === '5') { - e.reply(`请求失败:暂时不支持B服角色面板更新,请等待服务后续升级`); - } else { - e.reply(`请求失败:${data.msg || "请求错误,请稍后重试"}`); - } + e.reply(`请求失败:${data.msg || "可能是面板服务并发过高,请稍后重试"}`); return false; } - let details = data.avatarInfoList; if (!details || details.length === 0 || !details[0].propMap) { - e.reply(`请打开角色展柜的显示详情`); + e.reply(`请打开游戏内角色展柜的“显示详情”后,等待5分钟重新获取面板`); return false; } return Data.getData(uid, data); diff --git a/components/profile-data/miao.js b/components/profile-data/miao.js index 2219e75e..f3af90c4 100644 --- a/components/profile-data/miao.js +++ b/components/profile-data/miao.js @@ -10,10 +10,12 @@ const url = "http://49.232.91.210/profile"; let Miao = { key: "miao", cd: 1, - async request({ e, uid, avatar = '' }) { - let api = `${url}/list?uid=${uid}`; + async request({ e, uid, avatar = '', config }) { + let profileApi = config.miaoApi && lodash.isFunction(config.miaoApi) ? config.miaoApi : function ({ uid }) { + return `http://49.232.91.210/profile/list?uid=${uid}` + }; + let api = profileApi({ uid, avatar }); let data; - let req = await fetch(api); data = await req.json(); if (data.status !== 0) { @@ -21,7 +23,7 @@ let Miao = { return false; } if (!data.uidListData || data.uidListData.length === 0) { - e.reply(`请打开角色展柜的显示详情`); + e.reply(`请打开游戏内角色展柜的“显示详情”后,等待5分钟重新获取面板`); return false; } diff --git a/index.js b/index.js index 3574fbb8..145e338a 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,10 @@ export { profileArtisList, getProfileAll, profileHelp, - getOriginalPicture + getOriginalPicture, + uploadCharacterImg } from "./apps/character.js"; + import { wifeReg } from "./apps/character.js"; import { consStat, abyssPct, abyssTeam } from "./apps/stat.js"; @@ -17,9 +19,7 @@ import lodash from "lodash"; import common from "../../lib/common.js"; import { rule as adminRule, updateRes, sysCfg, updateMiaoPlugin, profileCfg } from "./apps/admin.js"; import { currentVersion } from "./components/Changelog.js"; -import { - uploadCharacterImage -} from "./apps/uploadCharacterImage.js"; + export { consStat, @@ -32,8 +32,7 @@ export { help, versionInfo, calendar, - profileCfg, - uploadCharacterImage + profileCfg }; @@ -43,6 +42,10 @@ let rule = { //reg: "noCheck", describe: "【#角色】角色详情", }, + uploadCharacterImg: { + reg: "^#*(喵喵)?(上传|添加)(.+)(照片|写真|图片|图像)\\s*$", + describe: "喵喵上传角色写真", + }, profileArtisList: { reg: "^#圣遗物列表\\s*(\\d{9})?$", describe: "【#角色】圣遗物列表", @@ -63,10 +66,6 @@ let rule = { reg: "^#?(获取|给我|我要|求|发|发下|发个|发一下)?原图(吧|呗)?$", describe: "【#原图】 回复角色卡片,可获取原图", }, - uploadCharacterImage: { - reg: "^#*喵喵(上传|添加)(.+)写真.*$", - describe: "喵喵上传角色写真", - }, consStat: { reg: "^#(喵喵)?角色(持有|持有率|命座|命之座|.命)(分布|统计|持有|持有率)?$", describe: "【#统计】 #角色持有率 #角色5命统计", @@ -80,7 +79,7 @@ let rule = { describe: "【#角色】 #深渊组队", }, wiki: { - reg: "^(#|喵喵)?.*(天赋|技能|命座|命之座|资料|照片|写真|图片|插画)$", + reg: "^(#|喵喵)?.*(天赋|技能|命座|命之座|资料|照片|写真|图片|图像)$", describe: "【#资料】 #神里天赋 #夜兰命座", }, help: { @@ -96,11 +95,11 @@ let rule = { describe: "【#角色】 设置伤害计算中目标敌人的等级", }, versionInfo: { - reg: "^#喵喵版本$", + reg: "^#?喵喵版本$", describe: "【#帮助】 喵喵版本介绍", }, calendar: { - reg: "^#喵喵(日历|活动|日历列表)$", + reg: "^#?喵喵(日历|活动|日历列表)$", describe: "【#日历】 活动日历", }, ...adminRule diff --git a/resources/character/artis-mark.html b/resources/character/artis-mark.html index 4453e367..efbe1f64 100644 --- a/resources/character/artis-mark.html +++ b/resources/character/artis-mark.html @@ -107,7 +107,7 @@
副词条最高分
主词条最高分
- {{each attrMap ds key}}{{if ds.type!== "plus"}} + {{each attrMap ds key}}{{if ds.type!== "plus" && charCfg.weight[key] > 0}}
{{ds.title}}
{{charCfg.weight[key]}}
@@ -125,10 +125,10 @@ {{/if}}{{/each}}
@@ -147,15 +147,7 @@
理之冠
-
最优主词条权重值
-
-
-
-
-
{{charCfg.maxMark.m3}}
-
{{charCfg.maxMark.m4}}
-
{{charCfg.maxMark.m5}}
-
-
-
理论最高原始总分
+
最高分(对齐前)
{{each charCfg.maxMark m key}} {{if key.length === 1}}
{{ mark( m / 6 )}}
@@ -163,13 +155,21 @@ {{/each}}
-
理论总分对齐比例
+
总分对齐比例
{{each charCfg.maxMark m key}} {{if key.length === 1}}
{{( 66 / (46.6/6/100 * m) * 100).toFixed(1)}}%
{{/if}} {{/each}}
+
+
最优主词缀权重
+
-
+
-
+
{{charCfg.maxMark.m3}}
+
{{charCfg.maxMark.m4}}
+
{{charCfg.maxMark.m5}}
+