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