upload miao-yunzai (#3)
Browse files- upload miao-yunzai (3bab65132a3410cf9337180811e8927cd9b3a28f)
Co-authored-by: Erzaozi <[email protected]>
This view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +5 -0
- Yunzai/plugins/miao-plugin/.gitignore +20 -0
- Yunzai/plugins/miao-plugin/CHANGELOG.md +377 -0
- Yunzai/plugins/miao-plugin/LICENSE +21 -0
- Yunzai/plugins/miao-plugin/README.md +106 -0
- Yunzai/plugins/miao-plugin/apps/admin.js +204 -0
- Yunzai/plugins/miao-plugin/apps/character.js +36 -0
- Yunzai/plugins/miao-plugin/apps/character/AvatarCard.js +132 -0
- Yunzai/plugins/miao-plugin/apps/character/AvatarWife.js +188 -0
- Yunzai/plugins/miao-plugin/apps/character/ImgUpload.js +258 -0
- Yunzai/plugins/miao-plugin/apps/gacha.js +26 -0
- Yunzai/plugins/miao-plugin/apps/gacha/Gacha.js +90 -0
- Yunzai/plugins/miao-plugin/apps/gacha/GachaData.js +444 -0
- Yunzai/plugins/miao-plugin/apps/help.js +23 -0
- Yunzai/plugins/miao-plugin/apps/help/Help.js +77 -0
- Yunzai/plugins/miao-plugin/apps/help/HelpTheme.js +68 -0
- Yunzai/plugins/miao-plugin/apps/index.js +16 -0
- Yunzai/plugins/miao-plugin/apps/poke.js +17 -0
- Yunzai/plugins/miao-plugin/apps/profile.js +143 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileArtis.js +96 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileChange.js +299 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileCommon.js +77 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileDetail.js +299 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileList.js +184 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileRank.js +280 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileStat.js +73 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileUtils.js +78 -0
- Yunzai/plugins/miao-plugin/apps/profile/ProfileWeapon.js +36 -0
- Yunzai/plugins/miao-plugin/apps/stat.js +37 -0
- Yunzai/plugins/miao-plugin/apps/stat/AbyssStat.js +164 -0
- Yunzai/plugins/miao-plugin/apps/stat/AbyssSummary.js +137 -0
- Yunzai/plugins/miao-plugin/apps/stat/AbyssTeam.js +190 -0
- Yunzai/plugins/miao-plugin/apps/stat/HutaoApi.js +73 -0
- Yunzai/plugins/miao-plugin/apps/wiki.js +29 -0
- Yunzai/plugins/miao-plugin/apps/wiki/Calendar.js +414 -0
- Yunzai/plugins/miao-plugin/apps/wiki/CalendarSr.js +303 -0
- Yunzai/plugins/miao-plugin/apps/wiki/CharMaterial.js +18 -0
- Yunzai/plugins/miao-plugin/apps/wiki/CharTalent.js +114 -0
- Yunzai/plugins/miao-plugin/apps/wiki/CharWiki.js +105 -0
- Yunzai/plugins/miao-plugin/apps/wiki/CharWikiData.js +108 -0
- Yunzai/plugins/miao-plugin/components/App.js +133 -0
- Yunzai/plugins/miao-plugin/components/Cfg.js +56 -0
- Yunzai/plugins/miao-plugin/components/Common.js +17 -0
- Yunzai/plugins/miao-plugin/components/Data.js +292 -0
- Yunzai/plugins/miao-plugin/components/Format.js +25 -0
- Yunzai/plugins/miao-plugin/components/MiaoError.js +17 -0
- Yunzai/plugins/miao-plugin/components/Version.js +109 -0
- Yunzai/plugins/miao-plugin/components/cfg/CfgData.js +47 -0
- Yunzai/plugins/miao-plugin/components/common/Elem.js +86 -0
- Yunzai/plugins/miao-plugin/components/common/Plugin.js +103 -0
.gitattributes
CHANGED
@@ -39,3 +39,8 @@ Yunzai/plugins/ws-plugin/resources/common/font/NZBZ.ttf filter=lfs diff=lfs merg
|
|
39 |
Yunzai/plugins/ws-plugin/resources/help/icon.png filter=lfs diff=lfs merge=lfs -text
|
40 |
Yunzai/plugins/genshin/resources/font/华文中宋.TTF filter=lfs diff=lfs merge=lfs -text
|
41 |
Yunzai/plugins/genshin/resources/font/HYWenHei-55W.ttf filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
39 |
Yunzai/plugins/ws-plugin/resources/help/icon.png filter=lfs diff=lfs merge=lfs -text
|
40 |
Yunzai/plugins/genshin/resources/font/华文中宋.TTF filter=lfs diff=lfs merge=lfs -text
|
41 |
Yunzai/plugins/genshin/resources/font/HYWenHei-55W.ttf filter=lfs diff=lfs merge=lfs -text
|
42 |
+
Yunzai/plugins/miao-plugin/resources/admin/imgs/bg.png filter=lfs diff=lfs merge=lfs -text
|
43 |
+
Yunzai/plugins/miao-plugin/resources/common/font/HYWH-65W.ttf filter=lfs diff=lfs merge=lfs -text
|
44 |
+
Yunzai/plugins/miao-plugin/resources/common/font/HYWH-65W.woff filter=lfs diff=lfs merge=lfs -text
|
45 |
+
Yunzai/plugins/miao-plugin/resources/common/font/NZBZ.ttf filter=lfs diff=lfs merge=lfs -text
|
46 |
+
Yunzai/plugins/miao-plugin/resources/help/icon.png filter=lfs diff=lfs merge=lfs -text
|
Yunzai/plugins/miao-plugin/.gitignore
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.psd
|
2 |
+
.idea
|
3 |
+
/tools/char-data-sprider.js
|
4 |
+
/components/cfg.json
|
5 |
+
/resources/miao-res-plus/
|
6 |
+
/components/setting.json
|
7 |
+
/config.js
|
8 |
+
*.css.map
|
9 |
+
/resources/character-img/*/upload/
|
10 |
+
/resources/help/help-list.js
|
11 |
+
/resources/help/help-cfg.js
|
12 |
+
/resources/help/theme/*
|
13 |
+
!/resources/help/theme/default
|
14 |
+
/config/character.js
|
15 |
+
/config/profile.js
|
16 |
+
/config/help.js
|
17 |
+
/config/cfg.js
|
18 |
+
/resources/profile/super-character/*
|
19 |
+
/resources/profile/normal-character/*
|
20 |
+
/node_modules/
|
Yunzai/plugins/miao-plugin/CHANGELOG.md
ADDED
@@ -0,0 +1,377 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 2.4.8
|
2 |
+
|
3 |
+
* 星铁面板支持面板变换功能
|
4 |
+
* 面板变换支持更换圣遗物套装,例如`#甘雨换乐团`、`#镜流换快枪手封印站`
|
5 |
+
* 微调面板页面的部分样式
|
6 |
+
|
7 |
+
# 2.4.1 ~ 2.4.7
|
8 |
+
|
9 |
+
* 初步支持星铁面板数据获取与展示
|
10 |
+
* 可使用`#星铁更新面板`来获取面板信息,通过`#希儿面板`来进行查看
|
11 |
+
* 星铁面板展示圣遗物评分,评分功能尚未完全完成,分值与样式不为最终版本
|
12 |
+
* 部分角色的伤害计算,以及圣遗物评分功能仍在补全中
|
13 |
+
* 星铁面板天赋展示更新,支持展示行迹信息
|
14 |
+
* 请配合使用Miao-Yunzai 3.1.0可达到最佳效果
|
15 |
+
* Miao-Yunzai可使用`*`来代填`#星铁`前缀,能区分游戏使用不同UID
|
16 |
+
* 其他Yunzai版本出现原神与星铁UID混淆情况为正常情况,请手动切换UID或命令后附加UID查询
|
17 |
+
* 角色数据及资源更新
|
18 |
+
* 增加原神4.0~4.2的角色信息,可通过`#水神天赋`、`#水神图鉴`查看
|
19 |
+
* 更新星铁1.2~1.3的角色数据与资源
|
20 |
+
* 伤害计算更新
|
21 |
+
* 增加林尼、卡夫卡、银狼的伤害计算**@Aluxes**
|
22 |
+
* 增加原神4.0相关的武器计算 **@SmallK111407**
|
23 |
+
* 增加菲米尼、符玄、玲可、三月七、娜塔莎、罗刹、黑塔的伤害计算 **@Aluxes**
|
24 |
+
* 初步增加星铁的排名功能 **@Simplxss**
|
25 |
+
* 增加`#喵喵api`命令,用于查看喵ApiToken的有效期
|
26 |
+
* 增加`#星铁日历`功能 **@Aluxes**
|
27 |
+
* 增加`#重载面板`功能,用于手工修改面板数据后主动读取
|
28 |
+
* 停用旧面板格式数据,非AttrIDs格式的数据不会展示,部分角色面板数据不展示是正常情况,重新更新面板数据即可
|
29 |
+
* 一些已知问题修复
|
30 |
+
|
31 |
+
# 2.4.0
|
32 |
+
|
33 |
+
* 增加`#角色记录``#抽卡统计`功能,可在`#喵喵设置`中开启
|
34 |
+
* `#角色记录`、`#武器记录`、`#常驻记录` 可查看对应池子的抽卡记录
|
35 |
+
* `#角色统计`、`#武器统计` 可按卡池汇总统计抽卡记录
|
36 |
+
* `#全部统计` 可将所有抽卡信息汇总展示
|
37 |
+
* 其余`#抽卡帮助`等相关功能均为Yunzai原生功能
|
38 |
+
* 面板服务增加国内专属面板服务 MiniGG-API
|
39 |
+
* 由小灰灰大佬**@MiniGrayGay**与Enka官方合作部署
|
40 |
+
* 国内节点,免费开放,请求速度会比Enka更快
|
41 |
+
* MiaoApi面板服务更新
|
42 |
+
* 使用新版接口获取面板,大幅提高响应速度
|
43 |
+
* 使用statsIds存储圣遗物数据,能够更精确的计算角色属性
|
44 |
+
* `#喵喵设置`中可区分国服、B服、外服分别设置面板服务器,具体参见喵喵设置
|
45 |
+
* `#面板`、`#角色`等页面使用Q版头像(@QuAn_、Misaaa),可在#喵喵设置 中关闭
|
46 |
+
* 增加白术、卡维的角色信息,可通过`#白术天赋`、`#白术图鉴`等查看
|
47 |
+
* 部分已知问题调整或优化
|
48 |
+
* 圣遗物、天赋更新策略及更新逻辑优化
|
49 |
+
* 面板更新的提醒文案逻辑优化
|
50 |
+
* `#雷神面板` 属性部分样式调整,增加圣遗物评分权重展示
|
51 |
+
* 圣遗物评级的分数上限微调
|
52 |
+
* 增加3.6新圣遗物数据及资源
|
53 |
+
* 增加绮良良的角色信息,可通过`#绮良良天赋`、`#绮良良图鉴`等查看
|
54 |
+
* 面板服务增加由**Snap Hutao**提供的Enka转发代理,可通过`#喵喵设置面板服务4`进行选择
|
55 |
+
* 面板详情的圣遗物词条增加词条数展示
|
56 |
+
* 角色面板使用平均词条值作为词条数计算基础
|
57 |
+
* 部分角色资源文件结构调整
|
58 |
+
|
59 |
+
# 2.3.0
|
60 |
+
|
61 |
+
* 重写底层面板、角色数据获取与保存逻辑
|
62 |
+
* 底层完全兼容面板及Mys数据,对于miao-plugin的大部分场景可做到数据通用
|
63 |
+
* 角色数据及天赋增加缓存逻辑,有缓存数据情况下可在ck失效/验证码等情况下正常使用大部分功能
|
64 |
+
* 全量使用通过圣遗物属性计算得到的面板数据
|
65 |
+
* 面板底层数据结构及存储逻辑优化,兼容老版本数据
|
66 |
+
* Enka服务下使用statsIds存储圣遗物数据,能够更精确的计算角色属性
|
67 |
+
* 增加`#角色`功能,查询并展示Mys角色信息
|
68 |
+
* Yunzai需要跟随游戏版本升级的功能会逐步在miao-plugin中提供,以保障基础功能相对长期可用
|
69 |
+
* 大部分功能目前默认关闭,可在`#喵喵设置`中设置并开启
|
70 |
+
* 为`#喵喵设置`增加更多配置项
|
71 |
+
* 允许禁用非实装角色资料,关闭可禁用非实装角色资料及面板替换
|
72 |
+
* 允许禁用面板替换功能
|
73 |
+
* 允许禁用获取角色或面板原图功能
|
74 |
+
* 可选择面板服务,可选喵喵Api优先(需具备Token)或Enka优先
|
75 |
+
* 可设置群排名人数、圣遗物列表展示数 **@SmallK111407**
|
76 |
+
* 角色信息及伤害计算更新
|
77 |
+
* 更新迪希雅、米卡的最新天赋与命座数据
|
78 |
+
* 增加瑶瑶伤害计算
|
79 |
+
* 其他功能及界面优化,部分已知问题调整
|
80 |
+
* `#上传深渊` 界面与样式调整
|
81 |
+
* `#刷新排名`、`#禁用排名`、`#启用排名`可由群管理员进行管理
|
82 |
+
* 增加`#删除面板`命令,目前限绑定CK用户使用删除自己UID数据,Bot主人可删除任意UID数据
|
83 |
+
|
84 |
+
# 2.2.0
|
85 |
+
|
86 |
+
* 增加面板替换功能,可通过命令更换面板的圣遗物、武器、天赋命座等,用于伤害计算
|
87 |
+
* `#雷神面板换稻光换90级满命` / `#刻晴面板换雷神圣遗物` 等命令
|
88 |
+
* 更多命令参见 `#面板帮助`,请根据需求吟唱。后续会提供更细致的咒语详解
|
89 |
+
* 增加角色面板立绘图相关命令 **@cvs**
|
90 |
+
* 支持`#上传刻晴面板图`上传
|
91 |
+
* 新增`#刻晴面板图列表`
|
92 |
+
* 可通过指令查询当前可看的面板立绘
|
93 |
+
* 立绘支持`#原图`指令
|
94 |
+
* 角色立绘支持随机,用于面板场景
|
95 |
+
* 图像支持webp及png格式
|
96 |
+
* 普通立绘:**resources/profile/normal-character/**
|
97 |
+
* 彩蛋立绘(满命/ACE/三皇冠):**resources/profile/super-character/**
|
98 |
+
* 单张立绘请放置在普通&彩蛋目录下,以**角色全名**为**文件名**,例如**刻晴.webp**
|
99 |
+
* 如需多张随机,请在普通&彩蛋目录下,以**角色全名**为**目录**名,任意文件名为文件名,例如 **刻晴/1.png**
|
100 |
+
* 较低版本的Yunzai可能无法正常使用miao-plugin
|
101 |
+
* 部分底层结构升级
|
102 |
+
* 去除插件内自带的V2/V3兼容逻辑,使用runtime进行V2/V3兼容,如使用遇到问题请升级至最新版Yunzai
|
103 |
+
* 底层增加面板计算逻辑, 圣遗物数据底层存储格式与处理逻辑初步升级
|
104 |
+
* 圣遗物主词条评分规则微调,可能会影响部分角色评分
|
105 |
+
* 元素杯属性不符会触发主词缀评分惩罚
|
106 |
+
* 充能主词条不再触发主词缀评分惩罚
|
107 |
+
* 增加`#启用排名``#禁用排名`命令,可在全局启用排名情况下,在特定群内禁用排名功能
|
108 |
+
* 更新迪希雅、米卡、瑶瑶、艾尔海森的信息,可通过`#瑶瑶天赋`、`#瑶瑶图鉴`等查看
|
109 |
+
* 增加散兵、艾尔海森 **@panganqi**、珐露珊的伤害计算
|
110 |
+
* 增加归终、米卡的角色图像
|
111 |
+
* 其他一些已知问题修正与样式优化
|
112 |
+
|
113 |
+
# 2.1.0
|
114 |
+
|
115 |
+
* 增加群内排名功能
|
116 |
+
* 默认关闭,如需启用可通过 `#喵喵设置排名开启`进行打开
|
117 |
+
* 统计为bot本地统计,只统计在群内主动查看过的面板数据
|
118 |
+
* 可通过 `#面板`、`#心海面板`、`#更新面板`等命令来触发排名数据更新
|
119 |
+
* 增加命令 `#刷新排名`,获取群成员面板数据,刷新当前排名 **@munnks**
|
120 |
+
* `#雷神排名` 使用个人头像作为排行头像展示(首次使用可使用 `#刷新排名`以更新uid信息)
|
121 |
+
* 增加排名相关命令
|
122 |
+
* 增加 `#最强雷神`、`#最高分甘雨`命令,查看当前统计中最高练度/最高圣遗物评分的面板数据
|
123 |
+
* 增加 `#雷神排名`、`#甘雨圣遗物排名`命令,查看当前群中角色的排名数据
|
124 |
+
* 增加 `#重置排名`、`#重置刻晴排名`命令,来重置当前群的排名统计
|
125 |
+
* 面板及伤害计算升级
|
126 |
+
* `#雷神面板`圣遗物支持展示强化次数
|
127 |
+
* `#面板`会展示角色名命座信息
|
128 |
+
* 底层元素反应计算逻辑更新 **@冷落**
|
129 |
+
* 增加纳西妲的伤害计算
|
130 |
+
* `#喵喵设置` 部分配置项及功能改进
|
131 |
+
* 删除一些无效或暂不支持的配置项
|
132 |
+
* 配置存储位置变更为**config/cfg.js**。原设置会自动迁移
|
133 |
+
* 喵喵设置中增加排名限制门槛,支持限制 有16个角色数据/包含御三家角色 才能参与排名,防止被非群成员uid刷榜
|
134 |
+
* `#日历` 页面样式微调,功能升级
|
135 |
+
* 日历中会展示角色生日
|
136 |
+
* 日历会展示本日可刷天赋角色列表
|
137 |
+
* 增加3.3角色信息及图片,可通过 `#散兵天赋`、`#珐露珊命座`查看
|
138 |
+
* 一些样式及功能点优化
|
139 |
+
* 优化character的进入判定逻辑,防止一些额外的log触发
|
140 |
+
* 角色相关命令在V3下会联合V3的角色别名一同查询
|
141 |
+
* `#深渊组队`使用新版胡桃API进行组队信息获取
|
142 |
+
* 增加命令 `#最强排行`、`#最高分排行` 查看群排行
|
143 |
+
* 增加莱依拉的伤害计算及圣遗物评分权重
|
144 |
+
|
145 |
+
# 2.0.0
|
146 |
+
|
147 |
+
* 底层架构升级,以V3为主要版本,V2做兼容处理
|
148 |
+
* `#深渊配队`、`#戳一戳` 适配V3
|
149 |
+
* `#喵喵帮助`配置功能升级
|
150 |
+
* 支持自定义帮助皮肤包,皮肤目录为**resources/help/theme**
|
151 |
+
* 若有多套皮肤包,默认随机使用,可通过**config/help.js**指定
|
152 |
+
* 支持配帮助文字颜色及容器颜色与透明度
|
153 |
+
* 支持图片毛玻璃效果,默认开启,可通过配置关闭
|
154 |
+
* `注意1:` 如之前更改过底图可能会在更新后失效,可将自定义底图放置在新建的皮肤包目录内
|
155 |
+
* `注意2:` 为统一配置目录,帮助配置文件迁移至**config/help.js**,如之前自定义过配置文件,help-cfg.js仍能够识别,但建议移至新配置目录以使用后续更多功能
|
156 |
+
* `#面板练度统计` 功能调整,样式重写
|
157 |
+
* 样式由深色调整为浅色方案
|
158 |
+
* 在未绑定CK时,使用本地面板数据展示练度信息
|
159 |
+
* 重写 `#刻晴`、`#老婆`的角色卡片
|
160 |
+
* 样式整体升级,展示信息重新排版
|
161 |
+
* 未绑定CK时,会同时使用本地面板数据进行展示
|
162 |
+
* `#上传深渊`队伍人数少于4人时展示样式优化
|
163 |
+
* MysApi内部逻辑重写
|
164 |
+
* 在未绑定CK时,会使用本地面板数据综合计算,以使信息展示更完备
|
165 |
+
* 优化V3下获取Uid及CK的逻辑,防止一些情况下触发报错
|
166 |
+
* 武器、圣遗物 meta数据及图像资源逻辑更新
|
167 |
+
* 重构武器及圣遗物的底层处理逻辑,重构页面引用图像资源的逻辑
|
168 |
+
* 图像资源更新为webp格式
|
169 |
+
* 增加多莉的伤害计算
|
170 |
+
* 其他已知Bug修复
|
171 |
+
|
172 |
+
# 1.11.0
|
173 |
+
|
174 |
+
* 面板圣遗物评分初步增加流派判定能力
|
175 |
+
* 实验性,尚未完全稳定,可能会导致一些角色圣遗物评分变化,如遇问题请反馈
|
176 |
+
* 目前实验暴力芭芭拉、血牛钟离的判定
|
177 |
+
* `#刻晴面板`、`#芭芭拉圣遗物`支持展示角色时装
|
178 |
+
* 如果角色装备了时装,面板的角色图会展示时装立绘
|
179 |
+
* 需要重新 `#更新面板`以获取时装数据
|
180 |
+
* 增加赛诺、妮露、坎蒂丝的角色信息,可以通过 `#妮露天赋`、`#妮露命座`查看角色信息了
|
181 |
+
* 角色面板支持旅行者,暂未支持伤害计算及圣遗物评分
|
182 |
+
* 需要重新更新旅行者的面板数据
|
183 |
+
* `#雷主天赋`、`#草主命座`功能升级
|
184 |
+
* 页面样式微调,内部处理逻辑升级
|
185 |
+
* 支持旅行者天赋及命座信息查看
|
186 |
+
* 增加 `#心海图鉴`功能,可查看突破材料及常用武器
|
187 |
+
* 功能尚未完全稳定,信息还在继续补全中
|
188 |
+
* 如无需使用,master可通过 `#喵喵设置图鉴关闭`关闭,防止覆盖图鉴插件等图鉴功能
|
189 |
+
* 框架底层角色相关逻辑重构,角色图像资源迁移为webp格式
|
190 |
+
* 若遇到图像资源无法正常展示,可联系喵喵反馈
|
191 |
+
|
192 |
+
# 1.10.0
|
193 |
+
|
194 |
+
* 新增 `#面板练度统计`功能
|
195 |
+
* 可展示当前角色天赋及圣遗物练度信息
|
196 |
+
* 需要用户绑定Cookie,圣遗物评分需要本地获取并查看过对应角色面板
|
197 |
+
* `#上传深渊`使用图片渲染深渊结果,同时可被 `#喵喵深渊`触发
|
198 |
+
* 可展示本期深渊的全部角色信息,包括组队、天赋及圣遗物
|
199 |
+
* 数据会上传至胡桃Api进行伤害排名,并展示在页面内
|
200 |
+
* 可在 `#喵喵设置`中启用 `#喵喵深渊`作为默认 `#深渊`,默认关闭
|
201 |
+
* 启用后不会覆盖 `#上期深渊`以及 `#深渊12层`具体楼层的命令
|
202 |
+
* `#面板`、`#更新面板`命令使用图片渲染结果
|
203 |
+
* `#雷神面板`展示数据API及更新时间
|
204 |
+
* Enka面板服务支持配置代理 **@永恒的小黑屋**
|
205 |
+
* 如需配置可在**miao-plugin/config/profile.js**文件中配置
|
206 |
+
* `#更新面板`支持配置更新API,适配Enka新校验逻辑
|
207 |
+
* B服角色使用Enka服务进行面板信息获取
|
208 |
+
* 感谢Enka官方 **@Algoinde**的官方授权及UA**校**验
|
209 |
+
* 感谢 **@MiniGrayGay**提供的Enka服务中转,若面板更新失败可尝试在**miao-plugin/config/profile.js**文件中配置切换更新API
|
210 |
+
* 更新面板增加单用户更新间隔控制,默认5分钟
|
211 |
+
* `#深渊出场率`、`#角色持有率` 增加样本数量展示,增加数据使用授权提示
|
212 |
+
* 部分角色的圣遗物评分增加充能的词条评分权重
|
213 |
+
* 重构部分components、models逻辑,重构部分伤害计算逻辑
|
214 |
+
* 伤害计算支持除旅行者外的全部角色
|
215 |
+
* 伤害计算暂未包含3.0新元素反应,后续统一补充
|
216 |
+
|
217 |
+
# 1.9.0
|
218 |
+
|
219 |
+
* 初步适配Yunzai V3
|
220 |
+
* 部分功能可能无法正常使用,会逐步适配
|
221 |
+
* 部分依赖MysApi查询的功能在V3下暂时只支持查自己
|
222 |
+
* 增加提纳里、柯莱、多莉的资料及角色图像
|
223 |
+
* 可通过 `#柯莱天赋`、`#柯莱命座`查看资料
|
224 |
+
* 增加 `#深渊使用率`命令,数据源自DGP-Studio胡桃API
|
225 |
+
* 新增 `#上传深渊数据`命令
|
226 |
+
* 上传自己角色的深渊挑战数据及角色列表,并展示在本期深渊中伤害与承伤排名
|
227 |
+
* 上传数据用于 `#角色持有率 #深渊出场率`等统计,可使统计更加及时准确
|
228 |
+
* 数据统计及服务来自DGP-Studio胡桃API
|
229 |
+
* 增加 `#添加刻晴图像`命令,感谢 **@叶**
|
230 |
+
* 可通过命令上传添加指定角色图片,上传至 **resources/character-img/刻晴/upload**
|
231 |
+
* 请将图像与命令一同发送,后续会支持at图像及命令后发送图像
|
232 |
+
* `#刻晴` 角色卡片功能升级
|
233 |
+
* `#老婆设置刻晴,心海`不再检查是否具有角色或展示在米游社展柜
|
234 |
+
* `#刻晴` 角色卡片优先使用面板数据进行展示,无面板数据时使用米游社数据
|
235 |
+
* 在未能获取到角色数据时也会展示角色卡片
|
236 |
+
* 支持戳一戳返回喵喵版角色卡片,暂不支持V3 Yunzai
|
237 |
+
* 需要使用喵喵分支Yunzai以支持此能力,如需切换可在Yunzai根目录输入下方命令后更新重启
|
238 |
+
* `git remote set-url origin https://gitee.com/yoimiya-kokomi/Yunzai-Bot`
|
239 |
+
* 可通过 `#喵喵设置` 关闭戳一戳
|
240 |
+
* 支持定义新角色及别名
|
241 |
+
* 新增角色 派蒙、瑶瑶、白术、伐难、应达、散兵、女士、萍姥姥、仆人、少女、富人、博士、木偶、丑角、队长、妮露、纳西妲 的角色配置及图片
|
242 |
+
* 自定义角色可使用 `#派蒙` `#派蒙图片`触发图片��看,`#女儿设置派蒙`进行设置。后续会支持更多场景
|
243 |
+
* 如需扩展可在喵喵config/character.js中定义
|
244 |
+
* `#喵喵帮助`增加对自定义配置文件的支持
|
245 |
+
* 角色伤害计算增加 鹿野院平藏、烟绯
|
246 |
+
* `#喵喵日历`现在可通过 `#日历 #日历列表`触发
|
247 |
+
|
248 |
+
# 1.8.0
|
249 |
+
|
250 |
+
* `#角色面板`、`#圣遗物列表` 使用新的圣遗物评分逻辑计算评分
|
251 |
+
* 新的圣遗物评分规针对不同角色进行了细化,对不同角色的评分进行了拉齐
|
252 |
+
* 不同角色基于不同词条权重进行计算。感谢 **@糖炒栗子 @秋声 @49631073**等的权重梳理
|
253 |
+
* 增加 `#雷神圣遗物`命令
|
254 |
+
* 展示指定角色圣遗物及评分计算详情
|
255 |
+
* 展示新版圣遗物评分逻辑与计算规则
|
256 |
+
* 增加 `#原图`命令,可获取喵喵角色卡片原图,感谢 **@牧星长** 提供功能
|
257 |
+
* 对由 `#老婆 #刻晴`发出的角色卡片图回复 `#原图`可获取对应图像
|
258 |
+
* `#角色面板`现在支持B服角色数据获取
|
259 |
+
* 数据来自喵喵API,目前开放调用无需Token,仅限喵喵插件用户使用
|
260 |
+
* 已知问题:角色天赋的皇冠及命座加成效果显示可能有问题,后期fix
|
261 |
+
* `#录入角色面板` 功能恢复
|
262 |
+
* 可对已有面板数据的角色手工输入更改面板属性,用于伤害测算
|
263 |
+
* 例如 `#录入雷神面板 暴击80,暴伤250`
|
264 |
+
* 暂不支持设置武器、圣遗物、命座、天赋。后续会增加支持
|
265 |
+
* 部分页面样式调整及功能优化
|
266 |
+
* `#角色持有率` 等增加提示说明
|
267 |
+
* `#圣遗物列表` 展示个数提升至28,且根据新版圣遗物评分规则进行词条高亮
|
268 |
+
* `#喵喵更新` 的自动重启功能适配node app方式启动的Yunzai-Bot,感谢 **@SirlyDreamer**
|
269 |
+
* 角色图像增加小清新开关,默认关闭
|
270 |
+
* 对增量包内的角色图像进行分级,较为清凉的图像独立管理
|
271 |
+
* 勇士们可使用 `#喵喵设置小清新开启` 启用
|
272 |
+
* 伤害计算增加扩散、感电的计算逻辑,感谢 **@49631073**的逻辑梳理
|
273 |
+
* `#角色面板` 伤害计算增加部分角色,目前支持
|
274 |
+
* 长柄武器:雷神、胡桃、魈、钟离、香菱
|
275 |
+
* 法器:神子、心海、可莉、凝光、芭芭拉、莫娜
|
276 |
+
* 弓:甘雨、宵宫、公子,九条,迪奥娜、安柏、皇女、温迪、夜兰
|
277 |
+
* 单手剑:绫人、绫华、刻晴、阿贝多、行秋、班尼特、七七、凯亚、琴、万叶ⁿᵉʷ、久岐忍ⁿᵉʷ
|
278 |
+
* 双手剑:一斗、优菈、迪卢克、诺艾尔、重云
|
279 |
+
|
280 |
+
# 1.7.0
|
281 |
+
|
282 |
+
* `#更新面板` 功能升级
|
283 |
+
* 该功能可直接使用,不再需要token
|
284 |
+
* 在查询新用户时会自动使用,自动使用的CD 12小时
|
285 |
+
* 支持国际服UID,目前暂不支持2及5开头的UID
|
286 |
+
* 服务来自enka api,部分网络可能无法请求,请科学处理,后续会增加转发服务。
|
287 |
+
* 由于服务逻辑与之前数据不一致,部分角色的属性及伤害计算可能会不准确,如有发现请反馈给喵喵
|
288 |
+
* `#面板`、`#更新面板`、`#角色面板`、`#角色伤害`、`#圣遗物列表`不再需要绑定cookie,支持查他人
|
289 |
+
* 使用 `#面板`命令可查看已获取面板数据的角色列表
|
290 |
+
* 默认查询自己UID,同时也可通过命令+uid方式指定查询对象
|
291 |
+
* 由于整体逻辑变化,喵喵1.6.0之前更新的面板数据无法查看,需要重新更新数据
|
292 |
+
* 增加 `#喵喵面板设置`命令,可更精细的设置是否允许好友/临时对话/群使用面板功能
|
293 |
+
* 由 `#录入xx面板` 录入的数据暂时屏蔽
|
294 |
+
* `#角色面板`、`#喵喵日历` 部分细节样式调整
|
295 |
+
* `#角色面板` 伤害计算增加部分角色,目前支持
|
296 |
+
* 长柄武器:雷神、胡桃、魈、钟离、香菱
|
297 |
+
* 法器:神子、心海、可莉、凝光、芭芭拉、莫娜ⁿᵉʷ
|
298 |
+
* 弓:甘雨、宵宫、公子,九条,迪奥娜、安柏、皇女ⁿᵉʷ、温迪ⁿᵉʷ、夜兰ⁿᵉʷ
|
299 |
+
* 单手剑:绫人、绫华、刻晴、阿贝多、行秋、班尼特、七七、凯亚、琴ⁿᵉʷ
|
300 |
+
* 双手剑:一斗、优菈、迪卢克、诺艾尔、重云
|
301 |
+
|
302 |
+
# 1.6.0
|
303 |
+
|
304 |
+
* `#喵喵设置` 支持设置 面板查询 的功能开关
|
305 |
+
* `#喵喵版本` 使用图片展示更新信息
|
306 |
+
* `#喵喵日历` 升级
|
307 |
+
* 增加 `#喵喵日历列表`命令,以列表形式展示活动信息
|
308 |
+
* 增加从活动详情信息中解析活动日期的逻辑,使一些活动日期更加准确
|
309 |
+
* 增加鹿野院平藏的角色信息,可通过 `#平藏天赋`、`#平藏命座`查看信息
|
310 |
+
* 其他升级调整
|
311 |
+
* `#深渊出场率`、`#角色持有率` 等页面功能及样式微调
|
312 |
+
* `#角色面板` 伤害计算增加双手剑计算逻辑,增加物伤计算逻辑
|
313 |
+
* 页面版权信息展示Yunzai及喵喵版本号
|
314 |
+
* `#角色面板` 伤害计算增加部分角色,目前支持
|
315 |
+
* 长柄武器:雷神、胡桃、魈、钟离、香菱
|
316 |
+
* 法器:神子、心海、可莉ⁿᵉʷ、凝光ⁿᵉʷ、芭芭拉ⁿᵉʷ
|
317 |
+
* 弓:甘雨、宵宫、公子,九条ⁿᵉʷ,迪奥娜ⁿᵉʷ、安柏ⁿᵉʷ
|
318 |
+
* 单手剑:��人、绫华、刻晴、阿贝多、行秋、班尼特、七七ⁿᵉʷ、凯亚ⁿᵉʷ
|
319 |
+
* 双手剑:一斗ⁿᵉʷ、优菈ⁿᵉʷ、迪卢克ⁿᵉʷ、诺艾尔ⁿᵉʷ、重云ⁿᵉʷ
|
320 |
+
|
321 |
+
# 1.5.0
|
322 |
+
|
323 |
+
* 增加 `#喵喵日历` 功能
|
324 |
+
* 【!请注意!】此功能需要安装moment库,请在Yunzai安装目录下运行 `npm install moment`后再进行升级
|
325 |
+
* 展示当前进行中及即将开始的活动,包括深境螺旋
|
326 |
+
* `#角色面板` 伤害计算目前支持
|
327 |
+
* 长柄武器:雷神、胡桃、魈、钟离、香菱ⁿᵉʷ
|
328 |
+
* 法器:神子、心海
|
329 |
+
* 弓:甘雨、宵宫、公子
|
330 |
+
* 单手剑:绫人、绫华、刻晴、阿贝多ⁿᵉʷ、行秋ⁿᵉʷ、班尼特ⁿᵉʷ
|
331 |
+
* 底层升级:抽象了部分公共组件为tpl模板以提高复用度,css改为less处理
|
332 |
+
|
333 |
+
# 1.4.0
|
334 |
+
|
335 |
+
* 增加 `#深渊配队` 功能
|
336 |
+
* 根据当前账号的角色练度及本期深渊出场数据,推荐较匹配的配队方案
|
337 |
+
* 深渊出场数据来自DGP-Studio胡桃API
|
338 |
+
* 配队方案仅供参考
|
339 |
+
* `#角色面板` 伤害计算新增部分角色
|
340 |
+
* 目前支持:雷神、胡桃、魈、神子、甘雨、宵宫、公子、绫人、绫华、心海、钟离
|
341 |
+
* `#角色面板` 一些功能升级与调整
|
342 |
+
* 支持对治疗量、护盾量的计算与展示
|
343 |
+
* 修复冰融化、少女4等buff等buff遗漏或错误导致的伤害计算偏差
|
344 |
+
* `#老婆` 功能支持对jpeg格式的图片格式识别
|
345 |
+
|
346 |
+
# 1.3.0
|
347 |
+
|
348 |
+
* 增加 `#雷神伤害` 功能
|
349 |
+
* 可计算圣遗物副词条置换带来的伤害变化,可用于圣遗物副词条侧重方向的参考
|
350 |
+
* 可以查看指定角色伤害计算的Buff列表
|
351 |
+
* `#角色面板` 伤害计算新增部分角色
|
352 |
+
* 目前支持:雷神、胡桃、魈、神子、甘雨、宵宫、公子、绫人、绫华
|
353 |
+
* `#角色面板` 功能升级
|
354 |
+
* 优化无角色面板数据时的引导
|
355 |
+
* 优化返回的图像格式及分辨率,平衡响应速度及显示效果
|
356 |
+
* 增加 `#圣遗物列表` 功能,对已经获取面板的所有角色圣遗物进行评分,并展示高评分的圣遗物列表
|
357 |
+
* 增加 `#角色面板列表` / `#角色面板帮助` 命令
|
358 |
+
* 增加 `#更新胡桃面板` 命令,获取单个角色面板数据,每天可更新5次
|
359 |
+
* 更改 `#更新全部面板` 命令,获取角色展柜全部8个角色,每天可更新3次
|
360 |
+
|
361 |
+
# 1.2.0
|
362 |
+
|
363 |
+
* `#角色面板` 增加伤害计算功能
|
364 |
+
* 目前支持角色:雷神、胡桃、魈、神子、甘雨
|
365 |
+
* 可通过 `#怪物等级85` 命令设定怪物等级,以获得更准确的计算结果
|
366 |
+
* 计算伤害为满Buff情况,后续会出更详细的Buff及计算展示
|
367 |
+
* `#获取游戏角色详情`命令在服务侧增加基于UID的天频度限制
|
368 |
+
* 增加 `#喵喵更新` 功能
|
369 |
+
* 感谢 @碎月 @清秋 的代码支持
|
370 |
+
* 若更新成功会重启Yunzai,需要Yunzai以 npm run start 模式启动
|
371 |
+
* 尚未经充分测试,请有一定容错能力的勇士尝试
|
372 |
+
* 增加 `#喵喵版本`命令查询版本信息
|
373 |
+
|
374 |
+
# 1.1.0
|
375 |
+
|
376 |
+
* 增加 `#喵喵帮助`用于查看帮助命令
|
377 |
+
* 增加 `#喵喵设置`用于设置喵喵相关功能
|
Yunzai/plugins/miao-plugin/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2023 Yoimiya
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
Yunzai/plugins/miao-plugin/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Miao-Plugin 说明
|
2 |
+
|
3 |
+
`miao-plugin`是一个`Yunzai-Bot`的升级插件,提供包括角色面板、角色查询等角色相关功能。
|
4 |
+
|
5 |
+
具体功能可在安装插件后 通过 `#喵喵帮助` 进行查看。如需进行设置则可通过 `#喵喵设置` 命令进行管理。
|
6 |
+
|
7 |
+
---
|
8 |
+
|
9 |
+
## 安装与更新
|
10 |
+
|
11 |
+
### 使用Git安装(推荐)
|
12 |
+
|
13 |
+
请将 miao-plugin 放置在 Yunzai-Bot 的 plugins 目录下,重启 Yunzai-Bot 后即可使用。
|
14 |
+
|
15 |
+
请使用 git 进行安装,以方便后续升级。在 Yunzai-Bot 根目录夹打开终端,运行下述指令之一
|
16 |
+
|
17 |
+
```
|
18 |
+
// 使用gitee
|
19 |
+
git clone --depth=1 https://gitee.com/yoimiya-kokomi/miao-plugin.git ./plugins/miao-plugin/
|
20 |
+
pnpm install -P
|
21 |
+
|
22 |
+
// 使用github
|
23 |
+
git clone --depth=1 https://github.com/yoimiya-kokomi/miao-plugin.git ./plugins/miao-plugin/
|
24 |
+
pnpm install -P
|
25 |
+
```
|
26 |
+
|
27 |
+
进行安装。安装完毕后,管理员只需发送 `#喵喵更新` 即可自动更新 miao-plugin。
|
28 |
+
|
29 |
+
### 手工下载安装(不推荐)
|
30 |
+
|
31 |
+
手工下载安装包,解压后将`miao-plugin-master`更名为`miao-plugin`,然后放置在Yunzai的plugins目录内
|
32 |
+
|
33 |
+
虽然此方式能够使用,但无法使用`#喵喵更新`进行更新,不利于后续升级,故不推荐使用
|
34 |
+
|
35 |
+
---
|
36 |
+
|
37 |
+
## Yunzai版本与支持
|
38 |
+
|
39 |
+
`miao-plugin` 支持V3 / V2 版本的Yunzai-Bot
|
40 |
+
|
41 |
+
* [Miao-Yunzai](https://github.com/yoimiya-kokomi/Miao-Yunzai) : 喵版Yunzai [Gitee](https://gitee.com/yoimiya-kokomi/Miao-Yunzai)
|
42 |
+
/ [Github](https://github.com/yoimiya-kokomi/Miao-Yunzai) ,本体不含签到功能,功能迭代较多,与miao-plugin打通,只建议新部署/迁移
|
43 |
+
* [Yunzai-V3](https://github.com/yoimiya-kokomi/Yunzai-Bot) :Yunzai V3 - 喵喵维护版,icqq版本,与原版Yunza功能基本一致,会保持卡池更新,功能相对稳定,可从原版Yunzai换源直接升级
|
44 |
+
* [Yunzai-V3](https://gitee.com/Le-niao/Yunzai-Bot) :Yunzai V3 - 乐神原版,oicq版本,可能会遇到登录问题
|
45 |
+
|
46 |
+
---
|
47 |
+
|
48 |
+
## 功能说明
|
49 |
+
|
50 |
+
### #雷神面板
|
51 |
+
|
52 |
+
使用指令 `#面板帮助` 即可了解如何使用此功能。
|
53 |
+
|
54 |
+
#### #更新面板
|
55 |
+
|
56 |
+
`#更新面板` 依赖于面板查询API,面板服务由 http://enka.network/ 提供。
|
57 |
+
|
58 |
+
> 查询功能经Enka官方授权([issue#63](https://github.com/yoimiya-kokomi/miao-plugin/issues/63#issuecomment-1199348789)),感谢Enka提供的面板查询服务
|
59 |
+
>
|
60 |
+
> 如果可以的话,也请在Patreon上支持Enka,或提供闲置的原神账户,具体可在[Enka官网](http://enka.network/) Discord联系
|
61 |
+
>
|
62 |
+
> [issue#63](https://github.com/yoimiya-kokomi/miao-plugin/issues/63#issuecomment-1199734496)
|
63 |
+
|
64 |
+
> 可尝试使用`MiniGG-Api`面板服务 [@MiniGrayGay](https://github.com/MiniGrayGay)<br>
|
65 |
+
> 发送 `#喵喵设置面板服务332` 修改国服&B服的面板查询由 `MiniGG-Api` 处理
|
66 |
+
|
67 |
+
#### #雷神伤害
|
68 |
+
|
69 |
+
喵喵面板附带的伤害计算功能由喵喵本地计算。如计算有偏差 #雷神伤害 查看伤害加成信息,如确认伤害计算有误可提供伤害录屏截图及uid进行反馈
|
70 |
+
|
71 |
+
#### #雷神圣遗物
|
72 |
+
|
73 |
+
圣遗物评分为喵喵版评分规则
|
74 |
+
|
75 |
+
---
|
76 |
+
|
77 |
+
**在有一定阅读理解能力基础下,建议阅读 [CHANGELOG.md](CHANGELOG.md) 以了解更多内容。**
|
78 |
+
|
79 |
+
其余文档咕咕咕中
|
80 |
+
|
81 |
+
---
|
82 |
+
|
83 |
+
# 免责声明
|
84 |
+
|
85 |
+
1. `miao-plugin`自身的UI与代码均开放,无需征得特殊同意,可任意使用。能备注来源最好,但不强求
|
86 |
+
2. 以上声明但仅代表`miao-plugin`自身的范畴,请尊重Yunzai本体及其他插件作者的努力,勿将Yunzai及其他插件用于以盈利为目的的场景
|
87 |
+
3. miao-plugin的图片与其他素材均来自于网络,仅供交流学习使用,如有侵权请联系,会立即删除
|
88 |
+
|
89 |
+
# 资源
|
90 |
+
|
91 |
+
* [Miao-Yunzai](https://github.com/yoimiya-kokomi/Miao-Yunzai) : 喵版Yunzai [Gitee](https://gitee.com/yoimiya-kokomi/Miao-Yunzai)
|
92 |
+
/ [Github](https://github.com/yoimiya-kokomi/Miao-Yunzai)
|
93 |
+
* [Yunzai-V3](https://github.com/yoimiya-kokomi/Yunzai-Bot) :Yunzai V3 - 喵喵维护版(使用 icqq)
|
94 |
+
* [Yunzai-V3](https://gitee.com/Le-niao/Yunzai-Bot) :Yunzai V3 - 乐神原版(使用 oicq)
|
95 |
+
* [miao-plugin](https://github.com/yoimiya-kokomi/miao-plugin) : 喵喵插件 [Gitee](https://gitee.com/yoimiya-kokomi/miao-plugin)
|
96 |
+
/ [Github](https://github.com/yoimiya-kokomi/miao-plugin)
|
97 |
+
|
98 |
+
# 其他&感谢
|
99 |
+
|
100 |
+
* [Enka.Network](https://enka.network/): 感谢Enka提供的面板服务
|
101 |
+
* [Snap.Hutao](https://hut.ao/) : 感谢 DGP Studio 开发的 [胡桃 API](https://github.com/DGP-Studio/Snap.Hutao.Server)
|
102 |
+
* QQ群(暂时停止新加入,请见谅)
|
103 |
+
* Yunzai-Bot 官方QQ群:213938015
|
104 |
+
* 喵喵Miao-Plugin QQ群:607710456
|
105 |
+
* [爱发电](https://afdian.net/@kokomi) :欢迎老板打赏,喵~
|
106 |
+
|
Yunzai/plugins/miao-plugin/apps/admin.js
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import fs from 'fs'
|
2 |
+
import lodash from 'lodash'
|
3 |
+
import { exec } from 'child_process'
|
4 |
+
import { Cfg, Common, Data, Version, App } from '#miao'
|
5 |
+
import fetch from 'node-fetch'
|
6 |
+
|
7 |
+
let keys = lodash.map(Cfg.getCfgSchemaMap(), (i) => i.key)
|
8 |
+
let app = App.init({
|
9 |
+
id: 'admin',
|
10 |
+
name: '喵喵设置',
|
11 |
+
desc: '喵喵设置'
|
12 |
+
})
|
13 |
+
|
14 |
+
let sysCfgReg = new RegExp(`^#喵喵设置\\s*(${keys.join('|')})?\\s*(.*)$`)
|
15 |
+
|
16 |
+
app.reg({
|
17 |
+
updateRes: {
|
18 |
+
rule: /^#喵喵(强制)?(更新图像|图像更新)$/,
|
19 |
+
fn: updateRes,
|
20 |
+
desc: '【#管理】更新素材'
|
21 |
+
},
|
22 |
+
update: {
|
23 |
+
rule: /^#喵喵(强制)?更新$/,
|
24 |
+
fn: updateMiaoPlugin,
|
25 |
+
desc: '【#管理】喵喵更新'
|
26 |
+
},
|
27 |
+
sysCfg: {
|
28 |
+
rule: sysCfgReg,
|
29 |
+
fn: sysCfg,
|
30 |
+
desc: '【#管理】系统设置'
|
31 |
+
},
|
32 |
+
miaoApiInfo: {
|
33 |
+
rule: /^#喵喵api$/,
|
34 |
+
fn: miaoApiInfo,
|
35 |
+
desc: '【#管理】喵喵Api'
|
36 |
+
}
|
37 |
+
})
|
38 |
+
|
39 |
+
export default app
|
40 |
+
|
41 |
+
const _path = process.cwd()
|
42 |
+
const resPath = `${_path}/plugins/miao-plugin/resources/`
|
43 |
+
const plusPath = `${resPath}/miao-res-plus/`
|
44 |
+
|
45 |
+
const checkAuth = async function (e) {
|
46 |
+
if (!e.isMaster) {
|
47 |
+
e.reply(`只有主人才能命令喵喵哦~
|
48 |
+
(*/ω\*)`)
|
49 |
+
return false
|
50 |
+
}
|
51 |
+
return true
|
52 |
+
}
|
53 |
+
|
54 |
+
async function sysCfg (e) {
|
55 |
+
if (!await checkAuth(e)) {
|
56 |
+
return true
|
57 |
+
}
|
58 |
+
|
59 |
+
let cfgReg = sysCfgReg
|
60 |
+
let regRet = cfgReg.exec(e.msg)
|
61 |
+
let cfgSchemaMap = Cfg.getCfgSchemaMap()
|
62 |
+
|
63 |
+
if (!regRet) {
|
64 |
+
return true
|
65 |
+
}
|
66 |
+
|
67 |
+
if (regRet[1]) {
|
68 |
+
// 设置模式
|
69 |
+
let val = regRet[2] || ''
|
70 |
+
|
71 |
+
let cfgSchema = cfgSchemaMap[regRet[1]]
|
72 |
+
if (cfgSchema.input) {
|
73 |
+
val = cfgSchema.input(val)
|
74 |
+
} else {
|
75 |
+
val = cfgSchema.type === 'num' ? (val * 1 || cfgSchema.def) : !/关闭/.test(val)
|
76 |
+
}
|
77 |
+
Cfg.set(cfgSchema.cfgKey, val)
|
78 |
+
}
|
79 |
+
|
80 |
+
let schema = Cfg.getCfgSchema()
|
81 |
+
let cfg = Cfg.getCfg()
|
82 |
+
let imgPlus = fs.existsSync(plusPath)
|
83 |
+
|
84 |
+
// 渲染图像
|
85 |
+
return await Common.render('admin/index', {
|
86 |
+
schema,
|
87 |
+
cfg,
|
88 |
+
imgPlus,
|
89 |
+
isMiao: Version.isMiao
|
90 |
+
}, { e, scale: 1.4 })
|
91 |
+
}
|
92 |
+
|
93 |
+
async function updateRes (e) {
|
94 |
+
if (!await checkAuth(e)) {
|
95 |
+
return true
|
96 |
+
}
|
97 |
+
let isForce = e.msg.includes('强制')
|
98 |
+
let command = ''
|
99 |
+
if (fs.existsSync(`${resPath}/miao-res-plus/`)) {
|
100 |
+
e.reply('开始尝试更新,请耐心等待~')
|
101 |
+
command = 'git pull'
|
102 |
+
if (isForce) {
|
103 |
+
command = 'git checkout . && git pull'
|
104 |
+
}
|
105 |
+
exec(command, { cwd: `${resPath}/miao-res-plus/` }, function (error, stdout, stderr) {
|
106 |
+
console.log(stdout)
|
107 |
+
if (/(Already up[ -]to[ -]date|已经是最新的)/.test(stdout)) {
|
108 |
+
e.reply('目前所有图片都已经是最新了~')
|
109 |
+
return true
|
110 |
+
}
|
111 |
+
let numRet = /(\d*) files changed,/.exec(stdout)
|
112 |
+
if (numRet && numRet[1]) {
|
113 |
+
e.reply(`报告主人,更新成功,此次更新了${numRet[1]}个图片~`)
|
114 |
+
return true
|
115 |
+
}
|
116 |
+
if (error) {
|
117 |
+
e.reply('更新失败!\nError code: ' + error.code + '\n' + error.stack + '\n 请稍后重试。')
|
118 |
+
} else {
|
119 |
+
e.reply('图片加量包更新成功~')
|
120 |
+
}
|
121 |
+
})
|
122 |
+
} else {
|
123 |
+
command = `git clone https://gitee.com/yoimiya-kokomi/miao-res-plus.git "${resPath}/miao-res-plus/" --depth=1`
|
124 |
+
e.reply('开始尝试安装图片加量包,可能会需要一段时间,请耐心等待~')
|
125 |
+
exec(command, function (error, stdout, stderr) {
|
126 |
+
if (error) {
|
127 |
+
e.reply('角色图片加量包安装失败!\nError code: ' + error.code + '\n' + error.stack + '\n 请稍后重试。')
|
128 |
+
} else {
|
129 |
+
e.reply('角色图片加量包安装成功!您后续也可以通过 #喵喵更新图像 命令来更新图像')
|
130 |
+
}
|
131 |
+
})
|
132 |
+
}
|
133 |
+
return true
|
134 |
+
}
|
135 |
+
|
136 |
+
let timer
|
137 |
+
|
138 |
+
async function updateMiaoPlugin (e) {
|
139 |
+
if (!await checkAuth(e)) {
|
140 |
+
return true
|
141 |
+
}
|
142 |
+
let isForce = e.msg.includes('强制')
|
143 |
+
let command = 'git pull'
|
144 |
+
if (isForce) {
|
145 |
+
command = 'git checkout . && git pull'
|
146 |
+
e.reply('正在执行强制更新操作,请稍等')
|
147 |
+
} else {
|
148 |
+
e.reply('正在执行更新操作,请稍等')
|
149 |
+
}
|
150 |
+
exec(command, { cwd: `${_path}/plugins/miao-plugin/` }, function (error, stdout, stderr) {
|
151 |
+
if (/(Already up[ -]to[ -]date|已经是最新的)/.test(stdout)) {
|
152 |
+
e.reply('目前已经是最新版喵喵了~')
|
153 |
+
return true
|
154 |
+
}
|
155 |
+
if (error) {
|
156 |
+
e.reply('喵喵更新失败!\nError code: ' + error.code + '\n' + error.stack + '\n 请稍后重试。')
|
157 |
+
return true
|
158 |
+
}
|
159 |
+
e.reply('喵喵更新成功,正在尝试重新启动Yunzai以应用更新...')
|
160 |
+
timer && clearTimeout(timer)
|
161 |
+
Data.setCacheJSON('miao:restart-msg', {
|
162 |
+
msg: '重启成功,新版喵喵已经生效',
|
163 |
+
qq: e.user_id
|
164 |
+
}, 30)
|
165 |
+
timer = setTimeout(function () {
|
166 |
+
let command = 'npm run start'
|
167 |
+
if (process.argv[1].includes('pm2')) {
|
168 |
+
command = 'npm run restart'
|
169 |
+
}
|
170 |
+
exec(command, function (error, stdout, stderr) {
|
171 |
+
if (error) {
|
172 |
+
e.reply('自动重启失败,请手动重启以应用新版喵喵。\nError code: ' + error.code + '\n' + error.stack + '\n')
|
173 |
+
Bot.logger.error(`重启失败\n${error.stack}`)
|
174 |
+
return true
|
175 |
+
} else if (stdout) {
|
176 |
+
Bot.logger.mark('重启成功,运行已转为后台,查看日志请用命令:npm run log')
|
177 |
+
Bot.logger.mark('停止后台运行命令:npm stop')
|
178 |
+
process.exit()
|
179 |
+
}
|
180 |
+
})
|
181 |
+
}, 1000)
|
182 |
+
})
|
183 |
+
return true
|
184 |
+
}
|
185 |
+
|
186 |
+
async function miaoApiInfo (e) {
|
187 |
+
if (!await checkAuth(e)) {
|
188 |
+
return true
|
189 |
+
}
|
190 |
+
let { diyCfg } = await Data.importCfg('profile')
|
191 |
+
let { qq, token } = (diyCfg?.miaoApi || {})
|
192 |
+
if (!qq || !token) {
|
193 |
+
return e.reply('未正确填写miaoApi token,请检查miao-plugin/config/profile.js文件')
|
194 |
+
}
|
195 |
+
if (token.length !== 32) {
|
196 |
+
return e.reply('miaoApi token格式错误')
|
197 |
+
}
|
198 |
+
let req = await fetch(`http://miao.games/api/info?qq=${qq}&token=${token}`)
|
199 |
+
let data = await req.json()
|
200 |
+
if (data.status !== 0) {
|
201 |
+
return e.reply('token检查错误,请求失败')
|
202 |
+
}
|
203 |
+
e.reply(data.msg)
|
204 |
+
}
|
Yunzai/plugins/miao-plugin/apps/character.js
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { uploadCharacterImg } from './character/ImgUpload.js'
|
2 |
+
import { getOriginalPicture } from './profile/ProfileUtils.js'
|
3 |
+
import Avatar from './character/AvatarCard.js'
|
4 |
+
import Wife from './character/AvatarWife.js'
|
5 |
+
import { App } from '#miao'
|
6 |
+
|
7 |
+
let app = App.init({
|
8 |
+
id: 'character',
|
9 |
+
name: '角色查询'
|
10 |
+
})
|
11 |
+
|
12 |
+
app.reg({
|
13 |
+
character: {
|
14 |
+
rule: /^#喵喵角色卡片$/,
|
15 |
+
fn: Avatar.render,
|
16 |
+
check: Avatar.check,
|
17 |
+
name: '角色卡片'
|
18 |
+
},
|
19 |
+
uploadImg: {
|
20 |
+
rule: /^#*(喵喵)?(上传|添加)(.+)(照片|写真|图片|图像)\s*$/,
|
21 |
+
fn: uploadCharacterImg,
|
22 |
+
name: '上传角色写真'
|
23 |
+
},
|
24 |
+
wife: {
|
25 |
+
rule: Wife.reg,
|
26 |
+
fn: Wife.render,
|
27 |
+
describe: '#老公 #老婆 查询'
|
28 |
+
},
|
29 |
+
originalPic: {
|
30 |
+
rule: /^#?(获取|给我|我要|求|发|发下|发个|发一下)?原图(吧|呗)?$/,
|
31 |
+
fn: getOriginalPicture,
|
32 |
+
describe: '【#原图】 回复角色卡片,可获取原图'
|
33 |
+
}
|
34 |
+
})
|
35 |
+
|
36 |
+
export default app
|
Yunzai/plugins/miao-plugin/apps/character/AvatarCard.js
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Character, MysApi, Player } from '#miao.models'
|
2 |
+
import { Cfg, Common } from '#miao'
|
3 |
+
import lodash from 'lodash'
|
4 |
+
import moment from 'moment'
|
5 |
+
|
6 |
+
let Avatar = {
|
7 |
+
render (e) {
|
8 |
+
if (!e.char) {
|
9 |
+
return false
|
10 |
+
}
|
11 |
+
return Avatar.renderAvatar(e, e.char?.name)
|
12 |
+
},
|
13 |
+
async renderAvatar (e, avatar, renderType = 'card') {
|
14 |
+
// 如果传递的是名字,则获取
|
15 |
+
if (typeof (avatar) === 'string') {
|
16 |
+
// 检查角色
|
17 |
+
let char = Character.get(avatar)
|
18 |
+
if (!char) {
|
19 |
+
return false
|
20 |
+
}
|
21 |
+
let mys = await MysApi.init(e)
|
22 |
+
if (!mys) return true
|
23 |
+
if (!char.isRelease) {
|
24 |
+
avatar = { id: char.id, name: char.name, detail: false }
|
25 |
+
} else {
|
26 |
+
let player = Player.create(e)
|
27 |
+
await player.refreshMysDetail(1)
|
28 |
+
await player.refreshTalent(char.id, 1)
|
29 |
+
avatar = player.getAvatar(char.id)
|
30 |
+
if (!avatar) {
|
31 |
+
avatar = { id: char.id, name: char.name, detail: false }
|
32 |
+
}
|
33 |
+
}
|
34 |
+
}
|
35 |
+
return await Avatar.renderCard(e, avatar, renderType)
|
36 |
+
},
|
37 |
+
|
38 |
+
async renderCard (e, avatar, renderType = 'card') {
|
39 |
+
let char = Character.get(avatar.id)
|
40 |
+
if (!char) {
|
41 |
+
return false
|
42 |
+
}
|
43 |
+
let bg = char.getCardImg(Cfg.get('charPicSe', false))
|
44 |
+
if (renderType === 'photo') {
|
45 |
+
e.reply(segment.image(`file://${process.cwd()}/plugins/miao-plugin/resources/${bg.img}`))
|
46 |
+
return true
|
47 |
+
}
|
48 |
+
let uid = e.uid || (e.targetUser && e.targetUser.uid)
|
49 |
+
let data = {}
|
50 |
+
let custom = char.isCustom
|
51 |
+
let isRelease = char.isRelease
|
52 |
+
if (isRelease && avatar.hasData) {
|
53 |
+
data = avatar.getDetail()
|
54 |
+
data.imgs = char.imgs
|
55 |
+
data.source = avatar._source
|
56 |
+
data.artis = avatar.getArtisDetail()
|
57 |
+
data.updateTime = moment(new Date(avatar._time)).format('MM-DD HH:mm')
|
58 |
+
if (data.hasTalent) {
|
59 |
+
data.talent = avatar.talent
|
60 |
+
data.talentMap = ['a', 'e', 'q']
|
61 |
+
// 计算皇冠个数
|
62 |
+
data.crownNum = lodash.filter(lodash.map(data.talent, (d) => d.original), (d) => d >= 10).length
|
63 |
+
}
|
64 |
+
} else {
|
65 |
+
data = char.getData('id,name,sName')
|
66 |
+
}
|
67 |
+
|
68 |
+
let width = 600
|
69 |
+
let imgCss = ''
|
70 |
+
let scale = 1.2
|
71 |
+
if (bg.mode === 'left') {
|
72 |
+
const height = 480
|
73 |
+
width = height * bg.width / bg.height
|
74 |
+
imgCss = `img.bg{width:auto;height:${height}px;}`
|
75 |
+
scale = 1.45
|
76 |
+
}
|
77 |
+
// 渲染图像
|
78 |
+
let msgRes = await Common.render('character/character-card', {
|
79 |
+
saveId: uid,
|
80 |
+
uid,
|
81 |
+
bg,
|
82 |
+
widthStyle: `<style>html,body,#container{width:${width}px} ${imgCss}</style>`,
|
83 |
+
mode: bg.mode,
|
84 |
+
custom,
|
85 |
+
isRelease,
|
86 |
+
data
|
87 |
+
}, { e, scale, retMsgId: true })
|
88 |
+
if (msgRes) {
|
89 |
+
// 如果消息发送成功,就将message_id和图片路径存起来,3小时过期
|
90 |
+
const message_id = [e.message_id]
|
91 |
+
if (Array.isArray(msgRes.message_id)) {
|
92 |
+
message_id.push(...msgRes.message_id)
|
93 |
+
} else {
|
94 |
+
message_id.push(msgRes.message_id)
|
95 |
+
}
|
96 |
+
for (const i of message_id) {
|
97 |
+
await redis.set(`miao:original-picture:${i}`, JSON.stringify({ type: 'character', img: bg.img }), { EX: 3600 * 3 })
|
98 |
+
}
|
99 |
+
}
|
100 |
+
return true
|
101 |
+
},
|
102 |
+
check (e) {
|
103 |
+
let msg = e.original_msg || e.msg
|
104 |
+
if (!msg || !/^#/.exec(msg)) {
|
105 |
+
return false
|
106 |
+
}
|
107 |
+
if (!Common.cfg('avatarCard')) {
|
108 |
+
return false
|
109 |
+
}
|
110 |
+
let uidRet = /[0-9]{9}/.exec(msg)
|
111 |
+
if (uidRet) {
|
112 |
+
e.uid = uidRet[0]
|
113 |
+
msg = msg.replace(uidRet[0], '')
|
114 |
+
}
|
115 |
+
let name = msg.replace(/#|老婆|老公|卡片/g, '').trim()
|
116 |
+
|
117 |
+
// cache gsCfg
|
118 |
+
Character.gsCfg = Character.gsCfg || e?.runtime?.gsCfg
|
119 |
+
|
120 |
+
let char = Character.get(name.trim())
|
121 |
+
|
122 |
+
if (!char) {
|
123 |
+
return false
|
124 |
+
}
|
125 |
+
|
126 |
+
e.msg = '#喵喵角色卡片'
|
127 |
+
e.char = char
|
128 |
+
return true
|
129 |
+
}
|
130 |
+
|
131 |
+
}
|
132 |
+
export default Avatar
|
Yunzai/plugins/miao-plugin/apps/character/AvatarWife.js
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// #老婆
|
2 |
+
import lodash from 'lodash'
|
3 |
+
import { Common } from '#miao'
|
4 |
+
import { Character, MysApi, Player } from '#miao.models'
|
5 |
+
import Avatar from './AvatarCard.js'
|
6 |
+
|
7 |
+
const relationMap = {
|
8 |
+
wife: {
|
9 |
+
keyword: '老婆,媳妇,妻子,娘子,宝贝'.split(','),
|
10 |
+
type: 0
|
11 |
+
},
|
12 |
+
husband: {
|
13 |
+
keyword: '老公,丈夫,夫君,郎君,死鬼'.split(','),
|
14 |
+
type: 1
|
15 |
+
},
|
16 |
+
gf: {
|
17 |
+
keyword: '女朋友,女友,女神,女王,女票'.split(','),
|
18 |
+
type: 0
|
19 |
+
},
|
20 |
+
bf: {
|
21 |
+
keyword: '男朋友,男友,男神,男票'.split(','),
|
22 |
+
type: 1
|
23 |
+
},
|
24 |
+
daughter: {
|
25 |
+
keyword: '女儿,闺女,小宝贝'.split(','),
|
26 |
+
type: 2
|
27 |
+
},
|
28 |
+
son: {
|
29 |
+
keyword: '儿子,犬子'.split(','),
|
30 |
+
type: 3
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
const relation = lodash.flatMap(relationMap, (d) => d.keyword)
|
35 |
+
const wifeReg = `^#?\\s*(${relation.join('|')})\\s*(设置|选择|指定|列表|查询|列表|是|是谁|照片|相片|图片|写真|图像)?\\s*([^\\d]*)\\s*(\\d*)$`
|
36 |
+
|
37 |
+
async function getAvatarList (player, type) {
|
38 |
+
await player.refreshMysDetail()
|
39 |
+
let list = []
|
40 |
+
player.forEachAvatar((avatar) => {
|
41 |
+
if (type !== false) {
|
42 |
+
if (!avatar.char.checkWifeType(type)) {
|
43 |
+
return true
|
44 |
+
}
|
45 |
+
}
|
46 |
+
list.push(avatar)
|
47 |
+
})
|
48 |
+
|
49 |
+
if (list.length <= 0) {
|
50 |
+
return false
|
51 |
+
}
|
52 |
+
let sortKey = 'level,fetter,weapon_level,rarity,weapon_rarity,cons,weapon_affix_level'
|
53 |
+
list = lodash.orderBy(list, sortKey, lodash.repeat('desc,', sortKey.length).split(','))
|
54 |
+
return list
|
55 |
+
}
|
56 |
+
|
57 |
+
const Wife = {
|
58 |
+
reg: wifeReg,
|
59 |
+
async render (e) {
|
60 |
+
let msg = e.msg || ''
|
61 |
+
if (!msg && !e.isPoke) return false
|
62 |
+
|
63 |
+
if (e.isPoke) {
|
64 |
+
if (!Common.cfg('avatarPoke')) {
|
65 |
+
return false
|
66 |
+
}
|
67 |
+
} else if (!Common.cfg('avatarCard')) {
|
68 |
+
return false
|
69 |
+
}
|
70 |
+
|
71 |
+
let msgRet = (new RegExp(wifeReg)).exec(msg)
|
72 |
+
if (e.isPoke) {
|
73 |
+
msgRet = []
|
74 |
+
} else if (!msgRet) {
|
75 |
+
return false
|
76 |
+
}
|
77 |
+
let target = msgRet[1]
|
78 |
+
let action = msgRet[2] || '卡片'
|
79 |
+
let actionParam = msgRet[3] || ''
|
80 |
+
|
81 |
+
if (!'设置,选择,挑选,指定'.split(',').includes(action) && actionParam) {
|
82 |
+
return false
|
83 |
+
}
|
84 |
+
|
85 |
+
let targetCfg = lodash.find(relationMap, (cfg, key) => {
|
86 |
+
cfg.key = key
|
87 |
+
return cfg.keyword.includes(target)
|
88 |
+
})
|
89 |
+
if (!targetCfg && !e.isPoke) return true
|
90 |
+
|
91 |
+
let avatarList = []
|
92 |
+
let avatar = {}
|
93 |
+
let wifeList = []
|
94 |
+
|
95 |
+
let mys = await MysApi.init(e)
|
96 |
+
if (!mys || !mys.uid) {
|
97 |
+
return true
|
98 |
+
}
|
99 |
+
let player = Player.create(e)
|
100 |
+
let selfUser = mys.selfUser
|
101 |
+
let isSelf = true
|
102 |
+
let renderType = (action === '卡片' ? 'card' : 'photo')
|
103 |
+
let addRet = []
|
104 |
+
switch (action) {
|
105 |
+
case '卡片':
|
106 |
+
case '照片':
|
107 |
+
case '相片':
|
108 |
+
case '图片':
|
109 |
+
case '写真':
|
110 |
+
// 展示老婆卡片
|
111 |
+
// 如果选择过,则进行展示
|
112 |
+
if (!e.isPoke) {
|
113 |
+
wifeList = await selfUser.getCfg(`wife.${targetCfg.key}`, [])
|
114 |
+
// 存在设置
|
115 |
+
if (wifeList && wifeList.length > 0 && isSelf && !e.isPoke) {
|
116 |
+
if (wifeList[0] === '随机') {
|
117 |
+
// 如果选择为全部,则从列表中随机选择一个
|
118 |
+
avatarList = await getAvatarList(player, targetCfg.type, mys)
|
119 |
+
let avatar = lodash.sample(avatarList)
|
120 |
+
return Avatar.renderAvatar(e, avatar, renderType)
|
121 |
+
} else {
|
122 |
+
// 如果指定过,则展示指定角色
|
123 |
+
return Avatar.renderAvatar(e, lodash.sample(wifeList), renderType)
|
124 |
+
}
|
125 |
+
}
|
126 |
+
}
|
127 |
+
// 如果未指定过,则从列表中排序并随机选择
|
128 |
+
avatarList = await getAvatarList(player, e.isPoke ? false : targetCfg.type, mys)
|
129 |
+
if (avatarList && avatarList.length > 0) {
|
130 |
+
avatar = lodash.sample(avatarList)
|
131 |
+
return await Avatar.renderAvatar(e, avatar, renderType)
|
132 |
+
}
|
133 |
+
e.reply('在当前米游社公开展示的角色中未能找到适合展示的角色..')
|
134 |
+
return true
|
135 |
+
case '设置':
|
136 |
+
case '选择':
|
137 |
+
case '挑选':
|
138 |
+
case '指定':
|
139 |
+
if (!isSelf) {
|
140 |
+
e.reply('只能指定自己的哦~')
|
141 |
+
return true
|
142 |
+
}
|
143 |
+
// 选择老婆
|
144 |
+
actionParam = actionParam.replace(/(,|、|;|;)/g, ',')
|
145 |
+
wifeList = actionParam.split(',')
|
146 |
+
if (lodash.intersection(['全部', '任意', '随机', '全都要'], wifeList).length > 0) {
|
147 |
+
addRet = ['随机']
|
148 |
+
} else {
|
149 |
+
wifeList = lodash.map(wifeList, (name) => {
|
150 |
+
let char = Character.get(name)
|
151 |
+
if (char && char.checkWifeType(targetCfg.type)) {
|
152 |
+
return char.name
|
153 |
+
}
|
154 |
+
})
|
155 |
+
wifeList = lodash.filter(lodash.uniq(wifeList), (d) => !!d)
|
156 |
+
addRet = wifeList
|
157 |
+
if (addRet.length === 0) {
|
158 |
+
e.reply(`在可选的${targetCfg.keyword[0]}列表中未能找到 ${actionParam} ~`)
|
159 |
+
return true
|
160 |
+
}
|
161 |
+
}
|
162 |
+
await selfUser.setCfg(`wife.${targetCfg.key}`, addRet)
|
163 |
+
e.reply(`${targetCfg.keyword[0]}已经设置:${addRet.join(',')}`)
|
164 |
+
return true
|
165 |
+
case '列表':
|
166 |
+
case '是':
|
167 |
+
case '是谁':
|
168 |
+
// 查看当前选择老婆
|
169 |
+
if (!isSelf) {
|
170 |
+
e.reply('只能查看自己的哦~')
|
171 |
+
return true
|
172 |
+
}
|
173 |
+
wifeList = await selfUser.getCfg(`wife.${targetCfg.key}`, [])
|
174 |
+
if (wifeList && wifeList.length > 0) {
|
175 |
+
e.reply(`你的${targetCfg.keyword[0]}是:${wifeList.join(',')}`)
|
176 |
+
} else {
|
177 |
+
e.reply(`尚未设置,回复#${targetCfg.keyword[0]}设置+角色名 来设置,如果设置多位请用逗号间隔`)
|
178 |
+
}
|
179 |
+
break
|
180 |
+
}
|
181 |
+
return true
|
182 |
+
},
|
183 |
+
async poke (e) {
|
184 |
+
return await Wife.render(e)
|
185 |
+
}
|
186 |
+
}
|
187 |
+
|
188 |
+
export default Wife
|
Yunzai/plugins/miao-plugin/apps/character/ImgUpload.js
ADDED
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import fs from 'fs'
|
2 |
+
import { promisify } from 'util'
|
3 |
+
import { pipeline } from 'stream'
|
4 |
+
import MD5 from 'md5'
|
5 |
+
import fetch from 'node-fetch'
|
6 |
+
import lodash from 'lodash'
|
7 |
+
import { Cfg, Data } from '#miao'
|
8 |
+
import { Character } from '#miao.models'
|
9 |
+
|
10 |
+
const resPath = process.cwd() + '/plugins/miao-plugin/resources/'
|
11 |
+
let regex = /^#?\s*(?:喵喵)?(?:上传|添加)(.+)(?:照片|写真|图片|图像)\s*$/
|
12 |
+
let profileRegex = /^#?\s*(?:喵喵)?(?:上传|添加)(.+)(?:面板图)\s*$/
|
13 |
+
let isProfile = false
|
14 |
+
|
15 |
+
export async function uploadCharacterImg (e) {
|
16 |
+
let promise = await isAllowedToUploadCharacterImage(e)
|
17 |
+
if (!promise) {
|
18 |
+
return false
|
19 |
+
}
|
20 |
+
|
21 |
+
let imageMessages = []
|
22 |
+
let msg = e.msg
|
23 |
+
let regRet = regex.exec(msg)
|
24 |
+
if (msg.includes('面板')) {
|
25 |
+
isProfile = true
|
26 |
+
regRet = profileRegex.exec(msg)
|
27 |
+
} else {
|
28 |
+
isProfile = false
|
29 |
+
}
|
30 |
+
|
31 |
+
// 通过解析正则获取消息中的角色名
|
32 |
+
if (!regRet || !regRet[1]) {
|
33 |
+
return false
|
34 |
+
}
|
35 |
+
let char = Character.get(regRet[1])
|
36 |
+
if (!char || !char.name) {
|
37 |
+
return false
|
38 |
+
}
|
39 |
+
let name = char.name
|
40 |
+
for (let val of e.message) {
|
41 |
+
if (val.type === 'image') {
|
42 |
+
imageMessages.push(val)
|
43 |
+
}
|
44 |
+
}
|
45 |
+
if (imageMessages.length === 0) {
|
46 |
+
let source
|
47 |
+
if (e.getReply) {
|
48 |
+
source = await e.getReply()
|
49 |
+
} else if (e.source) {
|
50 |
+
if (e.group?.getChatHistory) {
|
51 |
+
// 支持at图片添加,以及支持后发送
|
52 |
+
source = (await e.group.getChatHistory(e.source?.seq, 1)).pop()
|
53 |
+
} else if (e.friend?.getChatHistory) {
|
54 |
+
source = (await e.friend.getChatHistory((e.source?.time + 1), 1)).pop()
|
55 |
+
}
|
56 |
+
}
|
57 |
+
if (source) {
|
58 |
+
for (let val of source.message) {
|
59 |
+
if (val.type === 'image') {
|
60 |
+
imageMessages.push(val)
|
61 |
+
} else if (val.type === 'xml' || val.type === 'forward') {// 支持合并转发消息内置的图片批量上传,喵喵 喵喵喵? 喵喵喵喵
|
62 |
+
let resid
|
63 |
+
try {
|
64 |
+
resid = val.data.match(/m_resid="(\d|\w|\/|\+)*"/)[0].replace(/m_resid=|"/g, '')
|
65 |
+
} catch (err) {
|
66 |
+
console.log('Miao合并上传:转换id获取')
|
67 |
+
resid = val.id
|
68 |
+
}
|
69 |
+
if (!resid) break
|
70 |
+
let message = await e.bot.getForwardMsg(resid)
|
71 |
+
for (const item of message) {
|
72 |
+
for (const i of item.message) {
|
73 |
+
if (i.type === 'image') {
|
74 |
+
imageMessages.push(i)
|
75 |
+
}
|
76 |
+
}
|
77 |
+
}
|
78 |
+
}
|
79 |
+
}
|
80 |
+
}
|
81 |
+
}
|
82 |
+
if (imageMessages.length <= 0) {
|
83 |
+
e.reply('消息中未找到图片,请将要发送的图片与消息一同发送或引用要添加的图像..')
|
84 |
+
return true
|
85 |
+
}
|
86 |
+
await saveImages(e, name, imageMessages)
|
87 |
+
return true
|
88 |
+
}
|
89 |
+
|
90 |
+
async function saveImages (e, name, imageMessages) {
|
91 |
+
let imgMaxSize = e?.groupConfig?.imgMaxSize || 5
|
92 |
+
let pathSuffix = `character-img/${name}/upload`
|
93 |
+
if (isProfile) pathSuffix = `profile/normal-character/${name}`
|
94 |
+
let path = resPath + pathSuffix
|
95 |
+
|
96 |
+
if (!fs.existsSync(path)) {
|
97 |
+
Data.createDir("resources/" + pathSuffix, 'miao')
|
98 |
+
}
|
99 |
+
let senderName = lodash.truncate(e.sender.card, { length: 8 })
|
100 |
+
let imgCount = 0
|
101 |
+
let urlMap = {}
|
102 |
+
for (let val of imageMessages) {
|
103 |
+
if (!val.url || urlMap[val.url]) {
|
104 |
+
continue
|
105 |
+
}
|
106 |
+
urlMap[val.url] = true
|
107 |
+
const response = await fetch(val.url)
|
108 |
+
if (!response.ok) {
|
109 |
+
e.reply('图片下载失败。')
|
110 |
+
return true
|
111 |
+
}
|
112 |
+
if (response.headers.get('size') > 1024 * 1024 * imgMaxSize) {
|
113 |
+
e.reply([segment.at(e.user_id, senderName), '添加失败:图片太大了。'])
|
114 |
+
return true
|
115 |
+
}
|
116 |
+
let fileName = ""
|
117 |
+
let fileType = "png"
|
118 |
+
if (val.file) {
|
119 |
+
fileName = val.file.substring(0, val.file.lastIndexOf('.'))
|
120 |
+
fileType = val.file.substring(val.file.lastIndexOf('.') + 1)
|
121 |
+
}
|
122 |
+
if (response.headers.get('content-type') === 'image/gif') {
|
123 |
+
fileType = 'gif'
|
124 |
+
}
|
125 |
+
if (isProfile) fileType = 'webp'
|
126 |
+
let imgPath = `${path}/${fileName}.${fileType}`
|
127 |
+
const streamPipeline = promisify(pipeline)
|
128 |
+
await streamPipeline(response.body, fs.createWriteStream(imgPath))
|
129 |
+
|
130 |
+
// 使用md5作为文件名
|
131 |
+
let buffers = fs.readFileSync(imgPath)
|
132 |
+
let base64 = Buffer.from(buffers, 'base64').toString()
|
133 |
+
let md5 = MD5(base64)
|
134 |
+
let newImgPath = `${path}/${md5}.${fileType}`
|
135 |
+
if (fs.existsSync(newImgPath)) {
|
136 |
+
fs.unlink(newImgPath, (err) => {
|
137 |
+
console.log('unlink', err)
|
138 |
+
})
|
139 |
+
}
|
140 |
+
fs.rename(imgPath, newImgPath, () => {
|
141 |
+
})
|
142 |
+
imgCount++
|
143 |
+
Bot.logger.mark(`添加成功: ${newImgPath}`)
|
144 |
+
}
|
145 |
+
e.reply([segment.at(e.user_id, senderName), `\n成功添加${imgCount}张${name}${isProfile ? '面板图' : '图片'}。`])
|
146 |
+
return true
|
147 |
+
}
|
148 |
+
|
149 |
+
async function isAllowedToUploadCharacterImage (e) {
|
150 |
+
let sendMsg = /上传|添加/.test(e.msg) ? '添加' : '删除'
|
151 |
+
if (!e.message) {
|
152 |
+
return false
|
153 |
+
}
|
154 |
+
if (!e.msg) {
|
155 |
+
return false
|
156 |
+
}
|
157 |
+
// master直接返回true
|
158 |
+
if (e.isMaster) {
|
159 |
+
return true
|
160 |
+
}
|
161 |
+
if (e.isPrivate) {
|
162 |
+
e.reply(`只有主人才能${sendMsg}...`)
|
163 |
+
return false
|
164 |
+
}
|
165 |
+
let groupId = e.group_id
|
166 |
+
if (!groupId) {
|
167 |
+
return false
|
168 |
+
}
|
169 |
+
const addLimit = e.groupConfig?.imgAddLimit || 2
|
170 |
+
const isAdmin = ['owner', 'admin'].includes(e.sender.role)
|
171 |
+
if (addLimit === 2) {
|
172 |
+
e.reply(`只有主人才能${sendMsg}...`)
|
173 |
+
return false
|
174 |
+
}
|
175 |
+
if (addLimit === 1 && !isAdmin) {
|
176 |
+
e.reply(`只有管理员才能${sendMsg}...`)
|
177 |
+
return false
|
178 |
+
}
|
179 |
+
return true
|
180 |
+
}
|
181 |
+
|
182 |
+
// 仅支持面板图删除
|
183 |
+
export async function delProfileImg (e) {
|
184 |
+
let promise = await isAllowedToUploadCharacterImage(e)
|
185 |
+
if (!promise) {
|
186 |
+
return false
|
187 |
+
}
|
188 |
+
let char = Character.get(e.msg.replace(/#|面板图|列表|上传|删除|\d+/g, '').trim())
|
189 |
+
if (!char || !char.name) {
|
190 |
+
return false
|
191 |
+
}
|
192 |
+
let name = char.name
|
193 |
+
let pathSuffix = `profile/normal-character/${name}`
|
194 |
+
let path = resPath + pathSuffix
|
195 |
+
let num = e.msg.match(/\d+/)
|
196 |
+
if (!num) {
|
197 |
+
e.reply(`删除哪张捏?请输入数字序列号,可输入【#${name}面板图列表】查看序列号`)
|
198 |
+
return
|
199 |
+
}
|
200 |
+
try {
|
201 |
+
let imgs = fs.readdirSync(`${path}`).filter((file) => {
|
202 |
+
return /\.(png|webp)$/.test(file)
|
203 |
+
})
|
204 |
+
fs.unlinkSync(`${path}/${imgs[num - 1]}`)
|
205 |
+
e.reply('删除成功')
|
206 |
+
} catch (err) {
|
207 |
+
e.reply('删除失败,请检查序列号是否正确')
|
208 |
+
}
|
209 |
+
return true
|
210 |
+
}
|
211 |
+
|
212 |
+
export async function profileImgList (e) {
|
213 |
+
let msglist = []
|
214 |
+
let char = Character.get(e.msg.replace(/#|面板图列表/g, ''))
|
215 |
+
if (!char || !char.name) {
|
216 |
+
return false
|
217 |
+
}
|
218 |
+
if ([1, 0].includes(Cfg.get('originalPic') * 1)) {
|
219 |
+
e.reply('已禁止获取面板图列表')
|
220 |
+
return true
|
221 |
+
}
|
222 |
+
let name = char.name
|
223 |
+
let pathSuffix = `profile/normal-character/${name}`
|
224 |
+
let path = resPath + pathSuffix
|
225 |
+
if (!fs.existsSync(path)) {
|
226 |
+
e.reply(`暂无${char.name}的角色面板图`)
|
227 |
+
return true
|
228 |
+
}
|
229 |
+
try {
|
230 |
+
let imgs = fs.readdirSync(`${path}`).filter((file) => {
|
231 |
+
return /\.(png|webp)$/.test(file)
|
232 |
+
})
|
233 |
+
msglist.push({
|
234 |
+
message: [`当前查看的是${name}面板图,共${imgs.length}张,可输入【#删除${name}面板图(序列号)】进行删除`],
|
235 |
+
})
|
236 |
+
for (let i = 0; i < imgs.length; i++) {
|
237 |
+
// 合并转发最多99? 但是我感觉不会有这么多先不做处理
|
238 |
+
console.log(`${path}${imgs[i]}`)
|
239 |
+
msglist.push({
|
240 |
+
message: [`${i + 1}.`, segment.image(`file://${path}/${imgs[i]}`)],
|
241 |
+
})
|
242 |
+
}
|
243 |
+
let msg
|
244 |
+
if (e.group?.makeForwardMsg) {
|
245 |
+
msg = await e.group.makeForwardMsg(msglist)
|
246 |
+
} else if (e.friend?.makeForwardMsg) {
|
247 |
+
msg = await e.friend.makeForwardMsg(msglist)
|
248 |
+
} else {
|
249 |
+
msg = await Bot.makeForwardMsg(msglist)
|
250 |
+
}
|
251 |
+
let msgRsg = await e.reply(msg)
|
252 |
+
if (!msgRsg) e.reply('风控了,可私聊查看', true)
|
253 |
+
} catch (err) {
|
254 |
+
logger.error(err)
|
255 |
+
e.reply(`暂无${char.name}的角色面板图~`)
|
256 |
+
}
|
257 |
+
return true
|
258 |
+
}
|
Yunzai/plugins/miao-plugin/apps/gacha.js
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Gacha from './gacha/Gacha.js'
|
2 |
+
import { App, Cfg } from '#miao'
|
3 |
+
|
4 |
+
let app = App.init({
|
5 |
+
id: 'gacha',
|
6 |
+
name: '抽卡统计'
|
7 |
+
})
|
8 |
+
app.reg({
|
9 |
+
detail: {
|
10 |
+
name: '抽卡记录',
|
11 |
+
fn: Gacha.detail,
|
12 |
+
rule: /^#*喵喵(抽卡|抽奖|角色|武器|常驻|up)+池?(记录|祈愿|分析)$/,
|
13 |
+
yzRule: /^#*(抽卡|抽奖|角色|武器|常驻|up)+池?(记录|祈愿|分析)$/,
|
14 |
+
yzCheck: () => Cfg.get('gachaStat', false)
|
15 |
+
},
|
16 |
+
stat: {
|
17 |
+
name: '抽卡统计',
|
18 |
+
fn: Gacha.stat,
|
19 |
+
rule: /^#*喵喵(全部|抽卡|抽奖|角色|武器|常驻|up|版本)+池?统计$/,
|
20 |
+
yzRule: /^#*(全部|抽卡|抽奖|角色|武器|常驻|up|版本)+池?统计$/,
|
21 |
+
yzCheck: () => Cfg.get('gachaStat', false)
|
22 |
+
|
23 |
+
}
|
24 |
+
})
|
25 |
+
|
26 |
+
export default app
|
Yunzai/plugins/miao-plugin/apps/gacha/Gacha.js
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Common } from '#miao'
|
2 |
+
import { getTargetUid } from '../profile/ProfileCommon.js'
|
3 |
+
import GachaData from './GachaData.js'
|
4 |
+
import { Character, Player } from '#miao.models'
|
5 |
+
|
6 |
+
let Gacha = {
|
7 |
+
async detail (e) {
|
8 |
+
let msg = e.msg.replace(/#|抽卡|记录|祈愿|分析|池/g, '')
|
9 |
+
let type = 301
|
10 |
+
switch (msg) {
|
11 |
+
case 'up':
|
12 |
+
case '抽卡':
|
13 |
+
case '角色':
|
14 |
+
case '抽奖':
|
15 |
+
type = 301
|
16 |
+
break
|
17 |
+
case '常驻':
|
18 |
+
type = 200
|
19 |
+
break
|
20 |
+
case '武器':
|
21 |
+
type = 302
|
22 |
+
break
|
23 |
+
}
|
24 |
+
let uid = e.uid || await getTargetUid(e)
|
25 |
+
let qq = e.user_id
|
26 |
+
if (!uid || !qq) {
|
27 |
+
return false
|
28 |
+
}
|
29 |
+
|
30 |
+
let gacha = GachaData.analyse(e.user_id, uid, type)
|
31 |
+
if (!gacha) {
|
32 |
+
e.reply(`UID:${uid} 本地暂无抽卡信息,请通过【#抽卡帮助】获得绑定帮助...`)
|
33 |
+
return true
|
34 |
+
}
|
35 |
+
await Common.render('gacha/gacha-detail', {
|
36 |
+
save_id: uid,
|
37 |
+
uid,
|
38 |
+
gacha,
|
39 |
+
face: Gacha.getFace(uid)
|
40 |
+
}, { e, scale: 1.4, retMsgId: true })
|
41 |
+
},
|
42 |
+
async stat (e) {
|
43 |
+
let msg = e.msg.replace(/#|统计|分析|池/g, '')
|
44 |
+
let type = 'up'
|
45 |
+
if (/武器/.test(msg)) {
|
46 |
+
type = 'weapon'
|
47 |
+
} else if (/角色/.test(msg)) {
|
48 |
+
type = 'char'
|
49 |
+
} else if (/常驻/.test(msg)) {
|
50 |
+
type = 'normal'
|
51 |
+
} else if (/全部/.test(msg)) {
|
52 |
+
type = 'all'
|
53 |
+
}
|
54 |
+
let uid = e.uid || await getTargetUid(e)
|
55 |
+
let qq = e.user_id
|
56 |
+
if (!uid || !qq) {
|
57 |
+
return false
|
58 |
+
}
|
59 |
+
let gacha = GachaData.stat(e.user_id, uid, type)
|
60 |
+
if (!gacha) {
|
61 |
+
e.reply(`UID:${uid} 本地暂无抽卡信息,请通过【#抽卡帮助】获得绑定帮助...`)
|
62 |
+
return true
|
63 |
+
}
|
64 |
+
await Common.render('gacha/gacha-stat', {
|
65 |
+
save_id: uid,
|
66 |
+
uid,
|
67 |
+
gacha,
|
68 |
+
face: Gacha.getFace(uid)
|
69 |
+
}, { e, scale: 1.4 })
|
70 |
+
},
|
71 |
+
|
72 |
+
getFace (uid) {
|
73 |
+
let player = Player.create(uid)
|
74 |
+
|
75 |
+
let faceChar = Character.get(player.face || 10000014)
|
76 |
+
let imgs = faceChar?.imgs
|
77 |
+
if (!imgs?.face) {
|
78 |
+
imgs = Character.get(10000079).imgs
|
79 |
+
}
|
80 |
+
return {
|
81 |
+
banner: imgs?.banner,
|
82 |
+
face: imgs?.face,
|
83 |
+
qFace: imgs?.qFace,
|
84 |
+
name: player.name || '旅行者',
|
85 |
+
sign: player.sign,
|
86 |
+
level: player.level
|
87 |
+
}
|
88 |
+
}
|
89 |
+
}
|
90 |
+
export default Gacha
|
Yunzai/plugins/miao-plugin/apps/gacha/GachaData.js
ADDED
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import { Data } from '#miao'
|
3 |
+
import { Character, Weapon } from '#miao.models'
|
4 |
+
import { poolDetail } from '../../resources/meta/info/index.js'
|
5 |
+
import moment from 'moment'
|
6 |
+
|
7 |
+
let poolVersion = []
|
8 |
+
lodash.forEach(poolDetail, (ds) => {
|
9 |
+
poolVersion.push({
|
10 |
+
...ds,
|
11 |
+
start: new Date(ds.from),
|
12 |
+
end: new Date(ds.to)
|
13 |
+
})
|
14 |
+
})
|
15 |
+
let last = poolVersion[poolVersion.length - 1]
|
16 |
+
// 为未知卡池做兼容
|
17 |
+
poolVersion.push({
|
18 |
+
version: '新版本',
|
19 |
+
half: '?',
|
20 |
+
from: last.to,
|
21 |
+
to: '2025-12-31 23:59:59',
|
22 |
+
start: last.end,
|
23 |
+
end: new Date('2025-12-31 23:59:59')
|
24 |
+
})
|
25 |
+
|
26 |
+
let GachaData = {
|
27 |
+
|
28 |
+
// 获取JSON数据
|
29 |
+
readJSON (qq, uid, type) {
|
30 |
+
let logJson = []
|
31 |
+
// 获取本地数据 进行数据合并
|
32 |
+
logJson = Data.readJSON(`/data/gachaJson/${qq}/${uid}/${type}.json`, 'root')
|
33 |
+
let itemMap = {}
|
34 |
+
let nameMap = {}
|
35 |
+
let items = []
|
36 |
+
let ids = {}
|
37 |
+
lodash.forEach(logJson, (ds) => {
|
38 |
+
if (!nameMap[ds.name]) {
|
39 |
+
if (ds.item_type === '武器') {
|
40 |
+
let weapon = Weapon.get(ds.name)
|
41 |
+
if (weapon) {
|
42 |
+
nameMap[ds.name] = weapon.id
|
43 |
+
itemMap[weapon.id] = {
|
44 |
+
type: 'weapon',
|
45 |
+
count: 0,
|
46 |
+
...weapon.getData('star,name,abbr,img')
|
47 |
+
}
|
48 |
+
} else {
|
49 |
+
nameMap[ds.name] = 403
|
50 |
+
itemMap[403] = {
|
51 |
+
type: 'weapon',
|
52 |
+
count: 0,
|
53 |
+
star: 3,
|
54 |
+
name: '未知',
|
55 |
+
abbr: '未知',
|
56 |
+
img: ''
|
57 |
+
}
|
58 |
+
}
|
59 |
+
} else if (ds.item_type === '角色') {
|
60 |
+
let char = Character.get(ds.name)
|
61 |
+
if (char) {
|
62 |
+
nameMap[ds.name] = char.id
|
63 |
+
itemMap[char.id] = {
|
64 |
+
type: 'char',
|
65 |
+
count: 0,
|
66 |
+
...char.getData('star,name,abbr,img:face')
|
67 |
+
}
|
68 |
+
} else {
|
69 |
+
nameMap[ds.name] = 404
|
70 |
+
itemMap[404] = {
|
71 |
+
type: 'char',
|
72 |
+
count: 0,
|
73 |
+
star: 4,
|
74 |
+
name: '未知',
|
75 |
+
abbr: '未知',
|
76 |
+
img: ''
|
77 |
+
}
|
78 |
+
}
|
79 |
+
}
|
80 |
+
}
|
81 |
+
let id = nameMap[ds.name]
|
82 |
+
if (!id || !itemMap[id] || (ds.id && ids[ds.id])) {
|
83 |
+
return true
|
84 |
+
}
|
85 |
+
ids[ds.id] = true
|
86 |
+
items.push({
|
87 |
+
id,
|
88 |
+
logId: ds.id,
|
89 |
+
time: new Date(ds.time)
|
90 |
+
})
|
91 |
+
})
|
92 |
+
items = items.sort((a, b) => b.time - a.time)
|
93 |
+
return { items, itemMap }
|
94 |
+
},
|
95 |
+
|
96 |
+
// 卡池分析
|
97 |
+
analyse (qq, uid, type) {
|
98 |
+
let logData = GachaData.readJSON(qq, uid, type)
|
99 |
+
let fiveLog = []
|
100 |
+
let fourLog = []
|
101 |
+
let fiveNum = 0
|
102 |
+
let fourNum = 0
|
103 |
+
let fiveLogNum = 0
|
104 |
+
let fourLogNum = 0
|
105 |
+
let noFiveNum = 0
|
106 |
+
let noFourNum = 0
|
107 |
+
let wai = 0 // 歪
|
108 |
+
let weaponNum = 0
|
109 |
+
let weaponFourNum = 0
|
110 |
+
let bigNum = 0
|
111 |
+
let allNum = 0
|
112 |
+
|
113 |
+
let itemMap = logData.itemMap
|
114 |
+
if (logData.items.length === 0) {
|
115 |
+
return false
|
116 |
+
}
|
117 |
+
let currVersion
|
118 |
+
lodash.forEach(logData.items, (item) => {
|
119 |
+
if (!currVersion || (item.time < currVersion.start)) {
|
120 |
+
currVersion = GachaData.getVersion(item.time)
|
121 |
+
}
|
122 |
+
|
123 |
+
allNum++
|
124 |
+
let ds = itemMap[item.id]
|
125 |
+
let { star, type } = ds
|
126 |
+
ds.count++
|
127 |
+
if (star === 4) {
|
128 |
+
fourNum++
|
129 |
+
if (noFourNum === 0) {
|
130 |
+
noFourNum = fourLogNum
|
131 |
+
}
|
132 |
+
fourLogNum = 0
|
133 |
+
if (fourLog[ds.name]) {
|
134 |
+
fourLog[ds.name]++
|
135 |
+
} else {
|
136 |
+
fourLog[ds.name] = 1
|
137 |
+
}
|
138 |
+
if (type === 'weapon') {
|
139 |
+
weaponFourNum++
|
140 |
+
}
|
141 |
+
}
|
142 |
+
fourLogNum++
|
143 |
+
|
144 |
+
if (star === 5) {
|
145 |
+
fiveNum++
|
146 |
+
if (fiveLog.length > 0) {
|
147 |
+
fiveLog[fiveLog.length - 1].count = fiveLogNum
|
148 |
+
} else {
|
149 |
+
noFiveNum = fiveLogNum
|
150 |
+
}
|
151 |
+
fiveLogNum = 0
|
152 |
+
let isUp = false
|
153 |
+
// 歪了多少个
|
154 |
+
if (type === 'char') {
|
155 |
+
if (!currVersion.hasOwnProperty("char5") || currVersion.char5.includes(ds.name)) {
|
156 |
+
isUp = true
|
157 |
+
} else {
|
158 |
+
wai++
|
159 |
+
}
|
160 |
+
} else {
|
161 |
+
if (currVersion.weapon5.includes(ds.name)) {
|
162 |
+
isUp = true
|
163 |
+
} else {
|
164 |
+
wai++
|
165 |
+
}
|
166 |
+
}
|
167 |
+
|
168 |
+
fiveLog.push({
|
169 |
+
id: item.id,
|
170 |
+
isUp,
|
171 |
+
date: moment(item.time).format('MM-DD')
|
172 |
+
})
|
173 |
+
}
|
174 |
+
fiveLogNum++
|
175 |
+
})
|
176 |
+
|
177 |
+
if (fiveLog.length > 0) {
|
178 |
+
fiveLog[fiveLog.length - 1].count = fiveLogNum
|
179 |
+
} else {
|
180 |
+
// 没有五星
|
181 |
+
noFiveNum = allNum
|
182 |
+
}
|
183 |
+
|
184 |
+
// 四星最多
|
185 |
+
let fourItem = lodash.filter(lodash.values(itemMap), (ds) => ds.star === 4)
|
186 |
+
fourItem.push({ name: '无', count: 0 })
|
187 |
+
fourItem = fourItem.sort((a, b) => b.count - a.count)
|
188 |
+
|
189 |
+
// 平均5星
|
190 |
+
let fiveAvg = 0
|
191 |
+
let fourAvg = 0
|
192 |
+
if (fiveNum > 0) {
|
193 |
+
fiveAvg = ((allNum - noFiveNum) / fiveNum).toFixed(2)
|
194 |
+
}
|
195 |
+
// 平均四星
|
196 |
+
if (fourNum > 0) {
|
197 |
+
fourAvg = ((allNum - noFourNum) / fourNum).toFixed(2)
|
198 |
+
}
|
199 |
+
|
200 |
+
// 有效抽卡
|
201 |
+
let isvalidNum = 0
|
202 |
+
if (fiveNum > 0 && fiveNum > wai) {
|
203 |
+
if (fiveLog.length > 0 && !fiveLog[0].isUp) {
|
204 |
+
isvalidNum = (allNum - noFiveNum - fiveLog[0].count) / (fiveNum - wai)
|
205 |
+
} else {
|
206 |
+
isvalidNum = (allNum - noFiveNum) / (fiveNum - wai)
|
207 |
+
}
|
208 |
+
isvalidNum = isvalidNum.toFixed(2)
|
209 |
+
}
|
210 |
+
|
211 |
+
let upYs = isvalidNum * 160
|
212 |
+
if (upYs >= 10000) {
|
213 |
+
upYs = (upYs / 10000).toFixed(2) + 'w'
|
214 |
+
} else {
|
215 |
+
upYs = upYs.toFixed(0)
|
216 |
+
}
|
217 |
+
|
218 |
+
// 小保底不歪概率
|
219 |
+
let noWaiRate = 0
|
220 |
+
if (fiveNum > 0) {
|
221 |
+
noWaiRate = (fiveNum - bigNum - wai) / (fiveNum - bigNum)
|
222 |
+
noWaiRate = (noWaiRate * 100).toFixed(1)
|
223 |
+
}
|
224 |
+
|
225 |
+
if (noFiveNum > 0) {
|
226 |
+
fiveLog.unshift({
|
227 |
+
id: 888,
|
228 |
+
isUp: true,
|
229 |
+
count: noFiveNum,
|
230 |
+
date: moment().format('MM-DD')
|
231 |
+
})
|
232 |
+
itemMap['888'] = {
|
233 |
+
name: '已抽',
|
234 |
+
star: 5,
|
235 |
+
abbr: '已抽',
|
236 |
+
img: 'gacha/imgs/no-avatar.webp'
|
237 |
+
}
|
238 |
+
}
|
239 |
+
|
240 |
+
return {
|
241 |
+
stat: {
|
242 |
+
allNum,
|
243 |
+
noFiveNum,
|
244 |
+
noFourNum,
|
245 |
+
fiveNum,
|
246 |
+
fourNum,
|
247 |
+
fiveAvg,
|
248 |
+
fourAvg,
|
249 |
+
wai,
|
250 |
+
isvalidNum,
|
251 |
+
weaponNum,
|
252 |
+
weaponFourNum,
|
253 |
+
upYs
|
254 |
+
},
|
255 |
+
maxFour: fourItem[0],
|
256 |
+
fiveLog,
|
257 |
+
noWaiRate,
|
258 |
+
items: itemMap
|
259 |
+
}
|
260 |
+
},
|
261 |
+
|
262 |
+
// 卡池统计
|
263 |
+
stat (qq, uid, type) {
|
264 |
+
let items = []
|
265 |
+
let itemMap = {}
|
266 |
+
let hasVersion = true
|
267 |
+
let loadData = function (poolId) {
|
268 |
+
let gachaData = GachaData.readJSON(qq, uid, poolId)
|
269 |
+
items = items.concat(gachaData.items)
|
270 |
+
lodash.extend(itemMap, gachaData.itemMap || {})
|
271 |
+
}
|
272 |
+
if (['up', 'char', 'all'].includes(type)) {
|
273 |
+
loadData(301)
|
274 |
+
}
|
275 |
+
if (['up', 'weapon', 'all'].includes(type)) {
|
276 |
+
loadData(302)
|
277 |
+
}
|
278 |
+
if (['all', 'normal'].includes(type)) {
|
279 |
+
hasVersion = false
|
280 |
+
loadData(200)
|
281 |
+
}
|
282 |
+
|
283 |
+
items = items.sort((a, b) => b.time - a.time)
|
284 |
+
|
285 |
+
let versionData = []
|
286 |
+
let currVersion
|
287 |
+
|
288 |
+
if (lodash.isEmpty(items)) {
|
289 |
+
return false
|
290 |
+
}
|
291 |
+
|
292 |
+
let getCurr = function () {
|
293 |
+
if (currVersion && !lodash.isEmpty(currVersion)) {
|
294 |
+
let cv = currVersion
|
295 |
+
let temp = {
|
296 |
+
version: cv.version,
|
297 |
+
half: cv.half,
|
298 |
+
from: hasVersion ? moment(new Date(cv.from)).format('YY-MM-DD') : '',
|
299 |
+
to: hasVersion ? moment(new Date(cv.to)).format('YY-MM-DD') : '',
|
300 |
+
upIds: {}
|
301 |
+
}
|
302 |
+
let upName = {}
|
303 |
+
let items = []
|
304 |
+
let poolNames = []
|
305 |
+
lodash.forEach(cv.char5, (name) => {
|
306 |
+
upName[name] = true
|
307 |
+
let char = Character.get(name)
|
308 |
+
poolNames.push(char.abbr)
|
309 |
+
})
|
310 |
+
lodash.forEach(cv.weapon5, (name) => {
|
311 |
+
upName[name] = true
|
312 |
+
})
|
313 |
+
let w5Num = 0
|
314 |
+
let w5UpNum = 0
|
315 |
+
let c5Num = 0
|
316 |
+
let c5UpNum = 0
|
317 |
+
let c4Num = 0
|
318 |
+
let w4Num = 0
|
319 |
+
let w3Num = 0
|
320 |
+
lodash.forEach(cv.items, (num, id) => {
|
321 |
+
let item = itemMap[id]
|
322 |
+
let isUp = upName[item.name]
|
323 |
+
let star = item.star
|
324 |
+
if (isUp) {
|
325 |
+
temp.upIds[id] = item.name
|
326 |
+
}
|
327 |
+
items.push({ id, num, star: item.star, isUp: temp.upIds[id] ? 1 : 0 })
|
328 |
+
if (item.type === 'char') {
|
329 |
+
if (star === 5) {
|
330 |
+
c5Num += num
|
331 |
+
isUp && (c5UpNum += num)
|
332 |
+
} else {
|
333 |
+
c4Num += num
|
334 |
+
}
|
335 |
+
}
|
336 |
+
if (item.type === 'weapon') {
|
337 |
+
if (star === 5) {
|
338 |
+
w5Num += num
|
339 |
+
isUp && (w5UpNum += num)
|
340 |
+
} else {
|
341 |
+
star === 4 ? (w4Num += num) : (w3Num += num)
|
342 |
+
}
|
343 |
+
}
|
344 |
+
})
|
345 |
+
temp.name = poolNames.join(' / ')
|
346 |
+
temp.items = lodash.sortBy(items, ['star', 'num', 'isUp']).reverse()
|
347 |
+
temp.stats = {
|
348 |
+
w5Num,
|
349 |
+
w5UpNum,
|
350 |
+
c5Num,
|
351 |
+
c5UpNum,
|
352 |
+
c4Num,
|
353 |
+
w4Num,
|
354 |
+
w3Num,
|
355 |
+
upNum: w5UpNum + c5UpNum,
|
356 |
+
star5Num: w5Num + c5Num,
|
357 |
+
star4Num: w4Num + c4Num,
|
358 |
+
totalNum: w5Num + w4Num + w3Num + c5Num + c4Num
|
359 |
+
}
|
360 |
+
return temp
|
361 |
+
}
|
362 |
+
}
|
363 |
+
|
364 |
+
lodash.forEach(items, (ds) => {
|
365 |
+
if (!currVersion || (ds.time < currVersion.start && hasVersion)) {
|
366 |
+
if (currVersion) {
|
367 |
+
versionData.push(getCurr())
|
368 |
+
}
|
369 |
+
let v = GachaData.getVersion(ds.time, hasVersion)
|
370 |
+
if (!hasVersion) {
|
371 |
+
v.version = type === 'all' ? '全部统计' : '常驻池'
|
372 |
+
}
|
373 |
+
if (!v) {
|
374 |
+
return true
|
375 |
+
}
|
376 |
+
currVersion = {
|
377 |
+
...v,
|
378 |
+
items: {}
|
379 |
+
}
|
380 |
+
}
|
381 |
+
if (!currVersion.items[ds.id]) {
|
382 |
+
currVersion.items[ds.id] = 1
|
383 |
+
} else {
|
384 |
+
currVersion.items[ds.id]++
|
385 |
+
}
|
386 |
+
})
|
387 |
+
versionData.push(getCurr())
|
388 |
+
|
389 |
+
let stat = {}
|
390 |
+
lodash.forEach(versionData, (ds) => {
|
391 |
+
lodash.forEach(ds.stats, (num, key) => {
|
392 |
+
if (!stat[key]) {
|
393 |
+
stat[key] = num
|
394 |
+
} else {
|
395 |
+
stat[key] += num
|
396 |
+
}
|
397 |
+
})
|
398 |
+
})
|
399 |
+
stat.avgUpNum = stat.upNum === 0 ? 0 : ((stat.totalNum / stat.upNum).toFixed(1))
|
400 |
+
|
401 |
+
return {
|
402 |
+
versionData,
|
403 |
+
itemMap,
|
404 |
+
totalStat: stat
|
405 |
+
}
|
406 |
+
},
|
407 |
+
|
408 |
+
getVersion (time, hasVersion = true) {
|
409 |
+
if (hasVersion) {
|
410 |
+
for (let ds of poolVersion) {
|
411 |
+
if (time > ds.start && time < ds.end) {
|
412 |
+
return ds
|
413 |
+
}
|
414 |
+
}
|
415 |
+
}
|
416 |
+
return {
|
417 |
+
version: hasVersion === false ? '全部' : '未知',
|
418 |
+
half: '',
|
419 |
+
char5: [],
|
420 |
+
char4: [],
|
421 |
+
weapon5: [],
|
422 |
+
weapon4: []
|
423 |
+
}
|
424 |
+
},
|
425 |
+
|
426 |
+
getItem (ds) {
|
427 |
+
if (ds.item_type === '武器') {
|
428 |
+
let weapon = Weapon.get(ds.name)
|
429 |
+
return {
|
430 |
+
type: 'weapon',
|
431 |
+
count: 0,
|
432 |
+
...weapon.getData('id,star,name,abbr,img')
|
433 |
+
}
|
434 |
+
} else if (ds.item_type === '角色') {
|
435 |
+
let char = Character.get(ds.name)
|
436 |
+
return {
|
437 |
+
type: 'char',
|
438 |
+
count: 0,
|
439 |
+
...char.getData('id,star,name,abbr,face')
|
440 |
+
}
|
441 |
+
}
|
442 |
+
}
|
443 |
+
}
|
444 |
+
export default GachaData
|
Yunzai/plugins/miao-plugin/apps/help.js
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Help from './help/Help.js'
|
2 |
+
import { App } from '#miao'
|
3 |
+
|
4 |
+
let app = App.init({
|
5 |
+
id: 'help',
|
6 |
+
name: '喵喵帮助',
|
7 |
+
desc: '喵喵帮助'
|
8 |
+
})
|
9 |
+
|
10 |
+
app.reg({
|
11 |
+
help: {
|
12 |
+
rule: /^#?(喵喵)?(命令|帮助|菜单|help|说明|功能|指令|使用说明)$/,
|
13 |
+
fn: Help.render,
|
14 |
+
desc: '【#帮助】 #喵喵帮助'
|
15 |
+
},
|
16 |
+
version: {
|
17 |
+
rule: /^#?喵喵版本$/,
|
18 |
+
fn: Help.version,
|
19 |
+
desc: '【#帮助】 喵喵版本介绍'
|
20 |
+
}
|
21 |
+
})
|
22 |
+
|
23 |
+
export default app
|
Yunzai/plugins/miao-plugin/apps/help/Help.js
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Cfg, Common, Data, Version } from '#miao'
|
2 |
+
import fs from 'fs'
|
3 |
+
import lodash from 'lodash'
|
4 |
+
import HelpTheme from './HelpTheme.js'
|
5 |
+
|
6 |
+
const _path = process.cwd()
|
7 |
+
const helpPath = `${_path}/plugins/miao-plugin/resources/help`
|
8 |
+
|
9 |
+
const Help = {
|
10 |
+
async render (e) {
|
11 |
+
if (!/喵喵/.test(e.msg) && !Cfg.get('help', false)) {
|
12 |
+
return false
|
13 |
+
}
|
14 |
+
|
15 |
+
let custom = {}
|
16 |
+
let help = {}
|
17 |
+
if (fs.existsSync(`${helpPath}/help-cfg.js`)) {
|
18 |
+
console.log('miao-plugin: 检测到存在help-cfg.js配置\n建议将help-cfg.js移为config/help.js或重新复制config/help_default.js进行配置~')
|
19 |
+
help = await import(`file://${helpPath}/help-cfg.js?version=${new Date().getTime()}`)
|
20 |
+
} else if (fs.existsSync(`${helpPath}/help-list.js`)) {
|
21 |
+
console.log('miao-plugin: 检测到存在help-list.js配置,建议将help-list.js移为config/help.js或重新复制config/help_default.js进行配置~')
|
22 |
+
help = await import(`file://${helpPath}/help-list.js?version=${new Date().getTime()}`)
|
23 |
+
}
|
24 |
+
|
25 |
+
let { diyCfg, sysCfg } = await Data.importCfg('help')
|
26 |
+
|
27 |
+
// 兼容一下旧字段
|
28 |
+
if (lodash.isArray(help.helpCfg)) {
|
29 |
+
custom = {
|
30 |
+
helpList: help.helpCfg,
|
31 |
+
helpCfg: {}
|
32 |
+
}
|
33 |
+
} else {
|
34 |
+
custom = help
|
35 |
+
}
|
36 |
+
|
37 |
+
let helpConfig = lodash.defaults(diyCfg.helpCfg || {}, custom.helpCfg, sysCfg.helpCfg)
|
38 |
+
let helpList = diyCfg.helpList || custom.helpList || sysCfg.helpList
|
39 |
+
|
40 |
+
let helpGroup = []
|
41 |
+
|
42 |
+
lodash.forEach(helpList, (group) => {
|
43 |
+
if (group.auth && group.auth === 'master' && !e.isMaster) {
|
44 |
+
return true
|
45 |
+
}
|
46 |
+
|
47 |
+
lodash.forEach(group.list, (help) => {
|
48 |
+
let icon = help.icon * 1
|
49 |
+
if (!icon) {
|
50 |
+
help.css = 'display:none'
|
51 |
+
} else {
|
52 |
+
let x = (icon - 1) % 10
|
53 |
+
let y = (icon - x - 1) / 10
|
54 |
+
help.css = `background-position:-${x * 50}px -${y * 50}px`
|
55 |
+
}
|
56 |
+
})
|
57 |
+
|
58 |
+
helpGroup.push(group)
|
59 |
+
})
|
60 |
+
let themeData = await HelpTheme.getThemeData(diyCfg.helpCfg || {}, sysCfg.helpCfg || {})
|
61 |
+
return await Common.render('help/index', {
|
62 |
+
helpCfg: helpConfig,
|
63 |
+
helpGroup,
|
64 |
+
...themeData,
|
65 |
+
element: 'default'
|
66 |
+
}, { e, scale: 1.2 })
|
67 |
+
},
|
68 |
+
|
69 |
+
async version (e) {
|
70 |
+
return await Common.render('help/version-info', {
|
71 |
+
currentVersion: Version.version,
|
72 |
+
changelogs: Version.changelogs,
|
73 |
+
elem: 'cryo'
|
74 |
+
}, { e, scale: 1.2 })
|
75 |
+
}
|
76 |
+
}
|
77 |
+
export default Help
|
Yunzai/plugins/miao-plugin/apps/help/HelpTheme.js
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import fs from 'fs'
|
3 |
+
import { Data } from '#miao'
|
4 |
+
|
5 |
+
let HelpTheme = {
|
6 |
+
async getThemeCfg (theme, exclude) {
|
7 |
+
let dirPath = './plugins/miao-plugin/resources/help/theme/'
|
8 |
+
let ret = []
|
9 |
+
let names = []
|
10 |
+
let dirs = fs.readdirSync(dirPath)
|
11 |
+
lodash.forEach(dirs, (dir) => {
|
12 |
+
if (fs.existsSync(`${dirPath}${dir}/main.png`)) {
|
13 |
+
names.push(dir)
|
14 |
+
}
|
15 |
+
})
|
16 |
+
if (lodash.isArray(theme)) {
|
17 |
+
ret = lodash.intersection(theme, names)
|
18 |
+
} else if (theme === 'all') {
|
19 |
+
ret = names
|
20 |
+
}
|
21 |
+
if (exclude && lodash.isArray(exclude)) {
|
22 |
+
ret = lodash.difference(ret, exclude)
|
23 |
+
}
|
24 |
+
if (ret.length === 0) {
|
25 |
+
ret = ['default']
|
26 |
+
}
|
27 |
+
let name = lodash.sample(ret)
|
28 |
+
let resPath = '{{_res_path}}/help/theme/'
|
29 |
+
return {
|
30 |
+
main: `${resPath}${name}/main.png`,
|
31 |
+
bg: fs.existsSync(`${dirPath}${name}/bg.jpg`) ? `${resPath}${name}/bg.jpg` : `${resPath}default/bg.jpg`,
|
32 |
+
style: (await Data.importModule(`resources/help/theme/${name}/config.js`, 'miao')).style || {}
|
33 |
+
}
|
34 |
+
},
|
35 |
+
async getThemeData (diyStyle, sysStyle) {
|
36 |
+
let helpConfig = lodash.extend({}, sysStyle, diyStyle)
|
37 |
+
let colCount = Math.min(5, Math.max(parseInt(helpConfig?.colCount) || 3, 2))
|
38 |
+
let colWidth = Math.min(500, Math.max(100, parseInt(helpConfig?.colWidth) || 265))
|
39 |
+
let width = Math.min(2500, Math.max(800, colCount * colWidth + 30))
|
40 |
+
let theme = await HelpTheme.getThemeCfg(diyStyle.theme || sysStyle.theme, diyStyle.themeExclude || sysStyle.themeExclude)
|
41 |
+
let themeStyle = theme.style || {}
|
42 |
+
let ret = [`
|
43 |
+
body{background-image:url(${theme.bg});width:${width}px;}
|
44 |
+
.container{background-image:url(${theme.main});width:${width}px;}
|
45 |
+
.help-table .td,.help-table .th{width:${100 / colCount}%}
|
46 |
+
`]
|
47 |
+
let css = function (sel, css, key, def, fn) {
|
48 |
+
let val = Data.def(themeStyle[key], diyStyle[key], sysStyle[key], def)
|
49 |
+
if (fn) {
|
50 |
+
val = fn(val)
|
51 |
+
}
|
52 |
+
ret.push(`${sel}{${css}:${val}}`)
|
53 |
+
}
|
54 |
+
css('.help-title,.help-group', 'color', 'fontColor', '#ceb78b')
|
55 |
+
css('.help-title,.help-group', 'text-shadow', 'fontShadow', 'none')
|
56 |
+
css('.help-desc', 'color', 'descColor', '#eee')
|
57 |
+
css('.cont-box', 'background', 'contBgColor', 'rgba(43, 52, 61, 0.8)')
|
58 |
+
css('.cont-box', 'backdrop-filter', 'contBgBlur', 3, (n) => diyStyle.bgBlur === false ? 'none' : `blur(${n}px)`)
|
59 |
+
css('.help-group', 'background', 'headerBgColor', 'rgba(34, 41, 51, .4)')
|
60 |
+
css('.help-table .tr:nth-child(odd)', 'background', 'rowBgColor1', 'rgba(34, 41, 51, .2)')
|
61 |
+
css('.help-table .tr:nth-child(even)', 'background', 'rowBgColor2', 'rgba(34, 41, 51, .4)')
|
62 |
+
return {
|
63 |
+
style: `<style>${ret.join('\n')}</style>`,
|
64 |
+
colCount
|
65 |
+
}
|
66 |
+
}
|
67 |
+
}
|
68 |
+
export default HelpTheme
|
Yunzai/plugins/miao-plugin/apps/index.js
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import character from './character.js'
|
2 |
+
import profile from './profile.js'
|
3 |
+
import stat from './stat.js'
|
4 |
+
import wiki from './wiki.js'
|
5 |
+
import poke from './poke.js'
|
6 |
+
import help from './help.js'
|
7 |
+
import admin from './admin.js'
|
8 |
+
import gacha from './gacha.js'
|
9 |
+
|
10 |
+
let apps = { character, poke, profile, stat, wiki, gacha, admin, help }
|
11 |
+
let rules = {} // v3
|
12 |
+
for (let key in apps) {
|
13 |
+
rules[`${key}`] = apps[key].v3App()
|
14 |
+
}
|
15 |
+
|
16 |
+
export { rules as apps }
|
Yunzai/plugins/miao-plugin/apps/poke.js
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Wife from './character/AvatarWife.js'
|
2 |
+
import { App } from '#miao'
|
3 |
+
|
4 |
+
let app = App.init({
|
5 |
+
id: 'poke',
|
6 |
+
name: '戳一戳',
|
7 |
+
event: 'poke'
|
8 |
+
})
|
9 |
+
|
10 |
+
app.reg({
|
11 |
+
pockWife: {
|
12 |
+
fn: Wife.poke,
|
13 |
+
describe: '#老公 #老婆 查询'
|
14 |
+
}
|
15 |
+
})
|
16 |
+
|
17 |
+
export default app
|
Yunzai/plugins/miao-plugin/apps/profile.js
ADDED
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { profileHelp } from './profile/ProfileCommon.js'
|
2 |
+
import { profileArtisList } from './profile/ProfileArtis.js'
|
3 |
+
import ProfileDetail from './profile/ProfileDetail.js'
|
4 |
+
import ProfileStat from './profile/ProfileStat.js'
|
5 |
+
import ProfileList from './profile/ProfileList.js'
|
6 |
+
import { uploadCharacterImg, delProfileImg, profileImgList } from './character/ImgUpload.js'
|
7 |
+
import { enemyLv } from './profile/ProfileUtils.js'
|
8 |
+
import { groupRank, resetRank, refreshRank, manageRank } from './profile/ProfileRank.js'
|
9 |
+
import { App, Cfg } from '#miao'
|
10 |
+
|
11 |
+
let app = App.init({
|
12 |
+
id: 'profile',
|
13 |
+
name: '角色面板'
|
14 |
+
})
|
15 |
+
|
16 |
+
app.reg({
|
17 |
+
profileList: {
|
18 |
+
name: '面板角色列表',
|
19 |
+
desc: '查看当前已获取面板数据的角色列表',
|
20 |
+
fn: ProfileList.render,
|
21 |
+
rule: /^#(星铁|原神)?(面板角色|角色面板|面板)(列表)?\s*(\d{9})?$/
|
22 |
+
},
|
23 |
+
|
24 |
+
profileDetail: {
|
25 |
+
name: '角色面板',
|
26 |
+
fn: ProfileDetail.detail,
|
27 |
+
rule: /^#*([^#]+)\s*(详细|详情|面板|面版|圣遗物|武器[1-7]?|伤害([1-9]+\d*)?)\s*(\d{9})*(.*[换变改].*)?$/
|
28 |
+
},
|
29 |
+
|
30 |
+
profileChange: {
|
31 |
+
name: '角色面板计算',
|
32 |
+
fn: ProfileDetail.detail,
|
33 |
+
rule: /^#.+换.+$/
|
34 |
+
},
|
35 |
+
|
36 |
+
groupProfile: {
|
37 |
+
name: '群内最强',
|
38 |
+
fn: groupRank,
|
39 |
+
rule: /^#(群|群内)?(排名|排行)?(最强|最高|最高分|最牛|第一|极限)+.+/
|
40 |
+
},
|
41 |
+
|
42 |
+
resetRank: {
|
43 |
+
name: '重置排名',
|
44 |
+
fn: resetRank,
|
45 |
+
rule: /^#(重置|重设)(.*)(排名|排行)$/
|
46 |
+
},
|
47 |
+
|
48 |
+
refreshRank: {
|
49 |
+
name: '重置排名',
|
50 |
+
fn: refreshRank,
|
51 |
+
rule: /^#(刷新|更新|重新加载)(群内|群|全部)*(排名|排行)$/
|
52 |
+
},
|
53 |
+
|
54 |
+
manageRank: {
|
55 |
+
name: '打开关闭',
|
56 |
+
fn: manageRank,
|
57 |
+
rule: /^#(开启|打开|启用|关闭|禁用)(群内|群|全部)*(排名|排行)$/
|
58 |
+
},
|
59 |
+
|
60 |
+
rankList: {
|
61 |
+
name: '面板排名榜',
|
62 |
+
fn: groupRank,
|
63 |
+
rule: /^#(群|群内)?.+(排名|排行)(榜)?$/
|
64 |
+
},
|
65 |
+
|
66 |
+
artisList: {
|
67 |
+
name: '面板圣遗物列表',
|
68 |
+
fn: profileArtisList,
|
69 |
+
rule: /^#圣遗物列表\s*(\d{9})?$/
|
70 |
+
},
|
71 |
+
|
72 |
+
profileStat: {
|
73 |
+
name: '面板练度统计',
|
74 |
+
fn: ProfileStat.stat,
|
75 |
+
rule: /^#(面板|喵喵)练度统计$/,
|
76 |
+
yzRule: /^#*(我的)*(技能|天赋|武器|角色|练度|五|四|5|4|星)+(汇总|统计|列表)(force|五|四|5|4|星)*[ |0-9]*$/,
|
77 |
+
yzCheck: () => Cfg.get('profileStat', false)
|
78 |
+
},
|
79 |
+
|
80 |
+
avatarList: {
|
81 |
+
name: '角色查询',
|
82 |
+
fn: ProfileStat.avatarList,
|
83 |
+
rule: /^#喵喵(角色|查询)[ |0-9]*$/,
|
84 |
+
yzRule: /^(#(角色|查询|查询角色|角色查询|人物)[ |0-9]*$)|(^(#*uid|#*UID)\+*[1|2|5-9][0-9]{8}$)|(^#[\+|+]*[1|2|5-9][0-9]{8})/,
|
85 |
+
yzCheck: () => Cfg.get('avatarList', false)
|
86 |
+
},
|
87 |
+
|
88 |
+
profileHelp: {
|
89 |
+
name: '角色面板帮助',
|
90 |
+
fn: profileHelp,
|
91 |
+
rule: /^#(角色|换|更换)?面[板版]帮助$/
|
92 |
+
},
|
93 |
+
|
94 |
+
enemyLv: {
|
95 |
+
name: '敌人等级',
|
96 |
+
fn: enemyLv,
|
97 |
+
describe: '【#角色】 设置伤害计算中目标敌人的等级',
|
98 |
+
rule: /^#(敌人|怪物)等级\s*\d{1,3}\s*$/
|
99 |
+
},
|
100 |
+
|
101 |
+
profileRefresh: {
|
102 |
+
name: '面板更新',
|
103 |
+
describe: '【#角色】 获取游戏橱窗详情数据',
|
104 |
+
fn: ProfileList.refresh,
|
105 |
+
rule: /^#(星铁|原神)?(全部面板更新|更新全部面板|获取游戏角色详情|更新面板|面板更新)\s*(\d{9})?$/
|
106 |
+
},
|
107 |
+
|
108 |
+
uploadImg: {
|
109 |
+
name: '上传面板图',
|
110 |
+
describe: '【#上传刻晴面板图】 上传角色面板图',
|
111 |
+
fn: uploadCharacterImg,
|
112 |
+
rule: /^#?\s*(?:上传|添加)(.+)(?:面板图)\s*$/
|
113 |
+
},
|
114 |
+
|
115 |
+
delProfile: {
|
116 |
+
name: '删除面板图',
|
117 |
+
describe: '【#删除刻晴面板图1】 删除指定角色面板图(序号)',
|
118 |
+
fn: delProfileImg,
|
119 |
+
rule: /^#?\s*(?:移除|清除|删除)(.+)(?:面板图)(\d){1,}\s*$/
|
120 |
+
},
|
121 |
+
|
122 |
+
profileImgList: {
|
123 |
+
name: '面板图列表',
|
124 |
+
describe: '【#刻晴面板图列表】 删除指定角色面板图(序号)',
|
125 |
+
fn: profileImgList,
|
126 |
+
rule: /^#?\s*(.+)(?:面板图列表)\s*$/
|
127 |
+
},
|
128 |
+
|
129 |
+
profileDel: {
|
130 |
+
name: '删除面板',
|
131 |
+
describe: '【#角色】 删除游戏橱窗详情数据',
|
132 |
+
fn: ProfileList.del,
|
133 |
+
rule: /^#(删除全部面板|删除面板|删除面板数据)\s*(\d{9})?$/
|
134 |
+
},
|
135 |
+
|
136 |
+
profileReload:{
|
137 |
+
name: '重新加载面板',
|
138 |
+
fn:ProfileList.reload,
|
139 |
+
rule: /^#(星铁|原神)?(加载|重新加载|重载)面板\s*(\d{9})?$/
|
140 |
+
}
|
141 |
+
})
|
142 |
+
|
143 |
+
export default app
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileArtis.js
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* 角色圣遗物评分详情
|
3 |
+
*
|
4 |
+
* */
|
5 |
+
import lodash from 'lodash'
|
6 |
+
import { Cfg, Common } from '#miao'
|
7 |
+
import { getTargetUid, profileHelp, getProfileRefresh } from './ProfileCommon.js'
|
8 |
+
import { Artifact, Character, ProfileArtis, Player } from '#miao.models'
|
9 |
+
|
10 |
+
/*
|
11 |
+
* 角色圣遗物面板
|
12 |
+
* */
|
13 |
+
export async function profileArtis (e) {
|
14 |
+
let { uid, avatar } = e
|
15 |
+
let profile = e._profile || await getProfileRefresh(e, avatar)
|
16 |
+
if (!profile) {
|
17 |
+
return true
|
18 |
+
}
|
19 |
+
if (!profile.hasArtis()) {
|
20 |
+
e.reply('未能获得圣遗物详情,请重新获取面板信息后查看')
|
21 |
+
return true
|
22 |
+
}
|
23 |
+
let char = profile.char
|
24 |
+
let { game } = char
|
25 |
+
let charCfg = profile.artis.getCharCfg()
|
26 |
+
|
27 |
+
let { attrMap } = Artifact.getMeta()
|
28 |
+
|
29 |
+
let artisDetail = profile.getArtisMark()
|
30 |
+
let artisKeyTitle = ProfileArtis.getArtisKeyTitle()
|
31 |
+
|
32 |
+
// 渲染图像
|
33 |
+
return await Common.render('character/artis-mark', {
|
34 |
+
uid,
|
35 |
+
elem: char.elem,
|
36 |
+
splash: profile.costumeSplash,
|
37 |
+
data: profile,
|
38 |
+
costume: profile.costume ? '2' : '',
|
39 |
+
artisDetail,
|
40 |
+
artisKeyTitle,
|
41 |
+
attrMap,
|
42 |
+
charCfg,
|
43 |
+
game,
|
44 |
+
changeProfile: e._profileMsg
|
45 |
+
}, { e, scale: 1.3 })
|
46 |
+
}
|
47 |
+
|
48 |
+
/*
|
49 |
+
* 圣遗物列表
|
50 |
+
* */
|
51 |
+
export async function profileArtisList (e) {
|
52 |
+
let uid = await getTargetUid(e)
|
53 |
+
if (!uid) {
|
54 |
+
return true
|
55 |
+
}
|
56 |
+
|
57 |
+
let artis = []
|
58 |
+
let player = Player.create(uid)
|
59 |
+
player.forEachAvatar((avatar) => {
|
60 |
+
let profile = avatar.getProfile()
|
61 |
+
if (!profile) {
|
62 |
+
return true
|
63 |
+
}
|
64 |
+
let name = profile.name
|
65 |
+
let char = Character.get(name)
|
66 |
+
if (!profile.hasData || !profile.hasArtis()) {
|
67 |
+
return true
|
68 |
+
}
|
69 |
+
let profileArtis = profile.getArtisMark()
|
70 |
+
lodash.forEach(profileArtis.artis, (arti, idx) => {
|
71 |
+
arti.charWeight = profileArtis.charWeight
|
72 |
+
arti.avatar = name
|
73 |
+
arti.side = char.side
|
74 |
+
artis.push(arti)
|
75 |
+
})
|
76 |
+
})
|
77 |
+
|
78 |
+
if (artis.length === 0) {
|
79 |
+
e.reply('请先获取角色面板数据后再查看圣遗物列表...')
|
80 |
+
await profileHelp(e)
|
81 |
+
return true
|
82 |
+
}
|
83 |
+
artis = lodash.sortBy(artis, '_mark')
|
84 |
+
artis = artis.reverse()
|
85 |
+
let number = Cfg.get('artisNumber', 28)
|
86 |
+
artis = artis.slice(0, `${number}`)
|
87 |
+
let artisKeyTitle = ProfileArtis.getArtisKeyTitle()
|
88 |
+
|
89 |
+
// 渲染图像
|
90 |
+
return await Common.render('character/artis-list', {
|
91 |
+
save_id: uid,
|
92 |
+
uid,
|
93 |
+
artis,
|
94 |
+
artisKeyTitle
|
95 |
+
}, { e, scale: 1.4 })
|
96 |
+
}
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileChange.js
ADDED
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* 面板数据替换相关逻辑
|
3 |
+
*/
|
4 |
+
import lodash from 'lodash'
|
5 |
+
import { Data } from '#miao'
|
6 |
+
import { Character, ArtifactSet, ProfileData, Weapon, Player } from '#miao.models'
|
7 |
+
|
8 |
+
// 默认武器
|
9 |
+
let defWeapon = {
|
10 |
+
bow: '西风猎弓',
|
11 |
+
catalyst: '西风秘典',
|
12 |
+
claymore: '西风大剑',
|
13 |
+
polearm: '西风长枪',
|
14 |
+
sword: '西风剑'
|
15 |
+
}
|
16 |
+
|
17 |
+
const ProfileChange = {
|
18 |
+
/**
|
19 |
+
* 匹配消息
|
20 |
+
* @param msg
|
21 |
+
* @returns {{}}
|
22 |
+
*/
|
23 |
+
matchMsg (msg) {
|
24 |
+
if (!/(变|改|换)/.test(msg)) {
|
25 |
+
return false
|
26 |
+
}
|
27 |
+
msg = msg.toLowerCase().replace(/uid ?:? ?/, '').replace('', '')
|
28 |
+
let regRet = /^#*(\d{9})?(.+?)(详细|详情|面板|面版|圣遗物|伤害[1-7]?)?\s*(\d{9})?[变换改](.+)/.exec(msg)
|
29 |
+
if (!regRet || !regRet[2]) {
|
30 |
+
return false
|
31 |
+
}
|
32 |
+
let ret = {}
|
33 |
+
let change = {}
|
34 |
+
let char = Character.get(lodash.trim(regRet[2]).replace('星铁', ''))
|
35 |
+
if (!char) {
|
36 |
+
return false
|
37 |
+
}
|
38 |
+
const game = char.game
|
39 |
+
const isGs = game === 'gs'
|
40 |
+
const keyMap = isGs ? {
|
41 |
+
artis: '圣遗物',
|
42 |
+
arti1: '花,生之花',
|
43 |
+
arti2: '毛,羽,羽毛,死之羽',
|
44 |
+
arti3: '沙,沙漏,表,时之沙',
|
45 |
+
arti4: '杯,杯子,空之杯',
|
46 |
+
arti5: '头,冠,理之冠,礼冠,帽子,帽',
|
47 |
+
weapon: '武器'
|
48 |
+
} : {
|
49 |
+
artis: '圣遗物,遗器',
|
50 |
+
arti1: '头,帽子,头部',
|
51 |
+
arti2: '手,手套,手部',
|
52 |
+
arti3: '衣,衣服,甲,躯干,',
|
53 |
+
arti4: '鞋,靴,鞋子,靴子,脚,脚部',
|
54 |
+
arti5: '球,位面球',
|
55 |
+
arti6: '绳,线,链接绳,连接绳',
|
56 |
+
weapon: '武器,光锥'
|
57 |
+
}
|
58 |
+
let keyTitleMap = {}
|
59 |
+
lodash.forEach(keyMap, (val, key) => {
|
60 |
+
lodash.forEach(val.split(','), (v) => {
|
61 |
+
keyTitleMap[v] = key
|
62 |
+
})
|
63 |
+
})
|
64 |
+
const keyReg = new RegExp(`^(\\d{9})?\\s*(.+?)\\s*(\\d{9})?\\s*((?:${lodash.keys(keyTitleMap).join('|')}|\\+)+)$`)
|
65 |
+
|
66 |
+
ret.char = char.id
|
67 |
+
ret.mode = regRet[3] === '换' ? '面板' : regRet[3]
|
68 |
+
ret.uid = regRet[1] || regRet[4] || ''
|
69 |
+
ret.game = char.game
|
70 |
+
msg = regRet[5]
|
71 |
+
|
72 |
+
// 更换匹配
|
73 |
+
msg = msg.replace(/[变改]/g, '换')
|
74 |
+
lodash.forEach(msg.split('换'), (txt) => {
|
75 |
+
txt = lodash.trim(txt)
|
76 |
+
if (!txt) {
|
77 |
+
return true
|
78 |
+
}
|
79 |
+
// 匹配圣遗物
|
80 |
+
let keyRet = keyReg.exec(txt)
|
81 |
+
if (keyRet && keyRet[4]) {
|
82 |
+
let char = Character.get(lodash.trim(keyRet[2]))
|
83 |
+
if (char) {
|
84 |
+
if (char.game !== game) {
|
85 |
+
return true
|
86 |
+
}
|
87 |
+
lodash.forEach(keyRet[4].split('+'), (key) => {
|
88 |
+
key = lodash.trim(key)
|
89 |
+
let type = keyTitleMap[key]
|
90 |
+
change[type] = {
|
91 |
+
char: char.id || '',
|
92 |
+
uid: keyRet[1] || keyRet[3] || '',
|
93 |
+
type
|
94 |
+
}
|
95 |
+
})
|
96 |
+
} else if (keyRet[4].length > 2) {
|
97 |
+
return true
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
// 匹配圣遗物套装
|
102 |
+
let asMap = ArtifactSet.getAliasMap(game)
|
103 |
+
let asKey = lodash.keys(asMap).sort((a, b) => b.length - a.length).join('|')
|
104 |
+
let asReg = new RegExp(`^(${asKey})套?[2,4]?\\+?(${asKey})?套?[2,4]?\\+?(${asKey})?套?[2,4]?$`)
|
105 |
+
let asRet = asReg.exec(txt)
|
106 |
+
if (asRet && asRet[1] && asMap[asRet[1]]) {
|
107 |
+
if (game === 'gs') {
|
108 |
+
change.artisSet = [asMap[asRet[1]], asMap?.[asRet[2]] || asMap[asRet[1]]]
|
109 |
+
} else if (game === 'sr') {
|
110 |
+
for (let idx = 1; idx <= 3; idx++) {
|
111 |
+
let as = ArtifactSet.get(asMap?.[asRet[idx]])
|
112 |
+
if (as) { // 球&绳
|
113 |
+
change.artisSet = change.artisSet || []
|
114 |
+
let ca = change.artisSet
|
115 |
+
ca[as.sets?.[1] ? (ca[0] ? 1 : 0) : 2] = as.name
|
116 |
+
}
|
117 |
+
}
|
118 |
+
let ca = change.artisSet
|
119 |
+
if (ca && ca[0] && !ca[1]) {
|
120 |
+
ca[1] = ca[0]
|
121 |
+
}
|
122 |
+
}
|
123 |
+
return true
|
124 |
+
}
|
125 |
+
|
126 |
+
// 匹配武器
|
127 |
+
let wRet = /^(?:等?级?([1-9][0-9])?级?)?\s*(?:([1-5一二三四五满])?精炼?([1-5一二三四五])?)?\s*(?:等?级?([1-9][0-9])?级?)?\s*(.*)$/.exec(txt)
|
128 |
+
if (wRet && wRet[5]) {
|
129 |
+
let weaponName = lodash.trim(wRet[5])
|
130 |
+
let weapon = Weapon.get(weaponName, game, ret.char.game)
|
131 |
+
if (weapon || weaponName === '武器' || Weapon.isWeaponSet(weaponName)) {
|
132 |
+
let affix = wRet[2] || wRet[3]
|
133 |
+
affix = { 一: 1, 二: 2, 三: 3, 四: 4, 五: 5, 满: 5 }[affix] || affix * 1
|
134 |
+
let tmp = {
|
135 |
+
weapon: (Weapon.isWeaponSet(weaponName) ? weaponName : weapon?.name) || '',
|
136 |
+
affix: affix || '',
|
137 |
+
level: wRet[1] * 1 || wRet[4] * 1 || ''
|
138 |
+
}
|
139 |
+
if (lodash.values(tmp).join('')) {
|
140 |
+
change.weapon = tmp
|
141 |
+
}
|
142 |
+
return true
|
143 |
+
}
|
144 |
+
}
|
145 |
+
let char = change.char || {}
|
146 |
+
// 命座匹配
|
147 |
+
let consRet = /([0-6零一二三四五六满])(命|魂|星魂)/.exec(txt)
|
148 |
+
if (consRet && consRet[1]) {
|
149 |
+
let cons = consRet[1]
|
150 |
+
char.cons = Math.max(0, Math.min(6, lodash.isNaN(cons * 1) ? '零一二��四五六满'.split('').indexOf(cons) : cons * 1))
|
151 |
+
txt = txt.replace(consRet[0], '')
|
152 |
+
}
|
153 |
+
|
154 |
+
// 天赋匹配
|
155 |
+
let talentRet = (isGs ? /(?:天赋|技能|行迹)((?:[1][0-5]|[1-9])[ ,]?)((?:[1][0-5]|[1-9])[ ,]?)([1][0-5]|[1-9])/ :
|
156 |
+
/(?:天赋|技能|行迹)((?:[1][0-5]|[1-9])[ ,]?)((?:[1][0-5]|[1-9])[ ,]?)((?:[1][0-5]|[1-9])[ ,]?)([1][0-5]|[1-9])/).exec(txt)
|
157 |
+
if (talentRet) {
|
158 |
+
char.talent = {}
|
159 |
+
lodash.forEach((isGs ? 'aeq' : 'aetq').split(''), (key, idx) => {
|
160 |
+
char.talent[key] = talentRet[idx + 1] * 1 || 1
|
161 |
+
})
|
162 |
+
txt = txt.replace(talentRet[0], '')
|
163 |
+
}
|
164 |
+
|
165 |
+
let lvRet = /等级([1-9][0-9]?)|([1-9][0-9]?)级/.exec(txt)
|
166 |
+
if (lvRet && (lvRet[1] || lvRet[2])) {
|
167 |
+
char.level = (lvRet[1] || lvRet[2]) * 1
|
168 |
+
txt = txt.replace(lvRet[0], '')
|
169 |
+
}
|
170 |
+
txt = lodash.trim(txt)
|
171 |
+
if (txt) {
|
172 |
+
let chars = Character.get(txt)
|
173 |
+
if (chars && (chars.game === game)) {
|
174 |
+
char.char = chars.id
|
175 |
+
}
|
176 |
+
}
|
177 |
+
if (!lodash.isEmpty(char)) {
|
178 |
+
change.char = char
|
179 |
+
}
|
180 |
+
})
|
181 |
+
ret.change = lodash.isEmpty(change) ? false : change
|
182 |
+
return ret
|
183 |
+
},
|
184 |
+
|
185 |
+
/**
|
186 |
+
* 获取面板数据
|
187 |
+
* @param uid
|
188 |
+
* @param charid
|
189 |
+
* @param ds
|
190 |
+
* @param game
|
191 |
+
* @returns {ProfileData|boolean}
|
192 |
+
*/
|
193 |
+
getProfile (uid, charid, ds, game = 'gs') {
|
194 |
+
if (!charid) {
|
195 |
+
return false
|
196 |
+
}
|
197 |
+
|
198 |
+
const isGs = game === 'gs'
|
199 |
+
|
200 |
+
let player = Player.create(uid, game)
|
201 |
+
|
202 |
+
let source = player.getProfile(charid)
|
203 |
+
let dc = ds.char || {}
|
204 |
+
if (!source || !source.hasData) {
|
205 |
+
source = {}
|
206 |
+
}
|
207 |
+
|
208 |
+
let char = Character.get(dc?.char || source.id || charid)
|
209 |
+
if (!char) {
|
210 |
+
return false
|
211 |
+
}
|
212 |
+
let level = dc.level || source.level || 90
|
213 |
+
let promote = level === source.level ? source.promote : undefined
|
214 |
+
|
215 |
+
let profiles = {}
|
216 |
+
if (source && source.id) {
|
217 |
+
profiles[`${player.uid}:${source.id}`] = source
|
218 |
+
}
|
219 |
+
// 获取source
|
220 |
+
let getSource = function (cfg) {
|
221 |
+
if (!cfg || !cfg.char) {
|
222 |
+
return source
|
223 |
+
}
|
224 |
+
let cuid = cfg.uid || uid
|
225 |
+
let id = cfg.char || source.id
|
226 |
+
let key = cuid + ':' + id
|
227 |
+
if (!profiles[key]) {
|
228 |
+
let cPlayer = Player.create(cuid, game)
|
229 |
+
profiles[key] = cPlayer.getProfile(id) || {}
|
230 |
+
}
|
231 |
+
return profiles[key]?.id ? profiles[key] : source
|
232 |
+
}
|
233 |
+
// 初始化profile
|
234 |
+
let ret = new ProfileData({
|
235 |
+
uid,
|
236 |
+
id: char.id,
|
237 |
+
level,
|
238 |
+
cons: Data.def(dc.cons, source.cons, 0),
|
239 |
+
fetter: source.fetter || 10,
|
240 |
+
elem: char.elem,
|
241 |
+
dataSource: 'change',
|
242 |
+
_source: 'change',
|
243 |
+
promote,
|
244 |
+
trees: lodash.extend([], source.trees)
|
245 |
+
}, char.game, false)
|
246 |
+
|
247 |
+
// 设置武器
|
248 |
+
let wCfg = ds.weapon || {}
|
249 |
+
let wSource = getSource(wCfg).weapon || {}
|
250 |
+
let weapon = Weapon.get(wCfg?.weapon || wSource?.name || defWeapon[char.weaponType], char.game, char.weaponType)
|
251 |
+
if (char.isGs) {
|
252 |
+
if (!weapon || weapon.type !== char.weaponType) {
|
253 |
+
weapon = Weapon.get(defWeapon[char.weaponType], char.game)
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
let wDs = {
|
258 |
+
name: weapon.name,
|
259 |
+
star: weapon.star,
|
260 |
+
level: Math.min(weapon.maxLv || 90, wCfg.level || wSource.level || 90)
|
261 |
+
}
|
262 |
+
if (wSource.level === wDs.level) {
|
263 |
+
wDs.promote = wSource.promote
|
264 |
+
}
|
265 |
+
wDs.affix = Math.min(weapon.maxAffix || 5, wCfg.affix || ((wDs.star === 5 && wSource.star !== 5) ? 1 : (wSource.affix || 5)))
|
266 |
+
ret.setWeapon(wDs)
|
267 |
+
|
268 |
+
// 设置天赋
|
269 |
+
if (ds?.char?.talent) {
|
270 |
+
ret.setTalent(ds?.char?.talent, 'level')
|
271 |
+
} else {
|
272 |
+
ret.setTalent(source?.originalTalent || (isGs ? { a: 9, e: 9, q: 9 } : { a: 6, e: 8, t: 8, q: 8 }), 'original')
|
273 |
+
}
|
274 |
+
|
275 |
+
// 设置圣遗物
|
276 |
+
let artis = getSource(ds.artis)?.artis?.artis || {}
|
277 |
+
for (let idx = 1; idx <= (isGs ? 5 : 6); idx++) {
|
278 |
+
if (ds['arti' + idx]) {
|
279 |
+
let source = getSource(ds['arti' + idx])
|
280 |
+
if (source && source.artis && source.artis[idx]) {
|
281 |
+
artis[idx] = source.artis[idx]
|
282 |
+
}
|
283 |
+
}
|
284 |
+
let artisIdx = (isGs ? '00111' : '001122')[idx - 1]
|
285 |
+
if (artis[idx] && ds.artisSet && ds.artisSet[artisIdx]) {
|
286 |
+
let as = ArtifactSet.get(ds.artisSet[artisIdx], game)
|
287 |
+
if (as) {
|
288 |
+
artis[idx].id = as.getArti(idx)?.getIdByStar(artis[idx].star || 5)
|
289 |
+
artis[idx]._name = artis[idx].name = as.getArtiName(idx)
|
290 |
+
artis[idx]._set = artis[idx].set = as.name
|
291 |
+
}
|
292 |
+
}
|
293 |
+
}
|
294 |
+
ret.setArtis(artis)
|
295 |
+
ret.calcAttr()
|
296 |
+
return ret
|
297 |
+
}
|
298 |
+
}
|
299 |
+
export default ProfileChange
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileCommon.js
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* 面板公共方法及处理
|
3 |
+
* */
|
4 |
+
import { Version } from '#miao'
|
5 |
+
import { Character, MysApi, Player } from '#miao.models'
|
6 |
+
|
7 |
+
/*
|
8 |
+
* 获取面板查询的 目标uid
|
9 |
+
* */
|
10 |
+
const _getTargetUid = async function (e) {
|
11 |
+
let uidReg = /[1-9][0-9]{8}/
|
12 |
+
|
13 |
+
if (e.uid && uidReg.test(e.uid)) {
|
14 |
+
return e.uid
|
15 |
+
}
|
16 |
+
|
17 |
+
let uidRet = uidReg.exec(e.msg)
|
18 |
+
if (uidRet) {
|
19 |
+
return uidRet[0]
|
20 |
+
}
|
21 |
+
let uid = false
|
22 |
+
|
23 |
+
try {
|
24 |
+
let user = await MysApi.initUser(e)
|
25 |
+
|
26 |
+
if (!user || !user.uid) {
|
27 |
+
return false
|
28 |
+
}
|
29 |
+
uid = user.uid
|
30 |
+
if ((!uid || !uidReg.test(uid)) && !e._replyNeedUid) {
|
31 |
+
e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
|
32 |
+
e._replyNeedUid = true
|
33 |
+
return false
|
34 |
+
}
|
35 |
+
} catch (err) {
|
36 |
+
console.log(err)
|
37 |
+
}
|
38 |
+
return uid || false
|
39 |
+
}
|
40 |
+
|
41 |
+
export async function getTargetUid (e) {
|
42 |
+
let uid = await _getTargetUid(e)
|
43 |
+
if (uid) {
|
44 |
+
e.uid = uid
|
45 |
+
}
|
46 |
+
return uid
|
47 |
+
}
|
48 |
+
|
49 |
+
export async function getProfileRefresh (e, avatar) {
|
50 |
+
let char = Character.get(avatar)
|
51 |
+
if (!char) {
|
52 |
+
return false
|
53 |
+
}
|
54 |
+
|
55 |
+
let player = Player.create(e)
|
56 |
+
let profile = player.getProfile(char.id)
|
57 |
+
if (!profile || !profile.hasData) {
|
58 |
+
logger.mark(`本地无UID:${player.uid}的${char.name}面板数据,尝试自动请求...`)
|
59 |
+
await player.refresh({ profile: true })
|
60 |
+
profile = player.getProfile(char.id)
|
61 |
+
}
|
62 |
+
if (!profile || !profile.hasData) {
|
63 |
+
if (!e._isReplyed) {
|
64 |
+
e.reply(`请确认${char.name}已展示在【游戏内】的角色展柜中,并打开了“显示角色详情”。然后请使用 #更新面板\n命令来获取${char.name}的面板详情`)
|
65 |
+
}
|
66 |
+
return false
|
67 |
+
}
|
68 |
+
return profile
|
69 |
+
}
|
70 |
+
|
71 |
+
/*
|
72 |
+
* 面板帮助
|
73 |
+
* */
|
74 |
+
export async function profileHelp (e) {
|
75 |
+
e.reply(segment.image(`file://${process.cwd()}/plugins/miao-plugin/resources/character/imgs/help.jpg`))
|
76 |
+
return true
|
77 |
+
}
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileDetail.js
ADDED
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import { getTargetUid, getProfileRefresh } from './ProfileCommon.js'
|
3 |
+
import ProfileList from './ProfileList.js'
|
4 |
+
import { Cfg, Common, Data, Format } from '#miao'
|
5 |
+
import { MysApi, ProfileRank, ProfileArtis, Character, Weapon } from '#miao.models'
|
6 |
+
import ProfileChange from './ProfileChange.js'
|
7 |
+
import { profileArtis } from './ProfileArtis.js'
|
8 |
+
import { ProfileWeapon } from './ProfileWeapon.js'
|
9 |
+
|
10 |
+
let { diyCfg } = await Data.importCfg('profile')
|
11 |
+
|
12 |
+
// 查看当前角色
|
13 |
+
let ProfileDetail = {
|
14 |
+
async detail (e) {
|
15 |
+
let msg = e.original_msg || e.msg
|
16 |
+
if (!msg) {
|
17 |
+
return false
|
18 |
+
}
|
19 |
+
if (!/详细|详情|面板|面版|圣遗物|伤害|武器|换/.test(msg)) {
|
20 |
+
return false
|
21 |
+
}
|
22 |
+
let mode = 'profile'
|
23 |
+
let profileChange = false
|
24 |
+
let changeMsg = msg
|
25 |
+
let pc = ProfileChange.matchMsg(msg)
|
26 |
+
|
27 |
+
if (pc && pc.char && pc.change) {
|
28 |
+
if (!Cfg.get('profileChange')) {
|
29 |
+
e.reply('面板替换功能已禁用...')
|
30 |
+
return true
|
31 |
+
}
|
32 |
+
e.game = pc.game
|
33 |
+
e.isSr = e.game === 'sr'
|
34 |
+
e.uid = ''
|
35 |
+
e.msg = '#喵喵面板变换'
|
36 |
+
e.uid = pc.uid || await getTargetUid(e)
|
37 |
+
profileChange = ProfileChange.getProfile(e.uid, pc.char, pc.change, pc.game)
|
38 |
+
if (profileChange && profileChange.char) {
|
39 |
+
msg = `#${profileChange.char?.name}${pc.mode || '面板'}`
|
40 |
+
e._profile = profileChange
|
41 |
+
e._profileMsg = changeMsg
|
42 |
+
}
|
43 |
+
}
|
44 |
+
let uidRet = /[0-9]{9}/.exec(msg)
|
45 |
+
if (uidRet) {
|
46 |
+
e.uid = uidRet[0]
|
47 |
+
msg = msg.replace(uidRet[0], '')
|
48 |
+
}
|
49 |
+
|
50 |
+
let name = msg.replace(/#|老婆|老公|星铁|原神/g, '').trim()
|
51 |
+
msg = msg.replace('面版', '面板')
|
52 |
+
let dmgRet = /(?:伤害|武器)(\d*)$/.exec(name)
|
53 |
+
let dmgIdx = 0, idxIsInput = false
|
54 |
+
if (/(最强|最高|最高分|最牛|第一)/.test(msg)) {
|
55 |
+
mode = /(分|圣遗物|评分|ACE)/.test(msg) ? 'rank-mark' : 'rank-dmg'
|
56 |
+
name = name.replace(/(最强|最高分|第一|最高|最牛|圣遗物|评分|群)/g, '')
|
57 |
+
}
|
58 |
+
if (/(详情|详细|面板|面版)\s*$/.test(msg) && !/更新|录入|输入/.test(msg)) {
|
59 |
+
mode = 'profile'
|
60 |
+
name = name.replace(/(详情|详细|面板)/, '').trim()
|
61 |
+
} else if (dmgRet) {
|
62 |
+
// mode = /武器/.test(msg) ? 'weapon' : 'dmg'
|
63 |
+
mode = 'dmg'
|
64 |
+
name = name.replace(/(伤害|武器)+\d*/, '').trim()
|
65 |
+
if (dmgRet[1]) {
|
66 |
+
dmgIdx = dmgRet[1] * 1
|
67 |
+
// 标识是用户指定的序号
|
68 |
+
idxIsInput = true
|
69 |
+
}
|
70 |
+
} else if (/(详情|详细|面板)更新$/.test(msg) || (/更新/.test(msg) && /(详情|详细|面板)$/.test(msg))) {
|
71 |
+
mode = 'refresh'
|
72 |
+
name = name.replace(/详情|详细|面板|更新/g, '').trim()
|
73 |
+
} else if (/圣遗物/.test(msg)) {
|
74 |
+
mode = 'artis'
|
75 |
+
name = name.replace('圣遗物', '').trim()
|
76 |
+
}
|
77 |
+
if (!Common.cfg('avatarProfile')) {
|
78 |
+
return false // 面板开关关闭
|
79 |
+
}
|
80 |
+
let char = Character.get(name.trim())
|
81 |
+
if (!char) {
|
82 |
+
return false
|
83 |
+
}
|
84 |
+
if (/星铁/.test(msg) || char.isSr) {
|
85 |
+
e.isSr = true
|
86 |
+
}
|
87 |
+
|
88 |
+
let uid = e.uid || await getTargetUid(e)
|
89 |
+
if (!uid) {
|
90 |
+
return true
|
91 |
+
}
|
92 |
+
e.uid = uid
|
93 |
+
e.avatar = char.id
|
94 |
+
|
95 |
+
if (char.isCustom) {
|
96 |
+
e.reply('自定义角色暂不支持此功能')
|
97 |
+
return true
|
98 |
+
}
|
99 |
+
if (!char.isRelease) {
|
100 |
+
// 预设面板支持未实装角色
|
101 |
+
if (!profileChange && Number(e.uid) > 100000006) {
|
102 |
+
e.reply('角色尚未实装')
|
103 |
+
return true
|
104 |
+
}
|
105 |
+
// 但仅在未实装开启时展示
|
106 |
+
if (Cfg.get('notReleasedData') === false) {
|
107 |
+
e.reply('未实装角色面板已禁用...')
|
108 |
+
return true
|
109 |
+
}
|
110 |
+
}
|
111 |
+
|
112 |
+
if (mode === 'profile' || mode === 'dmg' || mode === 'weapon') {
|
113 |
+
return ProfileDetail.render(e, char, mode, { dmgIdx, idxIsInput })
|
114 |
+
} else if (mode === 'refresh') {
|
115 |
+
await ProfileList.refresh(e)
|
116 |
+
return true
|
117 |
+
} else if (mode === 'artis') {
|
118 |
+
return profileArtis(e)
|
119 |
+
}
|
120 |
+
return true
|
121 |
+
},
|
122 |
+
|
123 |
+
async render (e, char, mode = 'profile', params = {}) {
|
124 |
+
let selfUser = await MysApi.initUser(e)
|
125 |
+
|
126 |
+
if (!selfUser) {
|
127 |
+
e.reply('尚未绑定UID')
|
128 |
+
return true
|
129 |
+
}
|
130 |
+
|
131 |
+
let { uid } = e
|
132 |
+
|
133 |
+
if (char.isCustom) {
|
134 |
+
e.reply(`暂不支持自定义角色${char.name}的面板信息查看`)
|
135 |
+
return true
|
136 |
+
}
|
137 |
+
|
138 |
+
let profile = e._profile || await getProfileRefresh(e, char.id)
|
139 |
+
if (!profile) {
|
140 |
+
return true
|
141 |
+
}
|
142 |
+
char = profile.char || char
|
143 |
+
let a = profile.attr
|
144 |
+
let base = profile.base
|
145 |
+
let attr = {}
|
146 |
+
let game = char.game
|
147 |
+
let isGs = game === 'gs'
|
148 |
+
let isSr = !isGs
|
149 |
+
|
150 |
+
lodash.forEach((isGs ? 'hp,def,atk,mastery' : 'hp,def,atk,speed').split(','), (key) => {
|
151 |
+
let fn = (n) => Format.comma(n, key === 'hp' ? 0 : 1)
|
152 |
+
attr[key] = fn(a[key])
|
153 |
+
attr[`${key}Base`] = fn(base[key])
|
154 |
+
attr[`${key}Plus`] = fn(a[key] - base[key])
|
155 |
+
})
|
156 |
+
lodash.forEach((isGs ? 'cpct,cdmg,recharge,dmg' : 'cpct,cdmg,recharge,dmg,effPct,effDef,heal,stance').split(','), (key) => {
|
157 |
+
let fn = Format.pct
|
158 |
+
let key2 = key
|
159 |
+
if (key === 'dmg') {
|
160 |
+
if (isGs) {
|
161 |
+
if (a.phy > a.dmg) {
|
162 |
+
key2 = 'phy'
|
163 |
+
}
|
164 |
+
}
|
165 |
+
}
|
166 |
+
attr[key] = fn(a[key2])
|
167 |
+
attr[`${key}Base`] = fn(base[key2])
|
168 |
+
attr[`${key}Plus`] = fn(a[key2] - base[key2])
|
169 |
+
})
|
170 |
+
|
171 |
+
let weapon = Weapon.get(profile?.weapon?.name, game)
|
172 |
+
let w = profile.weapon
|
173 |
+
let wCfg = {}
|
174 |
+
if (mode === 'weapon') {
|
175 |
+
wCfg = weapon.calcAttr(w.level, w.promote)
|
176 |
+
wCfg.weapons = await ProfileWeapon.calc(profile)
|
177 |
+
}
|
178 |
+
|
179 |
+
let enemyLv = isGs ? (await selfUser.getCfg('char.enemyLv', 91)) : profile.level
|
180 |
+
let dmgCalc = await ProfileDetail.getProfileDmgCalc({ profile, enemyLv, mode, params })
|
181 |
+
|
182 |
+
let rank = false
|
183 |
+
if (e.group_id && !e._profile) {
|
184 |
+
rank = await ProfileRank.create({ group: e.group_id, uid, qq: e.user_id })
|
185 |
+
await rank.getRank(profile, true)
|
186 |
+
}
|
187 |
+
|
188 |
+
let artisDetail = profile.getArtisMark()
|
189 |
+
let artisKeyTitle = ProfileArtis.getArtisKeyTitle(game)
|
190 |
+
let data = profile.getData('name,abbr,cons,level,talent,dataSource,updateTime,imgs,costumeSplash')
|
191 |
+
if (isSr) {
|
192 |
+
let treeData = []
|
193 |
+
let treeMap = {}
|
194 |
+
// 属性
|
195 |
+
lodash.forEach('0113355778'.split(''), (pos, idx) => {
|
196 |
+
treeData[pos] = treeData[pos] || []
|
197 |
+
let tmp = { type: 'tree', img: `/meta-sr/public/icons/tree-cpct.webp` }
|
198 |
+
treeData[pos].push(tmp)
|
199 |
+
treeMap[idx + 201 + ''] = tmp
|
200 |
+
})
|
201 |
+
// 能力
|
202 |
+
lodash.forEach([2, 4, 6], (pos, idx) => {
|
203 |
+
let tmp = { type: 'talent', img: data.imgs[`tree${idx + 1}`] }
|
204 |
+
treeData[pos] = tmp
|
205 |
+
treeMap[idx + 101 + ''] = tmp
|
206 |
+
})
|
207 |
+
lodash.forEach(profile.trees, (id) => {
|
208 |
+
let ret = /([12][01][0-9])$/.exec(id + '')
|
209 |
+
if (ret && ret[1]) {
|
210 |
+
let treeId = ret[1]
|
211 |
+
if (treeMap?.[treeId]) {
|
212 |
+
treeMap[treeId].value = 1
|
213 |
+
}
|
214 |
+
if (treeId[0] === '2') {
|
215 |
+
treeMap[treeId].img = `/meta-sr/public/icons/tree-${char.detail?.tree?.[id]?.key}.webp`
|
216 |
+
}
|
217 |
+
}
|
218 |
+
})
|
219 |
+
data.treeData = treeData
|
220 |
+
}
|
221 |
+
data.weapon = profile.getWeaponDetail()
|
222 |
+
let renderData = {
|
223 |
+
save_id: uid,
|
224 |
+
uid,
|
225 |
+
game,
|
226 |
+
data,
|
227 |
+
attr,
|
228 |
+
elem: char.elem,
|
229 |
+
dmgCalc,
|
230 |
+
artisDetail,
|
231 |
+
artisKeyTitle,
|
232 |
+
bodyClass: `char-${char.name}`,
|
233 |
+
mode,
|
234 |
+
wCfg,
|
235 |
+
changeProfile: e._profileMsg
|
236 |
+
}
|
237 |
+
// 渲染图像
|
238 |
+
let msgRes = await Common.render('character/profile-detail', renderData, { e, scale: 1.6, retMsgId: true })
|
239 |
+
if (msgRes) {
|
240 |
+
// 如果消息发送成功,就将message_id和图片路径存起来,3小时过期
|
241 |
+
const message_id = [e.message_id]
|
242 |
+
if (Array.isArray(msgRes.message_id)) {
|
243 |
+
message_id.push(...msgRes.message_id)
|
244 |
+
} else {
|
245 |
+
message_id.push(msgRes.message_id)
|
246 |
+
}
|
247 |
+
for (const i of message_id) {
|
248 |
+
await redis.set(`miao:original-picture:${i}`, JSON.stringify({
|
249 |
+
type: 'profile',
|
250 |
+
img: renderData?.data?.costumeSplash
|
251 |
+
}), { EX: 3600 * 3 })
|
252 |
+
}
|
253 |
+
}
|
254 |
+
return true
|
255 |
+
},
|
256 |
+
|
257 |
+
async getProfileDmgCalc ({ profile, enemyLv, mode, params }) {
|
258 |
+
let dmgMsg = []
|
259 |
+
let dmgData = []
|
260 |
+
let dmgCalc = await profile.calcDmg({
|
261 |
+
enemyLv,
|
262 |
+
mode,
|
263 |
+
...params
|
264 |
+
})
|
265 |
+
if (dmgCalc && dmgCalc.ret) {
|
266 |
+
lodash.forEach(dmgCalc.ret, (ds) => {
|
267 |
+
if (ds.type !== 'text') {
|
268 |
+
ds.dmg = Format.comma(ds.dmg, 0)
|
269 |
+
ds.avg = Format.comma(ds.avg, 0)
|
270 |
+
}
|
271 |
+
dmgData.push(ds)
|
272 |
+
})
|
273 |
+
lodash.forEach(dmgCalc.msg, (msg) => {
|
274 |
+
msg.replace(':', ':')
|
275 |
+
dmgMsg.push(msg.split(':'))
|
276 |
+
})
|
277 |
+
|
278 |
+
dmgCalc.dmgMsg = dmgMsg
|
279 |
+
dmgCalc.dmgData = dmgData
|
280 |
+
}
|
281 |
+
|
282 |
+
if (mode === 'dmg' && dmgCalc.dmgRet) {
|
283 |
+
let basic = dmgCalc?.dmgCfg?.basicRet
|
284 |
+
lodash.forEach(dmgCalc.dmgRet, (row) => {
|
285 |
+
lodash.forEach(row, (ds) => {
|
286 |
+
ds.val = (ds.avg > basic.avg ? '+' : '') + Format.comma(ds.avg - basic.avg)
|
287 |
+
ds.dmg = Format.comma(ds.dmg, 0)
|
288 |
+
ds.avg = Format.comma(ds.avg, 0)
|
289 |
+
})
|
290 |
+
})
|
291 |
+
basic.dmg = Format.comma(basic.dmg)
|
292 |
+
basic.avg = Format.comma(basic.avg)
|
293 |
+
}
|
294 |
+
|
295 |
+
return dmgCalc
|
296 |
+
}
|
297 |
+
}
|
298 |
+
|
299 |
+
export default ProfileDetail
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileList.js
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import { getTargetUid } from './ProfileCommon.js'
|
3 |
+
import { Common, Data } from '#miao'
|
4 |
+
import { ProfileRank, Player, Character } from '#miao.models'
|
5 |
+
|
6 |
+
const ProfileList = {
|
7 |
+
/**
|
8 |
+
* 刷新面板
|
9 |
+
* @param e
|
10 |
+
* @returns {Promise<boolean|*>}
|
11 |
+
*/
|
12 |
+
async refresh (e) {
|
13 |
+
let uid = await getTargetUid(e)
|
14 |
+
if (!uid) {
|
15 |
+
e._replyNeedUid || e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
|
16 |
+
return true
|
17 |
+
}
|
18 |
+
|
19 |
+
// 数据更新
|
20 |
+
let player = Player.create(e)
|
21 |
+
await player.refreshProfile(2)
|
22 |
+
|
23 |
+
if (!player?._update?.length) {
|
24 |
+
e._isReplyed || e.reply('获取角色面板数据失败,请确认角色已在游戏内橱窗展示,并开放了查看详情。设置完毕后请5分钟后再进行请求~')
|
25 |
+
e._isReplyed = true
|
26 |
+
} else {
|
27 |
+
let ret = {}
|
28 |
+
lodash.forEach(player._update, (id) => {
|
29 |
+
let char = Character.get(id)
|
30 |
+
if (char) {
|
31 |
+
ret[char.name] = true
|
32 |
+
}
|
33 |
+
})
|
34 |
+
if (lodash.isEmpty(ret)) {
|
35 |
+
e._isReplyed || e.reply('获取角色面板数据失败,未能请求到角色数据。请确认角色已在游戏内橱窗展示,并开放了查看详情。设置完毕后请5分钟后再进行请求~')
|
36 |
+
e._isReplyed = true
|
37 |
+
} else {
|
38 |
+
e.newChar = ret
|
39 |
+
return await ProfileList.render(e)
|
40 |
+
}
|
41 |
+
}
|
42 |
+
return true
|
43 |
+
},
|
44 |
+
|
45 |
+
/**
|
46 |
+
* 渲染面板
|
47 |
+
* @param e
|
48 |
+
* @returns {Promise<boolean|*>}
|
49 |
+
*/
|
50 |
+
|
51 |
+
async render (e) {
|
52 |
+
let uid = await getTargetUid(e)
|
53 |
+
if (!uid) {
|
54 |
+
e._replyNeedUid || e.reply('请先发送【#绑定+你的UID】来绑定查询目标')
|
55 |
+
return true
|
56 |
+
}
|
57 |
+
|
58 |
+
let isSelfUid = false
|
59 |
+
if (e.runtime) {
|
60 |
+
let uids = e.runtime?.user?.ckUids || []
|
61 |
+
isSelfUid = uids.join(',').split(',').includes(uid + '')
|
62 |
+
}
|
63 |
+
let rank = false
|
64 |
+
|
65 |
+
let hasNew = false
|
66 |
+
let newCount = 0
|
67 |
+
|
68 |
+
let chars = []
|
69 |
+
let msg = ''
|
70 |
+
let newChar = {}
|
71 |
+
if (e.newChar) {
|
72 |
+
msg = '获取角色面板数据成功'
|
73 |
+
newChar = e.newChar
|
74 |
+
}
|
75 |
+
const cfg = await Data.importCfg('cfg')
|
76 |
+
// 获取面板数据
|
77 |
+
let player = Player.create(e)
|
78 |
+
let servName = Player.getProfileServName(uid, player.game)
|
79 |
+
if (!player.hasProfile) {
|
80 |
+
await player.refresh({ profile: true })
|
81 |
+
}
|
82 |
+
if (!player.hasProfile) {
|
83 |
+
e.reply(`本地暂无uid${uid}[${player.game}]的面板数据...`)
|
84 |
+
return true
|
85 |
+
}
|
86 |
+
let profiles = player.getProfiles()
|
87 |
+
|
88 |
+
// 检测标志位
|
89 |
+
let qq = (e.at && !e.atBot) ? e.at : e.user_id
|
90 |
+
await ProfileRank.setUidInfo({ uid, profiles, qq, uidType: isSelfUid ? 'ck' : 'bind' })
|
91 |
+
|
92 |
+
let groupId = e.group_id
|
93 |
+
if (groupId) {
|
94 |
+
rank = await ProfileRank.create({ groupId, uid, qq: e.user_id })
|
95 |
+
}
|
96 |
+
const rankCfg = await ProfileRank.getGroupCfg(groupId)
|
97 |
+
const groupRank = rank && (cfg?.diyCfg?.groupRank || false) && rankCfg.status !== 1
|
98 |
+
for (let id in profiles) {
|
99 |
+
let profile = profiles[id]
|
100 |
+
let char = profile.char
|
101 |
+
let tmp = char.getData('id,face,name,abbr,element,star')
|
102 |
+
let imgs = char.getImgs(profile.costume)
|
103 |
+
tmp.face = imgs.qFace || imgs.face
|
104 |
+
tmp.level = profile.level || 1
|
105 |
+
tmp.cons = profile.cons
|
106 |
+
tmp.isNew = 0
|
107 |
+
if (newChar[char.name]) {
|
108 |
+
tmp.isNew = 1
|
109 |
+
newCount++
|
110 |
+
}
|
111 |
+
if (rank) {
|
112 |
+
tmp.groupRank = await rank.getRank(profile, !!tmp.isNew)
|
113 |
+
}
|
114 |
+
chars.push(tmp)
|
115 |
+
}
|
116 |
+
|
117 |
+
if (newCount > 0) {
|
118 |
+
hasNew = newCount <= 8
|
119 |
+
}
|
120 |
+
|
121 |
+
chars = lodash.sortBy(chars, ['isNew', 'star', 'level', 'id'])
|
122 |
+
chars = chars.reverse()
|
123 |
+
|
124 |
+
player.save()
|
125 |
+
// 渲染图像
|
126 |
+
return await Common.render('character/profile-list', {
|
127 |
+
save_id: uid,
|
128 |
+
uid,
|
129 |
+
chars,
|
130 |
+
servName,
|
131 |
+
hasNew,
|
132 |
+
msg,
|
133 |
+
groupRank,
|
134 |
+
updateTime: player.getUpdateTime(),
|
135 |
+
allowRank: rank && rank.allowRank,
|
136 |
+
rankCfg
|
137 |
+
}, { e, scale: 1.6 })
|
138 |
+
},
|
139 |
+
/**
|
140 |
+
* 删除面板数据
|
141 |
+
* @param e
|
142 |
+
* @returns {Promise<boolean>}
|
143 |
+
*/
|
144 |
+
async del (e) {
|
145 |
+
let ret = /^#(删除全部面板|删除面板|删除面板数据)\s*(\d{9})?$/.exec(e.msg)
|
146 |
+
let uid = await getTargetUid(e)
|
147 |
+
if (!uid) {
|
148 |
+
return true
|
149 |
+
}
|
150 |
+
let targetUid = ret[2]
|
151 |
+
|
152 |
+
let user = e?.runtime?.user || {}
|
153 |
+
if (!user.hasCk && !e.isMaster) {
|
154 |
+
e.reply('为确保数据安全,目前仅允许绑定CK用户删除自己UID的面板数据,请联系Bot主人删除...')
|
155 |
+
return true
|
156 |
+
}
|
157 |
+
|
158 |
+
if (!targetUid) {
|
159 |
+
e.reply(`你确认要删除面板数据吗? 请回复 #删除面板${uid} 以删除面板数据`)
|
160 |
+
return true
|
161 |
+
}
|
162 |
+
|
163 |
+
let ckUids = (user?.ckUids || []).join(',').split(',')
|
164 |
+
if (!ckUids.includes(targetUid) && !e.isMaster) {
|
165 |
+
e.reply(`仅允许删除自己的UID数据[${ckUids.join(',')}]`)
|
166 |
+
return true
|
167 |
+
}
|
168 |
+
|
169 |
+
Player.delByUid(targetUid)
|
170 |
+
e.reply(`UID${targetUid}的本地数据已删除,排名数据已清除...`)
|
171 |
+
return true
|
172 |
+
},
|
173 |
+
|
174 |
+
async reload (e) {
|
175 |
+
let uid = await getTargetUid(e)
|
176 |
+
if (!uid) {
|
177 |
+
return true
|
178 |
+
}
|
179 |
+
let player = Player.create(e)
|
180 |
+
player.reload()
|
181 |
+
return ProfileList.render(e)
|
182 |
+
}
|
183 |
+
}
|
184 |
+
export default ProfileList
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileRank.js
ADDED
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ProfileDetail from './ProfileDetail.js'
|
2 |
+
import { Data, Common, Format, Cfg } from '#miao'
|
3 |
+
import { Character, ProfileRank, ProfileDmg, Player } from '#miao.models'
|
4 |
+
import lodash from 'lodash'
|
5 |
+
|
6 |
+
export async function groupRank (e) {
|
7 |
+
const groupRank = Common.cfg('groupRank')
|
8 |
+
let msg = e.original_msg || e.msg
|
9 |
+
let type = ''
|
10 |
+
if (/(排名|排行|列表)/.test(msg)) {
|
11 |
+
type = 'list'
|
12 |
+
} else if (/(最强|最高|最多|最高分|最牛|第一)/.test(msg)) {
|
13 |
+
type = 'detail'
|
14 |
+
} else if (/极限/.test(msg)) {
|
15 |
+
type = 'super'
|
16 |
+
}
|
17 |
+
let groupId = e.group_id
|
18 |
+
if (!type || (!groupId && type !== 'super')) {
|
19 |
+
return false
|
20 |
+
}
|
21 |
+
let mode = /(分|圣遗物|评分|ACE)/.test(msg) ? 'mark' : 'dmg'
|
22 |
+
mode = /(词条)/.test(msg) ? 'valid' : mode
|
23 |
+
mode = /(双爆)/.test(msg) ? 'crit' : mode
|
24 |
+
let name = msg.replace(/(#|星铁|最强|最高分|第一|词条|双爆|极限|最高|最多|最牛|圣遗物|评分|群内|群|排名|排行|面板|面版|详情|榜)/g, '')
|
25 |
+
let char = Character.get(name)
|
26 |
+
if (!char) {
|
27 |
+
// 名字不存在或不为列表模式,则返回false
|
28 |
+
if (name || type !== 'list') {
|
29 |
+
return false
|
30 |
+
}
|
31 |
+
}
|
32 |
+
if (/星铁/.test(msg) || char.isSr) {
|
33 |
+
e.isSr = true
|
34 |
+
}
|
35 |
+
// 对鲸泽佬的极限角色文件增加支持
|
36 |
+
if (type === 'super') {
|
37 |
+
let player = Player.create(100000000)
|
38 |
+
if (player.getProfile(char.id)) {
|
39 |
+
e.uid = 100000000
|
40 |
+
if (Cfg.get('notReleasedData') === false) {
|
41 |
+
e.reply('未实装角色面板已禁用...')
|
42 |
+
return true
|
43 |
+
}
|
44 |
+
return await ProfileDetail.render(e, char)
|
45 |
+
} else {
|
46 |
+
return true
|
47 |
+
}
|
48 |
+
}
|
49 |
+
// 正常群排名
|
50 |
+
let groupCfg = await ProfileRank.getGroupCfg(groupId)
|
51 |
+
if (!groupRank) {
|
52 |
+
e.reply('群面板排名功能已禁用,Bot主人可通过【#喵喵设置】启用...')
|
53 |
+
return true
|
54 |
+
}
|
55 |
+
if (groupCfg.status === 1) {
|
56 |
+
e.reply('本群已关闭群排名,群管理员或Bot主人可通过【#启用排名】启用...')
|
57 |
+
return true
|
58 |
+
}
|
59 |
+
if (type === 'detail') {
|
60 |
+
let uid = await ProfileRank.getGroupMaxUid(groupId, char.id, mode)
|
61 |
+
if (uid) {
|
62 |
+
e.uid = uid
|
63 |
+
return await ProfileDetail.render(e, char)
|
64 |
+
} else {
|
65 |
+
if (mode === 'dmg' && !ProfileDmg.dmgRulePath(char.name, char.game)) {
|
66 |
+
e.reply(`暂无排名:${char.name}暂不支持伤害计算,无法进行排名..`)
|
67 |
+
} else {
|
68 |
+
e.reply('暂无排名:请通过【#面板】查看角色面板以更新排名信息...')
|
69 |
+
}
|
70 |
+
}
|
71 |
+
} else if (type === 'list') {
|
72 |
+
if (mode === 'dmg' && char && !ProfileDmg.dmgRulePath(char.name, char.game)) {
|
73 |
+
e.reply(`暂无排名:${char.name}暂不支持伤害计算,无法进行排名..`)
|
74 |
+
} else {
|
75 |
+
let uids = []
|
76 |
+
if (char) {
|
77 |
+
uids = await ProfileRank.getGroupUidList(groupId, char ? char.id : '', mode)
|
78 |
+
} else {
|
79 |
+
uids = await ProfileRank.getGroupMaxUidList(groupId, mode)
|
80 |
+
}
|
81 |
+
if (uids.length > 0) {
|
82 |
+
return renderCharRankList({ e, uids, char, mode, groupId })
|
83 |
+
} else {
|
84 |
+
if (e.isSr){
|
85 |
+
e.reply('暂无排名:请通过【*面板】查看角色面板以更新排名信息...')
|
86 |
+
} else {
|
87 |
+
e.reply('暂无排名:请通过【#面板】查看角色面板以更新排名信息...')
|
88 |
+
}
|
89 |
+
}
|
90 |
+
}
|
91 |
+
return true
|
92 |
+
}
|
93 |
+
}
|
94 |
+
|
95 |
+
export async function resetRank (e) {
|
96 |
+
let groupId = e.group_id
|
97 |
+
if (!groupId) {
|
98 |
+
return true
|
99 |
+
}
|
100 |
+
if (!e.isMaster) {
|
101 |
+
e.reply('只有管理员可重置排名')
|
102 |
+
return true
|
103 |
+
}
|
104 |
+
let msg = e.original_msg || e.msg
|
105 |
+
let name = msg.replace(/(#|重置|重设|排名|排行|群|群内|面板|详情|面版)/g, '').trim()
|
106 |
+
let charId = ''
|
107 |
+
let charName = '全部角色'
|
108 |
+
if (name) {
|
109 |
+
let char = Character.get(name)
|
110 |
+
if (!char) {
|
111 |
+
e.reply(`重置排名失败,角色:${name}不存在`)
|
112 |
+
return true
|
113 |
+
}
|
114 |
+
charId = char.id
|
115 |
+
charName = char.name
|
116 |
+
}
|
117 |
+
await ProfileRank.resetRank(groupId, charId)
|
118 |
+
e.reply(`本群${charName}排名已重置...`)
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* 刷新群排名信息
|
123 |
+
* @param e
|
124 |
+
* @returns {Promise<boolean>}
|
125 |
+
*/
|
126 |
+
export async function refreshRank (e) {
|
127 |
+
let groupId = e.group_id || ''
|
128 |
+
if (!groupId) {
|
129 |
+
return true
|
130 |
+
}
|
131 |
+
if (!e.isMaster && !this.e.member?.is_admin) {
|
132 |
+
e.reply('只有主人及群管理员可刷新排名...')
|
133 |
+
return true
|
134 |
+
}
|
135 |
+
e.reply('面板数据刷新中,等待时间可能较长,请耐心等待...')
|
136 |
+
let game = e.isSr ? 'sr' : 'gs'
|
137 |
+
await ProfileRank.resetRank(groupId)
|
138 |
+
let uidMap = await ProfileRank.getUserUidMap(e, game)
|
139 |
+
let count = 0
|
140 |
+
for (let uid in uidMap) {
|
141 |
+
let { qq, type } = uidMap[uid]
|
142 |
+
let player = new Player(uid, game)
|
143 |
+
let profiles = player.getProfiles()
|
144 |
+
// 刷新rankLimit
|
145 |
+
await ProfileRank.setUidInfo({ uid, profiles, qq, uidType: type })
|
146 |
+
let rank = await ProfileRank.create({ groupId, uid, qq })
|
147 |
+
for (let id in profiles) {
|
148 |
+
let profile = profiles[id]
|
149 |
+
if (!profile.hasData) {
|
150 |
+
continue
|
151 |
+
}
|
152 |
+
await rank.getRank(profile, true)
|
153 |
+
}
|
154 |
+
if (rank.allowRank) {
|
155 |
+
count++
|
156 |
+
}
|
157 |
+
}
|
158 |
+
e.reply(`本群排名已刷新,共刷新${count}个UID数据...`)
|
159 |
+
}
|
160 |
+
|
161 |
+
export async function manageRank (e) {
|
162 |
+
let groupId = e.group_id
|
163 |
+
if (!groupId) {
|
164 |
+
return true
|
165 |
+
}
|
166 |
+
let isClose = /(关闭|禁用)/.test(e.msg)
|
167 |
+
if (!e.isMaster && !this.e.member?.is_admin) {
|
168 |
+
e.reply(`只有主人及群管理员可${isClose ? '禁用' : '启用'}排名...`)
|
169 |
+
return true
|
170 |
+
}
|
171 |
+
await ProfileRank.setGroupStatus(groupId, isClose ? 1 : 0)
|
172 |
+
if (isClose) {
|
173 |
+
e.reply('当前群排名功能已禁用...')
|
174 |
+
} else {
|
175 |
+
e.reply('当前群排名功能已启用...\n如数据有问题可通过【#刷新排名】命令来刷新当前群内排名')
|
176 |
+
}
|
177 |
+
}
|
178 |
+
|
179 |
+
async function renderCharRankList ({ e, uids, char, mode, groupId }) {
|
180 |
+
let list = []
|
181 |
+
for (let ds of uids) {
|
182 |
+
let uid = ds.uid || ds.value
|
183 |
+
let player = Player.create(uid, e.isSr ? 'sr' : 'gs')
|
184 |
+
let avatar = player.getAvatar(ds.charId || char.id)
|
185 |
+
if (!avatar) {
|
186 |
+
continue
|
187 |
+
}
|
188 |
+
let profile = avatar.getProfile()
|
189 |
+
|
190 |
+
if (profile) {
|
191 |
+
let profileRank = await ProfileRank.create({ groupId, uid })
|
192 |
+
let data = await profileRank.getRank(profile, true)
|
193 |
+
let mark = data?.mark?.data
|
194 |
+
let tmp = {
|
195 |
+
uid,
|
196 |
+
isMax: !char,
|
197 |
+
...avatar.getData('id,star,name,sName,level,fetter,cons,weapon,elem,talent,artisSet,imgs'),
|
198 |
+
artisMark: Data.getData(mark, 'mark,markClass,valid,crit')
|
199 |
+
}
|
200 |
+
let dmg = data?.dmg?.data
|
201 |
+
if (dmg && dmg.avg) {
|
202 |
+
let title = dmg.title
|
203 |
+
// 稍微缩短下title
|
204 |
+
if (title.length > 10) {
|
205 |
+
title = title.replace(/[ ·]*/g, '')
|
206 |
+
}
|
207 |
+
title = title.length > 10 ? title.replace(/伤害$/, '') : title
|
208 |
+
tmp.dmg = {
|
209 |
+
title: title,
|
210 |
+
avg: Format.comma(dmg.avg, 1)
|
211 |
+
}
|
212 |
+
}
|
213 |
+
if (uid) {
|
214 |
+
let userInfo = await ProfileRank.getUidInfo(uid)
|
215 |
+
try {
|
216 |
+
if (userInfo?.qq && e?.group?.pickMember) {
|
217 |
+
let member = e.group.pickMember(userInfo.qq)
|
218 |
+
if (member?.getAvatarUrl) {
|
219 |
+
let img = await member.getAvatarUrl()
|
220 |
+
if (img) {
|
221 |
+
tmp.qqFace = img
|
222 |
+
}
|
223 |
+
}
|
224 |
+
}
|
225 |
+
} catch (e) {
|
226 |
+
// console.log(e)
|
227 |
+
}
|
228 |
+
}
|
229 |
+
|
230 |
+
if (mode === 'crit') {
|
231 |
+
tmp._mark = mark?._crit * 6.6044 || 0
|
232 |
+
} else if (mode === 'valid') {
|
233 |
+
tmp._mark = mark?._valid || 0
|
234 |
+
} else {
|
235 |
+
tmp._mark = mark?._mark || 0
|
236 |
+
}
|
237 |
+
tmp._formatmark = Format.comma(tmp._mark, 1)
|
238 |
+
tmp._dmg = dmg?.avg || 0
|
239 |
+
tmp._star = 5 - tmp.star
|
240 |
+
list.push(tmp)
|
241 |
+
}
|
242 |
+
}
|
243 |
+
let title
|
244 |
+
if (char) {
|
245 |
+
let modeTitleMap = {}
|
246 |
+
if (e.isSr) {
|
247 |
+
modeTitleMap = {
|
248 |
+
dmg: '',
|
249 |
+
mark: '遗器评分',
|
250 |
+
crit: '双爆副词条',
|
251 |
+
valid: '加权有效词条'
|
252 |
+
}
|
253 |
+
} else {
|
254 |
+
modeTitleMap = {
|
255 |
+
dmg: '',
|
256 |
+
mark: '圣遗物评分',
|
257 |
+
crit: '双爆副词条',
|
258 |
+
valid: '加权有效词条'
|
259 |
+
}
|
260 |
+
}
|
261 |
+
title = `${e.isSr ? '*' : '#'}${char.name}${modeTitleMap[mode]}排行`
|
262 |
+
list = lodash.sortBy(list, mode === 'dmg' ? '_dmg' : '_mark').reverse()
|
263 |
+
} else {
|
264 |
+
title = `${e.isSr ? '*' : '#'}${mode === 'mark' ? '最高分' : '最强'}排行`
|
265 |
+
list = lodash.sortBy(list, ['uid', '_star', 'id'])
|
266 |
+
}
|
267 |
+
|
268 |
+
const rankCfg = await ProfileRank.getGroupCfg(groupId)
|
269 |
+
// 渲染图像
|
270 |
+
return await Common.render('character/rank-profile-list', {
|
271 |
+
save_id: char.id,
|
272 |
+
game: e.isSr ? 'sr' : 'gs',
|
273 |
+
list,
|
274 |
+
title,
|
275 |
+
elem: char.elem,
|
276 |
+
bodyClass: `char-${char.name}`,
|
277 |
+
rankCfg,
|
278 |
+
mode
|
279 |
+
}, { e, scale: 1.4 })
|
280 |
+
}
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileStat.js
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Common } from '#miao'
|
2 |
+
import { MysApi, Player, Character } from '#miao.models'
|
3 |
+
|
4 |
+
const ProfileStat = {
|
5 |
+
async stat (e) {
|
6 |
+
return ProfileStat.render(e, false)
|
7 |
+
},
|
8 |
+
|
9 |
+
async avatarList (e) {
|
10 |
+
return ProfileStat.render(e, true)
|
11 |
+
},
|
12 |
+
async render (e, isAvatarList = false) {
|
13 |
+
// 缓存时间,单位小时
|
14 |
+
let msg = e.msg.replace('#', '').trim()
|
15 |
+
if (msg === '角色统计' || msg === '武器统计') {
|
16 |
+
// 暂时避让一下抽卡分析的关键词
|
17 |
+
return false
|
18 |
+
}
|
19 |
+
|
20 |
+
let mys = await MysApi.init(e)
|
21 |
+
if (!mys || !mys.uid) return false
|
22 |
+
|
23 |
+
const uid = mys.uid
|
24 |
+
|
25 |
+
let player = Player.create(e)
|
26 |
+
|
27 |
+
let avatarRet = await player.refreshAndGetAvatarData({
|
28 |
+
index: 2,
|
29 |
+
detail: 1,
|
30 |
+
talent: isAvatarList ? 0 : 1,
|
31 |
+
rank: true,
|
32 |
+
retType: 'array',
|
33 |
+
sort: true
|
34 |
+
})
|
35 |
+
|
36 |
+
if (avatarRet.length === 0) {
|
37 |
+
e._isReplyed || e.reply(`查询失败,暂未获得#${uid}角色数据,请绑定CK或 #更新面板`)
|
38 |
+
return true
|
39 |
+
}
|
40 |
+
|
41 |
+
let faceChar = Character.get(player.face || avatarRet[0]?.id)
|
42 |
+
let imgs = faceChar.imgs
|
43 |
+
let face = {
|
44 |
+
banner: imgs?.banner,
|
45 |
+
face: imgs?.face,
|
46 |
+
qFace: imgs?.qFace,
|
47 |
+
name: player.name || `#${uid}`,
|
48 |
+
sign: player.sign,
|
49 |
+
level: player.level
|
50 |
+
}
|
51 |
+
|
52 |
+
let info = player.getInfo()
|
53 |
+
info.stats = info.stats || {}
|
54 |
+
info.statMap = {
|
55 |
+
achievement: '成就',
|
56 |
+
wayPoint: '锚点',
|
57 |
+
avatar: '角色',
|
58 |
+
avatar5: '五星角色',
|
59 |
+
goldCount: '金卡总数'
|
60 |
+
}
|
61 |
+
|
62 |
+
return await Common.render(isAvatarList ? 'character/avatar-list' : 'character/profile-stat', {
|
63 |
+
save_id: uid,
|
64 |
+
uid,
|
65 |
+
info,
|
66 |
+
updateTime: player.getUpdateTime(),
|
67 |
+
isSelfCookie: e.isSelfCookie,
|
68 |
+
face,
|
69 |
+
avatars: avatarRet
|
70 |
+
}, { e, scale: 1.4 })
|
71 |
+
}
|
72 |
+
}
|
73 |
+
export default ProfileStat
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileUtils.js
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Cfg } from '#miao'
|
2 |
+
import { MysApi } from '#miao.models'
|
3 |
+
|
4 |
+
/** 获取角色卡片的原图 */
|
5 |
+
export async function getOriginalPicture (e) {
|
6 |
+
let source
|
7 |
+
if (e.reply_id) {
|
8 |
+
source = { message_id: e.reply_id }
|
9 |
+
} else {
|
10 |
+
if (!e.hasReply && !e.source) {
|
11 |
+
return false
|
12 |
+
}
|
13 |
+
// 引用的消息不是自己的消息
|
14 |
+
if (e.source.user_id !== e.self_id) {
|
15 |
+
return false
|
16 |
+
}
|
17 |
+
// 引用的消息不是纯图片
|
18 |
+
if (!/^\[图片]$/.test(e.source.message)) {
|
19 |
+
return false
|
20 |
+
}
|
21 |
+
// 获取原消息
|
22 |
+
if (e.group?.getChatHistory) {
|
23 |
+
source = (await e.group.getChatHistory(e.source.seq, 1)).pop()
|
24 |
+
} else if (e.friend?.getChatHistory) {
|
25 |
+
source = (await e.friend.getChatHistory(e.source.time, 1)).pop()
|
26 |
+
}
|
27 |
+
}
|
28 |
+
let originalPic = Cfg.get('originalPic') * 1
|
29 |
+
if (source) {
|
30 |
+
let imgPath = await redis.get(`miao:original-picture:${source.message_id}`)
|
31 |
+
if (imgPath) {
|
32 |
+
try {
|
33 |
+
if (imgPath[0] === '{') {
|
34 |
+
imgPath = JSON.parse(imgPath)
|
35 |
+
} else {
|
36 |
+
imgPath = { img: imgPath, type: '' }
|
37 |
+
}
|
38 |
+
} catch (e) {
|
39 |
+
}
|
40 |
+
if (!e.isMaster) {
|
41 |
+
if (imgPath.type === 'character' && [2, 0].includes(originalPic)) {
|
42 |
+
e.reply('已禁止获取角色原图...')
|
43 |
+
return true
|
44 |
+
}
|
45 |
+
if (imgPath.type === 'profile' && [1, 0].includes(originalPic)) {
|
46 |
+
e.reply('已禁止获取面板原图...')
|
47 |
+
return true
|
48 |
+
}
|
49 |
+
}
|
50 |
+
if (imgPath && imgPath.img) {
|
51 |
+
e.reply(segment.image(`file://${process.cwd()}/plugins/miao-plugin/resources/${decodeURIComponent(imgPath.img)}`), false, { recallMsg: 30 })
|
52 |
+
}
|
53 |
+
return true
|
54 |
+
}
|
55 |
+
// 对at错图像的增加嘲讽...
|
56 |
+
e.reply(segment.image(`file://${process.cwd()}/plugins/miao-plugin/resources/common/face/what.jpg`))
|
57 |
+
return false
|
58 |
+
}
|
59 |
+
e.reply('消息太过久远了,俺也忘了原图是啥了,下次早点来吧~')
|
60 |
+
return false
|
61 |
+
}
|
62 |
+
|
63 |
+
/* #敌人等级 */
|
64 |
+
export async function enemyLv (e) {
|
65 |
+
let selfUser = await MysApi.initUser(e)
|
66 |
+
if (!selfUser || !e.msg) {
|
67 |
+
return true
|
68 |
+
}
|
69 |
+
let ret = /(敌人|怪物)等级\s*(\d{1,3})\s*$/.exec(e.msg)
|
70 |
+
if (ret && ret[2]) {
|
71 |
+
let lv = ret[2] * 1
|
72 |
+
await selfUser.setCfg('char.enemyLv', lv)
|
73 |
+
lv = await selfUser.getCfg('char.enemyLv', 91)
|
74 |
+
e.reply(`敌人等级已经设置为${lv}`)
|
75 |
+
return true
|
76 |
+
}
|
77 |
+
return true
|
78 |
+
}
|
Yunzai/plugins/miao-plugin/apps/profile/ProfileWeapon.js
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ProfileData, Weapon } from '#miao.models'
|
2 |
+
|
3 |
+
export const ProfileWeapon = {
|
4 |
+
async calc (profile, game = 'gs') {
|
5 |
+
let ret = []
|
6 |
+
await Weapon.forEach(async (w) => {
|
7 |
+
let weaponRet = w.getData('name,star,abbr,icon')
|
8 |
+
weaponRet.dmgs = []
|
9 |
+
for (let affix of [1, 5]) {
|
10 |
+
if (affix === 5 && w.maxAffix !== 5) {
|
11 |
+
continue
|
12 |
+
}
|
13 |
+
let tempProfile = new ProfileData({
|
14 |
+
...profile.getData('uid,id,level,cons,fetter,elem,promote,talent,artis'),
|
15 |
+
dataSource: 'change'
|
16 |
+
}, game, false)
|
17 |
+
|
18 |
+
tempProfile.setWeapon({
|
19 |
+
name: w.name,
|
20 |
+
star: w.star,
|
21 |
+
level: w.maxLv,
|
22 |
+
promote: w.maxPromote,
|
23 |
+
affix
|
24 |
+
})
|
25 |
+
tempProfile.calcAttr()
|
26 |
+
weaponRet.dmgs.push({
|
27 |
+
affix,
|
28 |
+
...await tempProfile.calcDmg({ mode: 'single' })
|
29 |
+
})
|
30 |
+
}
|
31 |
+
ret.push(weaponRet)
|
32 |
+
}, profile?.weapon?.type)
|
33 |
+
return ret
|
34 |
+
}
|
35 |
+
|
36 |
+
}
|
Yunzai/plugins/miao-plugin/apps/stat.js
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* 胡桃数据库的统计
|
3 |
+
*
|
4 |
+
* */
|
5 |
+
import { ConsStat, AbyssPct } from './stat/AbyssStat.js'
|
6 |
+
import { AbyssTeam } from './stat/AbyssTeam.js'
|
7 |
+
import { AbyssSummary } from './stat/AbyssSummary.js'
|
8 |
+
import { App } from '#miao'
|
9 |
+
|
10 |
+
let app = App.init({
|
11 |
+
id: 'stat',
|
12 |
+
name: '深渊统计'
|
13 |
+
})
|
14 |
+
|
15 |
+
app.reg({
|
16 |
+
consStat: {
|
17 |
+
rule: /^#(喵喵)?角色(持有|持有率|命座|命之座|.命)(分布|统计|持有|持有率)?$/,
|
18 |
+
fn: ConsStat,
|
19 |
+
desc: '【#统计】 #角色持有率 #角色5命统计'
|
20 |
+
},
|
21 |
+
abyssPct: {
|
22 |
+
rule: /^#(喵喵)?深渊(第?.{1,2}层)?(角色)?(出场|使用)(率|统计)*$/,
|
23 |
+
fn: AbyssPct,
|
24 |
+
desc: '【#统计】 #深渊出场率 #深渊12层出场率'
|
25 |
+
},
|
26 |
+
abyssTeam: {
|
27 |
+
rule: /^#深渊(组队|配队)$/,
|
28 |
+
fn: AbyssTeam,
|
29 |
+
describe: '【#角色】 #深渊组队'
|
30 |
+
},
|
31 |
+
abyssSummary: {
|
32 |
+
rule: /^#*(喵喵|上传|本期)*(深渊|深境|深境螺旋)[ |0-9]*(数据)?$/,
|
33 |
+
fn: AbyssSummary,
|
34 |
+
desc: '上传深渊'
|
35 |
+
}
|
36 |
+
})
|
37 |
+
export default app
|
Yunzai/plugins/miao-plugin/apps/stat/AbyssStat.js
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import HutaoApi from './HutaoApi.js'
|
2 |
+
import lodash from 'lodash'
|
3 |
+
import { Common } from '#miao'
|
4 |
+
import { Character } from '#miao.models'
|
5 |
+
|
6 |
+
export async function ConsStat (e) {
|
7 |
+
let consData = await HutaoApi.getCons()
|
8 |
+
let overview = await HutaoApi.getOverview()
|
9 |
+
|
10 |
+
if (!consData) {
|
11 |
+
e.reply('角色持有数据获取失败,请稍后重试~')
|
12 |
+
return true
|
13 |
+
}
|
14 |
+
|
15 |
+
let msg = e.msg
|
16 |
+
|
17 |
+
let mode = /持有/.test(msg) ? 'char' : 'cons'
|
18 |
+
|
19 |
+
let conNum = -1
|
20 |
+
if (mode === 'cons') {
|
21 |
+
lodash.forEach([/0|零/, /1|一/, /2|二/, /3|三/, /4|四/, /5|五/, /6|六|满/], (reg, idx) => {
|
22 |
+
if (reg.test(msg)) {
|
23 |
+
conNum = idx
|
24 |
+
return false
|
25 |
+
}
|
26 |
+
})
|
27 |
+
}
|
28 |
+
|
29 |
+
if (!consData && !consData.data) {
|
30 |
+
return true
|
31 |
+
}
|
32 |
+
|
33 |
+
let data = consData.data
|
34 |
+
|
35 |
+
let Lumine = lodash.filter(data, (ds) => ds.avatar === 10000007)[0] || {}
|
36 |
+
let Aether = lodash.filter(data, (ds) => ds.avatar === 10000005)[0] || {}
|
37 |
+
|
38 |
+
Lumine.holdingRate = (1 - Aether.holdingRate) || Lumine.holdingRate
|
39 |
+
|
40 |
+
let ret = []
|
41 |
+
|
42 |
+
lodash.forEach(data, (ds) => {
|
43 |
+
let char = Character.get(ds.avatar)
|
44 |
+
|
45 |
+
let data = {
|
46 |
+
name: char.name || ds.avatar,
|
47 |
+
abbr: char.abbr,
|
48 |
+
star: char.star || 3,
|
49 |
+
side: char.side,
|
50 |
+
hold: ds.holdingRate
|
51 |
+
}
|
52 |
+
|
53 |
+
if (mode === 'char') {
|
54 |
+
data.cons = lodash.map(ds.rate, (c) => {
|
55 |
+
c.value = c.value * ds.holdingRate
|
56 |
+
return c
|
57 |
+
})
|
58 |
+
} else {
|
59 |
+
data.cons = ds.rate
|
60 |
+
}
|
61 |
+
data.cons = lodash.sortBy(data.cons, ['id'])
|
62 |
+
|
63 |
+
ret.push(data)
|
64 |
+
})
|
65 |
+
|
66 |
+
if (conNum > -1) {
|
67 |
+
ret = lodash.sortBy(ret, [`cons[${conNum}].value`])
|
68 |
+
ret.reverse()
|
69 |
+
} else {
|
70 |
+
ret = lodash.sortBy(ret, ['hold'])
|
71 |
+
}
|
72 |
+
// 渲染图像
|
73 |
+
return await Common.render('stat/character', {
|
74 |
+
chars: ret,
|
75 |
+
mode,
|
76 |
+
conNum,
|
77 |
+
totalCount: overview?.data?.totalPlayerCount || 0,
|
78 |
+
lastUpdate: consData.lastUpdate,
|
79 |
+
pct: function (num) {
|
80 |
+
return (num * 100).toFixed(2)
|
81 |
+
}
|
82 |
+
}, { e, scale: 1.5 })
|
83 |
+
}
|
84 |
+
|
85 |
+
export async function AbyssPct (e) {
|
86 |
+
let mode = /使用/.test(e.msg) ? 'use' : 'pct'
|
87 |
+
let modeName
|
88 |
+
let abyssData
|
89 |
+
let modeMulti = 1
|
90 |
+
|
91 |
+
if (mode === 'use') {
|
92 |
+
modeName = '使用率'
|
93 |
+
abyssData = await HutaoApi.getAbyssUse()
|
94 |
+
} else {
|
95 |
+
modeName = '出场率'
|
96 |
+
abyssData = await HutaoApi.getAbyssPct()
|
97 |
+
modeMulti = 8
|
98 |
+
}
|
99 |
+
let overview = await HutaoApi.getOverview()
|
100 |
+
|
101 |
+
if (!abyssData) {
|
102 |
+
e.reply(`深渊${modeName}数据获取失败,请稍后重试~`)
|
103 |
+
return true
|
104 |
+
}
|
105 |
+
|
106 |
+
let ret = []
|
107 |
+
let chooseFloor = -1
|
108 |
+
let msg = e.msg
|
109 |
+
|
110 |
+
const floorName = {
|
111 |
+
12: '十二层',
|
112 |
+
11: '十一层',
|
113 |
+
10: '十层',
|
114 |
+
9: '九层'
|
115 |
+
}
|
116 |
+
|
117 |
+
// 匹配深渊楼层信息
|
118 |
+
lodash.forEach(floorName, (cn, num) => {
|
119 |
+
let reg = new RegExp(`${cn}|${num}`)
|
120 |
+
if (reg.test(msg)) {
|
121 |
+
chooseFloor = num
|
122 |
+
return false
|
123 |
+
}
|
124 |
+
})
|
125 |
+
|
126 |
+
let data = abyssData.data
|
127 |
+
data = lodash.sortBy(data, 'floor')
|
128 |
+
data = data.reverse()
|
129 |
+
|
130 |
+
lodash.forEach(data, (floorData) => {
|
131 |
+
let avatars = []
|
132 |
+
lodash.forEach(floorData.avatarUsage, (ds) => {
|
133 |
+
let char = Character.get(ds.id)
|
134 |
+
if (char) {
|
135 |
+
avatars.push({
|
136 |
+
name: char.name,
|
137 |
+
star: char.star,
|
138 |
+
value: ds.value * modeMulti,
|
139 |
+
face: char.face
|
140 |
+
})
|
141 |
+
}
|
142 |
+
})
|
143 |
+
avatars = lodash.sortBy(avatars, 'value', ['asc'])
|
144 |
+
avatars.reverse()
|
145 |
+
if (chooseFloor === -1) {
|
146 |
+
avatars = avatars.slice(0, 14)
|
147 |
+
}
|
148 |
+
|
149 |
+
ret.push({
|
150 |
+
floor: floorData.floor,
|
151 |
+
avatars
|
152 |
+
})
|
153 |
+
})
|
154 |
+
|
155 |
+
return await Common.render('stat/abyss-pct', {
|
156 |
+
abyss: ret,
|
157 |
+
floorName,
|
158 |
+
chooseFloor,
|
159 |
+
mode,
|
160 |
+
modeName,
|
161 |
+
totalCount: overview?.data?.collectedPlayerCount || 0,
|
162 |
+
lastUpdate: abyssData.lastUpdate
|
163 |
+
}, { e, scale: 1.5 })
|
164 |
+
}
|
Yunzai/plugins/miao-plugin/apps/stat/AbyssSummary.js
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import HutaoApi from './HutaoApi.js'
|
3 |
+
import { Cfg, Common, Data } from '#miao'
|
4 |
+
import { Abyss, Character, MysApi, Player } from '#miao.models'
|
5 |
+
|
6 |
+
export async function AbyssSummary (e) {
|
7 |
+
let isMatch = /^#(喵喵|上传)深渊(数据)?$/.test(e.original_msg || e.msg || '')
|
8 |
+
if (!Cfg.get('uploadAbyssData', false) && !isMatch) {
|
9 |
+
return false
|
10 |
+
}
|
11 |
+
let mys = await MysApi.init(e, 'all')
|
12 |
+
if (!mys || !mys.uid) {
|
13 |
+
if (isMatch) {
|
14 |
+
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)
|
15 |
+
}
|
16 |
+
return false
|
17 |
+
}
|
18 |
+
let ret = {}
|
19 |
+
let uid = mys.uid
|
20 |
+
let player = Player.create(e)
|
21 |
+
let resDetail, resAbyss
|
22 |
+
try {
|
23 |
+
resAbyss = await mys.getSpiralAbyss(1)
|
24 |
+
let lvs = Data.getVal(resAbyss, 'floors.0.levels.0')
|
25 |
+
// 检查是否查询到了深渊信息
|
26 |
+
if (!lvs || !lvs.battles) {
|
27 |
+
e.reply('暂未获得本期深渊挑战数据...')
|
28 |
+
return true
|
29 |
+
} else if (lvs && lvs.battles && lvs.battles.length === 0) {
|
30 |
+
if (!mys.isSelfCookie) {
|
31 |
+
if (isMatch) {
|
32 |
+
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)
|
33 |
+
}
|
34 |
+
return false
|
35 |
+
}
|
36 |
+
}
|
37 |
+
resDetail = await mys.getCharacter()
|
38 |
+
if (!resDetail || !resAbyss || !resDetail.avatars || resDetail.avatars.length <= 3) {
|
39 |
+
e.reply('角色信息获取失败')
|
40 |
+
return true
|
41 |
+
}
|
42 |
+
delete resDetail._res
|
43 |
+
delete resAbyss._res
|
44 |
+
ret = await HutaoApi.uploadData({
|
45 |
+
uid,
|
46 |
+
resDetail,
|
47 |
+
resAbyss
|
48 |
+
})
|
49 |
+
} catch (err) {
|
50 |
+
// console.log(err);
|
51 |
+
}
|
52 |
+
// 更新player信息
|
53 |
+
player.setMysCharData(resDetail)
|
54 |
+
|
55 |
+
if (ret && ret.retcode === 0) {
|
56 |
+
let stat = []
|
57 |
+
if (ret.data) {
|
58 |
+
if (resAbyss.floors.length === 0) {
|
59 |
+
e.reply('暂未获得本期深渊挑战数据...')
|
60 |
+
return true
|
61 |
+
}
|
62 |
+
let abyss = new Abyss(resAbyss)
|
63 |
+
let abyssData = abyss.getData()
|
64 |
+
let avatarIds = abyss.getAvatars()
|
65 |
+
let overview = ret.info || (await HutaoApi.getOverview())?.data || {}
|
66 |
+
let addMsg = function (title, ds) {
|
67 |
+
let tmp = {}
|
68 |
+
if (!ds) {
|
69 |
+
return false
|
70 |
+
}
|
71 |
+
if (!ds.avatarId && !ds.id) {
|
72 |
+
return false
|
73 |
+
}
|
74 |
+
let char = Character.get(ds.avatarId || ds.id)
|
75 |
+
tmp.title = title
|
76 |
+
tmp.id = char.id
|
77 |
+
tmp.value = `${(ds.value / 10000).toFixed(1)} W`
|
78 |
+
let msg = []
|
79 |
+
tmp.msg = msg
|
80 |
+
let pct = (percent, name) => {
|
81 |
+
if (percent < 0.2) {
|
82 |
+
msg.push({
|
83 |
+
title: '少于',
|
84 |
+
value: (Math.max(0.1, 100 - percent * 100)).toFixed(1),
|
85 |
+
name: name
|
86 |
+
})
|
87 |
+
} else {
|
88 |
+
msg.push({
|
89 |
+
title: '超过',
|
90 |
+
value: (Math.min(99.9, percent * 100)).toFixed(1),
|
91 |
+
name: name
|
92 |
+
})
|
93 |
+
}
|
94 |
+
}
|
95 |
+
if (ds.percent) {
|
96 |
+
pct(ds.percent, char.abbr)
|
97 |
+
pct(ds.percentTotal, '总记录')
|
98 |
+
} else {
|
99 |
+
msg.push({
|
100 |
+
txt: '暂无统计信息'
|
101 |
+
})
|
102 |
+
}
|
103 |
+
stat.push(tmp)
|
104 |
+
}
|
105 |
+
addMsg('最强一击', ret.data?.damage || abyssData?.stat?.dmg || {})
|
106 |
+
addMsg('最高承伤', ret.data?.takeDamage || abyssData?.stat.takeDmg || {})
|
107 |
+
let abyssStat = abyssData?.stat || {}
|
108 |
+
lodash.forEach({ defeat: '最多击破', e: '元素战技', q: '元素爆发' }, (title, key) => {
|
109 |
+
if (abyssStat[key]) {
|
110 |
+
stat.push({
|
111 |
+
title,
|
112 |
+
id: abyssStat[key]?.id || 0,
|
113 |
+
value: `${abyssStat[key]?.value}次`
|
114 |
+
})
|
115 |
+
} else {
|
116 |
+
stat.push({})
|
117 |
+
}
|
118 |
+
})
|
119 |
+
await player.refreshTalent(avatarIds)
|
120 |
+
let avatarData = player.getAvatarData(avatarIds)
|
121 |
+
return await Common.render('stat/abyss-summary', {
|
122 |
+
abyss: abyssData,
|
123 |
+
avatars: avatarData,
|
124 |
+
stat,
|
125 |
+
save_id: uid,
|
126 |
+
totalCount: overview?.collectedPlayerCount || 0,
|
127 |
+
uid
|
128 |
+
}, { e, scale: 1.2 })
|
129 |
+
} else {
|
130 |
+
e.reply('暂未获得本期深渊挑战数据...')
|
131 |
+
return true
|
132 |
+
}
|
133 |
+
} else {
|
134 |
+
e.reply(`${ret.message || '上传失败'},请稍后重试...`)
|
135 |
+
}
|
136 |
+
return true
|
137 |
+
}
|
Yunzai/plugins/miao-plugin/apps/stat/AbyssTeam.js
ADDED
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import HutaoApi from './HutaoApi.js'
|
3 |
+
import { Common } from '#miao'
|
4 |
+
import { Character, MysApi, Player } from '#miao.models'
|
5 |
+
|
6 |
+
export async function AbyssTeam (e) {
|
7 |
+
let mys = await MysApi.init(e, 'all')
|
8 |
+
if (!mys || !mys.uid) {
|
9 |
+
e.reply(`请绑定ck后再使用${e.original_msg || e.msg}`)
|
10 |
+
return false
|
11 |
+
}
|
12 |
+
let player = Player.create(e)
|
13 |
+
await player.refreshMysDetail(2)
|
14 |
+
await player.refreshTalent()
|
15 |
+
|
16 |
+
let abyssData = await HutaoApi.getAbyssTeam()
|
17 |
+
if (!abyssData || !abyssData.data) {
|
18 |
+
e.reply('深渊组队数据获取失败,请稍后重试~')
|
19 |
+
return true
|
20 |
+
}
|
21 |
+
abyssData = abyssData.data
|
22 |
+
let avatarData = player.getAvatarData()
|
23 |
+
let avatarRet = {}
|
24 |
+
let data = {}
|
25 |
+
let noAvatar = {}
|
26 |
+
lodash.forEach(avatarData, (avatar) => {
|
27 |
+
let t = avatar.originalTalent || {}
|
28 |
+
avatarRet[avatar.id] = Math.min(avatar.level, (avatar.weapon?.level || 1)) * 100 + Math.max(t?.a || 1, t?.e || 1, t?.q || 1) * 1000
|
29 |
+
})
|
30 |
+
|
31 |
+
let getTeamCfg = (str) => {
|
32 |
+
let teams = str.split(',')
|
33 |
+
teams.sort()
|
34 |
+
let teamMark = 0
|
35 |
+
lodash.forEach(teams, (a) => {
|
36 |
+
if (!avatarRet[a]) {
|
37 |
+
teamMark = -1
|
38 |
+
noAvatar[a] = true
|
39 |
+
}
|
40 |
+
if (teamMark !== -1) {
|
41 |
+
teamMark += avatarRet[a] * 1
|
42 |
+
}
|
43 |
+
})
|
44 |
+
if (teamMark === -1) {
|
45 |
+
teamMark = 1
|
46 |
+
}
|
47 |
+
return {
|
48 |
+
key: teams.join(','),
|
49 |
+
mark: teamMark
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
let hasSame = function (team1, team2) {
|
54 |
+
for (let idx = 0; idx < team1.length; idx++) {
|
55 |
+
if (team2.includes(team1[idx])) {
|
56 |
+
return true
|
57 |
+
}
|
58 |
+
}
|
59 |
+
return false
|
60 |
+
}
|
61 |
+
|
62 |
+
lodash.forEach(abyssData, (ds) => {
|
63 |
+
let floor = ds.floor
|
64 |
+
if (!data[floor]) {
|
65 |
+
data[floor] = {
|
66 |
+
up: {},
|
67 |
+
down: {},
|
68 |
+
teams: []
|
69 |
+
}
|
70 |
+
}
|
71 |
+
lodash.forEach(['up', 'down'], (halfKey) => {
|
72 |
+
lodash.forEach(ds[halfKey], (ds) => {
|
73 |
+
let teamCfg = getTeamCfg(ds.item)
|
74 |
+
if (teamCfg) {
|
75 |
+
if (!data[floor][halfKey][teamCfg.key]) {
|
76 |
+
data[floor][halfKey][teamCfg.key] = {
|
77 |
+
count: 0,
|
78 |
+
mark: 0,
|
79 |
+
hasTeam: teamCfg.mark > 1
|
80 |
+
}
|
81 |
+
}
|
82 |
+
data[floor][halfKey][teamCfg.key].count += ds.rate
|
83 |
+
data[floor][halfKey][teamCfg.key].mark += ds.rate * teamCfg.mark
|
84 |
+
}
|
85 |
+
})
|
86 |
+
})
|
87 |
+
|
88 |
+
let temp = []
|
89 |
+
lodash.forEach(['up', 'down'], (halfKey) => {
|
90 |
+
lodash.forEach(data[floor][halfKey], (ds, team) => {
|
91 |
+
temp.push({
|
92 |
+
team,
|
93 |
+
teamArr: team.split(','),
|
94 |
+
half: halfKey,
|
95 |
+
count: ds.count,
|
96 |
+
mark: ds.mark,
|
97 |
+
mark2: 1,
|
98 |
+
hasTeam: ds.hasTeam
|
99 |
+
})
|
100 |
+
})
|
101 |
+
temp = lodash.sortBy(temp, 'mark')
|
102 |
+
data[floor].teams = temp.reverse()
|
103 |
+
})
|
104 |
+
})
|
105 |
+
|
106 |
+
let ret = {}
|
107 |
+
|
108 |
+
lodash.forEach(data, (floorData, floor) => {
|
109 |
+
ret[floor] = {}
|
110 |
+
let ds = ret[floor]
|
111 |
+
lodash.forEach(floorData.teams, (t1) => {
|
112 |
+
if (t1.mark2 <= 0) {
|
113 |
+
return true
|
114 |
+
}
|
115 |
+
lodash.forEach(floorData.teams, (t2) => {
|
116 |
+
if (t1.mark2 <= 0) {
|
117 |
+
return true
|
118 |
+
}
|
119 |
+
if (t1.half === t2.half || t2.mark2 <= 0) {
|
120 |
+
return true
|
121 |
+
}
|
122 |
+
|
123 |
+
let teamKey = t1.half === 'up' ? (t1.team + '+' + t2.team) : (t2.team + '+' + t1.team)
|
124 |
+
if (ds[teamKey]) {
|
125 |
+
return true
|
126 |
+
}
|
127 |
+
if (hasSame(t1.teamArr, t2.teamArr)) {
|
128 |
+
return true
|
129 |
+
}
|
130 |
+
|
131 |
+
ds[teamKey] = {
|
132 |
+
up: t1.half === 'up' ? t1 : t2,
|
133 |
+
down: t1.half === 'up' ? t2 : t1,
|
134 |
+
count: Math.min(t1.count, t2.count),
|
135 |
+
mark: t1.hasTeam && t2.hasTeam ? t1.mark + t2.mark : t1.count + t2.count // 如果不存在组队则进行评分惩罚
|
136 |
+
}
|
137 |
+
t1.mark2--
|
138 |
+
t2.mark2--
|
139 |
+
return false
|
140 |
+
})
|
141 |
+
if (lodash.keys(ds).length >= 20) {
|
142 |
+
return false
|
143 |
+
}
|
144 |
+
})
|
145 |
+
})
|
146 |
+
|
147 |
+
lodash.forEach(ret, (ds, floor) => {
|
148 |
+
ds = lodash.sortBy(lodash.values(ds), 'mark')
|
149 |
+
ds = ds.reverse()
|
150 |
+
ds = ds.slice(0, 4)
|
151 |
+
|
152 |
+
lodash.forEach(ds, (team) => {
|
153 |
+
team.up.teamArr = Character.sortIds(team.up.teamArr)
|
154 |
+
team.down.teamArr = Character.sortIds(team.down.teamArr)
|
155 |
+
})
|
156 |
+
|
157 |
+
ret[floor] = ds
|
158 |
+
})
|
159 |
+
|
160 |
+
let avatarMap = {}
|
161 |
+
|
162 |
+
lodash.forEach(avatarData, (ds) => {
|
163 |
+
let char = Character.get(ds.id)
|
164 |
+
avatarMap[ds.id] = {
|
165 |
+
id: ds.id,
|
166 |
+
name: ds.name,
|
167 |
+
star: ds.star,
|
168 |
+
level: ds.level,
|
169 |
+
cons: ds.cons,
|
170 |
+
face: char.face
|
171 |
+
}
|
172 |
+
})
|
173 |
+
|
174 |
+
lodash.forEach(noAvatar, (d, id) => {
|
175 |
+
let char = Character.get(id)
|
176 |
+
avatarMap[id] = {
|
177 |
+
id,
|
178 |
+
name: char.name,
|
179 |
+
face: char.face,
|
180 |
+
star: char.star,
|
181 |
+
level: 0,
|
182 |
+
cons: 0
|
183 |
+
}
|
184 |
+
})
|
185 |
+
|
186 |
+
return await Common.render('stat/abyss-team', {
|
187 |
+
teams: ret,
|
188 |
+
avatars: avatarMap
|
189 |
+
}, { e, scale: 1.5 })
|
190 |
+
}
|
Yunzai/plugins/miao-plugin/apps/stat/HutaoApi.js
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* 胡桃API Miao-Plugin 封装
|
3 |
+
* https://github.com/DGP-Studio/DGP.Genshin.HutaoAPI
|
4 |
+
*
|
5 |
+
* */
|
6 |
+
|
7 |
+
import fetch from 'node-fetch'
|
8 |
+
import { Data } from '#miao'
|
9 |
+
|
10 |
+
const host = 'http://miao.games/api/hutao'
|
11 |
+
|
12 |
+
function getApi (api) {
|
13 |
+
return `${host}?api=${api}`
|
14 |
+
}
|
15 |
+
|
16 |
+
let HutaoApi = {
|
17 |
+
async req (url, param = {}, EX = 3600) {
|
18 |
+
let cacheData = await Data.getCacheJSON(`miao:hutao:${url}`)
|
19 |
+
if (cacheData && cacheData.data && param.method !== 'POST') {
|
20 |
+
return cacheData
|
21 |
+
}
|
22 |
+
let response = await fetch(getApi(`${url}`), {
|
23 |
+
...param,
|
24 |
+
method: param.method || 'GET'
|
25 |
+
})
|
26 |
+
let retData = await response.json()
|
27 |
+
if (retData && retData.data && param.method !== 'POST') {
|
28 |
+
let d = new Date()
|
29 |
+
retData.lastUpdate = `${d.toLocaleDateString()} ${d.toTimeString().substr(0, 5)}`
|
30 |
+
await Data.setCacheJSON(`miao:hutao:${url}`, retData, EX)
|
31 |
+
}
|
32 |
+
return retData
|
33 |
+
},
|
34 |
+
|
35 |
+
// 角色持有及命座分布
|
36 |
+
async getCons () {
|
37 |
+
return await HutaoApi.req('/Statistics/Constellation')
|
38 |
+
},
|
39 |
+
|
40 |
+
async getAbyssPct () {
|
41 |
+
return await HutaoApi.req('/Statistics/AvatarParticipation')
|
42 |
+
},
|
43 |
+
|
44 |
+
async getAbyssUse () {
|
45 |
+
return await HutaoApi.req('/Statistics2/AvatarParticipation')
|
46 |
+
},
|
47 |
+
|
48 |
+
async getAbyssTeam () {
|
49 |
+
return await HutaoApi.req('/Statistics/Team/Combination')
|
50 |
+
},
|
51 |
+
|
52 |
+
async getOverview () {
|
53 |
+
return await HutaoApi.req('/Statistics/Overview')
|
54 |
+
},
|
55 |
+
|
56 |
+
async getUsage () {
|
57 |
+
return await HutaoApi.req('/Statistics/Avatar/AvatarCollocation')
|
58 |
+
},
|
59 |
+
|
60 |
+
async uploadData (data = {}) {
|
61 |
+
let body = JSON.stringify(data)
|
62 |
+
return await HutaoApi.req('/Record/UploadData', {
|
63 |
+
method: 'POST',
|
64 |
+
headers: {
|
65 |
+
'User-Agent': 'Yunzai-Bot/Miao-Plugin',
|
66 |
+
'Content-Type': 'text/json; charset=utf-8'
|
67 |
+
},
|
68 |
+
body
|
69 |
+
})
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
export default HutaoApi
|
Yunzai/plugins/miao-plugin/apps/wiki.js
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { App } from '#miao'
|
2 |
+
import Calendar from './wiki/Calendar.js'
|
3 |
+
import CharWiki from './wiki/CharWiki.js'
|
4 |
+
import CalendarSr from './wiki/CalendarSr.js'
|
5 |
+
|
6 |
+
let app = App.init({
|
7 |
+
id: 'wiki',
|
8 |
+
name: '角色资料'
|
9 |
+
})
|
10 |
+
app.reg({
|
11 |
+
wiki: {
|
12 |
+
rule: '^#喵喵WIKI$',
|
13 |
+
check: CharWiki.check,
|
14 |
+
fn: CharWiki.wiki,
|
15 |
+
desc: '【#资料】 #神里天赋 #夜兰命座'
|
16 |
+
},
|
17 |
+
calendar: {
|
18 |
+
rule: /^(#|喵喵)+(日历|日历列表)$/,
|
19 |
+
fn: Calendar.render,
|
20 |
+
desc: '【#日历】 原神活动日历'
|
21 |
+
},
|
22 |
+
calendarSr: {
|
23 |
+
rule: /^#(星铁)+(日历|日历列表)$/,
|
24 |
+
fn: CalendarSr.render,
|
25 |
+
desc: '【#星铁日历】 星铁活动日历'
|
26 |
+
}
|
27 |
+
})
|
28 |
+
|
29 |
+
export default app
|
Yunzai/plugins/miao-plugin/apps/wiki/Calendar.js
ADDED
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import fetch from 'node-fetch'
|
3 |
+
import moment from 'moment'
|
4 |
+
import { Common, Data, Cfg } from '#miao'
|
5 |
+
import { Character, Material } from '#miao.models'
|
6 |
+
|
7 |
+
const ignoreIds = [495, // 有奖问卷调查开启!
|
8 |
+
1263, // 米游社《原神》专属工具一览
|
9 |
+
423, // 《原神》玩家社区一览
|
10 |
+
422, // 《原神》防沉迷系统说明
|
11 |
+
762 // 《原神》公平运营声明
|
12 |
+
]
|
13 |
+
|
14 |
+
const ignoreReg = /(内容专题页|版本更新说明|调研|防沉迷|米游社|专项意见|更新修复与优化|问卷调查|版本更新通知|更新时间说明|预下载功能|周边限时|周边上新|角色演示)/
|
15 |
+
const fulltimeReg = /(魔神任务)/
|
16 |
+
|
17 |
+
let Cal = {
|
18 |
+
async reqCalData () {
|
19 |
+
let listApi = 'https://hk4e-api.mihoyo.com/common/hk4e_cn/announcement/api/getAnnList?game=hk4e&game_biz=hk4e_cn&lang=zh-cn&bundle_id=hk4e_cn&platform=pc®ion=cn_gf01&level=55&uid=100000000'
|
20 |
+
|
21 |
+
let request = await fetch(listApi)
|
22 |
+
let listData = await request.json()
|
23 |
+
|
24 |
+
let timeMap
|
25 |
+
let timeMapCache = await redis.get('miao:calendar:detail')
|
26 |
+
if (timeMapCache) {
|
27 |
+
timeMap = JSON.parse(timeMapCache) || {}
|
28 |
+
} else {
|
29 |
+
let detailApi = 'https://hk4e-api.mihoyo.com/common/hk4e_cn/announcement/api/getAnnContent?game=hk4e&game_biz=hk4e_cn&lang=zh-cn&bundle_id=hk4e_cn&platform=pc®ion=cn_gf01&level=55&uid=100000000'
|
30 |
+
let request2 = await fetch(detailApi)
|
31 |
+
let detailData = await request2.json()
|
32 |
+
timeMap = {}
|
33 |
+
if (detailData && detailData.data && detailData.data.list) {
|
34 |
+
let versionTime = {
|
35 |
+
2.7: '2022-05-31 11:00:00',
|
36 |
+
2.8: '2022-07-13 11:00:00',
|
37 |
+
3.0: '2022-08-24 11:00:00',
|
38 |
+
3.1: '2022-09-28 11:00:00',
|
39 |
+
3.2: '2022-11-02 11:00:00',
|
40 |
+
3.3: '2022-12-07 11:00:00'
|
41 |
+
}
|
42 |
+
lodash.forEach(detailData.data.list, (ds) => {
|
43 |
+
let vRet = /(\d\.\d)版本更新通知/.exec(ds.title)
|
44 |
+
if (vRet && vRet[1]) {
|
45 |
+
let content = /(?:更新时间)\s*〓([^〓]+)(?:〓|$)/.exec(ds.content)
|
46 |
+
if (content && content[1]) {
|
47 |
+
let tRet = /([0-9\\/\\: ]){9,}/.exec(content[1])
|
48 |
+
if (tRet && tRet[0]) {
|
49 |
+
versionTime[vRet[1]] = versionTime[vRet[1]] || tRet[0].replace('06:00', '11:00')
|
50 |
+
}
|
51 |
+
}
|
52 |
+
}
|
53 |
+
})
|
54 |
+
lodash.forEach(detailData.data.list, (ds) => {
|
55 |
+
let { ann_id: annId, content, title } = ds
|
56 |
+
if (ignoreReg.test(title)) {
|
57 |
+
return true
|
58 |
+
}
|
59 |
+
content = content.replace(/(<|<)[\w "%:;=\-\\/\\(\\),\\.]+(>|>)/g, '')
|
60 |
+
content = /(?:活动时间|祈愿介绍|任务开放时间|冒险....包|折扣时间)\s*〓([^〓]+)(〓|$)/.exec(content)
|
61 |
+
if (!content || !content[1]) {
|
62 |
+
return true
|
63 |
+
}
|
64 |
+
content = content[1]
|
65 |
+
let annTime = []
|
66 |
+
|
67 |
+
// 第一种简单格式
|
68 |
+
let timeRet = /(?:活动时间)?(?:〓|\s)*([0-9\\/\\: ~]{6,})/.exec(content)
|
69 |
+
if (timeRet && timeRet[1]) {
|
70 |
+
annTime = timeRet[1].split('~')
|
71 |
+
} else if (/\d\.\d版本更新后/.test(content)) {
|
72 |
+
let vRet = /(\d\.\d)版本更新后/.exec(content)
|
73 |
+
let vTime = ''
|
74 |
+
if (vRet && vRet[1] && versionTime[vRet[1]]) {
|
75 |
+
vTime = versionTime[vRet[1]]
|
76 |
+
}
|
77 |
+
if (!vTime) {
|
78 |
+
return true
|
79 |
+
}
|
80 |
+
if (/永久开放/.test(content)) {
|
81 |
+
annTime = [vTime, '2099/01/01 00:00:00']
|
82 |
+
} else {
|
83 |
+
timeRet = /([0-9\\/\\: ]){9,}/.exec(content)
|
84 |
+
if (timeRet && timeRet[0]) {
|
85 |
+
annTime = [vTime, timeRet[0]]
|
86 |
+
}
|
87 |
+
}
|
88 |
+
}
|
89 |
+
if (annTime.length === 2) {
|
90 |
+
timeMap[annId] = {
|
91 |
+
start: annTime[0].trim().replace(/\//g, '-'),
|
92 |
+
end: annTime[1].trim().replace(/\//g, '-')
|
93 |
+
}
|
94 |
+
}
|
95 |
+
})
|
96 |
+
}
|
97 |
+
let miaoApi = 'http://miao.games/api/calendar'
|
98 |
+
try {
|
99 |
+
request2 = await fetch(miaoApi)
|
100 |
+
let data = await request2.json()
|
101 |
+
if (data && data.status === 0 && data.data) {
|
102 |
+
lodash.forEach(data.data, (ds, id) => {
|
103 |
+
timeMap[id] = ds
|
104 |
+
})
|
105 |
+
}
|
106 |
+
} catch (e) {
|
107 |
+
}
|
108 |
+
await Data.setCacheJSON('miao:calendar:detail', timeMap, 60 * 10)
|
109 |
+
}
|
110 |
+
return { listData, timeMap }
|
111 |
+
},
|
112 |
+
|
113 |
+
getDateList () {
|
114 |
+
let today = moment()
|
115 |
+
let temp = today.add(-7, 'days')
|
116 |
+
let dateList = []
|
117 |
+
let month = 0
|
118 |
+
let date = []
|
119 |
+
let week = []
|
120 |
+
|
121 |
+
let startDate, endDate
|
122 |
+
|
123 |
+
for (let idx = 0; idx < 13; idx++) {
|
124 |
+
temp = today.add(1, 'days')
|
125 |
+
let m = temp.month() + 1
|
126 |
+
let d = temp.date()
|
127 |
+
if (month === 0) {
|
128 |
+
startDate = temp.format('YYYY-MM-DD')
|
129 |
+
month = m
|
130 |
+
}
|
131 |
+
if (month !== m && date.length > 0) {
|
132 |
+
dateList.push({
|
133 |
+
month,
|
134 |
+
date,
|
135 |
+
week
|
136 |
+
})
|
137 |
+
date = []
|
138 |
+
week = []
|
139 |
+
month = m
|
140 |
+
}
|
141 |
+
date.push(d)
|
142 |
+
week.push(temp.weekday())
|
143 |
+
if (idx === 12) {
|
144 |
+
dateList.push({
|
145 |
+
month,
|
146 |
+
date,
|
147 |
+
week
|
148 |
+
})
|
149 |
+
endDate = temp.format('YYYY-MM-DD')
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
let startTime = moment(startDate + ' 00:00:00')
|
154 |
+
let endTime = moment(endDate + ' 23:59:59')
|
155 |
+
|
156 |
+
let totalRange = endTime - startTime
|
157 |
+
return {
|
158 |
+
dateList,
|
159 |
+
startTime,
|
160 |
+
endTime,
|
161 |
+
totalRange,
|
162 |
+
nowLeft: (moment() - startTime) / totalRange * 100
|
163 |
+
}
|
164 |
+
},
|
165 |
+
|
166 |
+
// 深渊日历信息
|
167 |
+
getAbyssCal (s1, e1) {
|
168 |
+
let now = moment()
|
169 |
+
let check = []
|
170 |
+
let f = 'YYYY-MM'
|
171 |
+
let last = now.add(-1, 'M').format(f)
|
172 |
+
let lastM = now.format('MMMM')
|
173 |
+
let curr = now.add(1, 'M').format(f)
|
174 |
+
let currM = now.format('MMMM')
|
175 |
+
let next = now.add(1, 'M').format(f)
|
176 |
+
let nextM = now.format('MMMM')
|
177 |
+
|
178 |
+
check.push([moment(`${last}-16 04:00:00`), moment(`${curr}-01 03:59:59`), lastM + '下半'])
|
179 |
+
check.push([moment(`${curr}-01 04:00:00`), moment(`${curr}-16 03:59:59`), currM + '上半'])
|
180 |
+
check.push([moment(`${curr}-16 04:00:00`), moment(`${next}-01 03:59:59`), currM + '下半'])
|
181 |
+
check.push([moment(`${next}-01 04:00:00`), moment(`${next}-16 03:59:59`), nextM + '上半'])
|
182 |
+
|
183 |
+
let ret = []
|
184 |
+
lodash.forEach(check, (ds) => {
|
185 |
+
let [s2, e2] = ds
|
186 |
+
if ((s2 <= s1 && s1 <= e2) || (s2 <= e1 && e1 <= e2)) {
|
187 |
+
ret.push(ds)
|
188 |
+
}
|
189 |
+
})
|
190 |
+
return ret
|
191 |
+
},
|
192 |
+
|
193 |
+
/**
|
194 |
+
* 获取角色数据
|
195 |
+
* @param dateList
|
196 |
+
* @returns {{charBirth: {}, charNum: number, charTalent: (*|{})}}
|
197 |
+
*/
|
198 |
+
getCharData (dateList) {
|
199 |
+
let charBirth = {}
|
200 |
+
let charTalent = {}
|
201 |
+
// 初始化生日数据
|
202 |
+
lodash.forEach(dateList, (m) => {
|
203 |
+
lodash.forEach(m.date, (d) => {
|
204 |
+
charBirth[`${m.month}-${d}`] = []
|
205 |
+
})
|
206 |
+
})
|
207 |
+
// 初始化天赋数据
|
208 |
+
let now = moment(new Date())
|
209 |
+
if (now.hour() < 4) {
|
210 |
+
now = now.add(-1, 'days')
|
211 |
+
}
|
212 |
+
let week = now.weekday()
|
213 |
+
Material.forEach('talent', (material) => {
|
214 |
+
let data = material.getData('name,abbr,city,icon,week,cid')
|
215 |
+
data.chars = []
|
216 |
+
charTalent[material.name] = data
|
217 |
+
}, (ds) => ds.star === 4 && (week === 6 || ds.week === week % 3 + 1))
|
218 |
+
// 遍历角色数据
|
219 |
+
Character.forEach((char) => {
|
220 |
+
if (charBirth[char.birth] && (char.isRelease || char.birth !== '1-1')) {
|
221 |
+
charBirth[char.birth].push(char.getData('id,name:sName,star,face'))
|
222 |
+
}
|
223 |
+
let t = char.materials?.talent
|
224 |
+
if (t && charTalent[t] && !char.isTraveler) {
|
225 |
+
let data = char.getData('id,name:sName,star,face')
|
226 |
+
data.weekly = char.getMaterials('weekly')?.icon
|
227 |
+
charTalent[t].chars.push(data)
|
228 |
+
}
|
229 |
+
}, Cfg.get('notReleasedData') ? 'official' : 'release')
|
230 |
+
let charNum = 0
|
231 |
+
lodash.forEach(charBirth, (charList) => {
|
232 |
+
charNum = Math.max(charNum, charList.length)
|
233 |
+
})
|
234 |
+
charTalent = lodash.values(charTalent)
|
235 |
+
charTalent = lodash.sortBy(charTalent, 'cid')
|
236 |
+
lodash.forEach(charTalent, (ds) => {
|
237 |
+
ds.chars = lodash.sortBy(ds.chars, ['star', 'id']).reverse()
|
238 |
+
})
|
239 |
+
return { charBirth, charNum, charTalent }
|
240 |
+
},
|
241 |
+
|
242 |
+
/**
|
243 |
+
* 获取日历列表
|
244 |
+
* @param ds
|
245 |
+
* @param target
|
246 |
+
* @param startTime
|
247 |
+
* @param endTime
|
248 |
+
* @param totalRange
|
249 |
+
* @param now
|
250 |
+
* @param timeMap
|
251 |
+
* @param isAct
|
252 |
+
* @returns {boolean}
|
253 |
+
*/
|
254 |
+
getList (ds, target, { startTime, endTime, totalRange, now, timeMap = {} }, isAct = false) {
|
255 |
+
let type = isAct ? 'activity' : 'normal'
|
256 |
+
let id = ds.ann_id
|
257 |
+
let title = ds.title
|
258 |
+
let banner = isAct ? ds.banner : ''
|
259 |
+
let extra = { sort: isAct ? 5 : 10 }
|
260 |
+
let detail = timeMap[id] || {}
|
261 |
+
|
262 |
+
if (ignoreIds.includes(id) || ignoreReg.test(title) || detail.display === false) {
|
263 |
+
return false
|
264 |
+
}
|
265 |
+
|
266 |
+
if (/神铸赋形/.test(title)) {
|
267 |
+
type = 'weapon'
|
268 |
+
title = title.replace(/(单手剑|双手剑|长柄武器|弓|法器|·)/g, '')
|
269 |
+
extra.sort = 2
|
270 |
+
} else if (/祈愿/.test(title)) {
|
271 |
+
type = 'character'
|
272 |
+
let regRet = /·(.*)\(/.exec(title)
|
273 |
+
if (regRet[1]) {
|
274 |
+
let char = Character.get(regRet[1])
|
275 |
+
extra.banner2 = char.getImgs()?.card
|
276 |
+
extra.face = char.face
|
277 |
+
extra.character = regRet[1]
|
278 |
+
extra.elem = char.elem
|
279 |
+
extra.sort = 1
|
280 |
+
}
|
281 |
+
} else if (/纪行/.test(title)) {
|
282 |
+
type = 'pass'
|
283 |
+
}
|
284 |
+
|
285 |
+
let getDate = (d1, d2) => moment(d1 && d1.length > 6 ? d1 : d2)
|
286 |
+
let sDate = getDate(detail.start, ds.start_time)
|
287 |
+
let eDate = getDate(detail.end, ds.end_time)
|
288 |
+
let sTime = moment.max(sDate, startTime)
|
289 |
+
let eTime = moment.min(eDate, endTime)
|
290 |
+
|
291 |
+
let sRange = sTime - startTime
|
292 |
+
let eRange = eTime - startTime
|
293 |
+
|
294 |
+
let left = sRange / totalRange * 100
|
295 |
+
let width = eRange / totalRange * 100 - left
|
296 |
+
|
297 |
+
let label = ''
|
298 |
+
if (fulltimeReg.test(title) || eDate - sDate > 365 * 24 * 3600 * 1000) {
|
299 |
+
if (sDate < now) {
|
300 |
+
label = sDate.format('MM-DD HH:mm') + ' 后永久有效'
|
301 |
+
} else {
|
302 |
+
label = '永久有效'
|
303 |
+
}
|
304 |
+
} else if (now > sDate && eDate > now) {
|
305 |
+
label = eDate.format('MM-DD HH:mm') + ' (' + moment.duration(eDate - now).humanize() + '后结束)'
|
306 |
+
if (width > (isAct ? 38 : 55)) {
|
307 |
+
label = sDate.format('MM-DD HH:mm') + ' ~ ' + label
|
308 |
+
}
|
309 |
+
} else if (sDate > now) {
|
310 |
+
label = sDate.format('MM-DD HH:mm') + ' (' + moment.duration(sDate - now).humanize() + '后开始)'
|
311 |
+
} else if (isAct) {
|
312 |
+
label = sDate.format('MM-DD HH:mm') + ' ~ ' + eDate.format('MM-DD HH:mm')
|
313 |
+
}
|
314 |
+
if (sDate <= endTime && eDate >= startTime) {
|
315 |
+
target.push({
|
316 |
+
...extra,
|
317 |
+
id,
|
318 |
+
title,
|
319 |
+
type,
|
320 |
+
mergeStatus: ['activity', 'normal'].includes(type) ? 1 : 0,
|
321 |
+
banner,
|
322 |
+
icon: ds.tag_icon,
|
323 |
+
left,
|
324 |
+
width,
|
325 |
+
label,
|
326 |
+
duration: eTime - sTime,
|
327 |
+
start: sDate.format('MM-DD HH:mm'),
|
328 |
+
end: eDate.format('MM-DD HH:mm')
|
329 |
+
})
|
330 |
+
}
|
331 |
+
},
|
332 |
+
|
333 |
+
async get () {
|
334 |
+
moment.locale('zh-cn')
|
335 |
+
let now = moment()
|
336 |
+
|
337 |
+
let { listData, timeMap } = await Cal.reqCalData()
|
338 |
+
|
339 |
+
let dl = Cal.getDateList()
|
340 |
+
|
341 |
+
let list = []
|
342 |
+
let abyss = []
|
343 |
+
|
344 |
+
lodash.forEach(listData.data.list[1].list, (ds) => Cal.getList(ds, list, { ...dl, now, timeMap }, true))
|
345 |
+
lodash.forEach(listData.data.list[0].list, (ds) => Cal.getList(ds, list, { ...dl, now, timeMap }, false))
|
346 |
+
|
347 |
+
let abyssCal = Cal.getAbyssCal(dl.startTime, dl.endTime)
|
348 |
+
lodash.forEach(abyssCal, (t) => {
|
349 |
+
Cal.getList({
|
350 |
+
title: `「深境螺旋」· ${t[2]}`,
|
351 |
+
start_time: t[0].format('YYYY-MM-DD HH:mm'),
|
352 |
+
end_time: t[1].format('YYYY-MM-DD HH:mm')
|
353 |
+
}, abyss, { ...dl, now }, true)
|
354 |
+
})
|
355 |
+
|
356 |
+
list = lodash.sortBy(list, ['sort', 'start', 'duration'])
|
357 |
+
|
358 |
+
let charCount = 0
|
359 |
+
let charOld = 0
|
360 |
+
let weaponCount = 0
|
361 |
+
let ret = []
|
362 |
+
lodash.forEach(list, (li) => {
|
363 |
+
if (li.type === 'character') {
|
364 |
+
charCount++
|
365 |
+
li.left === 0 && charOld++
|
366 |
+
li.idx = charCount
|
367 |
+
}
|
368 |
+
if (li.type === 'weapon') {
|
369 |
+
weaponCount++
|
370 |
+
li.idx = weaponCount
|
371 |
+
}
|
372 |
+
if (li.mergeStatus === 1) {
|
373 |
+
lodash.forEach(list, (li2) => {
|
374 |
+
if (li2.mergeStatus === 1 && li.left + li.width <= li2.left) {
|
375 |
+
li.mergeStatus = 2
|
376 |
+
li2.mergeStatus = 2
|
377 |
+
ret.push([li, li2])
|
378 |
+
return false
|
379 |
+
}
|
380 |
+
})
|
381 |
+
}
|
382 |
+
if (li.mergeStatus !== 2) {
|
383 |
+
li.mergeStatus = 2
|
384 |
+
ret.push([li])
|
385 |
+
}
|
386 |
+
})
|
387 |
+
|
388 |
+
return {
|
389 |
+
game: 'gs',
|
390 |
+
...dl,
|
391 |
+
...Cal.getCharData(dl.dateList),
|
392 |
+
list: ret,
|
393 |
+
abyss,
|
394 |
+
charMode: `char-${charCount}-${charOld}`,
|
395 |
+
nowTime: now.format('YYYY-MM-DD HH:mm'),
|
396 |
+
nowDate: now.date()
|
397 |
+
}
|
398 |
+
},
|
399 |
+
|
400 |
+
async render (e) {
|
401 |
+
let calData = await Cal.get()
|
402 |
+
let mode = 'calendar'
|
403 |
+
if (/(日历列表|活动)$/.test(e.msg)) {
|
404 |
+
mode = 'list'
|
405 |
+
}
|
406 |
+
|
407 |
+
return await Common.render('wiki/calendar', {
|
408 |
+
...calData,
|
409 |
+
displayMode: mode
|
410 |
+
}, { e, scale: 1.1 })
|
411 |
+
}
|
412 |
+
}
|
413 |
+
|
414 |
+
export default Cal
|
Yunzai/plugins/miao-plugin/apps/wiki/CalendarSr.js
ADDED
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import fetch from 'node-fetch'
|
3 |
+
import moment from 'moment'
|
4 |
+
import Calendar from './Calendar.js'
|
5 |
+
import { Common, Data } from '#miao'
|
6 |
+
import { Character } from '#miao.models'
|
7 |
+
|
8 |
+
const ignoreIds = [
|
9 |
+
257, // 保密测试参与意愿调研
|
10 |
+
194, // 有奖问卷
|
11 |
+
203, // 《崩坏:星穹铁道》社媒聚合页上线
|
12 |
+
183, // 官方社群一览
|
13 |
+
187, // 《崩坏:星穹铁道》防沉迷系统公告
|
14 |
+
185, // 《崩坏:星穹铁道》公平运营声明
|
15 |
+
171 // 《崩坏:星穹铁道》社区专属工具一览
|
16 |
+
]
|
17 |
+
|
18 |
+
const ignoreReg = /(更新概览|游戏优化|优化说明|内容专题页|版本更新说明|循星归程|调研|防沉迷|米游社|专项意见|更新修复与优化|问卷调查|版本更新通知|更新时间说明|预下载功能|周边限时|周边上新|角色演示|角色PV|版本PV|动画短片|bilibili|激励计划|调整说明|攻略征集)/
|
19 |
+
|
20 |
+
let CalSr = {
|
21 |
+
async reqCalData () {
|
22 |
+
let listApi = 'https://hkrpg-api.mihoyo.com/common/hkrpg_cn/announcement/api/getAnnList?game=hkrpg&game_biz=hkrpg_cn&lang=zh-cn&auth_appid=announcement&authkey_ver=1&bundle_id=hkrpg_cn&channel_id=1&level=65&platform=pc®ion=prod_gf_cn&sdk_presentation_style=fullscreen&sdk_screen_transparent=true&sign_type=2&uid=100000000'
|
23 |
+
|
24 |
+
let request = await fetch(listApi)
|
25 |
+
let listData = await request.json()
|
26 |
+
|
27 |
+
let timeMap
|
28 |
+
let timeMapCache = await redis.get('miao:calendarSr:detail')
|
29 |
+
if (timeMapCache) {
|
30 |
+
timeMap = JSON.parse(timeMapCache) || {}
|
31 |
+
} else {
|
32 |
+
let detailApi = 'https://hkrpg-api-static.mihoyo.com/common/hkrpg_cn/announcement/api/getAnnContent?game=hkrpg&game_biz=hkrpg_cn&lang=zh-cn&bundle_id=hkrpg_cn&platform=pc®ion=prod_gf_cn&level=65&channel_id=1'
|
33 |
+
let request2 = await fetch(detailApi)
|
34 |
+
let detailData = await request2.json()
|
35 |
+
timeMap = {}
|
36 |
+
if (detailData && detailData.data && detailData.data.list) {
|
37 |
+
let versionTime = {
|
38 |
+
1.1: '2022-06-07 11:00:00'
|
39 |
+
}
|
40 |
+
lodash.forEach(detailData.data.list, (ds) => {
|
41 |
+
let vRet = /(\d\.\d)版本\S*更新(概览|说明)/.exec(ds.title)
|
42 |
+
if (vRet && vRet[1]) {
|
43 |
+
let content = /■(?:更新时间)\s*([^■]+)(?:■|$)/.exec(ds.content)
|
44 |
+
if (content && content[1]) {
|
45 |
+
let tRet = /([0-9\\/\\: ]){9,}/.exec(content[1])
|
46 |
+
if (tRet && tRet[0]) {
|
47 |
+
versionTime[vRet[1]] = versionTime[vRet[1]] || tRet[0].replace('06:00', '11:00')
|
48 |
+
}
|
49 |
+
}
|
50 |
+
}
|
51 |
+
})
|
52 |
+
let ret = function (ds) {
|
53 |
+
let { ann_id: annId, content, title } = ds
|
54 |
+
if (ignoreReg.test(title)) {
|
55 |
+
return true
|
56 |
+
}
|
57 |
+
content = content.replaceAll('\u003ch1 style=\"\"\u003e', '※')
|
58 |
+
content = content.replaceAll('\u003c/h1\u003e', '※')
|
59 |
+
content = content.replace(/(<|<)[\w "%:;=\-\\/\\(\\),\\.]+(>|>)/g, '')
|
60 |
+
content = /(?:活动时间|活动跃迁|开放时间|开启时间|折扣时间|上架时间)\s*※([^※]+)(※|$)/.exec(content)
|
61 |
+
if (!content || !content[1]) {
|
62 |
+
return true
|
63 |
+
}
|
64 |
+
content = content[1]
|
65 |
+
let annTime = []
|
66 |
+
|
67 |
+
// 第一种简单格式
|
68 |
+
let timeRet = /(?:活动时间)?(?:※|\s)*([0-9\\/\\: -]{6,})/.exec(content)
|
69 |
+
if (/\d\.\d版本更新后/.test(content)) {
|
70 |
+
let vRet = /(\d\.\d)版本更新后/.exec(content)
|
71 |
+
let vTime = ''
|
72 |
+
if (vRet && vRet[1] && versionTime[vRet[1]]) {
|
73 |
+
vTime = versionTime[vRet[1]]
|
74 |
+
}
|
75 |
+
if (!vTime) {
|
76 |
+
return true
|
77 |
+
}
|
78 |
+
if (/永久开放/.test(content)) {
|
79 |
+
annTime = [vTime, '2099/01/01 00:00:00']
|
80 |
+
} else {
|
81 |
+
timeRet = /([0-9\\/\\: ]){9,}/.exec(content)
|
82 |
+
if (timeRet && timeRet[0]) {
|
83 |
+
annTime = [vTime, timeRet[0]]
|
84 |
+
}
|
85 |
+
}
|
86 |
+
} else if (timeRet && timeRet[1]) {
|
87 |
+
annTime = timeRet[1].split('-')
|
88 |
+
}
|
89 |
+
|
90 |
+
if (annTime.length === 2) {
|
91 |
+
timeMap[annId] = {
|
92 |
+
start: annTime[0].trim().replace(/\//g, '-'),
|
93 |
+
end: annTime[1].trim().replace(/\//g, '-')
|
94 |
+
}
|
95 |
+
}
|
96 |
+
}
|
97 |
+
lodash.forEach(detailData.data.list, (ds) => ret(ds))
|
98 |
+
lodash.forEach(detailData.data.pic_list, (ds) => ret(ds))
|
99 |
+
}
|
100 |
+
await Data.setCacheJSON('miao:calendarSr:detail', timeMap, 60 * 10)
|
101 |
+
}
|
102 |
+
return { listData, timeMap }
|
103 |
+
},
|
104 |
+
|
105 |
+
// 深渊日历信息
|
106 |
+
getAbyssCal (s1, e1, versionStartTime) {
|
107 |
+
let check = []
|
108 |
+
let f = 'YYYY-MM-DD HH:mm:ss'
|
109 |
+
|
110 |
+
let abyss1Start = moment(versionStartTime, 'YYYY-MM-DD HH:mm:ss').add(5, 'days').subtract(3, 'hours').format(f)
|
111 |
+
let abyss1End = moment(abyss1Start).add(14, 'days').format(f)
|
112 |
+
let abyss2Start = abyss1End
|
113 |
+
let abyss2End = moment(abyss2Start).add(14, 'days').format(f)
|
114 |
+
let abyss3Start = abyss2End
|
115 |
+
let abyss3End = moment(abyss3Start).add(14, 'days').format(f)
|
116 |
+
let abyss4Start = abyss3End
|
117 |
+
let abyss4End = moment(abyss4Start).add(14, 'days').format(f)
|
118 |
+
let abyss0End = abyss1Start
|
119 |
+
let abyss0Start = moment(abyss0End).subtract(14, 'days').format(f)
|
120 |
+
|
121 |
+
check.push([moment(abyss0Start), moment(abyss0End)])
|
122 |
+
check.push([moment(abyss1Start), moment(abyss1End)])
|
123 |
+
check.push([moment(abyss2Start), moment(abyss2End)])
|
124 |
+
check.push([moment(abyss3Start), moment(abyss3End)])
|
125 |
+
check.push([moment(abyss4Start), moment(abyss4End)])
|
126 |
+
|
127 |
+
let ret = []
|
128 |
+
lodash.forEach(check, (ds) => {
|
129 |
+
let [s2, e2] = ds
|
130 |
+
if ((s2 <= s1 && s1 <= e2) || (s2 <= e1 && e1 <= e2)) {
|
131 |
+
ret.push(ds)
|
132 |
+
}
|
133 |
+
})
|
134 |
+
return ret
|
135 |
+
},
|
136 |
+
|
137 |
+
getList (ds, target, { startTime, endTime, totalRange, now, timeMap = {} }) {
|
138 |
+
let type = 'activity'
|
139 |
+
let id = ds.ann_id
|
140 |
+
let title = ds.title
|
141 |
+
let subTitle = ds.subtitle
|
142 |
+
let banner = ds.banner
|
143 |
+
let extra = { sort: 5 }
|
144 |
+
let detail = timeMap[id] || {}
|
145 |
+
|
146 |
+
if (ignoreIds.includes(id) || ignoreReg.test(title)) {
|
147 |
+
return true
|
148 |
+
}
|
149 |
+
|
150 |
+
if (/流光定影/.test(title)) {
|
151 |
+
type = 'weapon'
|
152 |
+
title = title.replace(/(限定5星光锥)/g, '')
|
153 |
+
extra.sort = 2
|
154 |
+
} else if (/跃迁/.test(subTitle)) {
|
155 |
+
type = 'character'
|
156 |
+
let regRet = /角色「(.*)((|\()/.exec(title)
|
157 |
+
if (regRet[1]) {
|
158 |
+
let char = Character.get(regRet[1])
|
159 |
+
extra.banner2 = char.getImgs()?.card
|
160 |
+
extra.face = char.face
|
161 |
+
extra.character = regRet[1]
|
162 |
+
extra.elem = char.elem
|
163 |
+
extra.sort = 1
|
164 |
+
}
|
165 |
+
} else if (/无名勋礼/.test(title)) {
|
166 |
+
type = 'pass'
|
167 |
+
}
|
168 |
+
|
169 |
+
let getDate = (d1, d2) => moment(d1 && d1.length > 6 ? d1 : d2)
|
170 |
+
let sDate = getDate(detail.start, ds.start_time)
|
171 |
+
let eDate = getDate(detail.end, ds.end_time)
|
172 |
+
let sTime = moment.max(sDate, startTime)
|
173 |
+
let eTime = moment.min(eDate, endTime)
|
174 |
+
|
175 |
+
let sRange = sTime - startTime
|
176 |
+
let eRange = eTime - startTime
|
177 |
+
|
178 |
+
let left = sRange / totalRange * 100
|
179 |
+
let width = eRange / totalRange * 100 - left
|
180 |
+
|
181 |
+
let label = ''
|
182 |
+
if (eDate - sDate > 365 * 24 * 3600 * 1000) {
|
183 |
+
if (sDate < now) {
|
184 |
+
label = sDate.format('MM-DD HH:mm') + ' 后永久有效'
|
185 |
+
} else {
|
186 |
+
label = '永久有效'
|
187 |
+
}
|
188 |
+
} else if (now > sDate && eDate > now) {
|
189 |
+
label = eDate.format('MM-DD HH:mm') + ' (' + moment.duration(eDate - now).humanize() + '后结束)'
|
190 |
+
if (width > 38) {
|
191 |
+
label = sDate.format('MM-DD HH:mm') + ' ~ ' + label
|
192 |
+
}
|
193 |
+
} else if (sDate > now) {
|
194 |
+
label = sDate.format('MM-DD HH:mm') + ' (' + moment.duration(sDate - now).humanize() + '后开始)'
|
195 |
+
} else {
|
196 |
+
label = sDate.format('MM-DD HH:mm') + ' ~ ' + eDate.format('MM-DD HH:mm')
|
197 |
+
}
|
198 |
+
if (sDate <= endTime && eDate >= startTime) {
|
199 |
+
target.push({
|
200 |
+
...extra,
|
201 |
+
id,
|
202 |
+
title,
|
203 |
+
type,
|
204 |
+
mergeStatus: ['activity'].includes(type) ? 1 : 0,
|
205 |
+
banner,
|
206 |
+
icon: ds.tag_icon,
|
207 |
+
left,
|
208 |
+
width,
|
209 |
+
label,
|
210 |
+
duration: eTime - sTime,
|
211 |
+
start: sDate.format('MM-DD HH:mm'),
|
212 |
+
end: eDate.format('MM-DD HH:mm')
|
213 |
+
})
|
214 |
+
}
|
215 |
+
},
|
216 |
+
|
217 |
+
async get () {
|
218 |
+
moment.locale('zh-cn')
|
219 |
+
let now = moment()
|
220 |
+
|
221 |
+
let { listData, timeMap } = await CalSr.reqCalData()
|
222 |
+
let dateList = Calendar.getDateList()
|
223 |
+
|
224 |
+
let resultList = []
|
225 |
+
let abyss = []
|
226 |
+
|
227 |
+
lodash.forEach(listData.data.list[0].list, (ds) => CalSr.getList(ds, resultList, { ...dateList, now, timeMap }))
|
228 |
+
lodash.forEach(listData.data.pic_list[0].type_list[0].list, (ds) => CalSr.getList(ds, resultList, { ...dateList, now, timeMap }))
|
229 |
+
|
230 |
+
let versionStartTime
|
231 |
+
lodash.forEach(listData.data.list[0].list, (ds) => {
|
232 |
+
if (/版本更新(概览|说明)/.test(ds.title)) {
|
233 |
+
versionStartTime = ds.start_time
|
234 |
+
}
|
235 |
+
})
|
236 |
+
|
237 |
+
let abyssCal = CalSr.getAbyssCal(dateList.startTime, dateList.endTime, versionStartTime)
|
238 |
+
lodash.forEach(abyssCal, (t) => {
|
239 |
+
CalSr.getList({
|
240 |
+
title: '「混沌回忆」',
|
241 |
+
start_time: t[0].format('YYYY-MM-DD HH:mm'),
|
242 |
+
end_time: t[1].format('YYYY-MM-DD HH:mm')
|
243 |
+
}, abyss, { ...dateList, now })
|
244 |
+
})
|
245 |
+
|
246 |
+
resultList = lodash.sortBy(resultList, ['sort', 'start', 'duration'])
|
247 |
+
|
248 |
+
let charCount = 0
|
249 |
+
let charOld = 0
|
250 |
+
let weaponCount = 0
|
251 |
+
let ret = []
|
252 |
+
lodash.forEach(resultList, (li) => {
|
253 |
+
if (li.type === 'character') {
|
254 |
+
charCount++
|
255 |
+
li.left === 0 && charOld++
|
256 |
+
li.idx = charCount
|
257 |
+
}
|
258 |
+
if (li.type === 'weapon') {
|
259 |
+
weaponCount++
|
260 |
+
li.idx = weaponCount
|
261 |
+
}
|
262 |
+
if (li.mergeStatus === 1) {
|
263 |
+
lodash.forEach(resultList, (li2) => {
|
264 |
+
if (li2.mergeStatus === 1 && li.left + li.width <= li2.left) {
|
265 |
+
li.mergeStatus = 2
|
266 |
+
li2.mergeStatus = 2
|
267 |
+
ret.push([li, li2])
|
268 |
+
return false
|
269 |
+
}
|
270 |
+
})
|
271 |
+
}
|
272 |
+
if (li.mergeStatus !== 2) {
|
273 |
+
li.mergeStatus = 2
|
274 |
+
ret.push([li])
|
275 |
+
}
|
276 |
+
})
|
277 |
+
|
278 |
+
return {
|
279 |
+
game: 'sr',
|
280 |
+
...dateList,
|
281 |
+
list: ret,
|
282 |
+
abyss,
|
283 |
+
charMode: `char-${charCount}-${charOld}`,
|
284 |
+
nowTime: now.format('YYYY-MM-DD HH:mm'),
|
285 |
+
nowDate: now.date()
|
286 |
+
}
|
287 |
+
},
|
288 |
+
|
289 |
+
async render (e) {
|
290 |
+
let calData = await CalSr.get()
|
291 |
+
let mode = 'calendar'
|
292 |
+
if (/(日历列表|活动)$/.test(e.msg)) {
|
293 |
+
mode = 'list'
|
294 |
+
}
|
295 |
+
|
296 |
+
return await Common.render('wiki/calendar', {
|
297 |
+
...calData,
|
298 |
+
displayMode: mode
|
299 |
+
}, { e, scale: 1.1 })
|
300 |
+
}
|
301 |
+
}
|
302 |
+
|
303 |
+
export default CalSr
|
Yunzai/plugins/miao-plugin/apps/wiki/CharMaterial.js
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Common } from '#miao'
|
2 |
+
|
3 |
+
const CharMaterial = {
|
4 |
+
async render ({ e, char }) {
|
5 |
+
let data = char.getData()
|
6 |
+
return await Common.render('wiki/character-material', {
|
7 |
+
// saveId: `info-${char.id}`,
|
8 |
+
data,
|
9 |
+
attr: char.getAttrList(),
|
10 |
+
detail: char.getDetail(),
|
11 |
+
imgs: char.getImgs(),
|
12 |
+
materials: char.getMaterials(),
|
13 |
+
elem: char.elem
|
14 |
+
}, { e, scale: 1.4 })
|
15 |
+
}
|
16 |
+
}
|
17 |
+
|
18 |
+
export default CharMaterial
|
Yunzai/plugins/miao-plugin/apps/wiki/CharTalent.js
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import { Common, Format } from '#miao'
|
3 |
+
|
4 |
+
const CharTalent = {
|
5 |
+
async render (e, mode, char) {
|
6 |
+
let lvs = []
|
7 |
+
for (let i = 1; i <= 15; i++) {
|
8 |
+
lvs.push('Lv' + i)
|
9 |
+
}
|
10 |
+
let detail = lodash.extend({}, char.getDetail())
|
11 |
+
if (char.game === 'sr') {
|
12 |
+
lodash.forEach(['cons', 'talent', 'treeData'], (key) => {
|
13 |
+
lodash.forEach(detail[key], (ds, idx) => {
|
14 |
+
if (ds.desc) {
|
15 |
+
if (key === 'talent' && ds.desc.split) {
|
16 |
+
let desc = CharTalent.getDesc(ds.desc, ds.tables, idx === 'a' || idx === 'a2' ? 5 : 8)
|
17 |
+
ds.desc = desc.desc
|
18 |
+
ds.tables = desc.tables
|
19 |
+
} else if (ds.desc.split) {
|
20 |
+
ds.desc = ds.desc.split('<br>')
|
21 |
+
}
|
22 |
+
}
|
23 |
+
})
|
24 |
+
})
|
25 |
+
}
|
26 |
+
return await Common.render('wiki/character-talent', {
|
27 |
+
saveId: `${mode}-${char.id}`,
|
28 |
+
...char.getData(),
|
29 |
+
game: char.game,
|
30 |
+
detail: char.getDetail(),
|
31 |
+
imgs: char.getImgs(),
|
32 |
+
mode,
|
33 |
+
lvs,
|
34 |
+
line: CharTalent.getLineData(char)
|
35 |
+
}, { e, scale: 1.1 })
|
36 |
+
},
|
37 |
+
getLineData (char) {
|
38 |
+
let ret = []
|
39 |
+
if (char.isSr) {
|
40 |
+
lodash.forEach({ hp: '基础生命', atk: '基础攻击', def: '基础防御', speed: '速度' }, (label, key) => {
|
41 |
+
ret.push({
|
42 |
+
num: Format.comma(char.getDetail().baseAttr[key], 1),
|
43 |
+
label
|
44 |
+
})
|
45 |
+
})
|
46 |
+
return ret
|
47 |
+
}
|
48 |
+
const attrMap = {
|
49 |
+
atkPct: '大攻击',
|
50 |
+
hpPct: '大生命',
|
51 |
+
defPct: '大防御',
|
52 |
+
cpct: '暴击',
|
53 |
+
cdmg: '爆伤',
|
54 |
+
recharge: '充能',
|
55 |
+
mastery: '精通',
|
56 |
+
heal: '治疗',
|
57 |
+
dmg: char.elemName + '伤',
|
58 |
+
phy: '物伤'
|
59 |
+
}
|
60 |
+
lodash.forEach({ hp: '基础生命', atk: '基础攻击', def: '基础防御' }, (label, key) => {
|
61 |
+
ret.push({
|
62 |
+
num: Format.comma(char.baseAttr[key], 1),
|
63 |
+
label
|
64 |
+
})
|
65 |
+
})
|
66 |
+
let ga = char.growAttr
|
67 |
+
ret.push({
|
68 |
+
num: ga.key === 'mastery' ? Format.comma(ga.value, 1) : ga.value,
|
69 |
+
label: `成长·${attrMap[ga.key]}`
|
70 |
+
})
|
71 |
+
return ret
|
72 |
+
},
|
73 |
+
// 获取精炼描述
|
74 |
+
getDesc (desc, tables, lv = 5) {
|
75 |
+
let reg = /\$(\d)\[[i|f1]\](\%?)/g
|
76 |
+
let ret
|
77 |
+
|
78 |
+
let idxFormat = {}
|
79 |
+
while ((ret = reg.exec(desc)) !== null) {
|
80 |
+
let idx = ret[1]
|
81 |
+
let pct = ret[2]
|
82 |
+
let value = tables?.[idx]?.values[lv - 1]
|
83 |
+
if (value) {
|
84 |
+
if (pct === '%') {
|
85 |
+
idxFormat[idx] = 'percent'
|
86 |
+
value = Format.percent(value)
|
87 |
+
} else {
|
88 |
+
idxFormat[idx] = 'comma'
|
89 |
+
value = Format.comma(value)
|
90 |
+
}
|
91 |
+
value = value + ` (lv${lv})`
|
92 |
+
desc = desc.replaceAll(ret[0], value)
|
93 |
+
}
|
94 |
+
}
|
95 |
+
let tableRet = []
|
96 |
+
lodash.forEach(tables, (ds, idx) => {
|
97 |
+
let values = []
|
98 |
+
lodash.forEach(ds.values, (v) => {
|
99 |
+
values.push(Format[idxFormat[idx] || 'comma'](v))
|
100 |
+
})
|
101 |
+
tableRet.push({
|
102 |
+
name: ds.name,
|
103 |
+
isSame: ds.isSame,
|
104 |
+
values
|
105 |
+
})
|
106 |
+
})
|
107 |
+
return {
|
108 |
+
desc: desc.split('<br>'),
|
109 |
+
tables: tableRet
|
110 |
+
}
|
111 |
+
}
|
112 |
+
}
|
113 |
+
|
114 |
+
export default CharTalent
|
Yunzai/plugins/miao-plugin/apps/wiki/CharWiki.js
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import CharTalent from './CharTalent.js'
|
3 |
+
import CharWikiData from './CharWikiData.js'
|
4 |
+
import CharMaterial from './CharMaterial.js'
|
5 |
+
import { Cfg, Common } from '#miao'
|
6 |
+
import { Character } from '#miao.models'
|
7 |
+
|
8 |
+
const wikiReg = /^(?:#|喵喵)?(.*)(天赋|技能|命座|命之座|资料|图鉴|照片|写真|图片|图像)$/
|
9 |
+
|
10 |
+
const CharWiki = {
|
11 |
+
check (e) {
|
12 |
+
let msg = e.original_msg || e.msg
|
13 |
+
if (!e.msg) {
|
14 |
+
return false
|
15 |
+
}
|
16 |
+
let ret = wikiReg.exec(msg)
|
17 |
+
if (!ret || !ret[1] || !ret[2]) {
|
18 |
+
return false
|
19 |
+
}
|
20 |
+
let mode = 'talent'
|
21 |
+
if (/命/.test(ret[2])) {
|
22 |
+
mode = 'cons'
|
23 |
+
} else if (/(图鉴|资料)/.test(ret[2])) {
|
24 |
+
mode = 'wiki'
|
25 |
+
if (!Common.cfg('charWiki')) {
|
26 |
+
return false
|
27 |
+
}
|
28 |
+
} else if (/图|画|写真|照片/.test(ret[2])) {
|
29 |
+
mode = 'pic'
|
30 |
+
if (!Common.cfg('charPic')) {
|
31 |
+
return false
|
32 |
+
}
|
33 |
+
} else if (/(材料|养成|成长)/.test(ret[2])) {
|
34 |
+
mode = 'material'
|
35 |
+
}
|
36 |
+
if (['cons', 'talent'].includes(mode) && !Common.cfg('charWikiTalent')) {
|
37 |
+
return false
|
38 |
+
}
|
39 |
+
let char = Character.get(ret[1])
|
40 |
+
if (!char || (char.isCustom && mode !== 'pic')) {
|
41 |
+
return false
|
42 |
+
}
|
43 |
+
e.wikiMode = mode
|
44 |
+
e.msg = '#喵喵WIKI'
|
45 |
+
e.char = char
|
46 |
+
return true
|
47 |
+
},
|
48 |
+
|
49 |
+
async wiki (e) {
|
50 |
+
let mode = e.wikiMode
|
51 |
+
let char = e.char
|
52 |
+
|
53 |
+
if (mode === 'pic') {
|
54 |
+
let img = char.getCardImg(Cfg.get('charPicSe', false), false)
|
55 |
+
if (img && img.img) {
|
56 |
+
e.reply(segment.image(`file://${process.cwd()}/plugins/miao-plugin/resources/${img.img}`))
|
57 |
+
} else {
|
58 |
+
e.reply('暂无图片')
|
59 |
+
}
|
60 |
+
return true
|
61 |
+
}
|
62 |
+
if (char.isCustom) {
|
63 |
+
if (mode === 'wiki') {
|
64 |
+
return false
|
65 |
+
}
|
66 |
+
e.reply('暂不支持自定义角色')
|
67 |
+
return true
|
68 |
+
}
|
69 |
+
if (!char.isRelease && Cfg.get('notReleasedData') === false) {
|
70 |
+
e.reply('未实装角色资料已禁用...')
|
71 |
+
return true
|
72 |
+
}
|
73 |
+
|
74 |
+
if (mode === 'wiki') {
|
75 |
+
if (char.source === 'amber') {
|
76 |
+
e.reply('暂不支持该角色图鉴展示')
|
77 |
+
return true
|
78 |
+
}
|
79 |
+
return await CharWiki.render({ e, char })
|
80 |
+
} else if (mode === 'material') {
|
81 |
+
return CharMaterial.render({ e, char })
|
82 |
+
}
|
83 |
+
return await CharTalent.render(e, mode, char)
|
84 |
+
},
|
85 |
+
|
86 |
+
async render ({ e, char }) {
|
87 |
+
let data = char.getData()
|
88 |
+
lodash.extend(data, char.getData('weaponTypeName,elemName'))
|
89 |
+
// 命座持有
|
90 |
+
let holding = await CharWikiData.getHolding(char.id)
|
91 |
+
let usage = await CharWikiData.getUsage(char.id)
|
92 |
+
return await Common.render('wiki/character-wiki', {
|
93 |
+
data,
|
94 |
+
attr: char.getAttrList(),
|
95 |
+
detail: char.getDetail(),
|
96 |
+
imgs: char.getImgs(),
|
97 |
+
holding,
|
98 |
+
usage,
|
99 |
+
materials: char.getMaterials(),
|
100 |
+
elem: char.elem
|
101 |
+
}, { e, scale: 1.4 })
|
102 |
+
}
|
103 |
+
}
|
104 |
+
|
105 |
+
export default CharWiki
|
Yunzai/plugins/miao-plugin/apps/wiki/CharWikiData.js
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import HutaoApi from '../stat/HutaoApi.js'
|
2 |
+
import lodash from 'lodash'
|
3 |
+
import { Format } from '#miao'
|
4 |
+
import { ArtifactSet, Weapon } from '#miao.models'
|
5 |
+
|
6 |
+
let CharWikiData = {
|
7 |
+
/**
|
8 |
+
* 角色命座持有
|
9 |
+
* @param id
|
10 |
+
* @returns {Promise<{}>}
|
11 |
+
*/
|
12 |
+
async getHolding (id) {
|
13 |
+
let consData = (await HutaoApi.getCons()).data || {}
|
14 |
+
consData = lodash.find(consData, (ds) => ds.avatar === id)
|
15 |
+
let holding = {}
|
16 |
+
if (consData) {
|
17 |
+
let { holdingRate, rate } = consData
|
18 |
+
rate = lodash.sortBy(rate, 'id')
|
19 |
+
holding.num = Format.percent(holdingRate)
|
20 |
+
holding.cons = []
|
21 |
+
lodash.forEach(rate, (ds) => {
|
22 |
+
holding.cons.push({
|
23 |
+
cons: ds.id,
|
24 |
+
num: Format.percent(ds.value)
|
25 |
+
})
|
26 |
+
})
|
27 |
+
}
|
28 |
+
return holding
|
29 |
+
},
|
30 |
+
|
31 |
+
/**
|
32 |
+
* 角色武器、圣遗物使用
|
33 |
+
* @param id
|
34 |
+
* @returns {Promise<{}|{artis: *[], weapons: *[]}>}
|
35 |
+
*/
|
36 |
+
async getUsage (id) {
|
37 |
+
let ud = (await HutaoApi.getUsage()).data || {}
|
38 |
+
if (!ud[id]) {
|
39 |
+
return {}
|
40 |
+
}
|
41 |
+
ud = ud[id]
|
42 |
+
return {
|
43 |
+
weapons: CharWikiData.getWeaponsData(ud.weapons),
|
44 |
+
artis: CharWikiData.getArtisData(ud.artis)
|
45 |
+
}
|
46 |
+
},
|
47 |
+
|
48 |
+
/**
|
49 |
+
* 武器使用
|
50 |
+
* @param data
|
51 |
+
* @returns {*[]}
|
52 |
+
*/
|
53 |
+
getWeaponsData (data = []) {
|
54 |
+
let weapons = []
|
55 |
+
|
56 |
+
lodash.forEach(data, (ds) => {
|
57 |
+
let weapon = Weapon.get(ds.item) || {}
|
58 |
+
weapons.push({
|
59 |
+
...weapon.getData('name,abbr,img,star'),
|
60 |
+
value: ds.rate
|
61 |
+
})
|
62 |
+
})
|
63 |
+
|
64 |
+
weapons = lodash.sortBy(weapons, 'value')
|
65 |
+
weapons = weapons.reverse()
|
66 |
+
lodash.forEach(weapons, (ds) => {
|
67 |
+
ds.value = Format.percent(ds.value, 1)
|
68 |
+
})
|
69 |
+
return weapons
|
70 |
+
},
|
71 |
+
|
72 |
+
/**
|
73 |
+
* 圣遗物使用
|
74 |
+
* @param data
|
75 |
+
* @returns {*[]}
|
76 |
+
*/
|
77 |
+
getArtisData (data = []) {
|
78 |
+
let artis = []
|
79 |
+
|
80 |
+
lodash.forEach(data, (ds) => {
|
81 |
+
let imgs = []
|
82 |
+
let abbrs = []
|
83 |
+
let ss = ds.item.split(',')
|
84 |
+
lodash.forEach(ss, (t) => {
|
85 |
+
t = t.split(':')
|
86 |
+
let artiSet = ArtifactSet.get(t[0])
|
87 |
+
if (artiSet) {
|
88 |
+
imgs.push(artiSet.img)
|
89 |
+
abbrs.push(artiSet.abbr + (ss.length === 1 ? t[1] : ''))
|
90 |
+
}
|
91 |
+
})
|
92 |
+
|
93 |
+
artis.push({
|
94 |
+
imgs,
|
95 |
+
title: abbrs.join('+'),
|
96 |
+
value: ds.rate
|
97 |
+
})
|
98 |
+
})
|
99 |
+
|
100 |
+
artis = lodash.sortBy(artis, 'value')
|
101 |
+
artis = artis.reverse()
|
102 |
+
artis.forEach((ds) => {
|
103 |
+
ds.value = Format.percent(ds.value)
|
104 |
+
})
|
105 |
+
return artis
|
106 |
+
}
|
107 |
+
}
|
108 |
+
export default CharWikiData
|
Yunzai/plugins/miao-plugin/components/App.js
ADDED
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import Plugin from './common/Plugin.js'
|
3 |
+
import { Version, MiaoError } from '#miao'
|
4 |
+
|
5 |
+
class App {
|
6 |
+
constructor (cfg) {
|
7 |
+
this.id = cfg.id
|
8 |
+
this.cfg = cfg
|
9 |
+
this.apps = {}
|
10 |
+
}
|
11 |
+
|
12 |
+
reg (key, fn, cfg = {}) {
|
13 |
+
if (lodash.isPlainObject(key)) {
|
14 |
+
lodash.forEach(key, (cfg, k) => {
|
15 |
+
this.reg(k, cfg.fn, cfg)
|
16 |
+
})
|
17 |
+
} else {
|
18 |
+
this.apps[key] = {
|
19 |
+
fn,
|
20 |
+
...cfg
|
21 |
+
}
|
22 |
+
}
|
23 |
+
}
|
24 |
+
|
25 |
+
// 获取v3执行方法
|
26 |
+
v3App () {
|
27 |
+
let cfg = this.cfg || {}
|
28 |
+
let rules = []
|
29 |
+
let check = []
|
30 |
+
let event = cfg.event
|
31 |
+
let cls = class extends Plugin {
|
32 |
+
constructor () {
|
33 |
+
super({
|
34 |
+
name: `喵喵:${cfg.name || cfg.id}`,
|
35 |
+
dsc: cfg.desc || cfg.name || '喵喵插件',
|
36 |
+
event: event === 'poke' ? 'notice.*.poke' : 'message',
|
37 |
+
priority: cfg.priority || 50,
|
38 |
+
rule: rules
|
39 |
+
})
|
40 |
+
}
|
41 |
+
|
42 |
+
accept (e) {
|
43 |
+
e.original_msg = e.original_msg || e.msg
|
44 |
+
for (let idx = 0; idx < check.length; idx++) {
|
45 |
+
if (check[idx](e, e.original_msg) === true) {
|
46 |
+
return true
|
47 |
+
}
|
48 |
+
}
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
for (let key in this.apps) {
|
53 |
+
let app = this.apps[key]
|
54 |
+
key = lodash.camelCase(key)
|
55 |
+
let rule = app.rule || app.reg || 'noCheck'
|
56 |
+
if (event !== 'poke') {
|
57 |
+
if (typeof (rule) === 'string') {
|
58 |
+
if (rule === 'noCheck') {
|
59 |
+
rule = '.*'
|
60 |
+
}
|
61 |
+
} else {
|
62 |
+
rule = lodash.trim(rule.toString(), '/')
|
63 |
+
}
|
64 |
+
} else {
|
65 |
+
rule = '.*'
|
66 |
+
}
|
67 |
+
|
68 |
+
rules.push({
|
69 |
+
reg: rule,
|
70 |
+
fnc: key
|
71 |
+
})
|
72 |
+
|
73 |
+
if (app.check) {
|
74 |
+
check.push(app.check)
|
75 |
+
}
|
76 |
+
|
77 |
+
cls.prototype[key] = async function (e) {
|
78 |
+
e = this.e || e
|
79 |
+
const self_id = e.self_id || e.bot?.uin || Bot.uin
|
80 |
+
if (event === 'poke') {
|
81 |
+
if (e.notice_type === 'group') {
|
82 |
+
if (e.target_id !== self_id && !e.isPoke) {
|
83 |
+
return false
|
84 |
+
}
|
85 |
+
// group状态下,戳一戳的发起人是operator
|
86 |
+
if (e.user_id === self_id) {
|
87 |
+
e.user_id = e.operator_id
|
88 |
+
}
|
89 |
+
}
|
90 |
+
e.isPoke = true
|
91 |
+
// 随便指定一个不太常见的msg以触发msg的正则
|
92 |
+
e.msg = '#poke#'
|
93 |
+
}
|
94 |
+
e.original_msg = e.original_msg || e.msg
|
95 |
+
try {
|
96 |
+
return await app.fn.call(this, e)
|
97 |
+
} catch (err) {
|
98 |
+
if (err?.message && (err instanceof MiaoError)) {
|
99 |
+
// 处理 MiaoError
|
100 |
+
return e.reply(err.message)
|
101 |
+
} else {
|
102 |
+
// 其他错误抛出
|
103 |
+
throw err
|
104 |
+
}
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
if (app.yzRule && app.yzCheck) {
|
109 |
+
let yzKey = `Yz${key}`
|
110 |
+
let yzRule = lodash.trim(app.yzRule.toString(), '/')
|
111 |
+
rules.push({
|
112 |
+
reg: yzRule,
|
113 |
+
fnc: yzKey
|
114 |
+
})
|
115 |
+
cls.prototype[yzKey] = async function (e) {
|
116 |
+
if (!Version.isMiao && !app.yzCheck()) {
|
117 |
+
return false
|
118 |
+
}
|
119 |
+
e = this.e || e
|
120 |
+
e.original_msg = e.original_msg || e.msg
|
121 |
+
return await app.fn.call(this, e)
|
122 |
+
}
|
123 |
+
}
|
124 |
+
}
|
125 |
+
return cls
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
App.init = function (cfg) {
|
130 |
+
return new App(cfg)
|
131 |
+
}
|
132 |
+
|
133 |
+
export default App
|
Yunzai/plugins/miao-plugin/components/Cfg.js
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import fs from 'fs'
|
2 |
+
import lodash from 'lodash'
|
3 |
+
import cfgData from './cfg/CfgData.js'
|
4 |
+
import { Version } from '#miao'
|
5 |
+
|
6 |
+
const _path = process.cwd()
|
7 |
+
const _cfgPath = `${_path}/plugins/miao-plugin/components/`
|
8 |
+
let cfg = {}
|
9 |
+
let miaoCfg = {}
|
10 |
+
|
11 |
+
|
12 |
+
try {
|
13 |
+
cfg = await cfgData.getCfg()
|
14 |
+
cfgData.saveCfg(cfg)
|
15 |
+
lodash.forEach(cfgData.getCfgSchemaMap(), (cm) => {
|
16 |
+
if (cm.miao) {
|
17 |
+
miaoCfg[cm.cfgKey] = true
|
18 |
+
}
|
19 |
+
})
|
20 |
+
} catch (e) {
|
21 |
+
// do nth
|
22 |
+
}
|
23 |
+
|
24 |
+
let Cfg = {
|
25 |
+
get (rote) {
|
26 |
+
if (Version.isMiao && miaoCfg[rote]) {
|
27 |
+
return true
|
28 |
+
}
|
29 |
+
return lodash.get(cfg, rote)
|
30 |
+
},
|
31 |
+
set (rote, val) {
|
32 |
+
cfg[rote] = val
|
33 |
+
cfgData.saveCfg(cfg)
|
34 |
+
},
|
35 |
+
del (rote) {
|
36 |
+
lodash.set(cfg, rote, undefined)
|
37 |
+
fs.writeFileSync(_cfgPath + 'cfg.json', JSON.stringify(cfg, null, '\t'))
|
38 |
+
},
|
39 |
+
getCfg () {
|
40 |
+
return cfg
|
41 |
+
},
|
42 |
+
getCfgSchema () {
|
43 |
+
return cfgData.getCfgSchema()
|
44 |
+
},
|
45 |
+
getCfgSchemaMap () {
|
46 |
+
return cfgData.getCfgSchemaMap()
|
47 |
+
},
|
48 |
+
scale (pct = 1) {
|
49 |
+
let scale = Cfg.get('renderScale', 100)
|
50 |
+
scale = Math.min(2, Math.max(0.5, scale / 100))
|
51 |
+
pct = pct * scale
|
52 |
+
return `style=transform:scale(${pct})`
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
export default Cfg
|
Yunzai/plugins/miao-plugin/components/Common.js
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import Cfg from './Cfg.js'
|
2 |
+
import Render from './common/Render.js'
|
3 |
+
|
4 |
+
const Common = {
|
5 |
+
render: Render.render,
|
6 |
+
cfg: Cfg.get,
|
7 |
+
sleep (ms) {
|
8 |
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
9 |
+
},
|
10 |
+
|
11 |
+
async downFile () {
|
12 |
+
console.log('down file')
|
13 |
+
}
|
14 |
+
|
15 |
+
}
|
16 |
+
|
17 |
+
export default Common
|
Yunzai/plugins/miao-plugin/components/Data.js
ADDED
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import fs from 'fs'
|
3 |
+
|
4 |
+
const _path = process.cwd()
|
5 |
+
const getRoot = (root = '') => {
|
6 |
+
if (!root) {
|
7 |
+
root = `${_path}/`
|
8 |
+
} else if (root === 'root' || root === 'yunzai') {
|
9 |
+
root = `${_path}/`
|
10 |
+
} else if (root === 'miao') {
|
11 |
+
root = `${_path}/plugins/miao-plugin/`
|
12 |
+
} else {
|
13 |
+
root = `${_path}/plugins/${root}/`
|
14 |
+
}
|
15 |
+
return root
|
16 |
+
}
|
17 |
+
|
18 |
+
let Data = {
|
19 |
+
|
20 |
+
getRoot,
|
21 |
+
|
22 |
+
/*
|
23 |
+
* 根据指定的path依次检查与创建目录
|
24 |
+
* */
|
25 |
+
createDir (path = '', root = '', includeFile = false) {
|
26 |
+
root = getRoot(root)
|
27 |
+
let pathList = path.split('/')
|
28 |
+
let nowPath = root
|
29 |
+
pathList.forEach((name, idx) => {
|
30 |
+
name = name.trim()
|
31 |
+
if (!includeFile && idx <= pathList.length - 1) {
|
32 |
+
nowPath += name + '/'
|
33 |
+
if (name) {
|
34 |
+
if (!fs.existsSync(nowPath)) {
|
35 |
+
fs.mkdirSync(nowPath)
|
36 |
+
}
|
37 |
+
}
|
38 |
+
}
|
39 |
+
})
|
40 |
+
},
|
41 |
+
|
42 |
+
/*
|
43 |
+
* 读取json
|
44 |
+
* */
|
45 |
+
readJSON (file = '', root = '') {
|
46 |
+
root = getRoot(root)
|
47 |
+
if (fs.existsSync(`${root}/${file}`)) {
|
48 |
+
try {
|
49 |
+
return JSON.parse(fs.readFileSync(`${root}/${file}`, 'utf8'))
|
50 |
+
} catch (e) {
|
51 |
+
console.log(e)
|
52 |
+
}
|
53 |
+
}
|
54 |
+
return {}
|
55 |
+
},
|
56 |
+
|
57 |
+
/*
|
58 |
+
* 写JSON
|
59 |
+
* */
|
60 |
+
writeJSON (cfg, data, root = '', space = 2) {
|
61 |
+
if (arguments.length > 1) {
|
62 |
+
return Data.writeJSON({
|
63 |
+
name: cfg,
|
64 |
+
data,
|
65 |
+
space,
|
66 |
+
root
|
67 |
+
})
|
68 |
+
}
|
69 |
+
// 检查并创建目录
|
70 |
+
let name = cfg.path ? (cfg.path + '/' + cfg.name) : cfg.name
|
71 |
+
Data.createDir(name, cfg.root, true)
|
72 |
+
root = getRoot(cfg.root)
|
73 |
+
data = cfg.data
|
74 |
+
delete data._res
|
75 |
+
data = JSON.stringify(data, null, cfg.space || 2)
|
76 |
+
if (cfg.rn) {
|
77 |
+
data = data.replaceAll('\n', '\r\n')
|
78 |
+
}
|
79 |
+
return fs.writeFileSync(`${root}/${name}`, data)
|
80 |
+
},
|
81 |
+
|
82 |
+
delFile (file, root = '') {
|
83 |
+
root = getRoot(root)
|
84 |
+
try {
|
85 |
+
if (fs.existsSync(`${root}/${file}`)) {
|
86 |
+
fs.unlinkSync(`${root}/${file}`)
|
87 |
+
}
|
88 |
+
return true
|
89 |
+
} catch (error) {
|
90 |
+
logger.error(`文件删除失败:${error}`)
|
91 |
+
}
|
92 |
+
return false
|
93 |
+
},
|
94 |
+
|
95 |
+
async getCacheJSON (key) {
|
96 |
+
try {
|
97 |
+
let txt = await redis.get(key)
|
98 |
+
if (txt) {
|
99 |
+
return JSON.parse(txt)
|
100 |
+
}
|
101 |
+
} catch (e) {
|
102 |
+
console.log(e)
|
103 |
+
}
|
104 |
+
return {}
|
105 |
+
},
|
106 |
+
|
107 |
+
async setCacheJSON (key, data, EX = 3600 * 24 * 90) {
|
108 |
+
await redis.set(key, JSON.stringify(data), { EX })
|
109 |
+
},
|
110 |
+
|
111 |
+
async redisGet (key, def = {}) {
|
112 |
+
try {
|
113 |
+
let txt = await redis.get(key)
|
114 |
+
if (txt) {
|
115 |
+
return JSON.parse(txt)
|
116 |
+
}
|
117 |
+
} catch (e) {
|
118 |
+
console.log(e)
|
119 |
+
}
|
120 |
+
return def
|
121 |
+
},
|
122 |
+
|
123 |
+
async redisSet (key, data, EX = 3600 * 24 * 90) {
|
124 |
+
await redis.set(key, JSON.stringify(data), { EX })
|
125 |
+
},
|
126 |
+
|
127 |
+
async importModule (file, root = '') {
|
128 |
+
root = getRoot(root)
|
129 |
+
if (!/\.js$/.test(file)) {
|
130 |
+
file = file + '.js'
|
131 |
+
}
|
132 |
+
if (fs.existsSync(`${root}/${file}`)) {
|
133 |
+
try {
|
134 |
+
let data = await import(`file://${root}/${file}?t=${new Date() * 1}`)
|
135 |
+
return data || {}
|
136 |
+
} catch (e) {
|
137 |
+
console.log(e)
|
138 |
+
}
|
139 |
+
}
|
140 |
+
return {}
|
141 |
+
},
|
142 |
+
|
143 |
+
async importDefault (file, root) {
|
144 |
+
let ret = await Data.importModule(file, root)
|
145 |
+
return ret.default || {}
|
146 |
+
},
|
147 |
+
|
148 |
+
async importCfg (key) {
|
149 |
+
let sysCfg = await Data.importModule(`config/system/${key}_system.js`, 'miao')
|
150 |
+
let diyCfg = await Data.importModule(`config/${key}.js`, 'miao')
|
151 |
+
if (diyCfg.isSys) {
|
152 |
+
console.error(`miao-plugin: config/${key}.js无效,已忽略`)
|
153 |
+
console.error(`如需配置请复制config/${key}_default.js为config/${key}.js,请勿复制config/system下的系统文件`)
|
154 |
+
diyCfg = {}
|
155 |
+
}
|
156 |
+
return {
|
157 |
+
sysCfg,
|
158 |
+
diyCfg
|
159 |
+
}
|
160 |
+
},
|
161 |
+
|
162 |
+
/*
|
163 |
+
* 返回一个从 target 中选中的属性的对象
|
164 |
+
*
|
165 |
+
* keyList : 获取字段列表,逗号分割字符串
|
166 |
+
* key1, key2, toKey1:fromKey1, toKey2:fromObj.key
|
167 |
+
*
|
168 |
+
* defaultData: 当某个字段为空时会选取defaultData的对应内容
|
169 |
+
* toKeyPrefix:返回数据的字段前缀,默认为空。defaultData中的键值无需包含toKeyPrefix
|
170 |
+
*
|
171 |
+
* */
|
172 |
+
|
173 |
+
getData (target, keyList = '', cfg = {}) {
|
174 |
+
target = target || {}
|
175 |
+
let defaultData = cfg.defaultData || {}
|
176 |
+
let ret = {}
|
177 |
+
// 分割逗号
|
178 |
+
if (typeof (keyList) === 'string') {
|
179 |
+
keyList = keyList.split(',')
|
180 |
+
}
|
181 |
+
|
182 |
+
lodash.forEach(keyList, (keyCfg) => {
|
183 |
+
// 处理通过:指定 toKey & fromKey
|
184 |
+
let _keyCfg = keyCfg.split(':')
|
185 |
+
let keyTo = _keyCfg[0].trim()
|
186 |
+
let keyFrom = (_keyCfg[1] || _keyCfg[0]).trim()
|
187 |
+
let keyRet = keyTo
|
188 |
+
if (cfg.lowerFirstKey) {
|
189 |
+
keyRet = lodash.lowerFirst(keyRet)
|
190 |
+
}
|
191 |
+
if (cfg.keyPrefix) {
|
192 |
+
keyRet = cfg.keyPrefix + keyRet
|
193 |
+
}
|
194 |
+
// 通过Data.getVal获取数据
|
195 |
+
ret[keyRet] = Data.getVal(target, keyFrom, defaultData[keyTo], cfg)
|
196 |
+
})
|
197 |
+
return ret
|
198 |
+
},
|
199 |
+
|
200 |
+
getVal (target, keyFrom, defaultValue) {
|
201 |
+
return lodash.get(target, keyFrom, defaultValue)
|
202 |
+
},
|
203 |
+
|
204 |
+
// 异步池,聚合请求
|
205 |
+
async asyncPool (poolLimit, array, iteratorFn) {
|
206 |
+
const ret = [] // 存储所有的异步任务
|
207 |
+
const executing = [] // 存储正在执行的异步任务
|
208 |
+
for (const item of array) {
|
209 |
+
// 调用iteratorFn函数创建异步任务
|
210 |
+
const p = Promise.resolve().then(() => iteratorFn(item, array))
|
211 |
+
// 保存新的异步任务
|
212 |
+
ret.push(p)
|
213 |
+
|
214 |
+
// 当poolLimit值小于或等于总任务个数时,进行并发控制
|
215 |
+
if (poolLimit <= array.length) {
|
216 |
+
// 当任务完成后,从正在执行的任务数组中移除已完成的任务
|
217 |
+
const e = p.then(() => executing.splice(executing.indexOf(e), 1))
|
218 |
+
executing.push(e) // 保存正在执行的异步任务
|
219 |
+
if (executing.length >= poolLimit) {
|
220 |
+
// 等待较快的任务执行完成
|
221 |
+
await Promise.race(executing)
|
222 |
+
}
|
223 |
+
}
|
224 |
+
}
|
225 |
+
return Promise.all(ret)
|
226 |
+
},
|
227 |
+
|
228 |
+
// sleep
|
229 |
+
sleep (ms) {
|
230 |
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
231 |
+
},
|
232 |
+
|
233 |
+
// 获取默认值
|
234 |
+
def () {
|
235 |
+
for (let idx in arguments) {
|
236 |
+
if (!lodash.isUndefined(arguments[idx])) {
|
237 |
+
return arguments[idx]
|
238 |
+
}
|
239 |
+
}
|
240 |
+
},
|
241 |
+
|
242 |
+
async forEach (data, fn) {
|
243 |
+
if (lodash.isArray(data)) {
|
244 |
+
for (let idx = 0; idx < data.length; idx++) {
|
245 |
+
let ret = fn(data[idx], idx)
|
246 |
+
if (ret instanceof Promise) {
|
247 |
+
ret = await ret
|
248 |
+
}
|
249 |
+
if (ret === false) {
|
250 |
+
break
|
251 |
+
}
|
252 |
+
}
|
253 |
+
} else if (lodash.isPlainObject(data)) {
|
254 |
+
for (const idx in data) {
|
255 |
+
let ret = fn(data[idx], idx)
|
256 |
+
if (ret instanceof Promise) {
|
257 |
+
ret = await ret
|
258 |
+
}
|
259 |
+
if (ret === false) {
|
260 |
+
break
|
261 |
+
}
|
262 |
+
}
|
263 |
+
}
|
264 |
+
},
|
265 |
+
|
266 |
+
// 循环字符串回调
|
267 |
+
eachStr: (arr, fn) => {
|
268 |
+
if (lodash.isString(arr)) {
|
269 |
+
arr = arr.replace(/\s*(;|;|、|,)\s*/, ',')
|
270 |
+
arr = arr.split(',')
|
271 |
+
} else if (lodash.isNumber(arr)) {
|
272 |
+
arr = [arr.toString()]
|
273 |
+
}
|
274 |
+
lodash.forEach(arr, (str, idx) => {
|
275 |
+
if (!lodash.isUndefined(str)) {
|
276 |
+
fn(str.trim ? str.trim() : str, idx)
|
277 |
+
}
|
278 |
+
})
|
279 |
+
},
|
280 |
+
|
281 |
+
regRet (reg, txt, idx) {
|
282 |
+
if (reg && txt) {
|
283 |
+
let ret = reg.exec(txt)
|
284 |
+
if (ret && ret[idx]) {
|
285 |
+
return ret[idx]
|
286 |
+
}
|
287 |
+
}
|
288 |
+
return false
|
289 |
+
}
|
290 |
+
}
|
291 |
+
|
292 |
+
export default Data
|
Yunzai/plugins/miao-plugin/components/Format.js
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lodash from 'lodash'
|
2 |
+
import Elem from './common/Elem.js'
|
3 |
+
import { Cfg } from '#miao'
|
4 |
+
|
5 |
+
let Format = {
|
6 |
+
...Elem,
|
7 |
+
int: function (d) {
|
8 |
+
return parseInt(d)
|
9 |
+
},
|
10 |
+
comma: function (num, fix = 0) {
|
11 |
+
num = parseFloat((num * 1).toFixed(fix))
|
12 |
+
let [integer, decimal] = String.prototype.split.call(num, '.')
|
13 |
+
let re = new RegExp(`\\d(?=(\\d{${Cfg.get('commaGroup', 3)}})+$)`, 'g')
|
14 |
+
integer = integer.replace(re, '$&,') // 正则先行断言 = /\d(?=(\d{3})+$)/g
|
15 |
+
return `${integer}${fix > 0 ? '.' + (decimal || lodash.repeat('0', fix)) : ''}`
|
16 |
+
},
|
17 |
+
pct: function (num, fix = 1) {
|
18 |
+
return (num * 1).toFixed(fix) + '%'
|
19 |
+
},
|
20 |
+
percent: function (num, fix = 1) {
|
21 |
+
return Format.pct(num * 100, fix)
|
22 |
+
}
|
23 |
+
}
|
24 |
+
|
25 |
+
export default Format
|
Yunzai/plugins/miao-plugin/components/MiaoError.js
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export default class MiaoError extends Error {
|
2 |
+
|
3 |
+
constructor(message) {
|
4 |
+
// 允许返回特殊消息,需传递数组,例如 [segment.image()]
|
5 |
+
if (Array.isArray(message)) {
|
6 |
+
super()
|
7 |
+
this._message = message
|
8 |
+
} else {
|
9 |
+
super(message);
|
10 |
+
}
|
11 |
+
}
|
12 |
+
|
13 |
+
get message() {
|
14 |
+
return this._message ? this._message : super.message;
|
15 |
+
}
|
16 |
+
|
17 |
+
}
|
Yunzai/plugins/miao-plugin/components/Version.js
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import fs from 'fs'
|
2 |
+
import lodash from 'lodash'
|
3 |
+
import { Data } from '#miao'
|
4 |
+
|
5 |
+
let packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
6 |
+
|
7 |
+
const getLine = function (line) {
|
8 |
+
line = line.replace(/(^\s*\*|\r)/g, '')
|
9 |
+
line = line.replace(/\s*`([^`]+`)/g, '<span class="cmd">$1')
|
10 |
+
line = line.replace(/`\s*/g, '</span>')
|
11 |
+
line = line.replace(/\s*\*\*([^\*]+\*\*)/g, '<span class="strong">$1')
|
12 |
+
line = line.replace(/\*\*\s*/g, '</span>')
|
13 |
+
line = line.replace(/ⁿᵉʷ/g, '<span class="new"></span>')
|
14 |
+
return line
|
15 |
+
}
|
16 |
+
|
17 |
+
const readLogFile = function (root, versionCount = 4) {
|
18 |
+
root = Data.getRoot(root)
|
19 |
+
let logPath = `${root}/CHANGELOG.md`
|
20 |
+
let logs = {}
|
21 |
+
let changelogs = []
|
22 |
+
let currentVersion
|
23 |
+
|
24 |
+
try {
|
25 |
+
if (fs.existsSync(logPath)) {
|
26 |
+
logs = fs.readFileSync(logPath, 'utf8') || ''
|
27 |
+
logs = logs.split('\n')
|
28 |
+
|
29 |
+
let temp = {}
|
30 |
+
let lastLine = {}
|
31 |
+
lodash.forEach(logs, (line) => {
|
32 |
+
if (versionCount <= -1) {
|
33 |
+
return false
|
34 |
+
}
|
35 |
+
let versionRet = /^#\s*([0-9a-zA-Z\\.~\s]+?)\s*$/.exec(line)
|
36 |
+
if (versionRet && versionRet[1]) {
|
37 |
+
let v = versionRet[1].trim()
|
38 |
+
if (!currentVersion) {
|
39 |
+
currentVersion = v
|
40 |
+
} else {
|
41 |
+
changelogs.push(temp)
|
42 |
+
if (/0\s*$/.test(v) && versionCount > 0) {
|
43 |
+
versionCount = 0
|
44 |
+
} else {
|
45 |
+
versionCount--
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
temp = {
|
50 |
+
version: v,
|
51 |
+
logs: []
|
52 |
+
}
|
53 |
+
} else {
|
54 |
+
if (!line.trim()) {
|
55 |
+
return
|
56 |
+
}
|
57 |
+
if (/^\*/.test(line)) {
|
58 |
+
lastLine = {
|
59 |
+
title: getLine(line),
|
60 |
+
logs: []
|
61 |
+
}
|
62 |
+
temp.logs.push(lastLine)
|
63 |
+
} else if (/^\s{2,}\*/.test(line)) {
|
64 |
+
lastLine.logs.push(getLine(line))
|
65 |
+
}
|
66 |
+
}
|
67 |
+
})
|
68 |
+
}
|
69 |
+
} catch (e) {
|
70 |
+
// do nth
|
71 |
+
}
|
72 |
+
return { changelogs, currentVersion }
|
73 |
+
}
|
74 |
+
|
75 |
+
const { changelogs, currentVersion } = readLogFile('miao')
|
76 |
+
|
77 |
+
|
78 |
+
const yunzaiVersion = packageJson.version
|
79 |
+
const isV3 = yunzaiVersion[0] === '3'
|
80 |
+
let isMiao = false
|
81 |
+
let name = "Yunzai-Bot"
|
82 |
+
if (packageJson.name === 'miao-yunzai') {
|
83 |
+
isMiao = true
|
84 |
+
name = "Miao-Yunzai"
|
85 |
+
} else if (packageJson.name === 'trss-yunzai') {
|
86 |
+
isMiao = true
|
87 |
+
name = "TRSS-Yunzai"
|
88 |
+
}
|
89 |
+
|
90 |
+
let Version = {
|
91 |
+
isV3,
|
92 |
+
isMiao,
|
93 |
+
name,
|
94 |
+
get version () {
|
95 |
+
return currentVersion
|
96 |
+
},
|
97 |
+
get yunzai () {
|
98 |
+
return yunzaiVersion
|
99 |
+
},
|
100 |
+
get changelogs () {
|
101 |
+
return changelogs
|
102 |
+
},
|
103 |
+
runtime () {
|
104 |
+
console.log(`未能找到e.runtime,请升级至最新版${isV3 ? 'V3' : 'V2'}-Yunzai以使用miao-plugin`)
|
105 |
+
},
|
106 |
+
readLogFile
|
107 |
+
}
|
108 |
+
|
109 |
+
export default Version
|
Yunzai/plugins/miao-plugin/components/cfg/CfgData.js
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { cfgSchema } from '../../config/system/cfg_system.js'
|
2 |
+
import lodash from 'lodash'
|
3 |
+
import { Data } from '../index.js'
|
4 |
+
import fs from 'node:fs'
|
5 |
+
|
6 |
+
let cfgData = {
|
7 |
+
saveCfg (cfg) {
|
8 |
+
let ret = []
|
9 |
+
lodash.forEach(cfgSchema, (cfgGroup) => {
|
10 |
+
ret.push(`/** ************ 【${cfgGroup.title}】 ************* */`)
|
11 |
+
lodash.forEach(cfgGroup.cfg, (cfgItem, cfgKey) => {
|
12 |
+
ret.push(`// ${cfgItem.desc || cfgItem.title}`)
|
13 |
+
let val = Data.def(cfg[cfgKey], cfgItem.def)
|
14 |
+
if (cfgItem.input) {
|
15 |
+
val = cfgItem.input(val)
|
16 |
+
}
|
17 |
+
ret.push(`export const ${cfgKey} = ${val.toString()}`, '')
|
18 |
+
})
|
19 |
+
})
|
20 |
+
fs.writeFileSync(`${process.cwd()}/plugins/miao-plugin/config/cfg.js`, ret.join('\n'), 'utf8')
|
21 |
+
},
|
22 |
+
|
23 |
+
async getCfg () {
|
24 |
+
let ret = lodash.toPlainObject(await Data.importModule('/config/cfg.js', 'miao'))
|
25 |
+
lodash.forEach(cfgSchema, (cfgGroup) => {
|
26 |
+
lodash.forEach(cfgGroup.cfg, (cfgItem, cfgKey) => {
|
27 |
+
ret[cfgKey] = Data.def(ret[cfgKey], cfgItem.def)
|
28 |
+
})
|
29 |
+
})
|
30 |
+
return ret
|
31 |
+
},
|
32 |
+
|
33 |
+
getCfgSchemaMap () {
|
34 |
+
let ret = {}
|
35 |
+
lodash.forEach(cfgSchema, (cfgGroup) => {
|
36 |
+
lodash.forEach(cfgGroup.cfg, (cfgItem, cfgKey) => {
|
37 |
+
ret[cfgItem.key] = cfgItem
|
38 |
+
cfgItem.cfgKey = cfgKey
|
39 |
+
})
|
40 |
+
})
|
41 |
+
return ret
|
42 |
+
},
|
43 |
+
getCfgSchema () {
|
44 |
+
return cfgSchema
|
45 |
+
}
|
46 |
+
}
|
47 |
+
export default cfgData
|
Yunzai/plugins/miao-plugin/components/common/Elem.js
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Data } from '../index.js'
|
2 |
+
import lodash from 'lodash'
|
3 |
+
|
4 |
+
const elemAlias = {
|
5 |
+
anemo: '风,蒙德',
|
6 |
+
geo: '岩,璃月',
|
7 |
+
electro: '雷,电,雷电,稻妻',
|
8 |
+
dendro: '草,须弥',
|
9 |
+
pyro: '火,纳塔',
|
10 |
+
hydro: '水,枫丹',
|
11 |
+
cryo: '冰,至冬'
|
12 |
+
}
|
13 |
+
|
14 |
+
const elemAliasSR = {
|
15 |
+
fire: '火',
|
16 |
+
ice: '冰',
|
17 |
+
wind: '风',
|
18 |
+
elec: '雷',
|
19 |
+
phy: '物理',
|
20 |
+
quantum: '量子',
|
21 |
+
imaginary: '虚数'
|
22 |
+
}
|
23 |
+
|
24 |
+
// 元素属性映射, 名称=>elem
|
25 |
+
let elemMap = {}
|
26 |
+
let elemMapSR = {}
|
27 |
+
|
28 |
+
// 标准元素名
|
29 |
+
let elemTitleMap = {}
|
30 |
+
let elemTitleMapSR = elemAliasSR
|
31 |
+
|
32 |
+
lodash.forEach(elemAlias, (txt, key) => {
|
33 |
+
elemMap[key] = key
|
34 |
+
elemTitleMap[key] = txt[0]
|
35 |
+
Data.eachStr(txt, (t) => (elemMap[t] = key))
|
36 |
+
})
|
37 |
+
lodash.forEach(elemAliasSR, (txt, key) => {
|
38 |
+
elemMapSR[key] = key
|
39 |
+
elemMapSR[txt] = key
|
40 |
+
})
|
41 |
+
|
42 |
+
const Elem = {
|
43 |
+
// 根据名称获取元素key
|
44 |
+
elem (elem = '', defElem = '', game = 'gs') {
|
45 |
+
elem = elem.toLowerCase()
|
46 |
+
return (game === 'gs' ? elemMap : elemMapSR)[elem] || defElem
|
47 |
+
},
|
48 |
+
|
49 |
+
// 根据key获取元素名
|
50 |
+
elemName (elem = '', defName = '') {
|
51 |
+
return elemTitleMap[Elem.elem(elem)] || defName
|
52 |
+
},
|
53 |
+
|
54 |
+
// 从字符串中匹配元素
|
55 |
+
matchElem (name = '', defElem = '', withName = false) {
|
56 |
+
const elemReg = new RegExp(`^(${lodash.keys(elemMap).join('|')})`)
|
57 |
+
let elemRet = elemReg.exec(name)
|
58 |
+
let elem = (elemRet && elemRet[1]) ? Elem.elem(elemRet[1]) : defElem
|
59 |
+
if (elem) {
|
60 |
+
if (withName) {
|
61 |
+
return {
|
62 |
+
elem,
|
63 |
+
name: name.replace(elemReg, '')
|
64 |
+
}
|
65 |
+
}
|
66 |
+
return elem
|
67 |
+
}
|
68 |
+
return ''
|
69 |
+
},
|
70 |
+
|
71 |
+
eachElem (fn, game = 'gs') {
|
72 |
+
lodash.forEach(game === 'gs' ? elemTitleMap : elemTitleMapSR, (title, key) => {
|
73 |
+
fn(key, title)
|
74 |
+
})
|
75 |
+
},
|
76 |
+
|
77 |
+
isElem (elem = '', game = 'gs') {
|
78 |
+
return !!(game === 'gs' ? elemMap : elemMapSR)[elem]
|
79 |
+
},
|
80 |
+
|
81 |
+
sameElem (key1, key2, game = 'gs') {
|
82 |
+
let map = (game === 'gs' ? elemMap : elemMapSR)
|
83 |
+
return map[key1] === map[key2]
|
84 |
+
}
|
85 |
+
}
|
86 |
+
export default Elem
|
Yunzai/plugins/miao-plugin/components/common/Plugin.js
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
* V3版Yunzai plugin
|
3 |
+
* */
|
4 |
+
let stateArr = {}
|
5 |
+
|
6 |
+
export default class plugin {
|
7 |
+
/**
|
8 |
+
* @param name 插件名称
|
9 |
+
* @param dsc 插件描述
|
10 |
+
* @param event 执行事件,默认message
|
11 |
+
* @param priority 优先级,数字越小优先级越高
|
12 |
+
* @param rule.reg 命令正则
|
13 |
+
* @param rule.fnc 命令执行方法
|
14 |
+
* @param rule.event 执行事件,默认message
|
15 |
+
* @param rule.log false时不显示执行日志
|
16 |
+
* @param rule.permission 权限 master,owner,admin,all
|
17 |
+
* @param task.name 定时任务名称
|
18 |
+
* @param task.cron 定时任务cron表达式
|
19 |
+
* @param task.fnc 定时任务方法名
|
20 |
+
* @param task.log false时不显示执行日志
|
21 |
+
*/
|
22 |
+
constructor (data) {
|
23 |
+
/** 插件名称 */
|
24 |
+
this.name = data.name
|
25 |
+
/** 插件描述 */
|
26 |
+
this.dsc = data.dsc
|
27 |
+
/** 监听事件,默认message https://oicqjs.github.io/oicq/#events */
|
28 |
+
this.event = data.event || 'message'
|
29 |
+
/** 优先级 */
|
30 |
+
this.priority = data.priority || 5000
|
31 |
+
/** 定时任务,可以是数组 */
|
32 |
+
this.task = {
|
33 |
+
/** 任务名 */
|
34 |
+
name: '',
|
35 |
+
/** 任务方法名 */
|
36 |
+
fnc: data.task?.fnc || '',
|
37 |
+
/** 任务cron表达式 */
|
38 |
+
cron: data.task?.cron || ''
|
39 |
+
}
|
40 |
+
|
41 |
+
/** 命令规则 */
|
42 |
+
this.rule = data.rule || []
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* @param msg 发送的消息
|
47 |
+
* @param quote 是否引用回复
|
48 |
+
* @param data.recallMsg 群聊是否撤回消息,0-120秒,0不撤回
|
49 |
+
* @param data.at 是否at用户
|
50 |
+
*/
|
51 |
+
reply (msg = '', quote = false, data = {}) {
|
52 |
+
if (!this.e.reply || !msg) return false
|
53 |
+
return this.e.reply(msg, quote, data)
|
54 |
+
}
|
55 |
+
|
56 |
+
conKey (isGroup = false) {
|
57 |
+
if (isGroup) {
|
58 |
+
return `${this.name}.${this.e.group_id}`
|
59 |
+
} else {
|
60 |
+
return `${this.name}.${this.userId || this.e.user_id}`
|
61 |
+
}
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* @param type 执行方法
|
66 |
+
* @param isGroup 是否群聊
|
67 |
+
* @param time 操作时间,默认120秒
|
68 |
+
*/
|
69 |
+
setContext (type, isGroup = false, time = 120) {
|
70 |
+
let key = this.conKey(isGroup)
|
71 |
+
if (!stateArr[key]) stateArr[key] = {}
|
72 |
+
stateArr[key][type] = this.e
|
73 |
+
if (time) {
|
74 |
+
/** 操作时间 */
|
75 |
+
setTimeout(() => {
|
76 |
+
if (stateArr[key][type]) {
|
77 |
+
delete stateArr[key][type]
|
78 |
+
this.e.reply('操作超时已取消', true)
|
79 |
+
}
|
80 |
+
}, time * 1000)
|
81 |
+
}
|
82 |
+
}
|
83 |
+
|
84 |
+
getContext () {
|
85 |
+
let key = this.conKey()
|
86 |
+
return stateArr[key]
|
87 |
+
}
|
88 |
+
|
89 |
+
getContextGroup () {
|
90 |
+
let key = this.conKey(true)
|
91 |
+
return stateArr[key]
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* @param type 执行方法
|
96 |
+
* @param isGroup 是否群聊
|
97 |
+
*/
|
98 |
+
finish (type, isGroup = false) {
|
99 |
+
if (stateArr[this.conKey(isGroup)] && stateArr[this.conKey(isGroup)][type]) {
|
100 |
+
delete stateArr[this.conKey(isGroup)][type]
|
101 |
+
}
|
102 |
+
}
|
103 |
+
}
|