diff options
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 133 |
1 files changed, 133 insertions, 0 deletions
@@ -0,0 +1,133 @@ +// Copyright 2020 Nick White. +// Use of this source code is governed by the GPLv3 +// license that can be found in the LICENSE file. + +// bbcschedule lists a day schedule from a BBC radio station (which +// can be in the past, present or future), including IDs that work +// with get_iplayer. +// +// This uses the BBC's broadcasts/schedules api, which is roughly +// documented at https://rms.api.bbc.co.uk/docs and +// https://rms.api.bbc.co.uk/docs/swagger.json +// +// An alternative to these APIs would be doing what get_iplayer does +// in get_links_schedule_mojo and get_links_schedule_json and +// extracting the contents of the <script type="application/ld+json"> +// with "@graph" from an HTML page like +// https://www.bbc.co.uk/schedules/p00fzl7j/2020/11/14 +// But who wants to parse HTML if it can be avoided, eh? +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "strings" + "time" +) + +const usage = `Usage: bbcschedule [-station stationname] [-date yyyy-mm-dd] [-v] + +Lists the schedule for a BBC radio station. +` + +const urlBase = "https://rms.api.bbc.co.uk/v2/broadcasts/schedules/%s/%s" + +// Broadcasts holds the parts of the JSON response that we care about. +// It is intended to be filled by json.Unmarshal. +type Broadcasts struct { + Data []struct { + Urn string + Start string + Duration int + Titles struct { + Primary string + } + Synopses struct { + Short string + Medium string + Long string + } + } +} + +// fmtDate just reformats an RFC3339 date into a "Kitchen" style time. +func fmtDate(d string) string { + t, err := time.Parse(time.RFC3339, d) + if err != nil { + return d + } + return t.Format(time.Kitchen) +} + +// idFromUrn gets the ID from an iPlayer URN. This is the appropriate +// ID to give to get_iplayer, so it's what we use. +func idFromUrn(u string) string { + s := strings.Split(u, ":") + if len(s) != 5 { + return u + } + return s[4] +} + +func main() { + station := flag.String("station", "bbc_radio_fourfm", "Radio station name (called 'service id' by BBC)") + date := flag.String("date", time.Now().Format("2006-01-02"), "Date of schedule") + verbose := flag.Bool("v", false, "Verbose - use long descriptions for programmes") + + flag.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), usage) + flag.PrintDefaults() + } + flag.Parse() + + if flag.NArg() > 0 { + flag.Usage() + return + } + + url := fmt.Sprintf(urlBase, *station, *date) + resp, err := http.Get(url) + if err != nil { + log.Fatalf("Error getting url %s: %v\n", url, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Fatalf("Error getting url %s, HTTP status code: %d\n", url, resp.StatusCode) + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Error reading response for %s: %v\n", url, err) + } + + var r Broadcasts + err = json.Unmarshal(b, &r) + if err != nil { + log.Fatalf("Error unmarshalling json for %s: %v\n", url, err) + } + + var longest int + for _, v := range r.Data { + l := len(v.Titles.Primary) + if l > longest { + longest = l + } + } + for _, v := range r.Data { + fmtString := fmt.Sprintf("%%7s | %%-%ds | %%3d minutes | %%s | %%s\n", longest) + var synopsis string + if *verbose && len(v.Synopses.Long) > 0 { + synopsis = v.Synopses.Long + } else if *verbose && len(v.Synopses.Medium) > 0 { + synopsis = v.Synopses.Medium + } else { + synopsis = v.Synopses.Short + } + fmt.Printf(fmtString, fmtDate(v.Start), v.Titles.Primary, v.Duration / 60, idFromUrn(v.Urn), synopsis) + } +} |