bms_table/lib.rs
1//! BMS 难度表数据获取与解析库
2//!
3//! 提供从网页或 JSON 源构建完整的 BMS 难度表数据结构,涵盖表头、课程、奖杯与谱面条目等。
4//! 结合可选特性实现网络抓取与 HTML 解析,适用于 CLI 工具、服务端程序或数据处理流水线。
5//!
6//! # 功能概述
7//!
8//! - 解析表头 JSON,支持收集未识别的额外字段
9//! - 解析谱面数据,兼容数组或 `{ charts: [...] }` 两种格式
10//! - 课程数据支持从 `md5`/`sha256` 列表自动转换为谱面条目
11//! - 可选特性 `reqwest` 提供一站式网络获取接口
12//! - 可选特性 `scraper` 支持从 HTML `<meta name="bmstable">` 提取头部 JSON 地址
13//!
14//! # 快速上手
15//!
16//! ```rust,no_run
17//! # #[tokio::main]
18//! # async fn main() -> anyhow::Result<()> {
19//! use bms_table::fetch::reqwest::fetch_bms_table;
20//!
21//! let table = fetch_bms_table("https://stellabms.xyz/sl/table.html").await?;
22//! println!("{}: {} charts", table.header.name, table.data.charts.len());
23//! # Ok(())
24//! # }
25//! ```
26//!
27//! # 特性说明
28//!
29//! - `reqwest`:启用网络获取功能(默认启用)
30//! - `scraper`:启用 HTML 解析(用于从页面提取 bmstable 头部地址)
31
32#![warn(missing_docs)]
33#![warn(clippy::must_use_candidate)]
34#![deny(rustdoc::broken_intra_doc_links)]
35#![cfg_attr(docsrs, feature(doc_cfg))]
36
37pub mod de;
38pub mod fetch;
39pub mod ser;
40
41use serde::{Deserialize, Serialize};
42use serde_json::Value;
43
44/// 顶层 BMS 难度表数据结构。
45///
46/// 将表头元数据与谱面数据打包在一起,便于在应用中一次性传递与使用。
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct BmsTable {
49 /// 表头信息与额外字段
50 pub header: BmsTableHeader,
51 /// 表数据,包含谱面列表
52 pub data: BmsTableData,
53}
54
55/// BMS 表头信息。
56///
57/// 该结构严格解析常见字段,并把未识别的字段保存在 `extra` 中,保证向前兼容。
58#[derive(Debug, Clone, PartialEq)]
59pub struct BmsTableHeader {
60 /// 表格名称,如 "Satellite"
61 pub name: String,
62 /// 表格符号,如 "sl"
63 pub symbol: String,
64 /// 谱面数据文件的URL(原样保存来自header JSON的字符串)
65 pub data_url: String,
66 /// 课程信息数组,每个元素是一个课程组的数组
67 pub course: Vec<Vec<CourseInfo>>,
68 /// 难度等级顺序,包含数字和字符串
69 pub level_order: Vec<String>,
70 /// 额外数据(来自header JSON中未识别的字段)
71 pub extra: Value,
72}
73
74/// BMS 表数据。
75///
76/// 仅包含谱面数组。解析时同时兼容纯数组与 `{ charts: [...] }` 两种输入形式。
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct BmsTableData {
79 /// 谱面数据
80 pub charts: Vec<ChartItem>,
81}
82
83/// 课程信息。
84///
85/// 描述一个课程的名称、约束、奖杯与谱面集合。解析阶段会将 `md5`/`sha256`
86/// 列表自动转换为对应的 `ChartItem`,并为缺失 `level` 的谱面补充默认值 `"0"`。
87#[derive(Debug, Clone, PartialEq, Serialize)]
88pub struct CourseInfo {
89 /// 课程名称,如 "Satellite Skill Analyzer 2nd sl0"
90 pub name: String,
91 /// 约束条件列表,如 ["grade_mirror", "gauge_lr2", "ln"]
92 #[serde(default)]
93 pub constraint: Vec<String>,
94 /// 奖杯信息列表,定义不同等级的奖杯要求
95 #[serde(default)]
96 pub trophy: Vec<Trophy>,
97 /// 谱面数据列表,包含该课程的所有谱面信息
98 #[serde(default)]
99 pub charts: Vec<ChartItem>,
100}
101
102/// 谱面数据项。
103///
104/// 描述单个 BMS 文件的相关元数据与资源链接。为空字符串的可选字段在反序列化时会
105/// 自动转换为 `None`,以提升数据质量。
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct ChartItem {
108 /// 难度等级,如 "0"
109 pub level: String,
110 /// 文件的MD5哈希值
111 pub md5: Option<String>,
112 /// 文件的SHA256哈希值
113 pub sha256: Option<String>,
114 /// 歌曲标题
115 pub title: Option<String>,
116 /// 歌曲副标题
117 pub subtitle: Option<String>,
118 /// 艺术家名称
119 pub artist: Option<String>,
120 /// 歌曲副艺术家
121 pub subartist: Option<String>,
122 /// 文件下载链接
123 pub url: Option<String>,
124 /// 差分文件下载链接(可选)
125 pub url_diff: Option<String>,
126 /// 额外数据
127 pub extra: Value,
128}
129
130/// 奖杯信息。
131///
132/// 定义达成特定奖杯的条件,包括最大 miss 率与最低得分率等要求。
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub struct Trophy {
135 /// 奖杯名称,如 "silvermedal" 或 "goldmedal"
136 pub name: String,
137 /// 最大miss率(百分比),如 5.0 表示最大5%的miss率
138 pub missrate: f64,
139 /// 最小得分率(百分比),如 70.0 表示至少70%的得分率
140 pub scorerate: f64,
141}