diff --git a/metadata/pool/pool.py b/metadata/pool/pool.py new file mode 100644 index 00000000..163e9169 --- /dev/null +++ b/metadata/pool/pool.py @@ -0,0 +1,12 @@ +from metadata.pool.pool_200 import POOL_200 +from metadata.pool.pool_301 import POOL_301 +from metadata.pool.pool_302 import POOL_302 + + +def get_pool_by_id(pool_type): + if pool_type == 200: + return POOL_200 + elif pool_type == 301: + return POOL_301 + elif pool_type == 302: + return POOL_302 diff --git a/metadata/pool/pool_200.py b/metadata/pool/pool_200.py new file mode 100644 index 00000000..004e4a13 --- /dev/null +++ b/metadata/pool/pool_200.py @@ -0,0 +1,11 @@ +POOL_200 = [ + { + "five": [ + "常驻池" + ], + "four": [], + "from": "2020-09-15 06:00:00", + "name": "常驻池", + "to": "2050-09-15 17:59:59" + } +] \ No newline at end of file diff --git a/metadata/pool/pool_301.py b/metadata/pool/pool_301.py new file mode 100644 index 00000000..d9757902 --- /dev/null +++ b/metadata/pool/pool_301.py @@ -0,0 +1,480 @@ +POOL_301 = [ + { + "five": [ + "赛诺", + "温迪" + ], + "four": [ + "久岐忍", + "早柚", + "坎蒂丝" + ], + "from": "2022-09-28 06:00:00", + "name": "劈裁冥昭|杯装之诗", + "to": "2022-10-14 17:59:59" + }, + { + "five": [ + "甘雨", + "心海" + ], + "four": [ + "行秋", + "砂糖", + "多莉" + ], + "from": "2022-09-09 18:00:00", + "name": "浮生孰来|浮岳虹珠", + "to": "2022-09-27 14:59:59" + }, + { + "five": [ + "提纳里", + "钟离" + ], + "four": [ + "云堇", + "辛焱", + "班尼特" + ], + "from": "2022-08-24 06:00:00", + "name": "巡御蘙荟|陵薮市朝", + "to": "2022-09-09 17:59:59" + }, + { + "five": [ + "宵宫" + ], + "four": [ + "云堇", + "辛焱", + "班尼特" + ], + "from": "2022-08-02 18:00:00", + "name": "焰色天河", + "to": "2022-08-23 14:59:59" + }, + { + "five": [ + "枫原万叶", + "可莉" + ], + "four": [ + "凝光", + "鹿野院平藏", + "托马" + ], + "from": "2022-07-13 06:00:00", + "name": "红叶逐荒波", + "to": "2022-08-02 17:59:59" + }, + { + "five": [ + "荒泷一斗" + ], + "four": [ + "烟绯", + "芭芭拉", + "诺艾尔" + ], + "from": "2022-06-21 18:00:00", + "name": "鬼门斗宴", + "to": "2022-07-12 14:59:59" + }, + { + "five": [ + "夜兰", + "魈" + ], + "four": [ + "烟绯", + "芭芭拉", + "诺艾尔" + ], + "from": "2022-05-31 06:00:00", + "name": "素霓伣天|烟火之邀", + "to": "2022-06-21 17:59:59" + }, + { + "five": [ + "神里绫华" + ], + "four": [ + "罗莎莉亚", + "早柚", + "雷泽" + ], + "from": "2022-04-19 17:59:59", + "name": "白鹭之庭", + "to": "2022-05-31 05:59:59" + }, + { + "five": [ + "神里绫人", + "温迪" + ], + "four": [ + "香菱", + "砂糖", + "云堇" + ], + "from": "2022-03-30 06:00:00", + "name": "苍流踏花|杯装之诗", + "to": "2022-04-19 17:59:59" + }, + { + "five": [ + "雷电将军", + "珊瑚宫心海" + ], + "four": [ + "辛焱", + "九条裟罗", + "班尼特" + ], + "from": "2022-03-08 18:00:00", + "name": "影寂天下人|浮岳虹珠", + "to": "2022-03-29 14:59:59" + }, + { + "five": [ + "八重神子" + ], + "four": [ + "菲谢尔", + "迪奥娜", + "托马" + ], + "from": "2022-02-16 06:00:00", + "name": "华紫樱绯", + "to": "2022-03-08 17:59:59" + }, + { + "five": [ + "甘雨", + "钟离" + ], + "four": [ + "行秋", + "北斗", + "烟绯" + ], + "from": "2022-01-25 18:00:00", + "name": "浮生孰来|陵薮市朝", + "to": "2022-02-15 14:59:59" + }, + { + "five": [ + "申鹤", + "魈" + ], + "four": [ + "云堇", + "凝光", + "重云" + ], + "from": "2022-01-05 06:00:00", + "name": "出尘入世|烟火之邀", + "to": "2022-01-25 17:59:59" + }, + { + "five": [ + "荒泷一斗" + ], + "four": [ + "五郎", + "芭芭拉", + "香菱" + ], + "from": "2021-12-14 18:00:00", + "name": "鬼门斗宴", + "to": "2022-01-04 14:59:59" + }, + { + "five": [ + "阿贝多", + "优菈" + ], + "four": [ + "班尼特", + "诺艾尔", + "罗莎莉亚" + ], + "from": "2021-11-24 06:00:00", + "name": "深秘之息|浪涌之瞬", + "to": "2021-12-14 17:59:59" + }, + { + "five": [ + "胡桃" + ], + "four": [ + "托马", + "迪奥娜", + "早柚" + ], + "from": "2021-11-02 18:00:00", + "name": "赤团开时", + "to": "2021-11-23 14:59:59" + }, + { + "five": [ + "达达利亚" + ], + "four": [ + "凝光", + "重云", + "烟绯" + ], + "from": "2021-10-13 06:00:00", + "name": "暂别冬都", + "to": "2021-11-02 17:59:59" + }, + { + "five": [ + "珊瑚宫心海" + ], + "four": [ + "罗莎莉亚", + "北斗", + "行秋" + ], + "from": "2021-09-21 18:00:00", + "name": "浮岳虹珠", + "to": "2021-10-12 14:59:59" + }, + { + "five": [ + "雷电将军" + ], + "four": [ + "九条裟罗", + "香菱", + "砂糖" + ], + "from": "2021-09-01 06:00:00", + "name": "影寂天下人", + "to": "2021-09-21 17:59:59" + }, + { + "five": [ + "宵宫" + ], + "four": [ + "早柚", + "迪奥娜", + "辛焱" + ], + "from": "2021-08-10 18:00:00", + "name": "焰色天河", + "to": "2021-08-31 14:59:59" + }, + { + "five": [ + "神里绫华" + ], + "four": [ + "凝光", + "重云", + "烟绯" + ], + "from": "2021-07-21 06:00:00", + "name": "白鹭之庭", + "to": "2021-08-10 17:59:59" + }, + { + "five": [ + "枫原万叶" + ], + "four": [ + "罗莎莉亚", + "班尼特", + "雷泽" + ], + "from": "2021-06-29 18:00:00", + "name": "红叶逐荒波", + "to": "2021-07-20 14:59:59" + }, + { + "five": [ + "可莉" + ], + "four": [ + "芭芭拉", + "砂糖", + "菲谢尔" + ], + "from": "2021-06-09 06:00:00", + "name": "逃跑的太阳", + "to": "2021-06-29 17:59:59" + }, + { + "five": [ + "优菈" + ], + "four": [ + "辛焱", + "行秋", + "北斗" + ], + "from": "2021-05-18 18:00:00", + "name": "浪沫的旋舞", + "to": "2021-06-08 14:59:59" + }, + { + "five": [ + "钟离" + ], + "four": [ + "烟绯", + "诺艾尔", + "迪奥娜" + ], + "from": "2021-04-28 06:00:00", + "name": "陵薮市朝", + "to": "2021-05-18 17:59:59" + }, + { + "five": [ + "达达利亚" + ], + "four": [ + "罗莎莉亚", + "芭芭拉", + "菲谢尔" + ], + "from": "2021-04-06 18:00:00", + "name": "暂别冬都", + "to": "2021-04-27 14:59:59" + }, + { + "five": [ + "温迪" + ], + "four": [ + "砂糖", + "雷泽", + "诺艾尔" + ], + "from": "2021-03-17 06:00:00", + "name": "杯装之诗", + "to": "2021-04-06 15:59:59" + }, + { + "five": [ + "胡桃" + ], + "four": [ + "行秋", + "香菱", + "重云" + ], + "from": "2021-03-02 18:00:00", + "name": "赤团开时", + "to": "2021-03-16 14:59:59" + }, + { + "five": [ + "刻晴" + ], + "four": [ + "凝光", + "班尼特", + "芭芭拉" + ], + "from": "2021-02-17 18:00:00", + "name": "鱼龙灯昼", + "to": "2021-03-02 15:59:59" + }, + { + "five": [ + "魈" + ], + "four": [ + "迪奥娜", + "北斗", + "辛焱" + ], + "from": "2021-02-03 06:00:00", + "name": "烟火之邀", + "to": "2021-02-17 15:59:59" + }, + { + "five": [ + "甘雨" + ], + "four": [ + "香菱", + "行秋", + "诺艾尔" + ], + "from": "2021-01-12 18:00:00", + "name": "浮生孰来", + "to": "2021-02-02 14:59:59" + }, + { + "five": [ + "阿贝多" + ], + "four": [ + "菲谢尔", + "砂糖", + "班尼特" + ], + "from": "2020-12-23 06:00:00", + "name": "深秘之息", + "to": "2021-01-12 15:59:59" + }, + { + "five": [ + "钟离" + ], + "four": [ + "辛焱", + "雷泽", + "重云" + ], + "from": "2020-12-01 18:00:00", + "name": "陵薮市朝", + "to": "2020-12-22 14:59:59" + }, + { + "five": [ + "达达利亚" + ], + "four": [ + "迪奥娜", + "北斗", + "凝光" + ], + "from": "2020-11-11 06:00:00", + "name": "暂别冬都", + "to": "2020-12-01 15:59:59" + }, + { + "five": [ + "可莉" + ], + "four": [ + "行秋", + "诺艾尔", + "砂糖" + ], + "from": "2020-10-20 18:00:00", + "name": "闪焰的驻足", + "to": "2020-11-10 14:59:59" + }, + { + "five": [ + "温迪" + ], + "four": [ + "芭芭拉", + "菲谢尔", + "香菱" + ], + "from": "2020-9-28 06:00:00", + "name": "杯装之诗", + "to": "2020-10-18 17:59:59" + } +] \ No newline at end of file diff --git a/metadata/pool/pool_302.py b/metadata/pool/pool_302.py new file mode 100644 index 00000000..4653e896 --- /dev/null +++ b/metadata/pool/pool_302.py @@ -0,0 +1,562 @@ +POOL_302 = [ + { + "five": [ + "赤沙之杖", + "终末嗟叹之诗" + ], + "four": [ + "匣里龙吟", + "玛海菈的水色", + "西风长枪", + "祭礼残章", + "西风猎弓" + ], + "from": "2022-09-28 06:00:00", + "name": "神铸赋形", + "to": "2022-10-14 17:59:59" + }, + { + "five": [ + "阿莫斯之弓", + "不灭月华" + ], + "four": [ + "祭礼剑", + "西风大剑", + "匣里灭辰", + "昭心", + "弓藏" + ], + "from": "2022-09-09 18:00:00", + "name": "神铸赋形", + "to": "2022-09-27 14:59:59" + }, + { + "five": [ + "猎人之径", + "贯虹之槊" + ], + "four": [ + "西风剑", + "钟剑", + "西风长枪", + "西风秘典", + "绝弦" + ], + "from": "2022-08-24 06:00:00", + "name": "神铸赋形", + "to": "2022-09-09 17:59:59" + }, + { + "five": [ + "飞雷之弦振", + "斫峰之刃" + ], + "four": [ + "暗巷的酒与诗", + "暗巷猎手", + "笛剑", + "祭礼大剑", + "匣里灭辰" + ], + "from": "2022-08-02 18:00:00", + "name": "神铸赋形", + "to": "2022-08-23 14:59:59" + }, + { + "five": [ + "苍古自由之誓", + "四风原典" + ], + "four": [ + "千岩古剑", + "匣里龙吟", + "匣里灭辰", + "祭礼残章", + "绝弦" + ], + "from": "2022-07-13 06:00:00", + "name": "神铸赋形", + "to": "2022-08-02 17:59:59" + }, + { + "five": [ + "赤角石溃杵", + "尘世之锁" + ], + "four": [ + "千岩古剑", + "匣里龙吟", + "匣里灭辰", + "祭礼残章", + "绝弦" + ], + "from": "2022-06-21 18:00:00", + "name": "神铸赋形", + "to": "2022-07-12 14:59:59" + }, + { + "five": [ + "若水", + "和璞鸢" + ], + "four": [ + "千岩长枪", + "祭礼剑", + "西风大剑", + "昭心", + "祭礼弓" + ], + "from": "2022-05-31 06:00:00", + "name": "神铸赋形", + "to": "2022-06-21 17:59:59" + }, + { + "five": [ + "雾切之回光", + "无工之剑" + ], + "four": [ + "西风剑", + "钟剑", + "西风长枪", + "西风秘典", + "西风猎弓" + ], + "from": "2022-04-19 17:59:59", + "name": "神铸赋形", + "to": "2022-05-31 05:59:59" + }, + { + "five": [ + "波乱月白经津", + "终末嗟叹之诗" + ], + "four": [ + "弓藏", + "笛剑", + "流浪乐章", + "匣里灭辰", + "祭礼大剑" + ], + "from": "2022-03-30 06:00:00", + "name": "神铸赋形", + "to": "2022-04-19 17:59:59" + }, + { + "five": [ + "薙草之稻光", + "不灭月华" + ], + "four": [ + "恶王丸", + "曚云之月", + "匣里龙吟", + "西风长枪", + "祭礼残章" + ], + "from": "2022-03-08 18:00:00", + "name": "神铸赋形", + "to": "2022-03-29 14:59:59" + }, + { + "five": [ + "神乐之真意", + "磐岩结绿" + ], + "four": [ + "祭礼剑", + "雨裁", + "断浪长鳍", + "昭心", + "绝弦" + ], + "from": "2022-02-16 06:00:00", + "name": "神铸赋形", + "to": "2022-03-08 17:59:59" + }, + { + "five": [ + "贯虹之槊", + "阿莫斯之弓" + ], + "four": [ + "西风剑", + "千岩古剑", + "匣里灭辰", + "西风秘典", + "祭礼弓" + ], + "from": "2022-01-25 18:00:00", + "name": "神铸赋形", + "to": "2022-02-15 14:59:59" + }, + { + "five": [ + "息灾", + "和璞鸢" + ], + "four": [ + "笛剑", + "西风大剑", + "千岩长枪", + "流浪乐章", + "西风猎弓" + ], + "from": "2022-01-05 06:00:00", + "name": "神铸赋形", + "to": "2022-01-25 17:59:59" + }, + { + "five": [ + "赤角石溃杵", + "天空之翼" + ], + "four": [ + "暗巷闪光", + "钟剑", + "西风长枪", + "祭礼残章", + "幽夜华尔兹" + ], + "from": "2021-12-14 18:00:00", + "name": "神铸赋形", + "to": "2022-01-04 14:59:59" + }, + { + "five": [ + "苍古自由之誓", + "松籁响起之时" + ], + "four": [ + "匣里龙吟", + "祭礼大剑", + "匣里灭辰", + "暗巷的酒与诗", + "暗巷猎手" + ], + "from": "2021-11-24 06:00:00", + "name": "神铸赋形", + "to": "2021-12-14 17:59:59" + }, + { + "five": [ + "护摩之杖", + "终末嗟叹之诗" + ], + "four": [ + "祭礼剑", + "雨裁", + "断浪长鳍", + "流浪乐章", + "曚云之月" + ], + "from": "2021-11-02 18:00:00", + "name": "神铸赋形", + "to": "2021-11-23 14:59:59" + }, + { + "five": [ + "冬极白星", + "尘世之锁" + ], + "four": [ + "西风剑", + "恶王丸", + "西风长枪", + "昭心", + "弓藏" + ], + "from": "2021-10-13 06:00:00", + "name": "神铸赋形", + "to": "2021-11-02 17:59:59" + }, + { + "five": [ + "不灭月华", + "磐岩结绿" + ], + "four": [ + "笛剑", + "西风大剑", + "匣里灭辰", + "西风秘典", + "绝弦" + ], + "from": "2021-09-21 18:00:00", + "name": "神铸赋形", + "to": "2021-10-12 14:59:59" + }, + { + "five": [ + "薙草之稻光", + "无工之剑" + ], + "four": [ + "匣里龙吟", + "钟剑", + "西风长枪", + "流浪乐章", + "祭礼弓" + ], + "from": "2021-09-01 06:00:00", + "name": "神铸赋形", + "to": "2021-09-21 17:59:59" + }, + { + "five": [ + "飞雷之弦振", + "天空之刃" + ], + "four": [ + "祭礼剑", + "雨裁", + "匣里灭辰", + "祭礼残章", + "西风猎弓" + ], + "from": "2021-08-10 18:00:00", + "name": "神铸赋形", + "to": "2021-08-31 14:59:59" + }, + { + "five": [ + "雾切之回光", + "天空之脊" + ], + "four": [ + "西风剑", + "祭礼大剑", + "西风长枪", + "西风秘典", + "绝弦" + ], + "from": "2021-07-21 06:00:00", + "name": "神铸赋形", + "to": "2021-08-10 17:59:59" + }, + { + "five": [ + "苍古自由之誓", + "天空之卷" + ], + "four": [ + "暗巷闪光", + "西风大剑", + "匣里灭辰", + "暗巷的酒与诗", + "暗巷猎手" + ], + "from": "2021-06-29 18:00:00", + "name": "神铸赋形", + "to": "2021-07-20 14:59:59" + }, + { + "five": [ + "天空之傲", + "四风原典" + ], + "four": [ + "匣里龙吟", + "钟剑", + "西风长枪", + "流浪乐章", + "幽夜华尔兹" + ], + "from": "2021-06-09 06:00:00", + "name": "神铸赋形", + "to": "2021-06-29 17:59:59" + }, + { + "five": [ + "松籁响起之时", + "风鹰剑" + ], + "four": [ + "祭礼剑", + "雨裁", + "匣里灭辰", + "祭礼残章", + "弓藏" + ], + "from": "2021-05-18 18:00:00", + "name": "神铸赋形", + "to": "2021-06-08 14:59:59" + }, + { + "five": [ + "斫峰之刃", + "尘世之锁" + ], + "four": [ + "笛剑", + "千岩古剑", + "祭礼弓", + "昭心", + "千岩长枪" + ], + "from": "2021-04-28 06:00:00", + "name": "神铸赋形", + "to": "2021-05-18 17:59:59" + }, + { + "five": [ + "天空之翼", + "四风原典" + ], + "four": [ + "西风剑", + "祭礼大剑", + "暗巷猎手", + "西风秘典", + "西风长枪" + ], + "from": "2021-04-06 18:00:00", + "name": "神铸赋形", + "to": "2021-04-27 14:59:59" + }, + { + "five": [ + "终末嗟叹之诗", + "天空之刃" + ], + "four": [ + "暗巷闪光", + "西风大剑", + "西风猎弓", + "暗巷的酒与诗", + "匣里灭辰" + ], + "from": "2021-03-17 06:00:00", + "name": "神铸赋形", + "to": "2021-04-06 15:59:59" + }, + { + "five": [ + "护摩之杖", + "狼的末路" + ], + "four": [ + "匣里龙吟", + "千岩古剑", + "祭礼弓", + "流浪乐章", + "千岩长枪" + ], + "from": "2021-02-23 18:00:00", + "name": "神铸赋形", + "to": "2021-03-16 14:59:59" + }, + { + "five": [ + "磐岩结绿", + "和璞鸢" + ], + "four": [ + "笛剑", + "祭礼大剑", + "弓藏", + "昭心", + "西风长枪" + ], + "from": "2021-02-03 06:00:00", + "name": "神铸赋形", + "to": "2021-02-23 15:59:59" + }, + { + "five": [ + "阿莫斯之弓", + "天空之傲" + ], + "four": [ + "祭礼剑", + "钟剑", + "匣里灭辰", + "昭心", + "西风猎弓" + ], + "from": "2021-01-12 18:00:00", + "name": "神铸赋形", + "to": "2021-02-02 14:59:59" + }, + { + "five": [ + "斫峰之刃", + "天空之卷" + ], + "four": [ + "西风剑", + "西风大剑", + "西风长枪", + "祭礼残章", + "绝弦" + ], + "from": "2020-12-23 06:00:00", + "name": "神铸赋形", + "to": "2021-01-12 15:59:59" + }, + { + "five": [ + "贯虹之槊", + "无工之剑" + ], + "four": [ + "匣里龙吟", + "钟剑", + "西风秘典", + "西风猎弓", + "匣里灭辰" + ], + "from": "2020-12-01 18:00:00", + "name": "神铸赋形", + "to": "2020-12-22 14:59:59" + }, + { + "five": [ + "天空之翼", + "尘世之锁" + ], + "four": [ + "笛剑", + "雨裁", + "昭心", + "弓藏", + "西风长枪" + ], + "from": "2020-11-11 06:00:00", + "name": "神铸赋形", + "to": "2020-12-01 15:59:59" + }, + { + "five": [ + "四风原典", + "狼的末路" + ], + "four": [ + "祭礼剑", + "祭礼大剑", + "祭礼残章", + "祭礼弓", + "匣里灭辰" + ], + "from": "2020-10-20 18:00:00", + "name": "神铸赋形", + "to": "2020-11-10 14:59:59" + }, + { + "five": [ + "风鹰剑", + "阿莫斯之弓" + ], + "four": [ + "祭礼剑", + "祭礼大剑", + "祭礼残章", + "祭礼弓", + "匣里灭辰" + ], + "from": "2020-09-28 06:00:00", + "name": "神铸赋形", + "to": "2020-10-18 17:59:59" + } +] \ No newline at end of file diff --git a/modules/apihelper/gacha_log.py b/modules/apihelper/gacha_log.py new file mode 100644 index 00000000..3b3df4e7 --- /dev/null +++ b/modules/apihelper/gacha_log.py @@ -0,0 +1,531 @@ +import contextlib +import datetime +import json +import time +from pathlib import Path +from typing import List, Dict, Tuple, Optional, Union + +import aiofiles +from genshin import Client, InvalidAuthkey +from genshin.models import BannerType +from pydantic import BaseModel + +from core.base.assets import AssetsService +from metadata.pool.pool import get_pool_by_id +from metadata.shortname import roleToId, weaponToId +from utils.const import PROJECT_ROOT + +GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "gacha_log") +GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True) +GACHA_TYPE_LIST = { + BannerType.NOVICE: '新手祈愿', + BannerType.PERMANENT: '常驻祈愿', + BannerType.WEAPON: '武器祈愿', + BannerType.CHARACTER1: '角色祈愿', + BannerType.CHARACTER2: '角色祈愿' +} + + +class FiveStarItem(BaseModel): + name: str + icon: str + count: int + type: str + isUp: bool + isBig: bool + time: datetime.datetime + + +class FourStarItem(BaseModel): + name: str + icon: str + count: int + type: str + time: datetime.datetime + + +class GachaItem(BaseModel): + id: str + name: str + gacha_type: str + item_type: str + rank_type: str + time: datetime.datetime + + +class GachaLogInfo(BaseModel): + user_id: str + uid: str + update_time: datetime.datetime + item_list: Dict[str, List[GachaItem]] = { + '角色祈愿': [], + '武器祈愿': [], + '常驻祈愿': [], + '新手祈愿': [], + } + + +class Pool: + def __init__(self, five: List[str], four: List[str], name: str, to: str, **kwargs): + self.five = five + self.real_name = name + self.name = "、".join(self.five) + self.four = four + self.from_ = kwargs.get("from") + self.to = to + self.from_time = datetime.datetime.strptime(self.from_, "%Y-%m-%d %H:%M:%S") + self.to_time = datetime.datetime.strptime(self.to, "%Y-%m-%d %H:%M:%S") + self.start = self.from_time + self.start_init = False + self.end = self.to_time + self.dict = {} + self.count = 0 + + def parse(self, item: Union[FiveStarItem, FourStarItem]): + if self.from_time <= item.time <= self.to_time: + if self.dict.get(item.name): + self.dict[item.name]["count"] += 1 + else: + self.dict[item.name] = { + "name": item.name, + "icon": item.icon, + "count": 1, + "rank_type": 5 if isinstance(item, FiveStarItem) else 4, + } + + def count_item(self, item: List[GachaItem]): + for i in item: + if self.from_time <= i.time <= self.to_time: + self.count += 1 + if not self.start_init: + self.start = i.time + self.end = i.time + + def to_list(self): + return list(self.dict.values()) + + +class GachaLog: + @staticmethod + async def load_json(path): + async with aiofiles.open(path, 'r', encoding='utf-8') as f: + return json.loads(await f.read()) + + @staticmethod + async def save_json(path, data): + async with aiofiles.open(path, 'w', encoding='utf-8') as f: + if isinstance(data, dict): + return await f.write(json.dumps(data, ensure_ascii=False, indent=4)) + await f.write(data) + + @staticmethod + async def load_history_info(user_id: str, uid: str) -> Tuple[GachaLogInfo, bool]: + """读取历史抽卡记录数据 + :param user_id: 用户id + :param uid: 原神uid + :return: 抽卡记录数据 + """ + file_path = GACHA_LOG_PATH / f'{user_id}-{uid}.json' + if file_path.exists(): + return GachaLogInfo.parse_obj(await GachaLog.load_json(file_path)), True + else: + return GachaLogInfo(user_id=user_id, + uid=uid, + update_time=datetime.datetime.now()), False + + @staticmethod + async def save_gacha_log_info(user_id: str, uid: str, info: GachaLogInfo): + """保存抽卡记录数据 + :param user_id: 用户id + :param uid: 原神uid + :param info: 抽卡记录数据 + """ + save_path = GACHA_LOG_PATH / f'{user_id}-{uid}.json' + save_path_bak = GACHA_LOG_PATH / f'{user_id}-{uid}.json.bak' + # 将旧数据备份一次 + with contextlib.suppress(PermissionError): + if save_path.exists(): + if save_path_bak.exists(): + save_path_bak.unlink() + save_path.rename(save_path.parent / f'{save_path.name}.bak') + # 写入数据 + await GachaLog.save_json(save_path, info.json()) + + @staticmethod + async def gacha_log_to_uigf(user_id: str, uid: str) -> Tuple[bool, str, Optional[Path]]: + """抽卡日记转换为 UIGF 格式 + :param user_id: 用户ID + :param uid: 游戏UID + :return: 转换是否成功、转换信息、UIGF文件目录 + """ + data, state = await GachaLog.load_history_info(user_id, uid) + if not state: + return False, f'UID{uid} 还没有导入任何抽卡记录数据。', None + save_path = GACHA_LOG_PATH / f'{user_id}-{uid}-uigf.json' + uigf_dict = { + 'info': { + 'uid': uid, + 'lang': 'zh-cn', + 'export_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'export_timestamp': int(time.time()), + 'export_app': 'TGPaimonBot', + 'export_app_version': "v3", + 'uigf_version': 'v2.2' + }, + 'list': [] + } + for items in data.item_list.values(): + for item in items: + uigf_dict['list'].append({ + 'gacha_type': item.gacha_type, + 'item_id': '', + 'count': '1', + 'time': item.time.strftime('%Y-%m-%d %H:%M:%S'), + 'name': item.name, + 'item_type': item.item_type, + 'rank_type': item.rank_type, + 'id': item.id, + 'uigf_gacha_type': item.gacha_type + }) + await GachaLog.save_json(save_path, uigf_dict) + return True, '', save_path + + @staticmethod + async def import_gacha_log_data(user_id: int, data: dict): + new_num = 0 + try: + uid = data['info']['uid'] + gacha_log, _ = await GachaLog.load_history_info(str(user_id), uid) + for item in data['list']: + pool_name = GACHA_TYPE_LIST[BannerType(int(item['gacha_type']))] + item_info = GachaItem.parse_obj(item) + if item_info not in gacha_log.item_list[pool_name]: + gacha_log.item_list[pool_name].append(item_info) + new_num += 1 + for i in gacha_log.item_list.values(): + i.sort(key=lambda x: (x.time, x.id)) + gacha_log.update_time = datetime.datetime.now() + await GachaLog.save_gacha_log_info(str(user_id), uid, gacha_log) + return "导入完成,本次没有新增数据" if new_num == 0 else f"导入完成,本次共新增{new_num}条抽卡记录" + except Exception: + return "导入失败,数据格式错误" + + @staticmethod + async def get_gacha_log_data(user_id: int, client: Client, authkey: str) -> str: + """ + 使用authkey获取抽卡记录数据,并合并旧数据 + :param user_id: 用户id + :param client: genshin client + :param authkey: authkey + :return: 更新结果 + """ + new_num = 0 + gacha_log, _ = await GachaLog.load_history_info(str(user_id), str(client.uid)) + try: + for pool_id, pool_name in GACHA_TYPE_LIST.items(): + async for data in client.wish_history(pool_id, authkey=authkey): + item = GachaItem( + id=str(data.id), + name=data.name, + gacha_type=str(data.banner_type.value), + item_type=data.type, + rank_type=str(data.rarity), + time=datetime.datetime(data.time.year, + data.time.month, + data.time.day, + data.time.hour, + data.time.minute, + data.time.second) + ) + + if item not in gacha_log.item_list[pool_name]: + gacha_log.item_list[pool_name].append(item) + new_num += 1 + except InvalidAuthkey: + return "更新数据失败,authkey 无效" + for i in gacha_log.item_list.values(): + i.sort(key=lambda x: (x.time, x.id)) + gacha_log.update_time = datetime.datetime.now() + await GachaLog.save_gacha_log_info(str(user_id), str(client.uid), gacha_log) + return '更新完成,本次没有新增数据' if new_num == 0 else f'更新完成,本次共新增{new_num}条抽卡记录' + + @staticmethod + def check_avatar_up(name: str, gacha_time: datetime.datetime) -> bool: + if name in {'莫娜', '七七', '迪卢克', '琴'}: + return False + elif name == "刻晴": + start_time = datetime.datetime.strptime("2021-02-17 18:00:00", "%Y-%m-%d %H:%M:%S") + end_time = datetime.datetime.strptime("2021-03-02 15:59:59", "%Y-%m-%d %H:%M:%S") + if not (start_time < gacha_time < end_time): + return False + elif name == "提纳里": + start_time = datetime.datetime.strptime("2022-08-24 06:00:00", "%Y-%m-%d %H:%M:%S") + end_time = datetime.datetime.strptime("2022-09-09 17:59:59", "%Y-%m-%d %H:%M:%S") + if not (start_time < gacha_time < end_time): + return False + return True + + @staticmethod + async def get_all_5_star_items(data: List[GachaItem], assets: AssetsService, pool_name: str = "角色祈愿"): + """ + 获取所有5星角色 + :param data: 抽卡记录 + :param assets: 资源服务 + :param pool_name: 池子名称 + :return: 5星角色列表 + """ + count = 0 + result = [] + for item in data: + count += 1 + if item.rank_type == '5': + if item.item_type == "角色" and pool_name in {"角色祈愿", "常驻祈愿"}: + result.append( + FiveStarItem( + name=item.name, + icon=(await assets.avatar(roleToId(item.name)).icon()).as_uri(), + count=count, + type="角色", + isUp=GachaLog.check_avatar_up(item.name, item.time) if pool_name == "角色祈愿" else False, + isBig=(not result[-1].isUp) if result and pool_name == "角色祈愿" else False, + time=item.time, + ) + ) + elif item.item_type == "武器" and pool_name in {"武器祈愿", "常驻祈愿"}: + result.append( + FiveStarItem( + name=item.name, + icon=(await assets.weapon(weaponToId(item.name)).icon()).as_uri(), + count=count, + type="武器", + isUp=False, + isBig=False, + time=item.time, + ) + ) + count = 0 + result.reverse() + return result, count + + @staticmethod + async def get_all_4_star_items(data: List[GachaItem], assets: AssetsService): + """ + 获取 no_fout_star + :param data: 抽卡记录 + :param assets: 资源服务 + :return: no_fout_star + """ + count = 0 + result = [] + for item in data: + count += 1 + if item.rank_type == '4': + if item.item_type == "角色": + result.append( + FourStarItem( + name=item.name, + icon=(await assets.avatar(roleToId(item.name)).icon()).as_uri(), + count=count, + type="角色", + time=item.time, + ) + ) + elif item.item_type == "武器": + result.append( + FourStarItem( + name=item.name, + icon=(await assets.weapon(weaponToId(item.name)).icon()).as_uri(), + count=count, + type="武器", + time=item.time, + ) + ) + count = 0 + result.reverse() + return result, count + + @staticmethod + def get_301_pool_data(total: int, + all_five: List[FiveStarItem], + no_five_star: int, + no_four_star: int): + # 总共五星 + five_star = len(all_five) + five_star_up = len([i for i in all_five if i.isUp]) + five_star_big = len([i for i in all_five if i.isBig]) + # 五星平均 + five_star_avg = round(total / five_star, 2) if five_star != 0 else 0 + # 小保底不歪 + small_protect = round((five_star_up - five_star_big) / (five_star - five_star_big) * 100.0, 1) if \ + five_star - five_star_big != 0 else "0.0" + # 五星常驻 + five_star_const = five_star - five_star_up + # UP 平均 + up_avg = round(total / five_star_up, 2) if five_star_up != 0 else 0 + # UP 花费原石 + up_cost = sum(i.count * 160 for i in all_five if i.isUp) + up_cost = f"{round(up_cost / 10000, 2)}w" if up_cost >= 10000 else up_cost + return [ + [ + {"num": no_five_star, "unit": "抽", "lable": "未出五星"}, + {"num": five_star, "unit": "个", "lable": "五星"}, + {"num": five_star_avg, "unit": "抽", "lable": "五星平均"}, + {"num": small_protect, "unit": "%", "lable": "小保底不歪"}, + ], + [ + {"num": no_four_star, "unit": "抽", "lable": "未出四星"}, + {"num": five_star_const, "unit": "个", "lable": "五星常驻"}, + {"num": up_avg, "unit": "抽", "lable": "UP平均"}, + {"num": up_cost, "unit": "", "lable": "UP花费原石"}, + ] + ] + + @staticmethod + def get_200_pool_data(total: int, all_five: List[FiveStarItem], all_four: List[FourStarItem], + no_five_star: int, no_four_star: int): + # 总共五星 + five_star = len(all_five) + # 五星平均 + five_star_avg = round(total / five_star, 2) if five_star != 0 else 0 + # 五星武器 + five_star_weapon = len([i for i in all_five if i.type == "武器"]) + # 总共四星 + four_star = len(all_four) + # 四星平均 + four_star_avg = round(total / four_star, 2) if four_star != 0 else 0 + # 四星最多 + four_star_name_list = [i.name for i in all_four] + four_star_max = max(four_star_name_list, key=four_star_name_list.count) + four_star_max_count = four_star_name_list.count(four_star_max) + return [ + [ + {"num": no_five_star, "unit": "抽", "lable": "未出五星"}, + {"num": five_star, "unit": "个", "lable": "五星"}, + {"num": five_star_avg, "unit": "抽", "lable": "五星平均"}, + {"num": five_star_weapon, "unit": "个", "lable": "五星武器"}, + ], + [ + {"num": no_four_star, "unit": "抽", "lable": "未出四星"}, + {"num": four_star, "unit": "个", "lable": "四星"}, + {"num": four_star_avg, "unit": "抽", "lable": "四星平均"}, + {"num": four_star_max_count, "unit": four_star_max, "lable": "四星最多"}, + ] + ] + + @staticmethod + def get_302_pool_data(total: int, all_five: List[FiveStarItem], all_four: List[FourStarItem], + no_five_star: int, no_four_star: int): + # 总共五星 + five_star = len(all_five) + # 五星平均 + five_star_avg = round(total / five_star, 2) if five_star != 0 else 0 + # 四星武器 + four_star_weapon = len([i for i in all_four if i.type == "武器"]) + # 总共四星 + four_star = len(all_four) + # 四星平均 + four_star_avg = round(total / four_star, 2) if four_star != 0 else 0 + # 四星最多 + four_star_name_list = [i.name for i in all_four] + four_star_max = max(four_star_name_list, key=four_star_name_list.count) + four_star_max_count = four_star_name_list.count(four_star_max) + return [ + [ + {"num": no_five_star, "unit": "抽", "lable": "未出五星"}, + {"num": five_star, "unit": "个", "lable": "五星"}, + {"num": five_star_avg, "unit": "抽", "lable": "五星平均"}, + {"num": four_star_weapon, "unit": "个", "lable": "四星武器"}, + ], + [ + {"num": no_four_star, "unit": "抽", "lable": "未出四星"}, + {"num": four_star, "unit": "个", "lable": "四星"}, + {"num": four_star_avg, "unit": "抽", "lable": "四星平均"}, + {"num": four_star_max_count, "unit": four_star_max, "lable": "四星最多"}, + ] + ] + + @staticmethod + async def get_analysis(user_id: int, client: Client, pool: BannerType, assets: AssetsService): + """ + 获取抽卡记录分析数据 + :param user_id: 用户id + :param client: genshin client + :param pool: 池子类型 + :param assets: 资源服务 + :return: 分析数据 + """ + gacha_log, status = await GachaLog.load_history_info(str(user_id), str(client.uid)) + if not status: + return "获取数据失败,未找到抽卡记录" + pool_name = GACHA_TYPE_LIST[pool] + data = gacha_log.item_list[pool_name] + total = len(data) + if total == 0: + return "获取数据失败,未找到抽卡记录" + all_five, no_five_star = await GachaLog.get_all_5_star_items(data, assets, pool_name) + all_four, no_four_star = await GachaLog.get_all_4_star_items(data, assets) + summon_data = None + if pool == BannerType.CHARACTER1: + summon_data = GachaLog.get_301_pool_data(total, all_five, no_five_star, no_four_star) + elif pool == BannerType.WEAPON: + summon_data = GachaLog.get_302_pool_data(total, all_five, all_four, no_five_star, no_four_star) + elif pool == BannerType.PERMANENT: + summon_data = GachaLog.get_200_pool_data(total, all_five, all_four, no_five_star, no_four_star) + last_time = data[0].time.strftime("%Y-%m-%d %H:%M") + first_time = data[-1].time.strftime("%Y-%m-%d %H:%M") + return { + "uid": client.uid, + "allNum": total, + "type": pool.value, + "typeName": pool_name, + "line": summon_data, + "firstTime": first_time, + "lastTime": last_time, + "fiveLog": all_five, + "fourLog": all_four[:18], + } + + @staticmethod + async def get_pool_analysis(user_id: int, client: Client, pool: BannerType, assets: AssetsService, group: bool): + """ + 获取抽卡记录分析数据 + :param user_id: 用户id + :param client: genshin client + :param pool: 池子类型 + :param assets: 资源服务 + :param group: 是否群组 + :return: 分析数据 + """ + gacha_log, status = await GachaLog.load_history_info(str(user_id), str(client.uid)) + if not status: + return "获取数据失败,未找到抽卡记录" + pool_name = GACHA_TYPE_LIST[pool] + data = gacha_log.item_list[pool_name] + total = len(data) + if total == 0: + return "获取数据失败,未找到抽卡记录" + all_five, _ = await GachaLog.get_all_5_star_items(data, assets, pool_name) + all_four, _ = await GachaLog.get_all_4_star_items(data, assets) + pool_data = [] + up_pool_data = [Pool(**i) for i in get_pool_by_id(pool.value)] + for up_pool in up_pool_data: + for item in all_five: + up_pool.parse(item) + for item in all_four: + up_pool.parse(item) + up_pool.count_item(data) + for up_pool in up_pool_data: + pool_data.append({ + "count": up_pool.count, + "list": up_pool.to_list(), + "name": up_pool.name, + "start": up_pool.start.strftime("%Y-%m-%d"), + "end": up_pool.end.strftime("%Y-%m-%d"), + }) + pool_data = [i for i in pool_data if i["count"] > 0] + return { + "uid": client.uid, + "typeName": pool_name, + "pool": pool_data[:6] if group else pool_data, + "hasMore": len(pool_data) > 6, + } diff --git a/plugins/genshin/gacha/__init__.py b/plugins/genshin/gacha/__init__.py index 6ae1c8ec..e69de29b 100644 --- a/plugins/genshin/gacha/__init__.py +++ b/plugins/genshin/gacha/__init__.py @@ -1,5 +0,0 @@ -from .gacha import Gacha - - -class GachaPlugins(Gacha): - pass diff --git a/plugins/genshin/gacha/gacha_log.py b/plugins/genshin/gacha/gacha_log.py new file mode 100644 index 00000000..152ce907 --- /dev/null +++ b/plugins/genshin/gacha/gacha_log.py @@ -0,0 +1,229 @@ +import json +import os +from io import BytesIO + +from genshin.models import BannerType +from pyppeteer import launch +from telegram import Update, User +from telegram.constants import ChatAction +from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters, ConversationHandler + +from core.base.assets import AssetsService +from core.baseplugin import BasePlugin +from core.cookies.error import CookiesNotFoundError +from core.plugin import Plugin, handler, conversation +from core.template import TemplateService +from core.user.error import UserNotFoundError +from modules.apihelper.gacha_log import GachaLog as GachaLogService +from utils.bot import get_all_args +from utils.decorators.error import error_callable +from utils.decorators.restricts import restricts +from utils.helpers import get_genshin_client +from utils.log import logger + +INPUT_URL, INPUT_FILE = 10100, 10101 + + +class GachaLog(Plugin.Conversation, BasePlugin.Conversation): + """ 抽卡记录导入/导出/分析""" + + def __init__(self, template_service: TemplateService = None, assets: AssetsService = None): + self.template_service = template_service + self.browser: launch = None + self.current_dir = os.getcwd() + self.resources_dir = os.path.join(self.current_dir, "resources") + self.character_gacha_card = {} + self.user_time = {} + self.assets_service = assets + + @staticmethod + def from_url_get_authkey(url: str) -> str: + """从 UEL 解析 authkey + :param url: URL + :return: authkey + """ + try: + return url.split("authkey=")[1].split("&")[0] + except IndexError: + return url + + @staticmethod + async def _refresh_user_data(user: User, data: dict = None, authkey: str = None) -> str: + """刷新用户数据 + :param user: 用户 + :param data: 数据 + :param authkey: 认证密钥 + :return: 返回信息 + """ + try: + logger.debug("尝试获取已绑定的原神账号") + client = await get_genshin_client(user.id) + if authkey: + return await GachaLogService.get_gacha_log_data(user.id, client, authkey) + if data: + return await GachaLogService.import_gacha_log_data(user.id, data) + except (UserNotFoundError, CookiesNotFoundError): + logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息") + return "未查询到您所绑定的账号信息,请先私聊派蒙绑定账号" + + @conversation.entry_point + @handler(CommandHandler, command="gacha_log_refresh", filters=filters.ChatType.PRIVATE, block=True) + @handler(MessageHandler, filters=filters.Regex("^更新抽卡记录(.*)") & filters.ChatType.PRIVATE, block=True) + @restricts() + @error_callable + async def command_start(self, update: Update, context: CallbackContext) -> int: + message = update.effective_message + user = update.effective_user + args = get_all_args(context) + if not args: + await message.reply_text("请发送从游戏中获取到的抽卡记录链接\n\n" + "获取抽卡记录链接教程:https://paimon.moe/wish/import") + return INPUT_URL + authkey = self.from_url_get_authkey(args[0]) + data = await self._refresh_user_data(user, authkey=authkey) + await message.reply_text(data) + + @conversation.state(state=INPUT_URL) + @handler.message(filters=filters.TEXT & ~filters.COMMAND, + block=True) + @restricts() + @error_callable + async def import_data_from_url(self, update: Update, _: CallbackContext) -> int: + message = update.effective_message + user = update.effective_user + authkey = self.from_url_get_authkey(message.text) + reply = await message.reply_text("正在从米哈游服务器获取数据,请稍后") + text = await self._refresh_user_data(user, authkey=authkey) + await reply.edit_text(text) + return ConversationHandler.END + + @handler(CommandHandler, command="gacha_log_import", filters=filters.ChatType.PRIVATE, block=True) + @handler(MessageHandler, filters=filters.Regex("^导入抽卡记录(.*)") & filters.ChatType.PRIVATE, block=True) + @restricts() + @error_callable + async def command_start_import(self, update: Update, _: CallbackContext) -> None: + message = update.effective_message + user = update.effective_user + if message.reply_to_message: + document = message.reply_to_message.document + else: + document = message.document + if not document: + await message.reply_text("请回复符合 UIGF 标准的抽卡记录文件") + return + if not document.file_name.endswith(".json"): + await message.reply_text("文件格式错误,请发送符合 UIGF 标准的抽卡记录文件") + return + if document.file_size > 50 * 1024 * 1024: + await message.reply_text("文件过大,请发送小于 50MB 的文件") + return + try: + data = BytesIO() + await (await document.get_file()).download(out=data) + # bytesio to json + data = data.getvalue().decode("utf-8") + data = json.loads(data) + except Exception as exc: + logger.error(f"文件解析失败:{repr(exc)}") + await message.reply_text("文件解析失败,请检查文件是否符合 UIGF 标准") + return + reply = await message.reply_text("文件解析成功,正在导入数据") + try: + text = await self._refresh_user_data(user, data=data) + except Exception as exc: + logger.error(f"文件解析失败:{repr(exc)}") + await reply.edit_text("文件解析失败,请检查文件是否符合 UIGF 标准") + return + await reply.edit_text(text) + return + + @handler(CommandHandler, command="gacha_log_export", filters=filters.ChatType.PRIVATE, block=True) + @handler(MessageHandler, filters=filters.Regex("^导出抽卡记录(.*)") & filters.ChatType.PRIVATE, block=True) + @restricts() + @error_callable + async def command_start_export(self, update: Update, _: CallbackContext) -> None: + message = update.effective_message + user = update.effective_user + try: + client = await get_genshin_client(user.id) + state, text, path = await GachaLogService.gacha_log_to_uigf(str(user.id), str(client.uid)) + if state: + await message.reply_document(document=open(path, "rb+"), caption="抽卡记录导出文件") + else: + await message.reply_text(text) + except (UserNotFoundError, CookiesNotFoundError): + logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息") + await message.reply_text("未查询到您所绑定的账号信息,请先私聊派蒙绑定账号") + return + + @handler(CommandHandler, command="gacha_log", block=True) + @handler(MessageHandler, filters=filters.Regex("^抽卡记录(.*)"), block=True) + @restricts() + @error_callable + async def command_start_analysis(self, update: Update, context: CallbackContext) -> None: + message = update.effective_message + user = update.effective_user + pool_type = BannerType.CHARACTER1 + if args := get_all_args(context): + if "武器" in args: + pool_type = BannerType.WEAPON + elif "常驻" in args: + pool_type = BannerType.STANDARD + logger.info(f"用户 {user.full_name}[{user.id}] 抽卡记录命令请求 || 参数 {pool_type.name}") + try: + client = await get_genshin_client(user.id) + data = await GachaLogService.get_analysis(user.id, client, pool_type, self.assets_service) + if isinstance(data, str): + reply_message = await message.reply_text(data) + else: + await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) + png_data = await self.template_service.render('genshin/gacha_log', "gacha_log.html", data, + full_page=True, query_selector=".body_box") + reply_message = await message.reply_photo(png_data) + if filters.ChatType.GROUPS.filter(message): + self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300) + self._add_delete_message_job(context, message.chat_id, message.message_id, 300) + except (UserNotFoundError, CookiesNotFoundError): + logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息") + await message.reply_text("未查询到您所绑定的账号信息,请先私聊派蒙绑定账号") + return + + @handler(CommandHandler, command="gacha_count", block=True) + @handler(MessageHandler, filters=filters.Regex("^抽卡统计(.*)"), block=True) + @restricts() + @error_callable + async def command_start_count(self, update: Update, context: CallbackContext) -> None: + message = update.effective_message + user = update.effective_user + pool_type = BannerType.CHARACTER1 + if args := get_all_args(context): + if "武器" in args: + pool_type = BannerType.WEAPON + elif "常驻" in args: + pool_type = BannerType.STANDARD + logger.info(f"用户 {user.full_name}[{user.id}] 抽卡统计命令请求 || 参数 {pool_type.name}") + try: + client = await get_genshin_client(user.id) + group = filters.ChatType.GROUPS.filter(message) + data = await GachaLogService.get_pool_analysis(user.id, client, pool_type, self.assets_service, group) + if isinstance(data, str): + reply_message = await message.reply_text(data) + else: + await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) + document = False + if data["hasMore"] and not group: + document = True + data["hasMore"] = False + png_data = await self.template_service.render('genshin/gacha_count', "gacha_count.html", data, + full_page=True, query_selector=".body_box") + if document: + reply_message = await message.reply_document(png_data, filename="抽卡统计.png") + else: + reply_message = await message.reply_photo(png_data) + if filters.ChatType.GROUPS.filter(message): + self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300) + self._add_delete_message_job(context, message.chat_id, message.message_id, 300) + except (UserNotFoundError, CookiesNotFoundError): + logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息") + await message.reply_text("未查询到您所绑定的账号信息,请先私聊派蒙绑定账号") + return diff --git a/resources/genshin/gacha_count/example.html b/resources/genshin/gacha_count/example.html new file mode 100644 index 00000000..983e6798 --- /dev/null +++ b/resources/genshin/gacha_count/example.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + +
+
+
ID: 10001
+

抽卡统计-角色祈愿

+ +
+
+
+
+

「枫原万叶、可莉」

+ 98抽 +
+ 2022-08-02 - 2022-08-02 +
+
+
+
+ 20 + +
+
+
+
*完整数据请私聊查看
+ +
+ + diff --git a/resources/genshin/gacha_count/gacha_count.css b/resources/genshin/gacha_count/gacha_count.css new file mode 100644 index 00000000..191d032d --- /dev/null +++ b/resources/genshin/gacha_count/gacha_count.css @@ -0,0 +1,214 @@ +@font-face { + font-family: "tttgbnumber"; + src: url("./../../fonts/tttgbnumber.ttf"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "HYWenHei-55W"; + src: url("./../../fonts/汉仪文黑-85W.ttf"); + font-weight: normal; + font-style: normal; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; +} + +body { + font-size: 16px; + width: 530px; + color: #1e1f20; + transform: scale(1.5); + transform-origin: 0 0; +} + +.container { + width: 530px; + padding: 20px 15px 10px 15px; + background-color: #f5f6fb; +} + +.head_box { + border-radius: 15px; + font-family: tttgbnumber, serif; + padding: 10px 20px; + position: relative; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + +} + +.head_box .id_text { + font-size: 24px; +} + +.head_box .day_text { + font-size: 20px; +} + +.head_box .genshin_logo { + position: absolute; + top: 1px; + right: 15px; + width: 97px; +} + +.base_info { + position: relative; + padding-left: 10px; +} + +.uid { + font-family: tttgbnumber, serif; +} + +.pool_box { + font-family: HYWenHei-55W, serif; + border-radius: 12px; + margin-top: 20px; + margin-bottom: 20px; + padding: 10px 5px 5px 5px; + background: #fff; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + position: relative; +} + +.title_box { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.title { + white-space: nowrap; + max-width: 210px; + overflow: hidden; +} + +.name_box { + display: flex; + align-items: center; + flex: 1; +} + +.title_box .date { + margin-right: 10px; +} + +.list_box { + display: flex; + flex-wrap: wrap; +} + +.item { + margin: 0 0 10px 10px; + border-radius: 7px; + overflow: hidden; + box-shadow: 0 2px 6px 0 rgb(132 93 90 / 30%); + height: 70px; + width: 70px; + background: #e9e5dc; + position: relative; +} + +.item .role_img { + width: 100%; + overflow: hidden; + background-size: 100%; + background-repeat: no-repeat; + position: absolute; + top: 0; + /* filter: contrast(95%); */ +} + +.item .num { + position: absolute; + top: 0; + right: 0; + z-index: 9; + font-size: 18px; + text-align: center; + color: #fff; + border-radius: 3px; + padding: 1px 5px; + background: rgb(0 0 0 / 50%); + font-family: "tttgbnumber", serif; +} + +.label_301 { + background-color: rgb(235 106 75); +} + +.label_302 { + background-color: #e69449; +} + +.label_200 { + background-color: #757cc8; +} + +.label { + color: #fff; + border-radius: 10px; + font-size: 16px; + padding: 2px 7px; + vertical-align: 2px; +} + +.bg5 { + background-image: url(./../abyss/background/roleStarBg5.png); + width: 100%; + height: 70px; + /* filter: brightness(1.1); */ + background-size: 100%; + background-repeat: no-repeat; +} + +.bg4 { + width: 100%; + height: 70px; + background-image: url(./../abyss/background/roleStarBg4.png); + background-size: 100%; + background-repeat: no-repeat; +} + +.list_box .item .life1 { + background-color: #62a8ea; +} + +.list_box .item .life2 { + background-color: #62a8ea; +} + +.list_box .item .life3 { + background-color: #45b97c; +} + +.list_box .item .life4 { + background-color: #45b97c; +} + +.list_box .item .life5 { + background-color: #ff5722; +} + +.list_box .item .life6 { + background-color: #ff5722; +} + +.logo { + font-size: 14px; + font-family: "tttgbnumber", serif; + text-align: center; + color: #7994a7; +} + +.hasMore { + font-size: 12px; + margin: -6px 0 10px 6px; + color: #7f858a; +} diff --git a/resources/genshin/gacha_count/gacha_count.html b/resources/genshin/gacha_count/gacha_count.html new file mode 100644 index 00000000..0a834ebb --- /dev/null +++ b/resources/genshin/gacha_count/gacha_count.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + +
+
+
ID: {{uid}}
+

抽卡统计-{{typeName}}

+ +
+ {% for val in pool %} +
+
+
+

「{{val.name}}」

+ {{val.count}}抽 +
+ {% if typeName != "常驻祈愿" %} + {{val.start}} - {{val.end}} + {% endif %} +
+
+ {% for v in val.list %} +
+
+ {{v.count}} + +
+ {% endfor %} +
+
+ {% endfor %} + {% if hasMore %} +
*完整数据请私聊查看
+ {% endif %} + +
+ + diff --git a/resources/genshin/gacha_log/example.html b/resources/genshin/gacha_log/example.html new file mode 100644 index 00000000..ac1af953 --- /dev/null +++ b/resources/genshin/gacha_log/example.html @@ -0,0 +1,77 @@ + + + + + + + + + + + + + +
+
+ +
+
+ ID: 10001 +
+

+ 81抽 + 角色祈愿池 +

+ +
+ +
+
数据总览
+
+
+
1
+
未出五星
+
+
+ +
+ + 五星历史 2022-10-07 01:10 ~ 2022-10-07 23:10 + +
+ + +
+
+ UP + + +
80
+
+
+ +
+ + 四星最近历史 + +
+ +
+
+ + +
10
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/resources/genshin/gacha_log/gacha_log.css b/resources/genshin/gacha_log/gacha_log.css new file mode 100644 index 00000000..3fc58878 --- /dev/null +++ b/resources/genshin/gacha_log/gacha_log.css @@ -0,0 +1,341 @@ +@font-face { + font-family: "tttgbnumber"; + src: url("./../../fonts/tttgbnumber.ttf"); + font-weight: normal; + font-style: normal; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; +} + +body { + font-size: 18px; + color: #1e1f20; + font-family: PingFangSC-Medium, PingFang SC, sans-serif; + transform: scale(1.5); + transform-origin: 0 0; + width: 510px; +} + +.container { + width: 510px; + padding: 20px 15px 10px 15px; + background-color: #f5f6fb; +} + +.head_box { + border-radius: 15px; + font-family: tttgbnumber, sans-serif; + padding: 10px 20px; + position: relative; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); +} + +.head_box .id_text { + font-size: 24px; +} + +.head_box .day_text { + font-size: 20px; +} + +.head_box .genshin_logo { + position: absolute; + top: 1px; + right: 15px; + width: 97px; +} + +.logo { + font-size: 12px; + font-family: "tttgbnumber", serif; + text-align: center; + color: #7994a7; + position: relative; + padding-left: 10px; +} + +.data_box { + border-radius: 15px; + margin-top: 20px; + margin-bottom: 10px; + padding: 20px 0 5px 10px; + background: #fff; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + position: relative; +} + +.tab_lable { + position: absolute; + top: -10px; + left: -8px; + background: #d4b98c; + color: #fff; + font-size: 14px; + padding: 3px 10px; + border-radius: 15px 0 15px 15px; + z-index: 20; +} + +.data_line { + display: flex; + justify-content: space-around; + margin-bottom: 14px; + padding-right: 10px; +} + +.data_line_item { + width: 100px; + text-align: center; + + /* margin: 0 20px; */ +} + +.num { + font-family: tttgbnumber, serif; + font-size: 24px; +} + +.num .unit { + font-size: 12px; +} + +.data_box .lable { + font-size: 14px; + color: #7f858a; + line-height: 1; + margin-top: 3px; +} + +.info_box_border { + border-radius: 15px; + + /* margin-top: 20px; */ + margin-bottom: 20px; + padding: 6px 0 5px 10px; + background: #fff; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + position: relative; +} + +.card_list { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} + +.card_list .item { + margin: 0 8px 10px 0; + border-radius: 7px; + box-shadow: 0 2px 6px 0 rgb(132 93 90 / 30%); + height: 90px; + position: relative; + overflow: hidden; + background: #e7e5d9; +} + +.card_list .item img { + width: 70px; + height: 70px; + border-radius: 7px 7px 20px 0; +} + +.card_list .item.star5 img { + background-image: url(./../abyss/background/roleStarBg5.png); + width: 100%; + height: 70px; + /* filter: brightness(1.1); */ + background-size: 100%; + background-repeat: no-repeat; +} + +.card_list .item.star4 img { + width: 100%; + height: 70px; + background-image: url(./../abyss/background/roleStarBg4.png); + background-size: 100%; + background-repeat: no-repeat; +} + +.card_list .item .num { + position: absolute; + top: 0; + right: 0; + z-index: 9; + font-size: 18px; + text-align: center; + color: #fff; + border-radius: 3px; + padding: 1px 5px; + background: rgb(0 0 0 / 50%); + font-family: "tttgbnumber", serif; +} + +.card_list .item .name, +.card_list .item .num_name { + position: absolute; + top: 71px; + left: 0; + z-index: 9; + font-size: 12px; + text-align: center; + width: 100%; + height: 16px; + line-height: 18px; +} + +.card_list .item .num_name { + font-family: "tttgbnumber", serif; + font-size: 16px; +} + +.base_info { + position: relative; + padding-left: 10px; + margin: 5px 10px; +} + +.uid::before { + content: " "; + position: absolute; + width: 5px; + height: 24px; + border-radius: 1px; + left: 0; + top: 0; + background: #d3bc8d; +} + +.label_301 { + background-color: rgb(235 106 75); +} + +.label_302 { + background-color: #e69449; +} + +.label_200 { + background-color: #757cc8; +} + +.label { + color: #fff; + border-radius: 10px; + font-size: 12px; + padding: 2px 7px; + vertical-align: 2px; +} + +.ritem { + display: flex; + font-size: 12px; + margin-bottom: 5px; +} + +.info_role { + display: flex; + flex-wrap: wrap; + padding: 0 0 5px 9px; +} + +.ritem .role { + width: 20px; + height: 20px; + background-color: #ffb285; + border-radius: 100%; +} + +.ritem .weapon_box { + overflow: hidden; + width: 20px; + height: 20px; + border-radius: 100%; +} + +.ritem .weapon { + width: 20px; + height: 20px; + background-color: #ffb285; + border-radius: 100%; + transform: scale(1.5); + -webkit-transform: scale(1.5); + +} + +.ritem .role_text { + margin: 2px 3px 0 2px; + display: flex; + align-items: baseline; +} + +.ritem .role_name { + width: 24px; + white-space: nowrap; + overflow: hidden; +} + +.ritem .role_num { + width: 24px; +} + +.line_box { + height: 32px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: #7d7d7d; + padding-bottom: 5px; +} + +.line_box .line { + height: 2px; + flex-grow: 1; + background-color: #ebebeb; + margin: 0 10px; +} + +.red { + color: #f21000; +} + +.orange { + color: #ff8d00; +} + +.green { + color: #12d88c; +} + +.blue { + color: #4169e1; +} + +.purple { + color: #7500ff; +} + +.minimum { + position: absolute; + top: 0; + right: 0; + z-index: 9; + font-size: 12px; + text-align: center; + color: #fff; + border-radius: 3px; + padding: 1px 3px; + background-color: rgb(0 0 0 / 80%); + font-family: "tttgbnumber", serif; +} + +.hasMore { + font-size: 12px; + margin: 6px 0; + color: #7f858a; +} diff --git a/resources/genshin/gacha_log/gacha_log.html b/resources/genshin/gacha_log/gacha_log.html new file mode 100644 index 00000000..0f2b45b2 --- /dev/null +++ b/resources/genshin/gacha_log/gacha_log.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + +
+
+ +
+
+ ID: {{ uid }} +
+

+ {{ allNum }}抽 + {{ typeName }}池 +

+ +
+ +
+
数据总览
+ {% for val in line %} +
+ {% for item in val %} +
+
{{item.num}}{{item.unit}}
+
{{item.lable}}
+
+ {% endfor %} +
+ {% endfor %} + +
+ + 五星历史 {{firstTime}} ~ {{lastTime}} + +
+ + +
+ {% for val in fiveLog %} +
+ {% if val.isUp %} + UP + {% endif %} + + +
{{ val.count }}
+
+ {% endfor %} +
+ +
+ + 四星最近历史 + +
+ +
+ {% for val in fourLog %} +
+ + +
{{ val.count }}
+
+ {% endfor %} +
+
+ +
+
+ + \ No newline at end of file diff --git a/resources/genshin/gacha_log/img/提纳里.png b/resources/genshin/gacha_log/img/提纳里.png new file mode 100644 index 00000000..fb923acb Binary files /dev/null and b/resources/genshin/gacha_log/img/提纳里.png differ