lastfm_edit/lib.rs
1//! # lastfm-edit
2//!
3//! A Rust crate for programmatic access to Last.fm's scrobble editing functionality via web scraping.
4//!
5//! This crate provides a high-level interface for authenticating with Last.fm, browsing user libraries,
6//! and performing bulk edits on scrobbled tracks. It uses web scraping to access functionality not
7//! available through Last.fm's public API.
8//!
9//! ## Features
10//!
11//! - **Authentication**: Login to Last.fm with username/password
12//! - **Library browsing**: Paginated access to tracks, albums, and recent scrobbles
13//! - **Bulk editing**: Edit track names, artist names, and album information
14//! - **Async iterators**: Stream large datasets efficiently
15//! - **HTTP client abstraction**: Works with any HTTP client implementation
16//!
17//! ## Quick Start
18//!
19//! ```rust,no_run
20//! use lastfm_edit::{LastFmEditClient, AsyncPaginatedIterator, Result};
21//!
22//! #[tokio::main]
23//! async fn main() -> Result<()> {
24//! // Create client with any HTTP implementation
25//! let http_client = http_client::native::NativeClient::new();
26//! let mut client = LastFmEditClient::new(Box::new(http_client));
27//!
28//! // Login to Last.fm
29//! client.login("username", "password").await?;
30//!
31//! // Browse recent tracks
32//! let mut recent_tracks = client.recent_tracks();
33//! while let Some(track) = recent_tracks.next().await? {
34//! println!("{} - {}", track.artist, track.name);
35//! }
36//!
37//! Ok(())
38//! }
39//! ```
40//!
41//! ## Core Components
42//!
43//! - [`LastFmClient`] - Main client for interacting with Last.fm
44//! - [`Track`], [`Album`] - Data structures for music metadata
45//! - [`AsyncPaginatedIterator`] - Trait for streaming paginated data
46//! - [`ScrobbleEdit`] - Represents track edit operations
47//! - [`LastFmError`] - Error types for the crate
48//!
49//! ## Installation
50//!
51//! Add this to your `Cargo.toml`:
52//! ```toml
53//! [dependencies]
54//! lastfm-edit = "0.1.0"
55//! http-client = { version = "6.5", features = ["curl_client"] }
56//! tokio = { version = "1.0", features = ["full"] }
57//! ```
58//!
59//! ## Usage Patterns
60//!
61//! ### Basic Library Browsing
62//!
63//! ```rust,no_run
64//! use lastfm_edit::{LastFmEditClient, AsyncPaginatedIterator, Result};
65//!
66//! #[tokio::main]
67//! async fn main() -> Result<()> {
68//! let http_client = http_client::native::NativeClient::new();
69//! let mut client = LastFmEditClient::new(Box::new(http_client));
70//!
71//! client.login("username", "password").await?;
72//!
73//! // Get all tracks by an artist
74//! let mut tracks = client.artist_tracks("Radiohead");
75//! while let Some(track) = tracks.next().await? {
76//! println!("{} - {}", track.artist, track.name);
77//! }
78//!
79//! Ok(())
80//! }
81//! ```
82//!
83//! ### Bulk Track Editing
84//!
85//! ```rust,no_run
86//! use lastfm_edit::{LastFmEditClient, ScrobbleEdit, AsyncPaginatedIterator, Result};
87//!
88//! #[tokio::main]
89//! async fn main() -> Result<()> {
90//! let http_client = http_client::native::NativeClient::new();
91//! let mut client = LastFmEditClient::new(Box::new(http_client));
92//!
93//! client.login("username", "password").await?;
94//!
95//! // Find and edit tracks
96//! let tracks = client.artist_tracks("Artist Name").collect_all().await?;
97//! for track in tracks {
98//! if track.name.contains("(Remaster)") {
99//! let new_name = track.name.replace(" (Remaster)", "");
100//!
101//! // Create edit for this track
102//! let edit = ScrobbleEdit::from_track_info(
103//! &track.name,
104//! &track.name, // Use track name as album fallback
105//! &track.artist,
106//! 0 // No timestamp needed for bulk edit
107//! )
108//! .with_track_name(&new_name)
109//! .with_edit_all(true);
110//!
111//! let response = client.edit_scrobble(&edit).await?;
112//! if response.success {
113//! println!("Successfully edited: {} -> {}", track.name, new_name);
114//! }
115//! }
116//! }
117//!
118//! Ok(())
119//! }
120//! ```
121//!
122//! ### Recent Tracks Monitoring
123//!
124//! ```rust,no_run
125//! use lastfm_edit::{LastFmEditClient, AsyncPaginatedIterator, Result};
126//!
127//! #[tokio::main]
128//! async fn main() -> Result<()> {
129//! let http_client = http_client::native::NativeClient::new();
130//! let mut client = LastFmEditClient::new(Box::new(http_client));
131//!
132//! client.login("username", "password").await?;
133//!
134//! // Get recent tracks (first 100)
135//! let recent_tracks = client.recent_tracks().take(100).await?;
136//! println!("Found {} recent tracks", recent_tracks.len());
137//!
138//! Ok(())
139//! }
140//! ```
141//!
142//! ## License
143//!
144//! MIT
145
146pub mod album;
147pub mod client;
148pub mod edit;
149pub mod error;
150pub mod iterator;
151pub mod parsing;
152pub mod track;
153
154pub use album::{Album, AlbumPage};
155pub use client::LastFmEditClient;
156pub use edit::{EditResponse, ScrobbleEdit};
157pub use error::LastFmError;
158pub use iterator::{
159 ArtistAlbumsIterator, ArtistTracksIterator, AsyncPaginatedIterator, RecentTracksIterator,
160};
161pub use track::{Track, TrackPage};
162
163// Re-export scraper types for testing
164pub use scraper::Html;
165
166/// A convenient type alias for [`Result`] with [`LastFmError`] as the error type.
167pub type Result<T> = std::result::Result<T, LastFmError>;