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.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 ;; Parsing17
parse-chatters-response18
parse-chat-settings19
build-chat-message-body20
build-announcement-body22
;; API functions23
twitch-send-chat-message24
twitch-chatters25
twitch-chat-settings26
twitch-update-chat-settings27
twitch-send-announcement)29
(begin31
;; ---------------------------------------------------------------32
;; Parsing helpers33
;; ---------------------------------------------------------------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-id65
sender_id: sender-id66
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 color77
#{ message: message color: color }78
#{ message: message })))80
;; ---------------------------------------------------------------81
;; API functions82
;; ---------------------------------------------------------------84
;;; Send a chat message to a channel.85
;;; Requires user:write:chat scope.86
;;;87
;;; broadcaster-id: channel to send to88
;;; sender-id: the authenticated user's ID89
;;; message: text to send90
;;; Optional reply-parent-message-id for threading.91
(define (twitch-send-chat-message client broadcaster-id sender-id message92
. 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 channel102
;;; moderator-id: must be the broadcaster or a moderator103
;;; Optional opts dict:104
;;; first: number (1-1000, default: 100)105
;;; after: pagination cursor106
(define (twitch-chatters client broadcaster-id moderator-id . rest)107
(let ((opts (if (null? rest) #{} (car rest))))108
(let* ((params (list109
(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-append118
(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 (list127
(cons "broadcaster_id" broadcaster-id)128
(cons "moderator_id" moderator-id)))129
(url (string-append130
(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: boolean140
;;; follower-mode: boolean141
;;; follower-mode-duration: minutes (0 = any follower)142
;;; slow-mode: boolean143
;;; slow-mode-wait-time: seconds (3-120)144
;;; subscriber-mode: boolean145
;;; unique-chat-mode: boolean146
(define (twitch-update-chat-settings client broadcaster-id moderator-id147
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-append184
(twitch-api-url client "chat" "settings")185
(build-query-string186
(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 text194
;;; Optional color: "blue", "green", "orange", "purple", "primary"195
(define (twitch-send-announcement client broadcaster-id moderator-id196
message . rest)197
(let* ((color (if (null? rest) #f (car rest)))198
(body (build-announcement-body message color))199
(url (string-append200
(twitch-api-url client "chat" "announcements")201
(build-query-string202
(list (cons "broadcaster_id" broadcaster-id)203
(cons "moderator_id" moderator-id))))))204
(twitch-post/json client url body)))206
))