-
Notifications
You must be signed in to change notification settings - Fork 1
/
timeslot.go
262 lines (231 loc) · 7.63 KB
/
timeslot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package myradio
import (
"bytes"
"fmt"
"strconv"
"time"
"github.com/UniversityRadioYork/myradio-go/api"
)
// Time is a custom time wrapper
type Time struct {
time.Time
}
// UnmarshalJSON a method to convert myradio times to unit time stamps
func (t *Time) UnmarshalJSON(b []byte) (err error) {
var str = string(b)
if str == "\"The End of Time\"" {
*t = Time{}
} else {
i, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return err
}
*t = Time{time.Unix(i, 0)}
}
return
}
// CurrentAndNext stores a pair of current and next show.
type CurrentAndNext struct {
Next Show `json:"next"`
Current Show `json:"current"`
}
// Show contains a summary of information about a URY schedule timeslot.
type Show struct {
Title string `json:"title"`
Desc string `json:"desc"`
Photo string `json:"photo"`
StartTime Time `json:"start_time"`
EndTime Time `json:"end_time"` // Sometimes "The End of Time"
Presenters string `json:"presenters,omitempty"`
Url string `json:"url,omitempty"`
Id uint64 `json:"id,omitempty"`
}
// Ends determines whether the Show has a defined end time.
func (s *Show) Ends() bool {
// populateShowTimes() will define EndTime as zero if there isn't one.
return !s.EndTime.IsZero()
}
// Timeslot contains information about a single timeslot in the URY schedule.
// A timeslot is a single slice of time on the schedule, typically one hour long.
type Timeslot struct {
Season
TimeslotID uint64 `json:"timeslot_id"`
TimeslotNum int `json:"timeslot_num"`
Tags []string `json:"tags"`
Time Time `json:"time"`
StartTime time.Time
StartTimeRaw string `json:"start_time"`
Duration time.Duration
DurationRaw string `json:"duration"`
MixcloudStatus string `json:"mixcloud_status"`
}
// populateTimeslotTimes sets the times for the given Timeslot given their raw values.
func (t *Timeslot) populateTimeslotTimes() (err error) {
// Remember: a Timeslot is a supertype of Season.
if err = t.populateSeasonTimes(); err != nil {
return
}
t.StartTime, err = parseShortTime(t.StartTimeRaw)
if err != nil {
return
}
t.Duration, err = parseDuration(t.DurationRaw)
return
}
// TracklistItem represents a single item in a show tracklist.
type TracklistItem struct {
Track
Album Album `json:"album"`
EditLink Link `json:"editlink"`
DeleteLink Link `json:"deletelink"`
Time time.Time
TimeRaw int64 `json:"time"`
StartTime time.Time
StartTimeRaw string `json:"starttime"`
AudioLogID uint `json:"audiologid"`
}
// GetCurrentAndNext gets the current and next shows at the time of the call.
// This consumes one API request.
func (s *Session) GetCurrentAndNext() (can *CurrentAndNext, err error) {
if err = s.get("/timeslot/currentandnext").Into(&can); err != nil {
return
}
// Sometimes, we only get a Current, not a Next.
// Don't try populate times on a show that doesn't exist.
if can.Next.EndTime.IsZero() {
return
}
return
}
// GetPreviousTimeslots gets the previous shows at the time of the call.
// This consumes one API request.
func (s *Session) GetPreviousTimeslots(numOfTimeslots int) (timeslots []Timeslot, err error) {
rq := api.NewRequest("/timeslot/previoustimeslots")
rq.Params["n"] = []string{strconv.Itoa(numOfTimeslots)}
rs := s.do(rq)
if err = rs.Into(×lots); err != nil {
return
}
for k := range timeslots {
err = timeslots[k].populateTimeslotTimes()
if err != nil {
return
}
}
return
}
// GetWeekSchedule gets the weekly schedule for ISO 8601 week week of year year.
// If such a schedule exists, it returns the result as an map from ISO 8601 weekdays to timeslot slices.
// Thus, 1 maps to Monday's timeslots; 2 to Tuesday; and so on.
// Each slice progresses chronologically from start of URY day to finish of URY day.
// If no such schedule exists, it returns a map of empty slices.
// If an error occurred, this is returned in error, and the timeslot map is undefined.
// This consumes one API request.
func (s *Session) GetWeekSchedule(year, week int) (map[int][]Timeslot, error) {
// TODO(CaptainHayashi): proper errors
if year < 0 {
return nil, fmt.Errorf("year %d is too low", year)
}
if week < 1 || 53 < week {
return nil, fmt.Errorf("week %d is not within the ISO range 1..53", week)
}
rq := api.NewRequestf("/timeslot/weekschedule/%d", week)
rq.Params["year"] = []string{strconv.Itoa(year)}
rs := s.do(rq)
// MyRadio responds with an empty object when the schedule is empty, so we need to catch that.
// See https://github.com/UniversityRadioYork/MyRadio/issues/665 for details.
if rs.IsEmpty() {
return map[int][]Timeslot{
1: {},
2: {},
3: {},
4: {},
5: {},
6: {},
7: {},
}, nil
}
// The timeslots come to us with string keys labelled with the weekday.
// These timeslots start from "1" (Monday) and go up to "7" (Sunday).
// Note that this is different from Go's view of the week (0 = Sunday, 1 = Monday)!
stringyTimeslots := make(map[string][]Timeslot)
if ierr := rs.Into(&stringyTimeslots); ierr != nil {
return nil, ierr
}
return destringTimeslots(stringyTimeslots)
}
// destringTimeslots converts a week schedule from string indices to integer indices.
// It takes a map from strings "1"--"7" to day schedules, and returns a map from integers 1--7 to day schedules.
// It returns an error if any of the string indices cannot be converted.
func destringTimeslots(stringyTimeslots map[string][]Timeslot) (map[int][]Timeslot, error) {
timeslots := make(map[int][]Timeslot)
for sday, ts := range stringyTimeslots {
day, derr := strconv.Atoi(sday)
if derr != nil {
return nil, derr
}
for i := range ts {
if terr := ts[i].populateTimeslotTimes(); terr != nil {
return nil, terr
}
}
timeslots[day] = ts
}
return timeslots, nil
}
// GetTimeslot retrieves the timeslot with the given ID.
// This consumes one API request.
func (s *Session) GetTimeslot(id int) (timeslot Timeslot, err error) {
if err = s.getf("/timeslot/%d", id).Into(×lot); err != nil {
return
}
err = timeslot.populateTimeslotTimes()
return
}
// GetCurrentTimeslot retrieves the current timeslot.
// This consumes one API request.
func (s *Session) GetCurrentTimeslot() (timeslot Timeslot, err error) {
if err = s.get("/timeslot/currenttimeslot").Into(×lot); err != nil {
return
}
err = timeslot.populateTimeslotTimes()
return
}
// GetCurrentTimeslotAtTime retrieves the current timeslot.
// This consumes one API request.
func (s *Session) GetCurrentTimeslotAtTime(time int) (timeslot Timeslot, err error) {
paramMap := make(map[string][]string)
paramMap["time"] = []string{strconv.Itoa(time)}
if err = s.getWithQueryParams("/timeslot/currenttimeslot", paramMap).Into(×lot); err != nil {
return
}
err = timeslot.populateTimeslotTimes()
return
}
// GetTrackListForTimeslot retrieves the tracklist for the timeslot with the given ID.
// This consumes one API request.
func (s *Session) GetTrackListForTimeslot(id int) (tracklist []TracklistItem, err error) {
if err = s.getf("/tracklistItem/tracklistfortimeslot/%d", id).Into(&tracklist); err != nil {
return
}
for k, v := range tracklist {
tracklist[k].Time = time.Unix(tracklist[k].TimeRaw, 0)
tracklist[k].StartTime, err = time.Parse("02/01/2006 15:04:05", v.StartTimeRaw)
if err != nil {
return nil, err
}
}
return
}
// PutMessage sends a message to the given timeslot.
// This consumes one API request.
func (s *Session) PutMessage(id uint64, msg string) (err error) {
var timeslot Timeslot
msg = "message=" + msg
err = s.putf("/timeslot/%d/sendmessage", *bytes.NewBufferString(msg), id).Into(×lot)
if err != nil {
return
}
err = timeslot.populateTimeslotTimes()
return
}