-
-
Save benjacoblee/7a366f6b0c912f0dd754673e0cd1edf5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/home/benn/.asdf/shims/raku | |
use URI; | |
use URI::Escape; | |
use HTTP::Tiny; | |
use DOM::Tiny; | |
our $BASEDIR = "$*HOME/.lrix"; # $*HOME is a special variable | |
our $BASEURL = "https://www.musixmatch.com"; | |
our $LYRICS_NOT_AVAILABLE_ON_MXM = "Error: lyrics not available on musixmatch 😥"; # some songs are restricted on Musixmatch | |
our $ERROR_INVALID_INPUT = "Error: invalid input"; | |
multi sub MAIN($artist, $title) { | |
my $song_str = "{$artist.uc} - {$title.uc}"; | |
say_getting_lyrics_for($song_str); | |
my ($kebab_a, $kebab_t) = kebab($artist), kebab($title); | |
my %resp = get_lyrics($kebab_a, $kebab_t); | |
if %resp<lyrics> { | |
write_lyrics_to_f(%resp<lyrics>, $artist, $title) unless %resp<error>; # fail silently and retry | |
} else { | |
my ($percent_a, $percent_t) = percent_encode($artist), percent_encode($title); | |
my $qs = "$percent_a%20$percent_t"; | |
get_by_qs($qs); | |
} | |
} | |
multi sub MAIN($file) { | |
my @parsed = parse_text_file($file); | |
for @parsed { | |
say_getting_lyrics_for(percent_decode($_.uc)); | |
get_by_qs($_) | |
} | |
} | |
# We don't overload this because the implementation is exactly the same | |
multi sub get_lyrics($artist = "", $title = "", $slug = "") { | |
my $url = $slug ?? "$BASEURL/$slug" !! "$BASEURL/lyrics/$artist/$title"; | |
my %res = http_req_get_dom($url); | |
return %res if %res<error>; | |
my @lyrics = do for %res<dom>.find('.lyrics__content__ok') { $_.text if $_.text } | |
return %{error => Nil, lyrics => @lyrics} | |
} | |
sub get_search_results($qs) { | |
my $url = ("$BASEURL/search/$qs"); | |
my %res = http_req_get_dom($url); | |
return %res if %res<error>; | |
my @matches = []; | |
for %res<dom>.find('a.title') -> $el { | |
@matches.push(%{title => $el.all-text, url => $el.attr<href>}) | |
} | |
return %{error => Nil, matches => @matches} | |
} | |
#`( | |
Generic http get. It will either return: | |
1. Error, or | |
2. Representation of the DOM if request succeeded | |
Used by sub get_lyrics and sub get_search_results | |
) | |
sub http_req_get_dom($url) { | |
my $response = HTTP::Tiny.new.get($url); | |
return %{error => "Err: $response<reason>"} if not $response<status> == 200; | |
my $body = $response<content>.decode; | |
return %{error => Nil, dom => DOM::Tiny.parse($body)} | |
} | |
sub get_by_qs($qs) { | |
my @matches = get_matches($qs); | |
my $input = get; | |
exit if $input eq 0.Str; | |
try { | |
my $idx = $input.Int - 1; | |
if not $idx > @matches.elems and not $idx < 0 { | |
my %selection = @matches[$idx]; | |
my ($a, $t) = %selection<url>.split("/")[*-2..*]; | |
my $song_str = "{$a.uc} - {$t.uc}"; | |
say_getting_lyrics_for($song_str); | |
my %resp = get_lyrics("", "", %selection<url>); | |
return say %resp<error> if %resp<error>; | |
if %resp<lyrics> { | |
write_lyrics_to_f(%resp<lyrics>, $a, $t) | |
} else { | |
say $LYRICS_NOT_AVAILABLE_ON_MXM; | |
} | |
} | |
} | |
if $! { | |
say $ERROR_INVALID_INPUT; | |
} | |
} | |
sub get_matches($qs) { | |
my %resp = get_search_results($qs); | |
return say %resp<error> if %resp<error>; | |
my (@matches) = %resp<matches>; | |
say_pretty_matches(@matches); | |
return @matches; | |
} | |
sub determine_fp($fname) { | |
my $fp; | |
if %*ENV<LRIX_DOWNLOAD_DIR> { | |
try { | |
mkdir %*ENV<LRIX_DOWNLOAD_DIR> unless %*ENV<LRIX_DOWNLOAD_DIR>.IO ~~ :d; | |
$fp = "%*ENV<LRIX_DOWNLOAD_DIR>/$fname"; | |
} | |
if $! { | |
say $!; | |
} | |
} else { | |
my $basedir_exists = $BASEDIR.IO ~~ :d; | |
$BASEDIR.IO.mkdir unless $basedir_exists; | |
$fp = "$BASEDIR/$fname"; | |
} | |
return $fp; | |
} | |
sub write_lyrics_to_f(@resp, $a, $t) { | |
my $fname = "$a - $t.txt"; | |
my $fp = determine_fp($fname); | |
spurt($fp, ""); # clear file contents | |
for (@resp) -> $s { | |
say $s if $s; | |
try { | |
spurt($fp, $s, :append) unless not $s; | |
} | |
if $! { | |
die $!; | |
} | |
say "Wrote lyrics to $fp 🔥"; | |
} | |
} | |
# musixmatch artist / track names are usually kebab | |
sub kebab($str) { | |
my $tc = map { .tc },($str.split(" ")); | |
return $tc.join("-") | |
} | |
# for spaces | |
sub percent_encode($str) { | |
return $str.split(" ").join("%20"); | |
} | |
sub percent_decode($str) { | |
return $str.split("%20").join(" "); | |
} | |
sub say_getting_lyrics_for($song_str) { | |
say "Getting lyrics for \"$song_str\" ⏳"; | |
} | |
sub say_pretty_matches(@matches) { | |
say "Found results 🔍"; | |
for @matches.kv -> $i, %el { | |
my ($title, $url) = %el<title>, %el<url>; | |
say "{$i+1}. $title: $url" | |
} | |
say "0. Exit"; | |
} | |
sub parse_text_file($file) { | |
my $f = slurp($file).lines; | |
my @a = ($f.grep: { $^lines ne "" }).map: { | |
my $song_str = $^str.split("-").map: { $^str.trim } | |
percent_encode($song_str); | |
} | |
return @a; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment