summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go133
1 files changed, 133 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..d440abd
--- /dev/null
+++ b/main.go
@@ -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)
+ }
+}