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