#[path = "shared/common.rs"]
mod common;
use lastfm_edit::{LastFmEditClient, Result};
use regex::Regex;
#[tokio::main]
async fn main() -> Result<()> {
let client = common::setup_client().await?;
println!("=== Remaster & Year Removal Tool ===\n");
println!("๐ฏ This will remove 'remastered' text and year suffixes from track names");
println!("๐ Patterns include: '- 2009', '(2009)', '[2009]', '- Remaster', etc.\n");
let artist = std::env::args()
.nth(1)
.unwrap_or_else(|| "The Beatles".to_string());
println!("๐ต Processing tracks for artist: {artist}\n");
let remaster_patterns = vec![
Regex::new(r"(?i)\s*-\s*\d{4}\s*remaster(ed)?.*$").unwrap(),
Regex::new(r"(?i)\s*-\s*remaster(ed)?.*$").unwrap(),
Regex::new(r"(?i)\s*\(\d{4}\s*remaster(ed)?.*\)\s*$").unwrap(),
Regex::new(r"(?i)\s*\(remaster(ed)?.*\)\s*$").unwrap(),
Regex::new(r"(?i)\s*\[\d{4}\s*remaster(ed)?.*\]\s*$").unwrap(),
Regex::new(r"(?i)\s*\[remaster(ed)?.*\]\s*$").unwrap(),
Regex::new(r"(?i)\s*remaster(ed)?\s*(\d{4})?\s*$").unwrap(),
Regex::new(r"(?i)\s*-\s*(19[8-9]\d|20[0-3]\d)\s*$").unwrap(),
Regex::new(r"(?i)\s*\((19[8-9]\d|20[0-3]\d)\)\s*$").unwrap(),
Regex::new(r"(?i)\s*\[(19[8-9]\d|20[0-3]\d)\]\s*$").unwrap(),
Regex::new(r"(?i)\s*-\s*\d{4}\s*mix.*$").unwrap(),
Regex::new(r"(?i)\s*-\s*mix.*$").unwrap(),
];
let mut tracks_to_process = Vec::new();
let mut fetched_count = 0;
let mut page = 1;
loop {
match client.get_artist_tracks_page(&artist, page).await {
Ok(track_page) => {
if track_page.tracks.is_empty() {
println!("\n๐ Fetched all {fetched_count} tracks for {artist}");
break;
}
for track in track_page.tracks {
fetched_count += 1;
println!("๐ [{:3}] Found track: '{}'", fetched_count, track.name);
let mut cleaned_name = track.name.clone();
let mut needs_cleaning = false;
for pattern in &remaster_patterns {
if pattern.is_match(&cleaned_name) {
cleaned_name = pattern.replace(&cleaned_name, "").trim().to_string();
needs_cleaning = true;
}
}
if needs_cleaning && !cleaned_name.is_empty() {
tracks_to_process.push((track, cleaned_name));
}
}
if !track_page.has_next_page {
println!("\n๐ Fetched all {fetched_count} tracks for {artist}");
break;
}
page += 1;
}
Err(e) => {
println!("โ Error fetching tracks page {page}: {e}");
break;
}
}
}
println!(
"\n๐งน Starting remaster removal on {} tracks...\n",
tracks_to_process.len()
);
let mut processed_count = 0;
let mut edits_made = 0;
let mut rate_limit_hits = 0;
for (track, cleaned_name) in tracks_to_process {
processed_count += 1;
println!(
"๐ง [{:3}] Processing: '{}' -> '{}'",
processed_count, track.name, cleaned_name
);
println!(" ๐ Applying change...");
let edit_template =
lastfm_edit::ScrobbleEdit::from_track_and_artist(&track.name, &track.artist);
match client
.discover_scrobble_edit_variations(&edit_template)
.await
{
Ok(exact_edit_vec) => {
if let Some(exact_edit) = exact_edit_vec.into_iter().next() {
let mut edit_data = exact_edit.to_scrobble_edit();
edit_data.track_name = Some(cleaned_name.clone());
match client.edit_scrobble(&edit_data).await {
Ok(_) => {
edits_made += 1;
println!(" โ
Successfully cleaned track");
}
Err(e) => {
println!(" โ Error editing track: {e}");
if e.to_string().contains("RateLimit") {
rate_limit_hits += 1;
log::info!("Rate limit encountered during edit operation for track '{}' by '{}'", track.name, track.artist);
println!(" ๐จ RATE LIMIT DETECTED during edit operation!");
break;
}
}
}
} else {
println!(" โ ๏ธ No edit data found for track");
}
}
Err(e) => {
println!(" โ ๏ธ Couldn't load edit form: {e}");
if e.to_string().contains("RateLimit") {
rate_limit_hits += 1;
log::info!(
"Rate limit encountered during form load for track '{}' by '{}'",
track.name,
track.artist
);
println!(" ๐จ RATE LIMIT DETECTED during form load!");
break;
}
}
}
}
println!("\n=== Summary ===");
println!("๐ Tracks processed: {processed_count}");
println!("โ๏ธ Edits made: {edits_made}");
println!("๐จ Rate limit hits: {rate_limit_hits}");
if rate_limit_hits > 0 {
println!("\n๐ฏ Rate limiting was triggered.");
} else {
println!("\nโจ All changes completed successfully!");
}
Ok(())
}