# :project: markji-py
# :author: L-ING
# :copyright: (C) 2025 L-ING <hlf01@icloud.com>
# :license: MIT, see LICENSE for more details.
from __future__ import annotations
from dataclasses import dataclass, field
from dataclasses_json import DataClassJsonMixin, config
from datetime import datetime
from enum import StrEnum
from typing import NewType, Type
Path = NewType("Path", str)
"""路径"""
# 8位 eg. 20251234
UserID = NewType("UserID", int)
"""用户 ID"""
FolderID = NewType("FolderID", str)
"""文件夹 ID"""
DeckID = NewType("DeckID", str)
"""卡组 ID"""
ChapterID = NewType("ChapterID", str)
"""章节 ID"""
ChapterSetID = NewType("ChapterSetID", str)
"""章节集 ID"""
CardID = NewType("CardID", str)
"""卡片 ID"""
CardRootID = NewType("CardRootID", str)
"""卡片根 ID"""
FileID = NewType("FileID", str)
"""文件 ID"""
AccessSettingID = NewType("AccessSettingID", str)
"""访问设置 ID"""
[文档]
class UserGender(StrEnum):
"""
Enum 用户性别
* MALE: 男
* FEMALE: 女
* SECRECY: 保密
"""
MALE = "MALE"
FEMALE = "FEMALE"
SECRECY = "SECRECY"
[文档]
class Status(StrEnum):
"""
Enum 文件夹状态
* NORMAL: 正常
"""
NORMAL = "NORMAL"
[文档]
class ItemObjectClass(StrEnum):
"""
Enum 文件夹项目对象类
* FOLDER: 文件夹
* DECK: 卡组
* CARD: 卡片
"""
FOLDER = "FOLDER"
DECK = "DECK"
CARD = "CARD"
[文档]
class DeckSource(StrEnum):
"""
Enum 卡组来源
* SELF: 自己创建
* FORK: 收藏他人
"""
SELF = "SELF"
FORK = "FORK"
[文档]
class FileSource(StrEnum):
"""
Enum 文件来源 (语音)
* UPLOAD: 上传
* TTS: 语音合成
"""
UPLOAD = "UPLOAD"
TTS = "TTS"
[文档]
class LanguageCode(StrEnum):
"""
Enum 语言代码
* AR_DZ: 阿拉伯语 (阿尔及利亚)
* AR_AE: 阿拉伯语 (阿联酋)
* AR_EG: 阿拉伯语 (埃及)
* AR_BH: 阿拉伯语 (巴林)
* AR_QA: 阿拉伯语 (卡塔尔)
* AR_KW: 阿拉伯语 (科威特)
* AR_LY: 阿拉伯语 (利比亚)
* AR_MA: 阿拉伯语 (摩洛哥)
* AR_SA: 阿拉伯语 (沙特阿拉伯)
* AR_TN: 阿拉伯语 (突尼斯)
* AR_SY: 阿拉伯语 (叙利亚)
* AR_YE: 阿拉伯语 (也门)
* AR_IQ: 阿拉伯语 (伊拉克)
* AR_JO: 阿拉伯语 (约旦)
* AM_ET: 阿姆哈拉语 (埃塞俄比亚)
* GA_IE: 爱尔兰语 (爱尔兰)
* ET_EE: 爱沙尼亚语 (爱沙尼亚)
* BG_BG: 保加利亚语 (保加利亚)
* PL_PL: 波兰语 (波兰)
* FA_IR: 波斯语 (伊朗)
* DA_DK: 丹麦语 (丹麦)
* DE_AT: 德语 (奥地利)
* DE_DE: 德语 (德国)
* DE_CH: 德语 (瑞士)
* RU_RU: 俄语 (俄罗斯)
* FR_BE: 法语 (比利时)
* FR_FR: 法语 (法国)
* FR_CA: 法语 (加拿大)
* FR_CH: 法语 (瑞士)
* FIL_PH: 菲律宾语 (菲律宾)
* FI_FI: 芬兰语 (芬兰)
* KM_KH: 高棉语 (柬埔寨)
* GU_IN: 古吉拉特语 (印度)
* KO_KR: 韩语 (韩国)
* NL_BE: 荷兰语 (比利时)
* NL_NL: 荷兰语 (荷兰)
* GL_ES: 加利西亚语 (西班牙)
* CA_ES: 加泰罗尼亚语 (西班牙)
* CS_CZ: 捷克语 (捷克)
* HR_HR: 克罗地亚语 (克罗地亚)
* LV_LV: 拉脱维亚语 (拉脱维亚)
* LT_LT: 立陶宛语 (立陶宛)
* RO_RO: 罗马尼亚语 (罗马尼亚)
* MT_MT: 马耳他语 (马耳他)
* MR_IN: 马拉地语 (印度)
* MS_MY: 马来语 (马来西亚)
* BN_BD: 孟加拉语 (孟加拉国)
* MY_MM: 缅甸语 (缅甸)
* AF_ZA: 南非荷兰语 (南非)
* NB_NO: 挪威博克马尔语 (挪威)
* PT_BR: 葡萄牙语 (巴西)
* PT_PT: 葡萄牙语 (葡萄牙)
* JA_JP: 日语 (日本)
* SV_SE: 瑞典语 (瑞典)
* SK_SK: 斯洛伐克语 (斯洛伐克)
* SL_SI: 斯洛文尼亚语 (斯洛文尼亚)
* SW_KE: 斯瓦希里语 (肯尼亚)
* SW_TZ: 斯瓦希里语 (坦桑尼亚)
* SO_SO: 索马里语 (索马里)
* TE_IN: 泰卢固语 (印度)
* TA_LK: 泰米尔语 (斯里兰卡)
* TA_SG: 泰米尔语 (新加坡)
* TA_IN: 泰米尔语 (印度)
* TH_TH: 泰语 (泰国)
* TR_TR: 土耳其语 (土耳其)
* CY_GB: 威尔士语 (英国)
* UR_PK: 乌尔都语 (巴基斯坦)
* UR_IN: 乌尔都语 (印度)
* UK_UA: 乌克兰语 (乌克兰)
* UZ_UZ: 乌兹别克语 (乌兹别克斯坦)
* ES_NI: 西班牙语 (尼加拉瓜)
* ES_AR: 西班牙语 (阿根廷)
* ES_PY: 西班牙语 (巴拉圭)
* ES_PA: 西班牙语 (巴拿马)
* ES_PR: 西班牙语 (波多黎各)
* ES_BO: 西班牙语 (玻利维亚)
* ES_GQ: 西班牙语 (赤道几内亚)
* ES_DO: 西班牙语 (多米尼加共和国)
* ES_EC: 西班牙语 (厄瓜多尔)
* ES_CO: 西班牙语 (哥伦比亚)
* ES_CR: 西班牙语 (哥斯达黎加)
* ES_CU: 西班牙语 (古巴)
* ES_HN: 西班牙语 (洪都拉斯)
* ES_US: 西班牙语 (美国)
* ES_PE: 西班牙语 (秘鲁)
* ES_MX: 西班牙语 (墨西哥)
* ES_SV: 西班牙语 (萨尔瓦多)
* ES_GT: 西班牙语 (危地马拉)
* ES_VE: 西班牙语 (委内瑞拉)
* ES_UY: 西班牙语 (乌拉圭)
* ES_ES: 西班牙语 (西班牙)
* ES_CL: 西班牙语 (智利)
* HE_IL: 希伯来语 (以色列)
* EL_GR: 希腊语 (希腊)
* HU_HU: 匈牙利语 (匈牙利)
* SU_ID: 巽他语 (印度尼西亚)
* IT_IT: 意大利语 (意大利)
* HI_IN: 印地语 (印度)
* ID_ID: 印尼语 (印度尼西亚)
* EN_IE: 英语 (爱尔兰)
* EN_AU: 英语 (澳大利亚)
* EN_PH: 英语 (菲律宾)
* EN_CA: 英语 (加拿大)
* EN_KE: 英语 (肯尼亚)
* EN_US: 英语 (美国)
* EN_ZA: 英语 (南非)
* EN_NG: 英语 (尼日利亚)
* EN_TZ: 英语 (坦桑尼亚)
* EN_HK: 英语 (香港)
* EN_SG: 英语 (新加坡)
* EN_NZ: 英语 (新西兰)
* EN_IN: 英语 (印度)
* EN_GB: 英语 (英国)
* VI_VN: 越南语 (越南)
* JV_ID: 爪哇语 (印度尼西亚)
* ZH_CN: 中文 (中国)
* ZH_TW: 中文 (台湾)
* ZH_HK: 中文 (香港)
* ZU_ZA: 祖鲁语 (南非)
"""
AR_DZ = "ar-DZ"
AR_AE = "ar-AE"
AR_EG = "ar-EG"
AR_BH = "ar-BH"
AR_QA = "ar-QA"
AR_KW = "ar-KW"
AR_LY = "ar-LY"
AR_MA = "ar-MA"
AR_SA = "ar-SA"
AR_TN = "ar-TN"
AR_SY = "ar-SY"
AR_YE = "ar-YE"
AR_IQ = "ar-IQ"
AR_JO = "ar-JO"
AM_ET = "am-ET"
GA_IE = "ga-IE"
ET_EE = "et-EE"
BG_BG = "bg-BG"
PL_PL = "pl-PL"
FA_IR = "fa-IR"
DA_DK = "da-DK"
DE_AT = "de-AT"
DE_DE = "de-DE"
DE_CH = "de-CH"
RU_RU = "ru-RU"
FR_BE = "fr-BE"
FR_FR = "fr-FR"
FR_CA = "fr-CA"
FR_CH = "fr-CH"
FIL_PH = "fil-PH"
FI_FI = "fi-FI"
KM_KH = "km-KH"
GU_IN = "gu-IN"
KO_KR = "ko-KR"
NL_BE = "nl-BE"
NL_NL = "nl-NL"
GL_ES = "gl-ES"
CA_ES = "ca-ES"
CS_CZ = "cs-CZ"
HR_HR = "hr-HR"
LV_LV = "lv-LV"
LT_LT = "lt-LT"
RO_RO = "ro-RO"
MT_MT = "mt-MT"
MR_IN = "mr-IN"
MS_MY = "ms-MY"
BN_BD = "bn-BD"
MY_MM = "my-MM"
AF_ZA = "af-ZA"
NB_NO = "nb-NO"
PT_BR = "pt-BR"
PT_PT = "pt-PT"
JA_JP = "ja-JP"
SV_SE = "sv-SE"
SK_SK = "sk-SK"
SL_SI = "sl-SI"
SW_KE = "sw-KE"
SW_TZ = "sw-TZ"
SO_SO = "so-SO"
TE_IN = "te-IN"
TA_LK = "ta-LK"
TA_SG = "ta-SG"
TA_IN = "ta-IN"
TH_TH = "th-TH"
TR_TR = "tr-TR"
CY_GB = "cy-GB"
UR_PK = "ur-PK"
UR_IN = "ur-IN"
UK_UA = "uk-UA"
UZ_UZ = "uz-UZ"
ES_NI = "es-NI"
ES_AR = "es-AR"
ES_PY = "es-PY"
ES_PA = "es-PA"
ES_PR = "es-PR"
ES_BO = "es-BO"
ES_GQ = "es-GQ"
ES_DO = "es-DO"
ES_EC = "es-EC"
ES_CO = "es-CO"
ES_CR = "es-CR"
ES_CU = "es-CU"
ES_HN = "es-HN"
ES_US = "es-US"
ES_PE = "es-PE"
ES_MX = "es-MX"
ES_SV = "es-SV"
ES_GT = "es-GT"
ES_VE = "es-VE"
ES_UY = "es-UY"
ES_ES = "es-ES"
ES_CL = "es-CL"
HE_IL = "he-IL"
EL_GR = "el-GR"
HU_HU = "hu-HU"
SU_ID = "su-ID"
IT_IT = "it-IT"
HI_IN = "hi-IN"
ID_ID = "id-ID"
EN_IE = "en-IE"
EN_AU = "en-AU"
EN_PH = "en-PH"
EN_CA = "en-CA"
EN_KE = "en-KE"
EN_US = "en-US"
EN_ZA = "en-ZA"
EN_NG = "en-NG"
EN_TZ = "en-TZ"
EN_HK = "en-HK"
EN_SG = "en-SG"
EN_NZ = "en-NZ"
EN_IN = "en-IN"
EN_GB = "en-GB"
VI_VN = "vi-VN"
JV_ID = "jv-ID"
ZH_CN = "zh-CN"
ZH_TW = "zh-TW"
ZH_HK = "zh-HK"
ZU_ZA = "zu-ZA"
class _SearchScope(StrEnum):
"""
Enum 搜索范围
* ALL: 所有
* MINE: 我的
* REFERENCE: 引用(无效果)
"""
ALL = "ALL"
MINE = "MINE"
REFERENCE = "REFERENCE"
DECK = "DECK"
[文档]
class Datetime(datetime):
"""
继承自 `datetime.datetime`
"""
def _to_str(self) -> str:
# 2025-12-34T12:34:56.789Z
return self.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
@classmethod
def _field(cls: Type[Datetime]):
# default serialize and deserialize methods
return field(
metadata=config(encoder=lambda dt: dt._to_str(), decoder=cls.fromisoformat),
)
@classmethod
def _metadata(cls: Type[Datetime]):
# default serialize and deserialize methods
# used when a class is inherited from another class that has a member of this type
# and called _field() to define default serialize and deserialize methods, which
# cause `Fields without default values cannot appear after fields with default values`
return config(encoder=lambda dt: dt._to_str(), decoder=cls.fromisoformat)
[文档]
@dataclass
class UserLevel(DataClassJsonMixin):
"""
用户等级
:param int level: 等级
:param str description: 等级描述
"""
level: int
description: str
[文档]
@dataclass
class UserOAuth(DataClassJsonMixin):
"""
用户授权
:param str type: 授权类型
:param str appid: 授权AppID
:param str username: 用户名
"""
type: str
appid: str
username: str
[文档]
@dataclass
class FolderItem(DataClassJsonMixin):
"""
文件夹项目
:param str object_id: 对象ID
:param ItemObjectClass object_class: 对象类
"""
object_id: str
object_class: ItemObjectClass
[文档]
@dataclass
class DeckAccessSettingBasic(DataClassJsonMixin):
"""
卡组基本访问设置
:param bool validation_enabled: 是否启用验证
"""
validation_enabled: bool
[文档]
@dataclass
class DeckAccessSettingBrief(DeckAccessSettingBasic):
"""
卡组简要访问设置
更改是否可搜索且不启用验证时返回
:param bool validation_enabled: 是否启用验证
:id: AccessSettingID id: 访问设置ID
:param DeckID deck_id: 卡组ID
:param bool is_private: 是否私有
:param bool is_searchable: 是否可搜索
"""
id: AccessSettingID
deck_id: DeckID
is_private: bool
is_searchable: bool
[文档]
@dataclass
class DeckAccessSettingInfo(DeckAccessSettingBrief):
"""
卡组访问设置信息
启用验证但不启用密码时返回
:param bool validation_enabled: 是否启用验证
:id: AccessSettingID id: 访问设置ID
:param DeckID deck_id: 卡组ID
:param bool is_private: 是否私有
:param bool is_searchable: 是否可搜索
:param bool validation_request_access: 是否需要验证
:param bool validation_password_enabled: 是否启用密码验证
:param bool validation_redeem_code: 是否启用验证撤回码(暂无)
"""
validation_request_access: bool
validation_password_enabled: bool
validation_redeem_code: bool
[文档]
@dataclass
class DeckAccessSetting(DeckAccessSettingInfo):
"""
卡组访问设置
启用验证密码时返回
:param bool validation_enabled: 是否启用验证
:id: AccessSettingID id: 访问设置ID
:param DeckID deck_id: 卡组ID
:param bool is_private: 是否私有
:param bool is_searchable: 是否可搜索
:param bool validation_request_access: 是否需要验证
:param bool validation_password_enabled: 是否启用密码验证
:param bool validation_redeem_code: 是否启用验证撤回码(暂无)
:param str validation_password: 验证密码
"""
validation_password: str
[文档]
@dataclass
class CardReference(DataClassJsonMixin):
"""
卡片引用
:param CardRootID id: 对象ID
:param ItemObjectClass type: 对象类
"""
id: CardRootID
type: ItemObjectClass = ItemObjectClass.CARD
[文档]
@dataclass
class TTSItem(DataClassJsonMixin):
"""
语音合成项目
:param str text: 文本
:param LanguageCode locale: 语言代码
"""
text: str
locale: LanguageCode
[文档]
@dataclass
class MaskInfo(DataClassJsonMixin):
"""
图片遮罩信息
全新上传的遮罩不含 description 字段
:param str description: 描述
"""
description: str | None = None
[文档]
@dataclass
class ImageInfo(DataClassJsonMixin):
"""
图片信息
:param int width: 宽度
:param int height: 高度
:param str description: 描述
"""
width: int
height: int
description: str
[文档]
@dataclass
class AudioInfo(DataClassJsonMixin):
"""
音频信息
:param FileSource source: 文件来源
"""
source: FileSource
[文档]
@dataclass
class TTSInfo(AudioInfo):
"""
语音合成信息
:param FileSource source: 文件来源
:param list[TTSItem] content_slices: 语音合成信息
"""
content_slices: list[TTSItem]
def _select_media_type(info: dict) -> Type[MaskInfo | ImageInfo | AudioInfo | TTSInfo]:
if "width" in info and "height" in info and "description" in info:
return ImageInfo
elif "source" in info:
if info["source"] == "UPLOAD":
return AudioInfo
elif info["source"] == "TTS":
return TTSInfo
return MaskInfo
[文档]
@dataclass
class File(DataClassJsonMixin):
"""
文件
:param MaskInfo | ImageInfo | AudioInfo | TTSInfo info: 文件信息
:param int size: 文件大小
:param str mime: MIME类型
:param str url: 文件Url
:param FileID id: 文件ID
:param Datetime expire_time: 过期时间
"""
info: MaskInfo | ImageInfo | AudioInfo | TTSInfo = field(
metadata=config(decoder=lambda info: _select_media_type(info).from_dict(info))
)
size: int
mime: str
url: str
id: FileID
expire_time: Datetime = Datetime._field()
[文档]
@dataclass
class MaskItem(DataClassJsonMixin):
"""
遮罩项目
:param int top: 顶部位置
:param int left: 左侧位置
:param int width: 宽度
:param int height: 高度
:param int index: 索引
:param str type: 类型
"""
top: int
left: int
width: int
height: int
index: int
type: str = "rect"