From 54146a97af4a40e25864a29bbd67aba6de84f1cc Mon Sep 17 00:00:00 2001 From: SiHuaN Date: Thu, 20 Oct 2022 15:48:40 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20gacha=5Flog=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E4=BB=8E=20paimon.moe=20=E5=92=8C=E9=9D=9E?= =?UTF-8?q?=E5=B0=8F=E9=85=8B=E5=AF=BC=E5=87=BA=E7=9A=84=E6=8A=BD=E5=8D=A1?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/apihelper/gacha_log.py | 6 +- plugins/genshin/gacha/gacha_log.py | 170 +++++++++++++++++++++++++++-- poetry.lock | 49 +++++++-- pyproject.toml | 1 + resources/json/zh.json | 1 + 5 files changed, 205 insertions(+), 22 deletions(-) create mode 100644 resources/json/zh.json diff --git a/modules/apihelper/gacha_log.py b/modules/apihelper/gacha_log.py index 887c8aba..a47f2958 100644 --- a/modules/apihelper/gacha_log.py +++ b/modules/apihelper/gacha_log.py @@ -263,11 +263,13 @@ class GachaLog: return False, "导入失败,数据格式错误" @staticmethod - async def import_gacha_log_data(user_id: int, client: Client, data: dict): + async def import_gacha_log_data( + user_id: int, client: Client, data: dict, verify_uid: bool = True + ) -> Tuple[bool, str]: new_num = 0 try: uid = data["info"]["uid"] - if int(uid) != client.uid: + if verify_uid and int(uid) != client.uid: raise GachaLogAccountNotFound # 检查导入数据是否合法 all_items = [GachaItem(**i) for i in data["list"]] diff --git a/plugins/genshin/gacha/gacha_log.py b/plugins/genshin/gacha/gacha_log.py index 9a409e6a..8305e1f0 100644 --- a/plugins/genshin/gacha/gacha_log.py +++ b/plugins/genshin/gacha/gacha_log.py @@ -1,8 +1,13 @@ import json +from datetime import datetime +from enum import Enum from io import BytesIO +from os import sep import genshin from genshin.models import BannerType +from modules.apihelper.gacha_log import GachaLog as GachaLogService +from openpyxl import load_workbook from telegram import Update, User, Message, Document, InlineKeyboardButton, InlineKeyboardMarkup from telegram.constants import ChatAction from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters, ConversationHandler @@ -15,7 +20,6 @@ from core.plugin import Plugin, handler, conversation from core.template import TemplateService from core.user import UserService from core.user.error import UserNotFoundError -from modules.apihelper.gacha_log import GachaLog as GachaLogService from modules.apihelper.hyperion import SignIn from utils.bot import get_all_args from utils.decorators.admins import bot_admins_rights_check @@ -55,7 +59,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation): return url @staticmethod - async def _refresh_user_data(user: User, data: dict = None, authkey: str = None) -> str: + async def _refresh_user_data(user: User, data: dict = None, authkey: str = None, verify_uid: bool = True) -> str: """刷新用户数据 :param user: 用户 :param data: 数据 @@ -68,24 +72,172 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation): if authkey: return await GachaLogService.get_gacha_log_data(user.id, client, authkey) if data: - return await GachaLogService.import_gacha_log_data(user.id, client, data) + return await GachaLogService.import_gacha_log_data(user.id, client, data, verify_uid) except UserNotFoundError: logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息") return "派蒙没有找到您所绑定的账号信息,请先私聊派蒙绑定账号" + @staticmethod + def convert_paimonmoe_to_uigf(data: BytesIO) -> dict: + """转换 paimone.moe 或 非小酋 导出 xlsx 数据为 UIGF 格式 + :param data: paimon.moe 导出的 xlsx 数据 + :return: UIGF 格式数据 + """ + PAIMONMOE_VERSION = 3 + PM2UIGF_VERSION = 1 + PM2UIGF_NAME = "paimon_moe_to_uigf" + UIGF_VERSION = "v2.2" + + with open(f"resources{sep}json{sep}zh.json", "r") as load_f: + zh_dict = json.load(load_f) + + class XlsxType(Enum): + PAIMONMOE = 1 + FXQ = 2 + + class ItemType(Enum): + CHARACTER = "角色" + WEAPON = "武器" + + class UIGFGachaType(Enum): + BEGINNER = 100 + STANDARD = 200 + CHARACTER = 301 + WEAPON = 302 + + class Qiyr: + def __init__( + self, uigf_gacha_type: UIGFGachaType, item_type: ItemType, name: str, time: datetime, p: int, _id: int + ) -> None: + self.uigf_gacha_type = uigf_gacha_type + self.item_type = item_type + self.name = name + self.time = time + self.rank_type = p + self.id = _id + + def qy2_json(self): + return { + "gacha_type": self.uigf_gacha_type.value, # 注意! + "item_id": "", + "count": -1, + "time": self.time.strftime("%Y-%m-%d %H:%M:%S"), + "name": self.name, + "item_type": self.item_type.value, + "rank_type": self.rank_type, + "id": self.id, + "uigf_gacha_type": self.uigf_gacha_type.value, + } + + def from_paimon_moe(uigf_gacha_type: UIGFGachaType, item_type: str, name: str, time: str, p: int) -> Qiyr: + item_type = ItemType.CHARACTER if type == "Character" else ItemType.WEAPON + name = zh_dict[name] + + time = datetime.strptime(time, "%Y-%m-%d %H:%M:%S") + return Qiyr(uigf_gacha_type, item_type, name, time, p, 0) + + def from_fxq(uigf_gacha_type: UIGFGachaType, item_type: str, name: str, time: str, p: int, _id: int) -> Qiyr: + item_type = ItemType.CHARACTER if type == "角色" else ItemType.WEAPON + time = datetime.strptime(time, "%Y-%m-%d %H:%M:%S") + return Qiyr(uigf_gacha_type, item_type, name, time, p, _id) + + class uigf: + qiyes: list[Qiyr] + uid: int + export_time: datetime + export_app: str = PM2UIGF_NAME + export_app_version: str = PM2UIGF_VERSION + uigf_version = UIGF_VERSION + lang = "zh-cn" + + def __init__(self, qiyes: list[Qiyr], uid: int, export_time: datetime) -> None: + self.uid = uid + self.qiyes = qiyes + self.qiyes.sort(key=lambda x: x.time) + if self.qiyes[0].id == 0: # 如果是从paimon.moe导入的,那么就给id赋值 + for index, _ in enumerate(self.qiyes): + self.qiyes[index].id = index + 1 + self.export_time = export_time + self.export_time = export_time + + def export_json(self) -> dict: + json_d = { + "info": { + "uid": self.uid, + "lang": self.lang, + "export_time": self.export_time.strftime("%Y-%m-%d %H:%M:%S"), + "export_timestamp": self.export_time.timestamp(), + "export_app": self.export_app, + "export_app_version": self.export_app_version, + "uigf_version": self.uigf_version, + }, + "list": [], + } + for qiye in self.qiyes: + json_d["list"].append(qiye.qy2_json()) + return json_d + + wb = load_workbook(data) + + xlsx_type = XlsxType.PAIMONMOE if len(wb.worksheets) == 6 else XlsxType.FXQ # 判断是paimon.moe还是非小酋导出的 + + paimonmoe_sheets = { + UIGFGachaType.BEGINNER: "Beginners' Wish", + UIGFGachaType.STANDARD: "Standard", + UIGFGachaType.CHARACTER: "Character Event", + UIGFGachaType.WEAPON: "Weapon Event", + } + fxq_sheets = { + UIGFGachaType.BEGINNER: "新手祈愿", + UIGFGachaType.STANDARD: "常驻祈愿", + UIGFGachaType.CHARACTER: "角色活动祈愿", + UIGFGachaType.WEAPON: "武器活动祈愿", + } + qiyes = [] + if xlsx_type == XlsxType.PAIMONMOE: + ws = wb["Information"] + if ws["B2"].value != PAIMONMOE_VERSION: + raise Exception("PaimonMoe version not supported") + export_time = datetime.strptime(ws["B3"].value, "%Y-%m-%d %H:%M:%S") + for gacha_type in paimonmoe_sheets: + ws = wb[paimonmoe_sheets[gacha_type]] + for row in ws.iter_rows(min_row=2, values_only=True): + if row[0] is None: + break + qiyes.append(from_paimon_moe(gacha_type, row[0], row[1], row[2], row[3])) + else: + export_time = datetime.now() + for gacha_type in fxq_sheets: + ws = wb[fxq_sheets[gacha_type]] + for row in ws.iter_rows(min_row=2, values_only=True): + if row[0] is None: + break + qiyes.append(from_fxq(gacha_type, row[2], row[1], row[0], row[3], row[6])) + + u = uigf(qiyes, 0, export_time) + return u.export_json() + async def import_from_file(self, user: User, message: Message, document: Document = None) -> None: if not document: document = message.document - if not document.file_name.endswith(".json"): - await message.reply_text("文件格式错误,请发送符合 UIGF 标准的抽卡记录文件") + # TODO: 使用 mimetype 判断文件类型 + if document.file_name.endswith(".xlsx"): + file_type = "xlsx" + elif document.file_name.endswith(".json"): + file_type = "json" + else: + await message.reply_text("文件格式错误,请发送符合 UIGF 标准的 json 格式的抽卡记录文件或者 paimon.moe、非小酋导出的 xlsx 格式的抽卡记录文件") if document.file_size > 2 * 1024 * 1024: await message.reply_text("文件过大,请发送小于 2 MB 的文件") try: data = BytesIO() await (await document.get_file()).download(out=data) - # bytesio to json - data = data.getvalue().decode("utf-8") - data = json.loads(data) + if file_type == "json": + # bytesio to json + data = data.getvalue().decode("utf-8") + data = json.loads(data) + else: + data = self.convert_paimonmoe_to_uigf(data) except UnicodeDecodeError: await message.reply_text("文件解析失败,请检查文件编码是否正确或符合 UIGF 标准") return @@ -97,7 +249,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation): reply = await message.reply_text("文件解析成功,正在导入数据") await message.reply_chat_action(ChatAction.TYPING) try: - text = await self._refresh_user_data(user, data=data) + text = await self._refresh_user_data(user, data=data, verify_uid=file_type == "json") except Exception as exc: # pylint: disable=W0703 logger.error(f"文件解析失败:{repr(exc)}") text = "文件解析失败,请检查文件是否符合 UIGF 标准" diff --git a/poetry.lock b/poetry.lock index 9fbfd540..508babbb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -159,7 +159,7 @@ python-versions = ">=3.5" dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "backports.zoneinfo" @@ -234,7 +234,7 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode-backport = ["unicodedata2"] +unicode_backport = ["unicodedata2"] [[package]] name = "click" @@ -313,6 +313,14 @@ url = "https://github.com/mrwan200/EnkaNetwork.py" reference = "HEAD" resolved_reference = "03fd20e5f838727b7c394c11ea1360cfc3272d69" +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "fakeredis" version = "1.9.4" @@ -601,6 +609,17 @@ category = "main" optional = false python-versions = ">=3.8" +[[package]] +name = "openpyxl" +version = "3.0.10" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +et-xmlfile = "*" + [[package]] name = "packaging" version = "21.3" @@ -1022,19 +1041,19 @@ aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mariadb_connector = ["mariadb (>=1.0.1,!=1.1.2)"] mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] +mysql_connector = ["mysql-connector-python"] oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql_asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql_pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3_binary"] @@ -1266,7 +1285,7 @@ test = ["pytest", "pytest-asyncio", "flaky"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "d91f61fd8b26f32e2a8b4dfd790f9471276f386a4391ddbf141cf394bb074759" +content-hash = "5068cee1f0c7399e9021e86040bc5ace24eab3428fc43b1b6985732175c7a69a" [metadata.files] aiofiles = [ @@ -1491,6 +1510,10 @@ Deprecated = [ {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, ] "enkanetwork.py" = [] +et-xmlfile = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] fakeredis = [ {file = "fakeredis-1.9.4-py3-none-any.whl", hash = "sha256:61afe14095aad3e7413a0a6fe63041da1b4bc3e41d5228a33b60bd03fabf22d8"}, {file = "fakeredis-1.9.4.tar.gz", hash = "sha256:17415645d11994061f5394f3f1c76ba4531f3f8b63f9c55a8fd2120bebcbfae9"}, @@ -1917,6 +1940,10 @@ numpy = [ {file = "numpy-1.23.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962"}, {file = "numpy-1.23.4.tar.gz", hash = "sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c"}, ] +openpyxl = [ + {file = "openpyxl-3.0.10-py2.py3-none-any.whl", hash = "sha256:0ab6d25d01799f97a9464630abacbb34aafecdcaa0ef3cba6d6b3499867d0355"}, + {file = "openpyxl-3.0.10.tar.gz", hash = "sha256:e47805627aebcf860edb4edf7987b1309c1b3632f3750538ed962bbcc3bd7449"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, diff --git a/pyproject.toml b/pyproject.toml index e5713a16..7ac8742e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ fastapi = "^0.85.1" uvicorn = {extras = ["standard"], version = "^0.18.3"} sentry-sdk = "^1.9.10" GitPython = "^3.1.29" +openpyxl = "^3.0.10" [tool.poetry.extras] pyro = ["Pyrogram", "TgCrypto"] diff --git a/resources/json/zh.json b/resources/json/zh.json new file mode 100644 index 00000000..c7dbbdbe --- /dev/null +++ b/resources/json/zh.json @@ -0,0 +1 @@ +{"Kate":"凯特","Kamisato Ayaka":"神里绫华","Jean":"琴","Traveler":"旅行者","Lisa":"丽莎","Barbara":"芭芭拉","Kaeya":"凯亚","Diluc":"迪卢克","Razor":"雷泽","Amber":"安柏","Venti":"温迪","Xiangling":"香菱","Beidou":"北斗","Xingqiu":"行秋","Xiao":"魈","Ningguang":"凝光","Klee":"可莉","Zhongli":"钟离","Fischl":"菲谢尔","Bennett":"班尼特","Tartaglia":"达达利亚","Noelle":"诺艾尔","Qiqi":"七七","Chongyun":"重云","Ganyu":"甘雨","Albedo":"阿贝多","Diona":"迪奥娜","Mona":"莫娜","Keqing":"刻晴","Sucrose":"砂糖","Xinyan":"辛焱","Rosaria":"罗莎莉亚","Hu Tao":"胡桃","Kaedehara Kazuha":"枫原万叶","Yanfei":"烟绯","Yoimiya":"宵宫","Thoma":"托马","Eula":"优菈","Raiden Shogun":"雷电将军","Sayu":"早柚","Sangonomiya Kokomi":"珊瑚宫心海","Gorou":"五郎","Kujou Sara":"九条裟罗","Arataki Itto":"荒泷一斗","Yae Miko":"八重神子","Shikanoin Heizou":"鹿野院平藏","Yelan":"夜兰","Aloy":"埃洛伊","Shenhe":"申鹤","Yun Jin":"云堇","Kuki Shinobu":"久岐忍","Kamisato Ayato":"神里绫人","Collei":"柯莱","Dori":"多莉","Tighnari":"提纳里","Nilou":"妮露","Cyno":"赛诺","Candace":"坎蒂丝","Naked Model #1":"裸模1号","Naked Man":"裸男","Akuliya":"阿葵丽雅","Yaoyao":"瑶瑶","Shiro Maiden":"白盒少女","Greatsword Maiden":"大剑少女","Lance Warrioress":"长枪成女","Rx White-Box":"Rx白盒","Female Lead New Normal Attack":"女主新普攻","Male Lead New Normal Attack":"男主新普攻","Dull Blade":"无锋剑","Silver Sword":"银剑","Cool Steel":"冷刃","Harbinger of Dawn":"黎明神剑","Traveler's Handy Sword":"旅行剑","Dark Iron Sword":"暗铁剑","Fillet Blade":"吃虎鱼刀","Skyrider Sword":"飞天御剑","Favonius Sword":"西风剑","The Flute":"笛剑","Sacrificial Sword":"祭礼剑","Royal Longsword":"宗室长剑","Lion's Roar":"匣里龙吟","Prototype Rancour":"试作斩岩","Iron Sting":"铁蜂刺","Blackcliff Longsword":"黑岩长剑","The Black Sword":"黑剑","The Alley Flash":"暗巷闪光","Sword of Descension":"降临之剑","Festering Desire":"腐殖之剑","Amenoma Kageuchi":"天目影打刀","Cinnabar Spindle":"辰砂之纺锤","Kagotsurube Isshin":"笼钓瓶一心","Sapwood Blade":"原木刀","Xiphos' Moonlight":"西福斯的月光","Prized Isshin Blade":"「一心传」名刀","Aquila Favonia":"风鹰剑","Skyward Blade":"天空之刃","Freedom-Sworn":"苍古自由之誓","Summit Shaper":"斫峰之刃","Primordial Jade Cutter":"磐岩结绿","Mistsplitter Reforged":"雾切之回光","Haran Geppaku Futsu":"波乱月白经津","Key of Khaj-Nisut":"圣显之钥","Waster Greatsword":"训练大剑","Old Merc's Pal":"佣兵重剑","Ferrous Shadow":"铁影阔剑","Bloodtainted Greatsword":"沐浴龙血的剑","White Iron Greatsword":"白铁大剑","Quartz":"石英大剑","Debate Club":"以理服人","Skyrider Greatsword":"飞天大御剑","Favonius Greatsword":"西风大剑","The Bell":"钟剑","Sacrificial Greatsword":"祭礼大剑","Royal Greatsword":"宗室大剑","Rainslasher":"雨裁","Prototype Archaic":"试作古华","Whiteblind":"白影剑","Blackcliff Slasher":"黑岩斩刀","Serpent Spine":"螭骨剑","Lithic Blade":"千岩古剑","Snow-Tombed Starsilver":"雪葬的星银","Luxurious Sea-Lord":"衔珠海皇","Katsuragikiri Nagamasa":"桂木斩长正","Makhaira Aquamarine":"玛海菈的水色","Akuoumaru":"恶王丸","Forest Regalia":"森林王器","Skyward Pride":"天空之傲","Wolf's Gravestone":"狼的末路","Song of Broken Pines":"松籁响起之时","The Unforged":"无工之剑","Redhorn Stonethresher":"赤角石溃杵","Beginner's Protector":"新手长枪","Iron Point":"铁尖枪","White Tassel":"白缨枪","Halberd":"钺矛","Black Tassel":"黑缨枪","The Flagstaff":"「旗杆」","Dragon's Bane":"匣里灭辰","Prototype Starglitter":"试作星镰","Crescent Pike":"流月针","Blackcliff Pole":"黑岩刺枪","Deathmatch":"决斗之枪","Lithic Spear":"千岩长枪","Favonius Lance":"西风长枪","Royal Spear":"宗室猎枪","Dragonspine Spear":"龙脊长枪","Kitain Cross Spear":"喜多院十文字","\"The Catch\"":"「渔获」","Wavebreaker's Fin":"断浪长鳍","Moonpiercer":"贯月矢","Missive Windspear":"风信之锋","Staff of Homa":"护摩之杖","Skyward Spine":"天空之脊","Vortex Vanquisher":"贯虹之槊","Primordial Jade Winged-Spear":"和璞鸢","Calamity Queller":"息灾","Engulfing Lightning":"薙草之稻光","Staff of the Scarlet Sands":"赤沙之杖","Apprentice's Notes":"学徒笔记","Pocket Grimoire":"口袋魔导书","Magic Guide":"魔导绪论","Thrilling Tales of Dragon Slayers":"讨龙英杰谭","Otherworldly Story":"异世界行记","Emerald Orb":"翡玉法球","Twin Nephrite":"甲级宝珏","Amber Bead":"琥珀玥","Favonius Codex":"西风秘典","The Widsith":"流浪乐章","Sacrificial Fragments":"祭礼残章","Royal Grimoire":"宗室秘法录","Solar Pearl":"匣里日月","Prototype Amber":"试作金珀","Mappa Mare":"万国诸海图谱","Blackcliff Agate":"黑岩绯玉","Eye of Perception":"昭心","Wine and Song":"暗巷的酒与诗","Frostbearer":"忍冬之果","Dodoco Tales":"嘟嘟可故事集","Hakushin Ring":"白辰之环","Oathsworn Eye":"证誓之明瞳","Wandering Evenstar":"流浪的晚星","Fruit of Fulfillment":"盈满之实","Skyward Atlas":"天空之卷","Lost Prayer to the Sacred Winds":"四风原典","Memory of Dust":"尘世之锁","Everlasting Moonglow":"不灭月华","Kagura's Verity":"神乐之真意","Hunter's Bow":"猎弓","Seasoned Hunter's Bow":"历练的猎弓","Raven Bow":"鸦羽弓","Sharpshooter's Oath":"神射手之誓","Recurve Bow":"反曲弓","Slingshot":"弹弓","Messenger":"信使","Ebony Bow":"黑檀弓","Favonius Warbow":"西风猎弓","The Stringless":"绝弦","Sacrificial Bow":"祭礼弓","Royal Bow":"宗室长弓","Rust":"弓藏","Prototype Crescent":"试作澹月","Compound Bow":"钢轮弓","Blackcliff Warbow":"黑岩战弓","The Viridescent Hunt":"苍翠猎弓","Alley Hunter":"暗巷猎手","Fading Twilight":"落霞","Mitternachts Waltz":"幽夜华尔兹","Windblume Ode":"风花之颂","Hamayumi":"破魔之弓","Predator":"掠食者","Mouun's Moon":"曚云之月","King's Squire":"王下近侍","End of the Line":"竭泽","Skyward Harp":"天空之翼","Amos' Bow":"阿莫斯之弓","Elegy for the End":"终末嗟叹之诗","Polar Star":"冬极白星","Aqua Simulacra":"若水","Thundering Pulse":"飞雷之弦振","Hunter's Path":"猎人之径","Adventurer":"冒险家","Lucky Dog":"幸运儿","Traveling Doctor":"游医","Resolution of Sojourner":"行者之心","Tiny Miracle":"奇迹","Berserker":"战狂","Instructor":"教官","The Exile":"流放者","Defender's Will":"守护之心","Brave Heart":"勇士之心","Martial Artist":"武人","Gambler":"赌徒","Scholar":"学士","Prayers for Illumination":"祭火之人","Prayers for Destiny":"祭水之人","Prayers for Wisdom":"祭雷之人","Prayers to Springtime":"祭冰之人","Blizzard Strayer":"冰风迷途的勇士","Thundersoother":"平息鸣雷的尊者","Lavawalker":"渡过烈火的贤人","Maiden Beloved":"被怜爱的少女","Gladiator's Finale":"角斗士的终幕礼","Viridescent Venerer":"翠绿之影","Wanderer's Troupe":"流浪大地的乐团","Thundering Fury":"如雷的盛怒","Crimson Witch of Flames":"炽烈的炎之魔女","Noblesse Oblige":"昔日宗室之仪","Bloodstained Chivalry":"染血的骑士道","Archaic Petra":"悠古的磐岩","Retracing Bolide":"逆飞的流星","Heart of Depth":"沉沦之心","Tenacity of the Millelith":"千岩牢固","Pale Flame":"苍白之火","Shimenawa's Reminiscence":"追忆之注连","Emblem of Severed Fate":"绝缘之旗印","Husk of Opulent Dreams":"华馆梦醒形骸记","Ocean-Hued Clam":"海染砗磲","Vermillion Hereafter":"辰砂往生录","Echoes of an Offering":"来歆余响","Deepwood Memories":"深林的记忆","Gilded Dreams":"饰金之梦","Mystic Enhancement Ore":"精锻用魔矿","Fine Enhancement Ore":"精锻用良矿","Enhancement Ore":"精锻用杂矿","Mora":"摩拉","Hero's Wit":"大英雄的经验","Adventurer's Experience":"冒险家的经验","Wanderer's Advice":"流浪者的经验","Crown of Insight":"智识之冕","Fetters of the Dandelion Gladiator":"狮牙斗士的枷锁","Chaos Device":"混沌装置","Divining Scroll":"导能绘卷","Chains of the Dandelion Gladiator":"狮牙斗士的铁链","Chaos Circuit":"混沌回路","Sealed Scroll":"封魔绘卷","Shackles of the Dandelion Gladiator":"狮牙斗士的镣铐","Chaos Core":"混沌炉心","Forbidden Curse Scroll":"禁咒绘卷","Dream of the Dandelion Gladiator":"狮牙斗士的理想","Tile of Decarabian's Tower":"高塔孤王的破瓦","Heavy Horn":"沉重号角","Firm Arrowhead":"牢固的箭簇","Debris of Decarabian's City":"高塔孤王的残垣","Black Bronze Horn":"黑铜号角","Sharp Arrowhead":"锐利的箭簇","Fragment of Decarabian's Epic":"高塔孤王的断片","Black Crystal Horn":"黑晶号角","Weathered Arrowhead":"历战的箭簇","Scattered Piece of Decarabian's Dream":"高塔孤王的碎梦","Slime Condensate":"史莱姆凝液","Slime Secretions":"史莱姆清","Slime Concentrate":"史莱姆原浆","Boreal Wolf's Milk Tooth":"凛风奔狼的始龀","Dead Ley Line Branch":"地脉的旧枝","Boreal Wolf's Cracked Tooth":"凛风奔狼的裂齿","Dead Ley Line Leaves":"地脉的枯叶","Boreal Wolf's Broken Fang":"凛风奔狼的断牙","Boreal Wolf's Nostalgia":"凛风奔狼的怀乡","Grain of Aerosiderite":"漆黑陨铁的一粒","Fragile Bone Shard":"脆弱的骨片","Damaged Mask":"破损的面具","Piece of Aerosiderite":"漆黑陨铁的一片","Sturdy Bone Shard":"结实的骨片","Stained Mask":"污秽的面具","Bit of Aerosiderite":"漆黑陨铁的一角","Fossilized Bone Shard":"石化的骨片","Ominous Mask":"不祥的面具","Chunk of Aerosiderite":"漆黑陨铁的一块","Mist Veiled Lead Elixir":"雾海云间的铅丹","Mist Grass Pollen":"雾虚花粉","Treasure Hoarder Insignia":"寻宝鸦印","Mist Veiled Mercury Elixir":"雾海云间的汞丹","Mist Grass":"雾虚草囊","Silver Raven Insignia":"藏银鸦印","Mist Veiled Gold Elixir":"雾海云间的金丹","Mist Grass Wick":"雾虚灯芯","Golden Raven Insignia":"攫金鸦印","Mist Veiled Primo Elixir":"雾海云间的转还","Luminous Sands from Guyun":"孤云寒林的光砂","Hunter's Sacrificial Knife":"猎兵祭刀","Recruit's Insignia":"新兵的徽记","Lustrous Stone from Guyun":"孤云寒林的辉岩","Agent's Sacrificial Knife":"特工祭刀","Sergeant's Insignia":"士官的徽记","Relic from Guyun":"孤云寒林的圣骸","Inspector's Sacrificial Knife":"督察长祭刀","Lieutenant's Insignia":"尉官的徽记","Divine Body from Guyun":"孤云寒林的神体","Whopperflower Nectar":"骗骗花蜜","Shimmering Nectar":"微光花蜜","Energy Nectar":"原素花蜜","Prithiva Topaz Sliver":"坚牢黄玉碎屑","Cecilia":"塞西莉亚花","Prithiva Topaz Fragment":"坚牢黄玉断片","Basalt Pillar":"玄岩之塔","Prithiva Topaz Chunk":"坚牢黄玉块","Prithiva Topaz Gemstone":"坚牢黄玉","Teachings of Ballad":"「诗文」的教导","Guide to Ballad":"「诗文」的指引","Philosophies of Ballad":"「诗文」的哲学","Tusk of Monoceros Caeli":"吞天之鲸·只角","Agnidus Agate Sliver":"燃愿玛瑙碎屑","Small Lamp Grass":"小灯草","Agnidus Agate Fragment":"燃愿玛瑙断片","Everflame Seed":"常燃火种","Agnidus Agate Chunk":"燃愿玛瑙块","Agnidus Agate Gemstone":"燃愿玛瑙","Teachings of Freedom":"「自由」的教导","Guide to Freedom":"「自由」的指引","Philosophies of Freedom":"「自由」的哲学","Dvalin's Sigh":"东风的吐息","Varunada Lazurite Sliver":"涤净青金碎屑","Philanemo Mushroom":"慕风蘑菇","Varunada Lazurite Fragment":"涤净青金断片","Cleansing Heart":"净水之心","Varunada Lazurite Chunk":"涤净青金块","Varunada Lazurite Gemstone":"涤净青金","Ring of Boreas":"北风之环","Vajrada Amethyst Sliver":"最胜紫晶碎屑","Noctilucous Jade":"夜泊石","Vajrada Amethyst Fragment":"最胜紫晶断片","Lightning Prism":"雷光棱镜","Vajrada Amethyst Chunk":"最胜紫晶块","Vajrada Amethyst Gemstone":"最胜紫晶","Teachings of Gold":"「黄金」的教导","Guide to Gold":"「黄金」的指引","Philosophies of Gold":"「黄金」的哲学","Windwheel Aster":"风车菊","Teachings of Resistance":"「抗争」的教导","Guide to Resistance":"「抗争」的指引","Philosophies of Resistance":"「抗争」的哲学","Dvalin's Plume":"东风之翎","Shivada Jade Sliver":"哀叙冰玉碎屑","Cor Lapis":"石珀","Shivada Jade Fragment":"哀叙冰玉断片","Hoarfrost Core":"极寒之核","Shivada Jade Chunk":"哀叙冰玉块","Shivada Jade Gemstone":"哀叙冰玉","Teachings of Diligence":"「勤劳」的教导","Guide to Diligence":"「勤劳」的指引","Philosophies of Diligence":"「勤劳」的哲学","Calla Lily":"嘟嘟莲","Shard of a Foul Legacy":"魔王之刃·残片","Spirit Locket of Boreas":"北风的魂匣","Vayuda Turquoise Sliver":"自在松石碎屑","Dandelion Seed":"蒲公英籽","Vayuda Turquoise Fragment":"自在松石断片","Hurricane Seed":"飓风之种","Vayuda Turquoise Chunk":"自在松石块","Vayuda Turquoise Gemstone":"自在松石","Teachings of Prosperity":"「繁荣」的教导","Guide to Prosperity":"「繁荣」的指引","Philosophies of Prosperity":"「繁荣」的哲学","Valberry":"落落莓","Dvalin's Claw":"东风之爪","Glaze Lily":"琉璃百合","Violetgrass":"琉璃袋","Tail of Boreas":"北风之尾","Wolfhook":"钩钩果","Starconch":"星螺","Brilliant Diamond Sliver":"璀璨原钻碎屑","Brilliant Diamond Fragment":"璀璨原钻断片","Brilliant Diamond Chunk":"璀璨原钻块","Brilliant Diamond Gemstone":"璀璨原钻","Jueyun Chili":"绝云椒椒","Silk Flower":"霓裳花","Qingxin":"清心","Shadow of the Warrior":"武炼之魂·孤影","Juvenile Jade":"未熟之玉","Bloodjade Branch":"血玉之枝","Crystalline Bloom":"晶凝之华","Dragon Lord's Crown":"龙王之冕","Sea Ganoderma":"海灵芝","Marionette Core":"魔偶机心","Gilded Scale":"鎏金之鳞","Coral Branch of a Distant Sea":"远海夷地的瑚枝","Jeweled Branch of a Distant Sea":"远海夷地的玉枝","Jade Branch of a Distant Sea":"远海夷地的琼枝","Narukami's Wisdom":"鸣神御灵的明惠","Narukami's Joy":"鸣神御灵的欢喜","Narukami's Affection":"鸣神御灵的亲爱","Narukami's Valor":"鸣神御灵的勇武","Mask of the Wicked Lieutenant":"今昔剧画之恶尉","Mask of the Tiger's Bite":"今昔剧画之虎啮","Mask of the One-Horned":"今昔剧画之一角","Mask of the Kijin":"今昔剧画之鬼人","Teachings of Transience":"「浮世」的教导","Guide to Transience":"「浮世」的指引","Philosophies of Transience":"「浮世」的哲学","Teachings of Elegance":"「风雅」的教导","Guide to Elegance":"「风雅」的指引","Philosophies of Elegance":"「风雅」的哲学","Teachings of Light":"「天光」的教导","Guide to Light":"「天光」的指引","Philosophies of Light":"「天光」的哲学","Perpetual Heart":"恒常机关之心","Smoldering Pearl":"阴燃之珠","Old Handguard":"破旧的刀镡","Kageuchi Handguard":"影打刀镡","Famed Handguard":"名刀镡","Chaos Gear":"混沌机关","Chaos Axis":"混沌枢纽","Chaos Oculus":"混沌真眼","Dismal Prism":"黯淡棱镜","Crystal Prism":"水晶棱镜","Polarizing Prism":"偏光棱镜","Sakura Bloom":"绯樱绣球","Crystal Marrow":"晶化骨髓","Dendrobium":"血斛","Naku Weed":"鸣草","Amakumo Fruit":"天云草实","Storm Beads":"雷霆数珠","Molten Moment":"熔毁之刻","Ashen Heart":"灰烬之心","Spectral Husk":"浮游干核","Spectral Heart":"浮游幽核","Spectral Nucleus":"浮游晶化核","Sango Pearl":"珊瑚真珠","Dew of Repudiation":"排异之露","Hellfire Butterfly":"狱火之蝶","Concealed Claw":"隐兽指爪","Concealed Unguis":"隐兽利爪","Concealed Talon":"隐兽鬼爪","Fluorescent Fungus":"幽灯蕈","Onikabuto":"鬼兜虫","Riftborn Regalia":"兽境王器","Dragonheir's False Fin":"龙嗣伪鳍","The Meaning of Aeons":"万劫之真意","Mudra of the Malefic General":"凶将之手眼","Gloomy Statuette":"晦暗刻像","Dark Statuette":"夤夜刻像","Deathly Statuette":"幽邃刻像","Tears of the Calamitous God":"祸神之禊泪","Runic Fang":"符纹之齿","Teachings of Admonition":"「诤言」的教导","Guide to Admonition":"「诤言」的指引","Philosophies of Admonition":"「诤言」的哲学","Teachings of Ingenuity":"「巧思」的教导","Guide to Ingenuity":"「巧思」的指引","Philosophies of Ingenuity":"「巧思」的哲学","Teachings of Praxis":"「笃行」的教导","Guide to Praxis":"「笃行」的指引","Philosophies of Praxis":"「笃行」的哲学","Nagadus Emerald Sliver":"生长碧翡碎屑","Nagadus Emerald Fragment":"生长碧翡断片","Nagadus Emerald Chunk":"生长碧翡块","Nagadus Emerald Gemstone":"生长碧翡","Majestic Hooked Beak":"蕈王钩喙","Nilotpala Lotus":"月莲","Fungal Spores":"蕈兽孢子","Luminescent Pollen":"荧光孢粉","Crystalline Cyst Dust":"孢囊晶尘","Rukkhashava Mushrooms":"树王圣体菇","Echo of Scorching Might":"烈日威权的残响","Remnant Glow of Scorching Might":"烈日威权的余光","Dream of Scorching Might":"烈日威权的梦想","Olden Days of Scorching Might":"烈日威权的旧日","Inactivated Fungal Nucleus":"失活菌核","Dormant Fungal Nucleus":"休眠菌核","Robust Fungal Nucleus":"茁壮菌核","Faded Red Satin":"褪色红绸","Trimmed Red Silk":"镶边红绸","Rich Red Brocade":"织金红绸","Copper Talisman of the Forest Dew":"谧林涓露的铜符","Iron Talisman of the Forest Dew":"谧林涓露的铁符","Silver Talisman of the Forest Dew":"谧林涓露的银符","Golden Talisman of the Forest Dew":"谧林涓露的金符","Chaos Storage":"混沌容器","Chaos Module":"混沌模块","Chaos Bolt":"混沌锚栓","Oasis Garden's Reminiscence":"绿洲花园的追忆","Oasis Garden's Kindness":"绿洲花园的恩惠","Oasis Garden's Mourning":"绿洲花园的哀思","Oasis Garden's Truth":"绿洲花园的真谛","Traveler (Electro)":"旅行者 (雷元素)","Traveler (Anemo)":"旅行者 (风元素)","Traveler (Geo)":"旅行者 (岩元素)","Traveler (Dendro)":"旅行者 (草)"} \ No newline at end of file