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}