AtlatestRepositorysigil-twitch
sigil-twitch / tree / src / twitchschedule.sgl
1
;;; (twitch schedule) - Twitch stream schedule management.2
;;;3
;;; CRUD operations for schedule segments (recurring and non-recurring),4
;;; vacation settings, and iCalendar export.5
;;;6
;;; Requires channel:manage:schedule scope for write operations.7
;;; Read operations work with any app or user token.9
(define-library (twitch schedule)10
(import (sigil core)11
(sigil dict)12
(sigil string)13
(sigil struct)14
(sigil json)15
(only (sigil http) build-query-string)16
(sigil http client)17
(twitch))19
(export ;; Records20
twitch-schedule-segment21
twitch-schedule-segment?22
twitch-schedule-segment-id23
twitch-schedule-segment-start-time24
twitch-schedule-segment-end-time25
twitch-schedule-segment-title26
twitch-schedule-segment-canceled-until27
twitch-schedule-segment-category28
twitch-schedule-segment-is-recurring30
;; Parsing31
parse-schedule-segment33
;; API functions34
twitch-schedule35
twitch-create-segment36
twitch-update-segment37
twitch-delete-segment38
twitch-schedule-vacation39
twitch-schedule-ical)41
(begin43
;; ---------------------------------------------------------------44
;; Records45
;; ---------------------------------------------------------------47
(define-struct twitch-schedule-segment48
(id)49
(start-time default: #f)50
(end-time default: #f)51
(title default: "")52
(canceled-until default: #f)53
(category default: #{})54
(is-recurring default: #f))56
;; ---------------------------------------------------------------57
;; Parsing58
;; ---------------------------------------------------------------60
;;; Parse a schedule segment from the API response.61
(define (parse-schedule-segment data)62
(twitch-schedule-segment63
id: (dict-ref data id:)64
start-time: (dict-ref data start_time: #f)65
end-time: (dict-ref data end_time: #f)66
title: (dict-ref data title: "")67
canceled-until: (let ((v (dict-ref data canceled_until: #f)))68
(if (or (not v) (eq? v 'null)) #f v))69
category: (let ((cat (dict-ref data category: #f)))70
(if cat cat #{}))71
is-recurring: (dict-ref data is_recurring: #f)))73
;; ---------------------------------------------------------------74
;; API functions75
;; ---------------------------------------------------------------77
;;; Get the stream schedule for a broadcaster.78
;;; Returns a dict with segments: (list of twitch-schedule-segment)79
;;; and vacation: (dict or #f).80
;;;81
;;; Optional opts dict:82
;;; start-time: ISO 8601 datetime (filter segments on/after)83
;;; first: number (1-25, default: 20)84
;;; after: pagination cursor85
(define (twitch-schedule client broadcaster-id . rest)86
(let ((opts (if (null? rest) #{} (car rest))))87
(let* ((params (list88
(cons "broadcaster_id" broadcaster-id)89
(cons "start_time"90
(dict-ref opts start-time: #f))91
(cons "first"92
(if (dict-ref opts first: #f)93
(number->string (dict-ref opts first:))94
#f))95
(cons "after"96
(dict-ref opts after: #f))))97
(url (string-append98
(twitch-api-url client "schedule")99
(build-query-string params)))100
(response (twitch-get/json client url))101
(data (dict-ref response data: #{}))102
(segments (dict-ref data segments: #[]))103
(vacation (dict-ref data vacation: #f)))104
#{ segments: (map parse-schedule-segment (array->list segments))105
vacation: vacation })))107
;;; Create a new schedule segment.108
;;; Requires channel:manage:schedule scope.109
;;;110
;;; start-time: ISO 8601 datetime111
;;; timezone: IANA timezone string (e.g., "America/New_York")112
;;; Optional opts dict:113
;;; duration: minutes (default: 240)114
;;; title: segment title115
;;; category-id: game/category ID116
;;; is-recurring: boolean (default: #f)117
(define (twitch-create-segment client broadcaster-id start-time timezone118
. rest)119
(let ((opts (if (null? rest) #{} (car rest))))120
(let* ((body (let* ((b #{ start_time: start-time121
timezone: timezone122
is_recurring: (dict-ref opts is-recurring: #f) })123
(b (if (dict-ref opts duration: #f)124
(dict-set b duration:125
(dict-ref opts duration:))126
b))127
(b (if (dict-ref opts title: #f)128
(dict-set b title: (dict-ref opts title:))129
b))130
(b (if (dict-ref opts category-id: #f)131
(dict-set b category_id:132
(dict-ref opts category-id:))133
b)))134
b))135
(url (string-append136
(twitch-api-url client "schedule" "segment")137
(build-query-string138
(list (cons "broadcaster_id" broadcaster-id)))))139
(response (twitch-post/json client url body))140
(data (dict-ref response data: #{}))141
(segments (dict-ref data segments: #[])))142
(if (> (array-length segments) 0)143
(parse-schedule-segment (array-ref segments 0))144
#f))))146
;;; Update an existing schedule segment.147
;;; Requires channel:manage:schedule scope.148
;;;149
;;; updates dict may contain:150
;;; start-time: ISO 8601 datetime151
;;; timezone: IANA timezone string152
;;; duration: minutes153
;;; title: segment title154
;;; category-id: game/category ID155
;;; is-canceled: boolean (cancel next occurrence of recurring)156
(define (twitch-update-segment client broadcaster-id segment-id updates)157
(let* ((body (let* ((b #{})158
(b (if (dict-ref updates start-time: #f)159
(dict-set b start_time:160
(dict-ref updates start-time:))161
b))162
(b (if (dict-ref updates timezone: #f)163
(dict-set b timezone:164
(dict-ref updates timezone:))165
b))166
(b (if (dict-ref updates duration: #f)167
(dict-set b duration:168
(dict-ref updates duration:))169
b))170
(b (if (dict-ref updates title: #f)171
(dict-set b title: (dict-ref updates title:))172
b))173
(b (if (dict-ref updates category-id: #f)174
(dict-set b category_id:175
(dict-ref updates category-id:))176
b))177
(b (if (dict-ref updates is-canceled: #f)178
(dict-set b is_canceled:179
(dict-ref updates is-canceled:))180
b)))181
b))182
(url (string-append183
(twitch-api-url client "schedule" "segment")184
(build-query-string185
(list (cons "broadcaster_id" broadcaster-id)186
(cons "id" segment-id))))))187
(twitch-patch/json client url body)))189
;;; Delete a schedule segment.190
;;; WARNING: For recurring segments, this deletes the entire series.191
;;; Requires channel:manage:schedule scope.192
(define (twitch-delete-segment client broadcaster-id segment-id)193
(let ((url (string-append194
(twitch-api-url client "schedule" "segment")195
(build-query-string196
(list (cons "broadcaster_id" broadcaster-id)197
(cons "id" segment-id))))))198
(twitch-delete/json client url)))200
;;; Set or clear vacation mode on the schedule.201
;;; Requires channel:manage:schedule scope.202
;;;203
;;; To set vacation: pass enabled: #t with start-time:, end-time:,204
;;; and timezone:.205
;;; To clear vacation: pass enabled: #f (other fields ignored).206
(define (twitch-schedule-vacation client broadcaster-id opts)207
(let* ((enabled (dict-ref opts enabled: #f))208
(body (if enabled209
#{ is_vacation_enabled: #t210
vacation_start_time: (dict-ref opts start-time:)211
vacation_end_time: (dict-ref opts end-time:)212
timezone: (dict-ref opts timezone:) }213
#{ is_vacation_enabled: #f }))214
(url (string-append215
(twitch-api-url client "schedule" "settings")216
(build-query-string217
(list (cons "broadcaster_id" broadcaster-id))))))218
(twitch-put/json client url body)))220
;;; Get the schedule as iCalendar (.ics) format.221
;;; Returns the raw iCalendar string.222
(define (twitch-schedule-ical client broadcaster-id)223
(let ((url (string-append224
(twitch-api-url client "schedule" "icalendar")225
(build-query-string226
(list (cons "broadcaster_id" broadcaster-id))))))227
(check-twitch-response/raw228
(http-get url headers: (twitch-auth-headers client)))))230
))