CikeyQi erzaozi commited on
Commit
be07d61
·
1 Parent(s): 4f91af3

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
Files changed (50) hide show
  1. .gitattributes +5 -0
  2. Yunzai/plugins/miao-plugin/.gitignore +20 -0
  3. Yunzai/plugins/miao-plugin/CHANGELOG.md +377 -0
  4. Yunzai/plugins/miao-plugin/LICENSE +21 -0
  5. Yunzai/plugins/miao-plugin/README.md +106 -0
  6. Yunzai/plugins/miao-plugin/apps/admin.js +204 -0
  7. Yunzai/plugins/miao-plugin/apps/character.js +36 -0
  8. Yunzai/plugins/miao-plugin/apps/character/AvatarCard.js +132 -0
  9. Yunzai/plugins/miao-plugin/apps/character/AvatarWife.js +188 -0
  10. Yunzai/plugins/miao-plugin/apps/character/ImgUpload.js +258 -0
  11. Yunzai/plugins/miao-plugin/apps/gacha.js +26 -0
  12. Yunzai/plugins/miao-plugin/apps/gacha/Gacha.js +90 -0
  13. Yunzai/plugins/miao-plugin/apps/gacha/GachaData.js +444 -0
  14. Yunzai/plugins/miao-plugin/apps/help.js +23 -0
  15. Yunzai/plugins/miao-plugin/apps/help/Help.js +77 -0
  16. Yunzai/plugins/miao-plugin/apps/help/HelpTheme.js +68 -0
  17. Yunzai/plugins/miao-plugin/apps/index.js +16 -0
  18. Yunzai/plugins/miao-plugin/apps/poke.js +17 -0
  19. Yunzai/plugins/miao-plugin/apps/profile.js +143 -0
  20. Yunzai/plugins/miao-plugin/apps/profile/ProfileArtis.js +96 -0
  21. Yunzai/plugins/miao-plugin/apps/profile/ProfileChange.js +299 -0
  22. Yunzai/plugins/miao-plugin/apps/profile/ProfileCommon.js +77 -0
  23. Yunzai/plugins/miao-plugin/apps/profile/ProfileDetail.js +299 -0
  24. Yunzai/plugins/miao-plugin/apps/profile/ProfileList.js +184 -0
  25. Yunzai/plugins/miao-plugin/apps/profile/ProfileRank.js +280 -0
  26. Yunzai/plugins/miao-plugin/apps/profile/ProfileStat.js +73 -0
  27. Yunzai/plugins/miao-plugin/apps/profile/ProfileUtils.js +78 -0
  28. Yunzai/plugins/miao-plugin/apps/profile/ProfileWeapon.js +36 -0
  29. Yunzai/plugins/miao-plugin/apps/stat.js +37 -0
  30. Yunzai/plugins/miao-plugin/apps/stat/AbyssStat.js +164 -0
  31. Yunzai/plugins/miao-plugin/apps/stat/AbyssSummary.js +137 -0
  32. Yunzai/plugins/miao-plugin/apps/stat/AbyssTeam.js +190 -0
  33. Yunzai/plugins/miao-plugin/apps/stat/HutaoApi.js +73 -0
  34. Yunzai/plugins/miao-plugin/apps/wiki.js +29 -0
  35. Yunzai/plugins/miao-plugin/apps/wiki/Calendar.js +414 -0
  36. Yunzai/plugins/miao-plugin/apps/wiki/CalendarSr.js +303 -0
  37. Yunzai/plugins/miao-plugin/apps/wiki/CharMaterial.js +18 -0
  38. Yunzai/plugins/miao-plugin/apps/wiki/CharTalent.js +114 -0
  39. Yunzai/plugins/miao-plugin/apps/wiki/CharWiki.js +105 -0
  40. Yunzai/plugins/miao-plugin/apps/wiki/CharWikiData.js +108 -0
  41. Yunzai/plugins/miao-plugin/components/App.js +133 -0
  42. Yunzai/plugins/miao-plugin/components/Cfg.js +56 -0
  43. Yunzai/plugins/miao-plugin/components/Common.js +17 -0
  44. Yunzai/plugins/miao-plugin/components/Data.js +292 -0
  45. Yunzai/plugins/miao-plugin/components/Format.js +25 -0
  46. Yunzai/plugins/miao-plugin/components/MiaoError.js +17 -0
  47. Yunzai/plugins/miao-plugin/components/Version.js +109 -0
  48. Yunzai/plugins/miao-plugin/components/cfg/CfgData.js +47 -0
  49. Yunzai/plugins/miao-plugin/components/common/Elem.js +86 -0
  50. 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&region=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&region=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(/(<|&lt;)[\w "%:;=\-\\/\\(\\),\\.]+(>|&gt;)/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&region=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&region=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(/(<|&lt;)[\w "%:;=\-\\/\\(\\),\\.]+(>|&gt;)/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
+ }