1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// UG-Scraper - A basic rust API for getting data from Ultimate Guitar
// Copyright (C) 2025 Linus Tibert
//
// This program was originally published under the MIT licence as seen
// here: https://github.com/Lich-Corals/ug-tab-scraper-rs/blob/mistress/LICENCE
/// Functions used by other modules for network access
pub mod network;
/// API for searching tabs on UG
pub mod search_scraper;
/// API for getting a tab from UG
pub mod tab_scraper;
/// Errors possibly occuring in the crate
pub mod error {
use std::error::Error;
use std::fmt;
/// Possible errors
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum UGError {
/// Occurs when an unsupported HTML is attempted to be evaluated.
InvalidHTMLError,
/// Occurs when an unsupported URL is attempted to be downloaded as a tab.
InvalidURLError,
/// Occurs when a tab without any available metadata is attempted to be downloaded.
NoBasicDataMatchError,
/// Occurs when any data extracting function gets unexpected data from UG.
///
/// E.g. if a string value is found in a place where a float is expected.
UnexpectedWebResultError,
/// Is returned by types_and_values::get_data_type() if the provided string does not match any known type of tab.
UnknownTypeError,
}
impl fmt::Display for UGError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.clone())
}
}
impl Error for UGError {}
}
/// Types, constants and closely associated functions which are used by across the crate
///
/// All of the defined types have the [`serde::Serialize`] and [`serde::Deserialize`] traits.
pub mod types {
use crate::error::UGError;
use serde::{Deserialize, Serialize};
use std::fmt;
/// A list of tab types supported for downloading
///
/// Includes Chords, Tabs, Bass Tabs, Ukulele Chords and Drum Tabs
pub const SUPPORTED_DOWNLOAD_TYPES: [DataSetType; 5] = [
DataSetType::Chords,
DataSetType::Tab,
DataSetType::Bass,
DataSetType::Ukulele,
DataSetType::Drums,
];
/// Known types of tab on UG. Includes unsupported ones.
#[derive(Debug, PartialEq, Default, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
pub enum DataSetType {
#[default]
Unknown,
/// Downloading supported
Chords,
/// Downloading supported
Tab,
/// Downloading supported
Ukulele,
/// Downloading supported
Bass,
/// Downloading supported
Drums,
/// Downloading not supported
Official,
/// Downloading not supported
Pro,
/// Downloading not supported
Power,
/// Downloading not supported
Video,
}
impl fmt::Display for DataSetType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// Possible types of line in a `Song`
#[derive(Debug, PartialEq, Eq, Default, Clone, Copy, Hash, Serialize, Deserialize)]
pub enum DataType {
#[default]
/// Lines with Chords detected by UG
Chord,
/// Plain text
Lyric,
/// The title of a song section
///
/// (e.g.: \[chorus\], \[intro\], etc.)
SectionTitle,
}
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// A set of data returned as sarch result
///
/// This struct is normally automatically generated by [`crate::search_scraper::get_search_results`] or [`crate::search_scraper::search_page`].
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct SearchResult {
/// The basic metadata of the search result (tab)
pub basic_data: BasicSongData,
/// Amount of ratings given by users on UG
pub rating_count: u32,
/// Rating on UG (0.0 - 5.0)
pub rating_value: f32,
}
impl fmt::Display for SearchResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// A single line of a tab
///
/// This struct is normally generated by [`crate::tab_scraper::get_tab_lines`].
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct Line {
/// Type data stored on the line
pub line_type: DataType,
/// The contents on the line as plain text
pub text_data: String,
}
impl Line {
/// Replace german chord names with english ones
///
/// Musical notation is one of the things Germans did their own, slightly more complicated way.
/// This function will replace german names for chords with their english equivalents.
///
/// ## Example:
/// ```
/// use ug_scraper::types::{Line, DataType};
///
/// // Create a line with German chord names H, Dmoll, Fis, Es and B
/// let mut line: Line = Line { line_type: DataType::Chord,
/// text_data: "A H C Dmoll Fis Es B".to_string() };
///
/// // Replace the weïrd chord names
/// line = line.replace_german_names();
/// // Returns:
/// // "A B C Dm F# Eb Bb"
///
/// ```
///
/// Note:
/// Some of the German chord notations (e.g. Fes or Dmoll) are rarely found in any tabs.
/// But to ensure they don't confuse anyone, they are included in this function too.
pub fn replace_german_names(mut self) -> Line {
self.text_data += " ";
if self.line_type == DataType::Chord {
// German on index 0; other on index 1
let entries = [
("Ces", "Cb "),
("Cis", "C# "),
("Des", "Db "),
("Dis", "D# "),
("Es ", "Eb "),
("Eis", "E# "),
("Fes", "Fb "),
("Fis", "F# "),
("Ges", "Gb "),
("Gis", "G# "),
("As ", "Ab "),
("Ais", "A# "),
("H", "B"),
("Bes", "Bb "),
("His", "B# "),
("Bis", "B# "),
("dur", "maj"),
("moll", "m "),
];
if entries.iter().any(|entry| self.text_data.contains(entry.0)) {
self.text_data = self
.text_data
.replace("B", "Bb")
.replace("Bb ", "Bb ");
for entry in entries {
self.text_data =
self.text_data.replace(entry.0, entry.1)
}
}
}
let _ = self.text_data.split_off(self.text_data.len() - 1);
self
}
}
impl fmt::Display for Line {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// A full set of available data about a tab on UG
///
/// This struct is normally generated by [`crate::tab_scraper::get_song_data`].
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct Song {
/// A vector of all lines in the tab
pub lines: Vec<Line>,
/// The detailed metadata of the song.
///
/// This data is optional, because some types of tab (e.g. Drum) don't have any metadata.
pub metadata: Option<SongMetaData>,
/// Basic data about the tab
pub basic_data: BasicSongData,
}
impl fmt::Display for Song {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// Basic metadata every tab has
///
/// This struct is normally generated by [`crate::tab_scraper::get_basic_metadata`].
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct BasicSongData {
/// Title of the song
pub title: String,
/// Name of the artist
pub artist: String,
/// Link to the tab
pub tab_link: String,
/// UG ID of the song
///
/// Don't confuse this with the tab ID, which is only for a single tab!
pub song_id: u32,
/// UG ID of the tab
///
/// Don't confuse this with the song ID, which is for every tab of the song!
pub tab_id: u32,
/// The type of tab
pub data_type: DataSetType,
}
impl fmt::Display for BasicSongData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// Special metadata which is not available for every tab (type)
///
/// Tabs of the type `Drums` never have this. Bass tabs often don't have.
/// Many tabs are missing values of the metadata; thus, they are all options.
///
/// This struct is normally automatically generated when using [`crate::tab_scraper::get_song_data`].
#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
pub struct SongMetaData {
pub capo: Option<String>,
pub tonality: Option<String>,
pub tuning_name: Option<String>,
pub tuning: Option<String>,
}
impl fmt::Display for SongMetaData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// Get the data type associated with a string scraped from UG
///
/// ## Example:
/// ```
/// use ug_scraper::types::get_data_type;
///
/// get_data_type("Chords");
/// // Returns:
/// // enum variant DataSetType::Chords
/// ```
///
/// ## Supported strings:
/// * Chords
/// * Tabs
/// * Bass Tabs
/// * Ukulele Chords
/// * Drum Tabs
/// * Official
/// * Pro
/// * Power
/// * Video
///
/// Returns `UGError::UnknownTypeError` if type is unknown.
pub fn get_data_type(type_string: &str) -> Result<DataSetType, UGError> {
match type_string {
"Chords" => Ok(DataSetType::Chords),
"Tabs" => Ok(DataSetType::Tab),
"Bass Tabs" => Ok(DataSetType::Bass),
"Ukulele Chords" => Ok(DataSetType::Ukulele),
"Drum Tabs" => Ok(DataSetType::Drums),
"Official" => Ok(DataSetType::Official),
"Pro" => Ok(DataSetType::Pro),
"Power" => Ok(DataSetType::Power),
"Video" => Ok(DataSetType::Video),
_ => Err(UGError::UnknownTypeError),
}
}
#[cfg(test)]
mod tests {
use crate::types::Line;
#[test]
fn german_names_replacement() {
let german_line: Line = Line {
line_type: super::DataType::Chord,
text_data: "A H C Dmoll Fis Es B B Esus Es".to_string(),
};
let english_line: Line = Line {
line_type: super::DataType::Chord,
text_data: "A B C Dm F# Eb Bb Bb Esus Eb"
.to_string(),
};
assert_eq!(
german_line.replace_german_names().text_data,
english_line.text_data
);
}
}
}