AtlatestRepositorytube

tube / tree / src / tubeyoutube.sgl

1;;; (tube youtube) - YouTube-specific operations wrapping sigil-youtube.
2;;;
3;;; Upload with metadata, video listing with stats, analytics dashboard,
4;;; livestream management, and playlist management.
5;;;
6;;; Supports two authentication modes:
7;;; 1. Direct access token via YOUTUBE_ACCESS_TOKEN env var
8;;; 2. OAuth via YOUTUBE_CLIENT_ID/SECRET with auto-managed token storage
9
10(define-library (tube youtube)
11 (import (sigil core)
12 (sigil string)
13 (sigil dict)
14 (sigil struct)
15 (sigil json)
16 (sigil time)
17 (youtube)
18 (youtube upload)
19 (youtube analytics)
20 (youtube live)
21 (youtube playlist)
22 (youtube oauth)
23 (tube config))
24 (export require-youtube-client
25 tube-upload-video
26 tube-list-videos
27 tube-video-analytics
28 tube-youtube-status
29 tube-list-broadcasts
30 tube-create-broadcast
31 tube-go-live-youtube
32 tube-list-playlists
33 format-video
34 format-analytics-row
35 format-broadcast
36 format-playlist
37 ;; Re-exported from (youtube) for CLI use
38 youtube-video-id
39 youtube-video-title)
40 (begin
42 ;;; Build a youtube-client from config.
43 ;;;
44 ;;; Priority:
45 ;;; 1. If YOUTUBE_ACCESS_TOKEN is set, use it directly (legacy mode)
46 ;;; 2. If YOUTUBE_CLIENT_ID/SECRET are set, use OAuth with stored tokens
47 ;;; (auto-refreshes expired tokens)
48 ;;; 3. If YOUTUBE_API_KEY is set, use it for read-only access
49 ;;; 4. Otherwise, raise an error
50 (define (require-youtube-client config)
51 (let ((token (tube-config-youtube-access-token config))
52 (key (tube-config-youtube-api-key config))
53 (client-id (tube-config-youtube-client-id config))
54 (client-secret (tube-config-youtube-client-secret config)))
55 (cond
56 ;; Direct token — use as-is
57 (token
58 (youtube-client access-token: token
59 api-key: key))
60 ;; OAuth client credentials — load/refresh stored tokens
61 ((and client-id client-secret)
62 (let* ((oauth-cfg (youtube-oauth-config client-id client-secret))
63 (tokens (youtube-load-tokens)))
64 (if (not tokens)
65 (error (string-append
66 "No stored YouTube tokens found. "
67 "Run 'tube auth youtube' to authorize, "
68 "or set YOUTUBE_ACCESS_TOKEN directly.")))
69 (let-values (((client fresh-tokens)
70 (youtube-ensure-client oauth-cfg tokens
71 (current-second))))
72 client)))
73 ;; API key only — read-only
74 (key
75 (youtube-client api-key: key))
76 (else
77 (error (string-append
78 "No YouTube credentials set. "
79 "Set YOUTUBE_CLIENT_ID and YOUTUBE_CLIENT_SECRET for OAuth, "
80 "or YOUTUBE_ACCESS_TOKEN for direct token mode."))))))
82 ;; ============================================================
83 ;; Upload
84 ;; ============================================================
86 ;;; Upload a video to YouTube with metadata.
87 ;;; Returns the video resource dict on success.
88 (define (tube-upload-video config file-data title
89 . rest)
90 (let* ((client (require-youtube-client config))
91 (description (if (and (pair? rest) (car rest)) (car rest) ""))
92 (privacy (if (and (pair? rest) (pair? (cdr rest)) (cadr rest))
93 (cadr rest) "private"))
94 (metadata #{ snippet: #{ title: title
95 description: description
96 categoryId: "28" }
97 status: #{ privacyStatus: privacy } }))
98 (youtube-upload-video client metadata file-data)))
100 ;; ============================================================
101 ;; Video Listing
102 ;; ============================================================
104 ;;; Format a youtube-video record as a display string.
105 (define (format-video video)
106 (let ((stats (youtube-video-statistics video)))
107 (string-append
108 (youtube-video-id video) " "
109 (youtube-video-title video)
110 (if stats
111 (string-append
112 "\n Views: " (dict-ref stats viewCount: "0")
113 " Likes: " (dict-ref stats likeCount: "0")
114 " Comments: " (dict-ref stats commentCount: "0"))
115 ""))))
117 ;;; List recent videos from the authenticated channel.
118 ;;; Returns a list of youtube-video records.
119 (define (tube-list-videos config . rest)
120 (let* ((client (require-youtube-client config))
121 (limit (if (pair? rest) (car rest) 10))
122 (channel (youtube-channel-mine client))
123 (uploads-id (youtube-channel-uploads-playlist-id channel))
124 (items (youtube-playlist-items client uploads-id
125 max-results: limit))
126 (video-ids (map youtube-playlist-item-video-id items)))
127 (if (null? video-ids)
128 '()
129 (youtube-videos client video-ids))))
131 ;; ============================================================
132 ;; Analytics
133 ;; ============================================================
135 ;;; Format an analytics row as a display string.
136 (define (format-analytics-row row)
137 (string-join (map (lambda (v)
138 (if (string? v) v (number->string v)))
139 row)
140 "\t"))
142 ;;; Get a YouTube analytics summary for the given date range.
143 ;;; Returns a dict with column-headers and rows.
144 (define (tube-video-analytics config start-date end-date)
145 (let ((client (require-youtube-client config)))
146 (youtube-analytics-query client start-date end-date
147 "views,estimatedMinutesWatched,averageViewDuration,subscribersGained"
148 dimensions: "day"
149 sort: "-day")))
151 ;; ============================================================
152 ;; YouTube Status
153 ;; ============================================================
155 ;;; Get YouTube channel status overview.
156 ;;; Returns a formatted string with subscriber count, video count,
157 ;;; and recent video info.
158 (define (tube-youtube-status config)
159 (let* ((client (require-youtube-client config))
160 (channel (youtube-channel-mine client)))
161 (string-append
162 "YouTube Channel: " (youtube-channel-title channel) "\n"
163 "Subscribers: " (number->string (youtube-channel-subscriber-count channel)) "\n"
164 "Total Videos: " (number->string (youtube-channel-video-count channel)))))
166 ;; ============================================================
167 ;; Livestream
168 ;; ============================================================
170 ;;; Format a youtube-broadcast record.
171 (define (format-broadcast broadcast)
172 (string-append
173 (youtube-broadcast-id broadcast) " "
174 (youtube-broadcast-title broadcast) " "
175 "[" (youtube-broadcast-lifecycle-status broadcast) "]"
176 (let ((start (youtube-broadcast-scheduled-start broadcast)))
177 (if start (string-append " Scheduled: " start) ""))))
179 ;;; List upcoming/active broadcasts.
180 (define (tube-list-broadcasts config . rest)
181 (let* ((client (require-youtube-client config))
182 (status (if (pair? rest) (car rest) "upcoming")))
183 (youtube-list-broadcasts client broadcast-status: status)))
185 ;;; Create a new broadcast and stream, bind them.
186 ;;; Returns the broadcast record.
187 (define (tube-create-broadcast config title scheduled-start)
188 (let* ((client (require-youtube-client config))
189 (broadcast (youtube-create-broadcast client title scheduled-start
190 enable-auto-start: #t
191 enable-auto-stop: #t))
192 (stream (youtube-create-stream client
193 (string-append title " stream"))))
194 (youtube-bind-broadcast client
195 (youtube-broadcast-id broadcast)
196 (youtube-stream-id stream))
197 broadcast))
199 ;;; Transition a broadcast to live.
200 (define (tube-go-live-youtube config broadcast-id)
201 (let ((client (require-youtube-client config)))
202 (youtube-transition-broadcast client broadcast-id "live")))
204 ;; ============================================================
205 ;; Playlists
206 ;; ============================================================
208 ;;; Format a youtube-playlist record.
209 (define (format-playlist playlist)
210 (string-append
211 (youtube-playlist-id playlist) " "
212 (youtube-playlist-title playlist)
213 " (" (number->string (youtube-playlist-item-count playlist)) " items)"
214 " [" (youtube-playlist-privacy-status playlist) "]"))
216 ;;; List playlists for the authenticated channel.
217 (define (tube-list-playlists config)
218 (let ((client (require-youtube-client config)))
219 (youtube-playlists client)))
221 ))