-
-
Save twhite96/9c7122710aaf4fa3d4f8530f2a70031c to your computer and use it in GitHub Desktop.
Compare Kobo store prices
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
#!/usr/bin/perl -w | |
# Compare book prices across multiple Kobo stores | |
# | |
use strict; | |
use warnings; | |
use threads; | |
use WWW::Mechanize; | |
# FIXME | |
my $mech = WWW::Mechanize->new( | |
ssl_opts => { verify_hostname => 0 } | |
); | |
my %stores = ( | |
"Austria" => "https://www.kobo.com/at/de/ebook/", | |
"Australia" => "https://www.kobo.com/au/en/ebook/", | |
"Belgium (French)" => "https://www.kobo.com/be/fr/ebook/", | |
"Belgium (Dutch)" => "https://www.kobo.com/be/nl/ebook/", | |
"Brazil" => "https://www.kobo.com/br/pt/ebook/", | |
"Canada (English)" => "https://www.kobo.com/ca/en/ebook/", | |
"Canada (French)" => "https://www.kobo.com/ca/fr/ebook/", | |
"Switzerland (French)" => "https://www.kobo.com/ch/fr/ebook/", | |
"Germany" => "https://www.kobo.com/de/de/ebook/", | |
"Denmark" => "https://www.kobo.com/dk/da/ebook/", | |
"Spain" => "https://www.kobo.com/es/es/ebook/", | |
"Finland" => "https://www.kobo.com/fi/fi/ebook/", | |
"France" => "https://www.kobo.com/fr/fr/ebook/", | |
"UK" => "https://www.kobo.com/gb/en/ebook/", | |
"Greece" => "https://www.kobo.com/gr/en/ebook/", | |
"Hong Kong (English)" => "https://www.kobo.com/hk/en/ebook/", | |
"Hong Kong (Chinese)" => "https://www.kobo.com/hk/zh/ebook/", | |
"Eire" => "https://www.kobo.com/ie/en/ebook/", | |
"India" => "https://www.kobo.com/in/en/ebook/", | |
"Italy" => "https://www.kobo.com/it/it/ebook/", | |
"Japan" => "https://www.kobo.com/jp/ja/ebook/", | |
"Luxembourg" => "https://www.kobo.com/lu/fr/ebook/", | |
"Mexico" => "https://www.kobo.com/mx/es/ebook/", | |
"Malaysia" => "https://www.kobo.com/my/en/ebook/", | |
"Holland" => "https://www.kobo.com/nl/nl/ebook/", | |
"Norway" => "https://www.kobo.com/no/nb/ebook/", | |
"New Zealand" => "https://www.kobo.com/nz/en/ebook/", | |
"Poland" => "https://www.kobo.com/pl/pl/ebook/", | |
"Portugal" => "https://www.kobo.com/pt/pt/ebook/", | |
"Sweden" => "https://www.kobo.com/se/sv/ebook/", | |
"Singapore" => "https://www.kobo.com/sg/en/ebook/", | |
"Turkey" => "https://www.kobo.com/tr/tr/ebook/", | |
"Taiwan" => "https://www.kobo.com/tw/zh/ebook/", | |
"USA" => "https://www.kobo.com/us/en/ebook/", | |
"World-wide" => "https://www.kobo.com/ww/en/ebook/", | |
"South Africa" => "https://www.kobo.com/za/en/ebook/", | |
"Philippines" => "https://www.kobo.com/ph/en/ebook/", | |
"Thailand" => "https://www.kobo.com/th/en/ebook/", | |
"Cyprus" => "https://www.kobo.com/cy/en/ebook/", | |
"Czech Republic" => "https://www.kobo.com/cz/cs/ebook/", | |
"Estonia" => "https://www.kobo.com/ee/en/ebook/", | |
"Lithuania" => "https://www.kobo.com/lt/en/ebook/", | |
"Malta" => "https://www.kobo.com/mt/en/ebook/", | |
"Romania" => "https://www.kobo.com/ro/ro/ebook/", | |
"Slovak Republic" => "https://www.kobo.com/sk/en/ebook/", | |
"Slovenia" => "https://www.kobo.com/si/en/ebook/", | |
); | |
# Currency rates as of late-ish 2023. Should fetch these automatically... | |
my %rates = ( | |
"GBP" => 1.0, | |
"USD" => 0.79402, | |
"CAD" => 0.583979993380163, | |
"EUR" => 0.859160264040688, | |
"AUD" => 0.50942512511228, | |
"INR" => 0.009615580263062, | |
"BRL" => 0.163208031369973, | |
"DKK" => 0.115310137956743, | |
"HKD" => 0.101322338276436, | |
"JPY" => 0.00542642431523, | |
"MXN" => 0.047523831555867, | |
"NOK" => 0.074087672866888, | |
"PLN" => 0.192100811907984, | |
"SEK" => 0.072038674834173, | |
"CHF" => 0.899686520376176, | |
"TWD" => 0.024918649487445, # New Taiwan Dollar | |
"TRY" => 0.029931861113694, | |
"CZK" => 0.035607803812372, | |
"NZD" => 0.469622719489874, | |
"RON" => 0.174101094205896, | |
"ZAR" => 0.042517865896064, | |
"PHP" => 0.01402610689716, | |
"MYR" => 0.168817987152034, | |
"SGD" => 0.586037401138296, | |
); | |
my $book = shift; | |
# Allow parameter to be a complete URL - we just want the trailing part of the path | |
$book =~ s{.*/}{}; | |
# Thread method to fetch the price and currency for a country at a URL | |
sub get_price { | |
my $country = shift; | |
my $url = shift; | |
$mech->get($url); | |
my $content = $mech->content; | |
my $price; | |
if ($content =~ m{<meta.*?property="og:price".*?content="(.*?)".*?>}m) { | |
$price = $1; | |
$price =~ s/,/./; | |
} | |
my $currency; | |
if ($content =~ m{<meta.*?property="og:currency_code".*?content="(.*?)".*?>}m) { | |
$currency = $1; | |
} | |
return ($country, $price, $currency); | |
} | |
my @threads; | |
# Run one thread to check each store. This is simple but a bit excessive | |
foreach my $country (sort keys %stores) { | |
push @threads, threads->create(\&get_price, $country, $stores{$country} . $book); | |
} | |
my $cheapestPrice = undef; | |
my @cheapestStore; | |
my $longestCountry = 0; | |
my @results; | |
foreach my $thread (@threads) { | |
my ($country, $price, $currency) = $thread->join(); | |
$longestCountry = length($country) if length($country) > $longestCountry; | |
if (defined $price && defined $currency) { | |
if (exists $rates{$currency}) { | |
my $gbp = $rates{$currency} * $price; | |
push @results, [ $country, $currency, $price, $gbp ]; | |
if ((defined $cheapestPrice && $gbp < $cheapestPrice) | |
|| !defined $cheapestPrice) { | |
$cheapestPrice = $gbp; | |
@cheapestStore = (); | |
} | |
if ($gbp <= $cheapestPrice) { | |
push @cheapestStore, $country; | |
} | |
} else { | |
print STDERR "Unknown rate for $currency ($country)\n"; | |
} | |
} | |
} | |
foreach my $aref (sort {$a->[3] <=> $b->[3]} @results) { | |
printf("%-*s £%.2f %3s %s\n", $longestCountry, $aref->[0], $aref->[3], $aref->[1], $aref->[2]); | |
} | |
printf("\nCheapest price is £%.2f in:\n", $cheapestPrice); | |
foreach my $c (@cheapestStore) { | |
printf("\t%s %s\n", $c, $stores{$c} . $book); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment