支持抽卡记录导入/导出

This commit is contained in:
omg-xtao 2022-10-08 16:50:02 +08:00 committed by GitHub
parent 9b10e382b0
commit 7f2bb53c7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 2647 additions and 5 deletions

12
metadata/pool/pool.py Normal file
View File

@ -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

11
metadata/pool/pool_200.py Normal file
View File

@ -0,0 +1,11 @@
POOL_200 = [
{
"five": [
"常驻池"
],
"four": [],
"from": "2020-09-15 06:00:00",
"name": "常驻池",
"to": "2050-09-15 17:59:59"
}
]

480
metadata/pool/pool_301.py Normal file
View File

@ -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"
}
]

562
metadata/pool/pool_302.py Normal file
View File

@ -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"
}
]

View File

@ -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,
}

View File

@ -1,5 +0,0 @@
from .gacha import Gacha
class GachaPlugins(Gacha):
pass

View File

@ -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

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_count.css"/>
<link rel="preload" href="./../../fonts/汉仪文黑-85W.ttf" as="font">
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg4.png" as="image">
<style>
.head_box {
background-position-x: 42px;
background: #fff url(../gacha_log/img/提纳里.png) no-repeat;
background-size: auto 101%;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="head_box">
<div class="id_text">ID: 10001</div>
<h2 class="day_text">抽卡统计-角色祈愿</h2>
<img class="genshin_logo" src="./../../bot/help/background/genshin.png"/>
</div>
<div class="pool_box">
<div class="title_box">
<div class="name_box">
<div class="title"><h2>「枫原万叶、可莉」</h2></div>
<span class="label label_301">98抽</span>
</div>
<span class="date">2022-08-02 - 2022-08-02</span>
</div>
<div class="list_box">
<div class="item">
<div class="bg5"></div>
<span class="num life5">20</span>
<img class="role_img" src=""/>
</div>
</div>
</div>
<div class="hasMore">*完整数据请私聊查看</div>
<div class="logo">Template By Yunzai-Bot</div>
</div>
</body>
</html>

View File

@ -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;
}

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_count.css"/>
<link rel="preload" href="./../../fonts/汉仪文黑-85W.ttf" as="font">
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg4.png" as="image">
<style>
.head_box {
background-position-x: 42px;
background: #fff url(../gacha_log/img/提纳里.png) no-repeat;
background-size: auto 101%;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="head_box">
<div class="id_text">ID: {{uid}}</div>
<h2 class="day_text">抽卡统计-{{typeName}}</h2>
<img class="genshin_logo" src="./../../bot/help/background/genshin.png" alt=""/>
</div>
{% for val in pool %}
<div class="pool_box">
<div class="title_box">
<div class="name_box">
<div class="title"><h2>「{{val.name}}」</h2></div>
<span class="label label_301">{{val.count}}抽</span>
</div>
{% if typeName != "常驻祈愿" %}
<span class="date">{{val.start}} - {{val.end}}</span>
{% endif %}
</div>
<div class="list_box">
{% for v in val.list %}
<div class="item">
<div class="bg{{v.rank_type}}"></div>
<span class="num {% if v.count>=5 and v.rank_type == 5 %}life5{% endif %}">{{v.count}}</span>
<img class="role_img" src="{{ v.icon }}" alt=""/>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% if hasMore %}
<div class="hasMore">*完整数据请私聊查看</div>
{% endif %}
<div class="logo">Template By Yunzai-Bot</div>
</div>
</body>
</html>

View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_log.css"/>
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./img/提纳里.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<style>
.head_box {
background-position-x: 42px;
background: #fff url(./img/提纳里.png) no-repeat;
background-size: auto 101%;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="info_box">
<div class="head_box">
<div class="id_text">
ID: 10001
</div>
<h2 class="day_text">
81抽
<span class="label label_301">角色祈愿池</span>
</h2>
<img class="genshin_logo" src="./../../bot/help/background/genshin.png" alt=""/>
</div>
<div class="data_box">
<div class="tab_lable">数据总览</div>
<div class="data_line">
<div class="data_line_item">
<div class="num">1<span class="unit"></span></div>
<div class="lable">未出五星</div>
</div>
</div>
<div class="line_box">
<span class="line"></span>
<span class="text">五星历史 2022-10-07 01:10 ~ 2022-10-07 23:10</span>
<span class="line"></span>
</div>
<div class="card_list">
<div class="item star5">
<span class="minimum">UP</span>
<img class="role" src="" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">80</div>
</div>
</div>
<div class="line_box">
<span class="line"></span>
<span class="text">四星最近历史</span>
<span class="line"></span>
</div>
<div class="card_list">
<div class="item star4">
<img class="role" src="" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">10</div>
</div>
</div>
</div>
<div class="logo"> Template By Yunzai-Bot</div>
</div>
</div>
</body>
</html>

View File

@ -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;
}

View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_log.css"/>
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./img/提纳里.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg4.png" as="image">
<style>
.head_box {
background-position-x: 42px;
background: #fff url(./img/提纳里.png) no-repeat;
background-size: auto 101%;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="info_box">
<div class="head_box">
<div class="id_text">
ID: {{ uid }}
</div>
<h2 class="day_text">
{{ allNum }}抽
<span class="label label_{{type}}">{{ typeName }}池</span>
</h2>
<img class="genshin_logo" src="./../../bot/help/background/genshin.png" alt=""/>
</div>
<div class="data_box">
<div class="tab_lable">数据总览</div>
{% for val in line %}
<div class="data_line">
{% for item in val %}
<div class="data_line_item">
<div class="num">{{item.num}}<span class="unit">{{item.unit}}</span></div>
<div class="lable">{{item.lable}}</div>
</div>
{% endfor %}
</div>
{% endfor %}
<div class="line_box">
<span class="line"></span>
<span class="text">五星历史 {{firstTime}} ~ {{lastTime}}</span>
<span class="line"></span>
</div>
<div class="card_list">
{% for val in fiveLog %}
<div class="item star5">
{% if val.isUp %}
<span class="minimum">UP</span>
{% endif %}
<img class="role" src="{{ val.icon }}" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">{{ val.count }}</div>
</div>
{% endfor %}
</div>
<div class="line_box">
<span class="line"></span>
<span class="text">四星最近历史</span>
<span class="line"></span>
</div>
<div class="card_list">
{% for val in fourLog %}
<div class="item star4">
<img class="role" src="{{ val.icon }}" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">{{ val.count }}</div>
</div>
{% endfor %}
</div>
</div>
<div class="logo"> Template By Yunzai-Bot</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB