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}