AtlatestRepositorysigil-twitch

sigil-twitch / tree / src / twitchchat.sgl

1;;; (twitch chat) - Twitch chat operations.
2;;;
3;;; Send chat messages, list chatters, manage chat settings,
4;;; and send announcements via the Helix REST API.
5
6(define-library (twitch chat)
7 (import (sigil core)
8 (sigil dict)
9 (sigil string)
10 (sigil struct)
11 (sigil json)
12 (only (sigil http) build-query-string)
13 (sigil http client)
14 (twitch))
16 (export ;; Parsing
17 parse-chatters-response
18 parse-chat-settings
19 build-chat-message-body
20 build-announcement-body
22 ;; API functions
23 twitch-send-chat-message
24 twitch-chatters
25 twitch-chat-settings
26 twitch-update-chat-settings
27 twitch-send-announcement)
29 (begin
31 ;; ---------------------------------------------------------------
32 ;; Parsing helpers
33 ;; ---------------------------------------------------------------
35 ;;; Normalize chatters API response fields from snake_case to kebab-case.
36 (define (parse-chatters-response data)
37 #{ total: (dict-ref data total: 0)
38 data: (map (lambda (item)
39 #{ user-id: (dict-ref item user_id: "")
40 user-login: (dict-ref item user_login: "")
41 user-name: (dict-ref item user_name: "") })
42 (array->list (dict-ref data data: #[])))
43 cursor: (parse-pagination data) })
45 ;;; Normalize chat settings response fields from snake_case to kebab-case.
46 (define (parse-chat-settings data)
47 (let ((items (dict-ref data data: #[])))
48 (if (> (array-length items) 0)
49 (let ((s (array-ref items 0)))
50 #{ emote-mode: (dict-ref s emote_mode: #f)
51 follower-mode: (dict-ref s follower_mode: #f)
52 slow-mode: (dict-ref s slow_mode: #f)
53 subscriber-mode: (dict-ref s subscriber_mode: #f)
54 unique-chat-mode: (dict-ref s unique_chat_mode: #f)
55 slow-mode-wait-time:
56 (dict-ref s slow_mode_wait_time: 0)
57 follower-mode-duration:
58 (dict-ref s follower_mode_duration: #f) })
59 #{})))
61 ;;; Build chat message request body; exported for testability.
62 (define (build-chat-message-body broadcaster-id sender-id message . rest)
63 (let ((opts (if (null? rest) #{} (car rest))))
64 (let* ((b #{ broadcaster_id: broadcaster-id
65 sender_id: sender-id
66 message: message })
67 (b (if (dict-ref opts reply-parent-message-id: #f)
68 (dict-set b reply_parent_message_id:
69 (dict-ref opts reply-parent-message-id:))
70 b)))
71 b)))
73 ;;; Build announcement request body; exported for testability.
74 (define (build-announcement-body message . rest)
75 (let ((color (if (null? rest) #f (car rest))))
76 (if color
77 #{ message: message color: color }
78 #{ message: message })))
80 ;; ---------------------------------------------------------------
81 ;; API functions
82 ;; ---------------------------------------------------------------
84 ;;; Send a chat message to a channel.
85 ;;; Requires user:write:chat scope.
86 ;;;
87 ;;; broadcaster-id: channel to send to
88 ;;; sender-id: the authenticated user's ID
89 ;;; message: text to send
90 ;;; Optional reply-parent-message-id for threading.
91 (define (twitch-send-chat-message client broadcaster-id sender-id message
92 . rest)
93 (let* ((opts (if (null? rest) #{} (car rest)))
94 (body (build-chat-message-body broadcaster-id sender-id message opts))
95 (url (twitch-api-url client "chat" "messages")))
96 (twitch-post/json client url body)))
98 ;;; Get the list of chatters in a channel.
99 ;;; Requires moderator:read:chatters scope.
100 ;;;
101 ;;; broadcaster-id: the channel
102 ;;; moderator-id: must be the broadcaster or a moderator
103 ;;; Optional opts dict:
104 ;;; first: number (1-1000, default: 100)
105 ;;; after: pagination cursor
106 (define (twitch-chatters client broadcaster-id moderator-id . rest)
107 (let ((opts (if (null? rest) #{} (car rest))))
108 (let* ((params (list
109 (cons "broadcaster_id" broadcaster-id)
110 (cons "moderator_id" moderator-id)
111 (cons "first"
112 (if (dict-ref opts first: #f)
113 (number->string (dict-ref opts first:))
114 #f))
115 (cons "after"
116 (dict-ref opts after: #f))))
117 (url (string-append
118 (twitch-api-url client "chat" "chatters")
119 (build-query-string params)))
120 (data (twitch-get/json client url)))
121 (parse-chatters-response data))))
123 ;;; Get chat settings for a channel.
124 ;;; Returns a dict with chat mode settings.
125 (define (twitch-chat-settings client broadcaster-id moderator-id)
126 (let* ((params (list
127 (cons "broadcaster_id" broadcaster-id)
128 (cons "moderator_id" moderator-id)))
129 (url (string-append
130 (twitch-api-url client "chat" "settings")
131 (build-query-string params)))
132 (data (twitch-get/json client url)))
133 (parse-chat-settings data)))
135 ;;; Update chat settings for a channel.
136 ;;; Requires moderator:manage:chat_settings scope.
137 ;;;
138 ;;; settings dict may contain:
139 ;;; emote-mode: boolean
140 ;;; follower-mode: boolean
141 ;;; follower-mode-duration: minutes (0 = any follower)
142 ;;; slow-mode: boolean
143 ;;; slow-mode-wait-time: seconds (3-120)
144 ;;; subscriber-mode: boolean
145 ;;; unique-chat-mode: boolean
146 (define (twitch-update-chat-settings client broadcaster-id moderator-id
147 settings)
148 (let* ((body (let* ((b #{})
149 (b (if (not (eq? (dict-ref settings emote-mode: 'unset)
150 'unset))
151 (dict-set b emote_mode:
152 (dict-ref settings emote-mode:))
153 b))
154 (b (if (not (eq? (dict-ref settings follower-mode: 'unset)
155 'unset))
156 (dict-set b follower_mode:
157 (dict-ref settings follower-mode:))
158 b))
159 (b (if (dict-ref settings follower-mode-duration: #f)
160 (dict-set b follower_mode_duration:
161 (dict-ref settings follower-mode-duration:))
162 b))
163 (b (if (not (eq? (dict-ref settings slow-mode: 'unset)
164 'unset))
165 (dict-set b slow_mode:
166 (dict-ref settings slow-mode:))
167 b))
168 (b (if (dict-ref settings slow-mode-wait-time: #f)
169 (dict-set b slow_mode_wait_time:
170 (dict-ref settings slow-mode-wait-time:))
171 b))
172 (b (if (not (eq? (dict-ref settings subscriber-mode: 'unset)
173 'unset))
174 (dict-set b subscriber_mode:
175 (dict-ref settings subscriber-mode:))
176 b))
177 (b (if (not (eq? (dict-ref settings unique-chat-mode: 'unset)
178 'unset))
179 (dict-set b unique_chat_mode:
180 (dict-ref settings unique-chat-mode:))
181 b)))
182 b))
183 (url (string-append
184 (twitch-api-url client "chat" "settings")
185 (build-query-string
186 (list (cons "broadcaster_id" broadcaster-id)
187 (cons "moderator_id" moderator-id))))))
188 (twitch-patch/json client url body)))
190 ;;; Send an announcement in chat.
191 ;;; Requires moderator:manage:announcements scope.
192 ;;;
193 ;;; message: announcement text
194 ;;; Optional color: "blue", "green", "orange", "purple", "primary"
195 (define (twitch-send-announcement client broadcaster-id moderator-id
196 message . rest)
197 (let* ((color (if (null? rest) #f (car rest)))
198 (body (build-announcement-body message color))
199 (url (string-append
200 (twitch-api-url client "chat" "announcements")
201 (build-query-string
202 (list (cons "broadcaster_id" broadcaster-id)
203 (cons "moderator_id" moderator-id))))))
204 (twitch-post/json client url body)))
206 ))