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>;