Skip to content

Instantly share code, notes, and snippets.

@twhite96
Forked from chrisridd/kobocompare
Created July 10, 2024 17:33
Show Gist options
  • Save twhite96/9c7122710aaf4fa3d4f8530f2a70031c to your computer and use it in GitHub Desktop.
Save twhite96/9c7122710aaf4fa3d4f8530f2a70031c to your computer and use it in GitHub Desktop.
Compare Kobo store prices
#!/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