How to download the daily Bing wallpaper using F#

In this post we will see how to use F# scripting to download the daily Bing wallpaper on Windows or Linux.

It is basically a simple application of the F# JSON Type Provider.

Complete source code from this post.

Getting the download URL

First we have to get the image information in JSON format from here: http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US.

Here is a sample output:

{
  "images": [
    {
      "startdate": "20160826",
      "fullstartdate": "201608260700",
      "enddate": "20160827",
      "url": "/az/hprichbg/rb/PufferfishNest_EN-US10860680557_1920x1080.jpg",
      "urlbase": "/az/hprichbg/rb/PufferfishNest_EN-US10860680557",
      "copyright": "Sand pattern made by a pufferfish near Amami Ōshima, Kagoshima, Japan (© Yoji Okata/Minden Pictures)",
      "copyrightlink": "http://www.bing.com/search?q=Torquigener+albomaculosus&form=hpcapt&filters=HpDate:%2220160826_0700%22",
      "wp": true,
      "hsh": "91acacc9d01c3b057a0d7ba8870d80f4",
      "drk": 1,
      "top": 1,
      "bot": 1,
      "hs": []
    }
  ],
  "tooltips": {
    "loading": "Loading...",
    "previous": "Previous image",
    "next": "Next image",
    "walle": "This image is not available to download as wallpaper.",
    "walls": "Download this image. Use of this image is restricted to wallpaper only."
  }
}

To download the image, we need the value of the url property of the first item in images.

Using the JSON Type Provider

A really simple way to do this is to use the JSON Type Provider from the F# Data libraray.

After installing (e.g. with Paket) and referencing F# Data, we can generate the type based on the sample above like this:

#r @"packages/FSharp.Data/lib/net40/FSharp.Data.dll"

open FSharp.Data

type Wallpaper = JsonProvider<"data.json">

Then we load the real data with:

let url =
    @"http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US"
let data = Wallpaper.Load(url)

Type safety and autocomplete

Next we can access the images array with data.Images and access the url property from the first element with:

(data.Images |> Array.head).Url

It is very nice that we get type safety as well as autocomplete here.

Now the download URL can be created:

sprintf @"http://bing.com%s" (data.Images |> Array.head).Url

Downloading the image

Downloading a file is really easy and self-explanatory:

module Web =
    open System.Net

    let downloadFile (url:String) (dest:String) =
        use wc = new WebClient()
        wc.DownloadFile(url, dest)

Putting it together

That’s it. Now we can put everything together like this:

let fileName = 
    __SOURCE_DIRECTORY__ 
    </> sprintf @"images/%s.jpg" (Guid.NewGuid().ToString())
do Directory.CreateDirectory(__SOURCE_DIRECTORY__ </> @"images/") |> ignore
do Web.downloadFile BingWallpaper.downloadUrl fileName
do printfn @"%s" fileName

The </> operator is the infix version of Path.Combine.

The complete code

#r @"packages/FSharp.Data/lib/net40/FSharp.Data.dll"

open System
open System.IO

module BingWallpaper =
    open FSharp.Data

    type Wallpaper = JsonProvider<"data.json">

    let downloadUrl = 
        let url = 
            @"http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US"
        let data = Wallpaper.Load(url)
        sprintf @"http://bing.com%s" (data.Images |> Array.head).Url

module Web =
    open System.Net

    let downloadFile (url:String) (dest:String) =
        use wc = new WebClient()
        wc.DownloadFile(url, dest)

module Helper =
    let (</>) a b = Path.Combine(a,b)

module App =
    open Helper

    let run =
        let dirInfo = 
            Directory.CreateDirectory(__SOURCE_DIRECTORY__ </> @"images/") 
        let fileName = 
            dirInfo.FullName </> sprintf @"%s.jpg" (Guid.NewGuid().ToString())
        do Web.downloadFile BingWallpaper.downloadUrl fileName
        do printfn @"%s" fileName

App.run

Setting up the wallpaper

Now we can call the script from the command line and replace the current wallpaper.

On Ubuntu e.g. this script could be run after startup:

#!/bin/bash
path=$(fsharpi download_wallpaper.fsx)
file="file://$path"
gsettings set org.gnome.desktop.background picture-uri $file

To get this working on Linux the Mono and F# packages have to be installed. Everything works really smoothly with VS Code and Ionide.

Conclusion

We have seen that with F# and F# Type Providers it is really easy to parse JSON data e.g. coming from a web service.

The code is readable and concise.

Autocompletion and type safety make programming easy and fun.

Complete source code from this post.