Tag: jq

  • How much is that BLÅHAJ in the (terminal) window?

    How much is that BLÅHAJ in the (terminal) window?

    IKEA’s toy BLÅHAJ shark has become a beloved Internet icon over the past sev­er­al years. I thought it might be cute to write a lit­tle Perl to get info about it and even dis­play a cud­dly pic­ture right in the ter­mi­nal where I’m run­ning the code. Maybe this will give you some ideas for your own quick web clients. Of course, you could accom­plish all of these things using a pipeline of indi­vid­ual command-​line util­i­ties like curl, jq, and GNU core­uti­ls’ base64. These exam­ples focus on Perl as the glue, though.

    Warning: dodgy API ahead

    I haven’t found a publicly-​documented and ‑sup­port­ed offi­cial API for query­ing IKEA prod­uct infor­ma­tion but oth­ers have decon­struct­ed the company’s web site AJAX requests so we can use that instead. The alter­na­tive would be to scrape the IKEA web site direct­ly which, although pos­si­ble, would be more tedious and prone to fail­ure should their design change. An unof­fi­cial API is also unre­li­able but the sim­pler client code is eas­i­er to change should any errors surface.

    Enter the Mojolicious

    My orig­i­nal goal was to do this in a sin­gle line issued to the perl com­mand, and luck­i­ly the Mojolicious framework’s ojo mod­ule is tailor-​made for such things. By adding a -Mojo switch to the perl com­mand, you get over a dozen quick single-​character func­tions for spin­ning up a quick web appli­ca­tion or, in our case, mak­ing and inter­pret­ing web requests with­out a lot of cer­e­mo­ny. Here’s the start of my one-​line request to the IKEA API for infor­ma­tion on their BLÅHAJ prod­uct, using ojo’s g func­tion to per­form an HTTP GET and dis­play­ing the JSON from the response body to the terminal.

    $ perl -Mojo -E 'say g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->body'

    This cur­rent­ly returns over 2,400 lines of data, so after read­ing it over I’ll con­vert the response body JSON to a Perl data struc­ture and dump only the main prod­uct infor­ma­tion using ojo’s r func­tion:

    $ perl -Mojo -E 'say r g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}'
    {
      "availability" => [],
      "breathTaking" => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
      "colors" => [
        {
          "hex" => "0058a3",
          "id" => 10007,
          "name" => "blue"
        },
        {
          "hex" => "ffffff",
          "id" => 10156,
          "name" => "white"
        }
      ],
      "contextualImageUrl" => "https://www.ikea.com/us/en/images/products/blahaj-soft-toy-shark__0877371_pe633608_s5.jpg",
      "currencyCode" => "USD",
      "discount" => "",
      "features" => [],
      "gprDescription" => {
        "numberOfVariants" => 0,
        "variants" => []
      },
      "id" => 90373590,
      "itemMeasureReferenceText" => "39 \x{bc} \"",
      "itemNo" => 90373590,
      "itemNoGlobal" => 30373588,
      "itemType" => "ART",
      "lastChance" => $VAR1->{"breathTaking"},
      "mainImageAlt" => "BL\x{c5}HAJ Soft toy, shark, 39 \x{bc} \"",
      "mainImageUrl" => "https://www.ikea.com/us/en/images/products/blahaj-soft-toy-shark__0710175_pe727378_s5.jpg",
      "name" => "BL\x{c5}HAJ",
      "onlineSellable" => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
      "pipUrl" => "https://www.ikea.com/us/en/p/blahaj-soft-toy-shark-90373590/",
      "price" => {
        "decimals" => 99,
        "isRegularCurrency" => $VAR1->{"breathTaking"},
        "prefix" => "\$",
        "separator" => ".",
        "suffix" => "",
        "wholeNumber" => 19
      },
      "priceNumeral" => "19.99",
      "quickFacts" => [],
      "tag" => "NONE",
      "typeName" => "Soft toy"
    }

    If I just want the price I can do:

    $ perl -Mojo -E 'say g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}->@{qw(currencyCode priceNumeral)}'
    USD19.99

    That ->@{qw(currencyCode priceNumeral)} towards the end uses the post­fix ref­er­ence slic­ing syn­tax intro­duced exper­i­men­tal­ly in Perl v5.20 and made offi­cial in v5.24. If you’re using an old­er perl, you’d say:

    $ perl -Mojo -E 'say @{g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}}{qw(currencyCode priceNumeral)}'
    USD19.99

    I pre­fer the for­mer, though, because it’s eas­i­er to read left-to-right.

    But I’m not in the United States! Where’s my native currency?

    You can either replace the ​”us/en” in the URL above or use the core I18N::LangTags::Detect mod­ule added in Perl v5.8.5 if you’re real­ly deter­mined to be portable across dif­fer­ent users’ locales. This is real­ly stretch­ing the def­i­n­i­tion of ​”one-​liner,” though.

    $ LANG=de_DE.UTF-8 perl -Mojo -MI18N::LangTags::Detect -E 'my @lang = (split /-/, I18N::LangTags::Detect::detect)[1,0]; say g("https://sik.search.blue.cdtapps.com/" . join("/", @lang == 2 ? @lang : ("us", "en")) . "/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}->@{qw(currencyCode priceNumeral)}'
    EUR27.99

    Window dressing

    It’s hard to envi­sion cud­dling a num­ber, but luck­i­ly the prod­uct infor­ma­tion returned above links to a JPEG file in the mainImageUrl key. My favorite ter­mi­nal app iTerm2 can dis­play images inline from either a file or Base64 encod­ed data, so adding an extra HTTP request and encod­ing from the core MIME::Base64 mod­ule yields:

    $ perl -Mojo -MMIME::Base64 -E 'say "\c[]1337;File=inline=1;width=100%:", encode_base64(g(g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}{mainImageUrl})->body), "\cG"'

    (You could just send the image URL to iTerm2’s bun­dled imgcat util­i­ty, but where’s the fun in that?)

    $ imgcat --url `perl -Mojo -E 'print g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}{mainImageUrl}'`

    But I don’t have iTerm2 or a Mac!

    I got you. At the expense of a num­ber of oth­er depen­den­cies, here’s a ver­sion that will work on any ter­mi­nal that sup­ports 256-​color mode with ANSI codes using Image::Term256Color from CPAN and a Unicode font with block char­ac­ters. I’ll also use Term::ReadKey to size the image for the width of your win­dow. (Again, this stretch­es the def­i­n­i­tion of ​“one-​liner.”)

    $ perl -Mojo -MImage::Term256Color -MTerm::ReadKey -E 'say for Image::Term256Color::convert(g(g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}{mainImageUrl})->body, {scale_x => (GetTerminalSize)[0], utf8 => 1})'

    I hate Mojolicious! Can’t you just use core modules?

    Fine. Here’s retriev­ing the prod­uct price using HTTP::Tiny and the pure-​Perl JSON pars­er JSON::PP, which were added to core in ver­sion 5.14.

    $ perl -MHTTP::Tiny -MJSON::PP -E 'say @{decode_json(HTTP::Tiny->new->get("https://sik.search.blue.cdtapps.com/us/en/search-result-page?types=PRODUCT&q=BLÅHAJ")->{content})->{searchResultPage}{products}{main}{items}[0]{product}}{qw(currencyCode priceNumeral)}'
    USD19.99

    Fetching and dis­play­ing a pic­ture of the hug­gable shark using MIME::Base64 or Image::Term256Color as above is left as an exer­cise to the reader.