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.
8
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 ;; Records
20 twitch-schedule-segment
21 twitch-schedule-segment?
22 twitch-schedule-segment-id
23 twitch-schedule-segment-start-time
24 twitch-schedule-segment-end-time
25 twitch-schedule-segment-title
26 twitch-schedule-segment-canceled-until
27 twitch-schedule-segment-category
28 twitch-schedule-segment-is-recurring
30 ;; Parsing
31 parse-schedule-segment
33 ;; API functions
34 twitch-schedule
35 twitch-create-segment
36 twitch-update-segment
37 twitch-delete-segment
38 twitch-schedule-vacation
39 twitch-schedule-ical)
41 (begin
43 ;; ---------------------------------------------------------------
44 ;; Records
45 ;; ---------------------------------------------------------------
47 (define-struct twitch-schedule-segment
48 (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 ;; Parsing
58 ;; ---------------------------------------------------------------
60 ;;; Parse a schedule segment from the API response.
61 (define (parse-schedule-segment data)
62 (twitch-schedule-segment
63 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 functions
75 ;; ---------------------------------------------------------------
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 cursor
85 (define (twitch-schedule client broadcaster-id . rest)
86 (let ((opts (if (null? rest) #{} (car rest))))
87 (let* ((params (list
88 (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-append
98 (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 datetime
111 ;;; timezone: IANA timezone string (e.g., "America/New_York")
112 ;;; Optional opts dict:
113 ;;; duration: minutes (default: 240)
114 ;;; title: segment title
115 ;;; category-id: game/category ID
116 ;;; is-recurring: boolean (default: #f)
117 (define (twitch-create-segment client broadcaster-id start-time timezone
118 . rest)
119 (let ((opts (if (null? rest) #{} (car rest))))
120 (let* ((body (let* ((b #{ start_time: start-time
121 timezone: timezone
122 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-append
136 (twitch-api-url client "schedule" "segment")
137 (build-query-string
138 (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 datetime
151 ;;; timezone: IANA timezone string
152 ;;; duration: minutes
153 ;;; title: segment title
154 ;;; category-id: game/category ID
155 ;;; 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-append
183 (twitch-api-url client "schedule" "segment")
184 (build-query-string
185 (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-append
194 (twitch-api-url client "schedule" "segment")
195 (build-query-string
196 (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 enabled
209 #{ is_vacation_enabled: #t
210 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-append
215 (twitch-api-url client "schedule" "settings")
216 (build-query-string
217 (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-append
224 (twitch-api-url client "schedule" "icalendar")
225 (build-query-string
226 (list (cons "broadcaster_id" broadcaster-id))))))
227 (check-twitch-response/raw
228 (http-get url headers: (twitch-auth-headers client)))))
230 ))