Overview
Manage video content on Twitch, including:
Videos (VODs): Recorded broadcasts, highlights, and uploads
- Retrieve video metadata (title, duration, views)
- Delete videos from your channel
- Filter by type (archive, highlight, upload), game, and time period
Clips: Short highlight moments from streams
- Create clips from live streams
- Create clips from VODs at specific timestamps
- Retrieve clips by broadcaster, game, or clip ID
- Build automated clip compilation systems
Stream Markers: Timestamps you can place during a stream for VOD editing
- Create markers with descriptions
- Retrieve markers for a video
Prerequisites
- Videos:
channel:manage:videosto delete, no scope needed to read - Clips:
clips:editto create clips
Get Videos (VODs)
package main
import (
"context"
"fmt"
"log"
"github.com/Its-donkey/kappopher/helix"
)
func main() {
ctx := context.Background()
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)
// Get videos by user ID
videos, err := client.GetVideos(ctx, &helix.GetVideosParams{
UserID: "12345",
First: 10,
})
if err != nil {
log.Fatal(err)
}
fmt.Println("=== Recent Videos ===")
for _, video := range videos.Data {
fmt.Printf("\n%s\n", video.Title)
fmt.Printf(" ID: %s\n", video.ID)
fmt.Printf(" Type: %s\n", video.Type) // archive, highlight, upload
fmt.Printf(" Duration: %s\n", video.Duration)
fmt.Printf(" Views: %d\n", video.ViewCount)
fmt.Printf(" Created: %s\n", video.CreatedAt)
fmt.Printf(" URL: %s\n", video.URL)
}
// Get specific videos by ID
specificVideos, err := client.GetVideos(ctx, &helix.GetVideosParams{
IDs: []string{"video-id-1", "video-id-2"},
})
// Get videos by game
gameVideos, err := client.GetVideos(ctx, &helix.GetVideosParams{
GameID: "509658", // Just Chatting
First: 20,
Sort: "views", // time, trending, views
Type: "all", // all, archive, highlight, upload
})
// Filter by language and period
filteredVideos, err := client.GetVideos(ctx, &helix.GetVideosParams{
GameID: "509658",
Language: "en",
Period: "week", // all, day, week, month
})
}
Delete Videos
// Delete specific videos
deleted, err := client.DeleteVideos(ctx, []string{"video-id-1", "video-id-2"})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Deleted %d videos\n", len(deleted.Data))
for _, id := range deleted.Data {
fmt.Printf(" Deleted: %s\n", id)
}
Get Clips
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/Its-donkey/kappopher/helix"
)
func main() {
ctx := context.Background()
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)
broadcasterID := "12345"
// Get clips by broadcaster
clips, err := client.GetClips(ctx, &helix.GetClipsParams{
BroadcasterID: broadcasterID,
First: 20,
})
if err != nil {
log.Fatal(err)
}
fmt.Println("=== Top Clips ===")
for _, clip := range clips.Data {
fmt.Printf("\n%s\n", clip.Title)
fmt.Printf(" ID: %s\n", clip.ID)
fmt.Printf(" Creator: %s\n", clip.CreatorName)
fmt.Printf(" Views: %d\n", clip.ViewCount)
fmt.Printf(" Duration: %.1fs\n", clip.Duration)
fmt.Printf(" Created: %s\n", clip.CreatedAt)
fmt.Printf(" URL: %s\n", clip.URL)
fmt.Printf(" Thumbnail: %s\n", clip.ThumbnailURL)
}
// Get clips by game
gameClips, err := client.GetClips(ctx, &helix.GetClipsParams{
GameID: "509658",
First: 10,
})
// Get specific clips by ID
specificClips, err := client.GetClips(ctx, &helix.GetClipsParams{
IDs: []string{"clip-id-1", "clip-id-2"},
})
// Get clips from a time range
startTime := time.Now().AddDate(0, 0, -7) // Last 7 days
endTime := time.Now()
recentClips, err := client.GetClips(ctx, &helix.GetClipsParams{
BroadcasterID: broadcasterID,
StartedAt: &startTime,
EndedAt: &endTime,
First: 50,
})
// Get featured clips
featuredClips, err := client.GetClips(ctx, &helix.GetClipsParams{
BroadcasterID: broadcasterID,
IsFeatured: true,
})
}
Create Clips
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/Its-donkey/kappopher/helix"
)
func main() {
ctx := context.Background()
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)
broadcasterID := "12345"
// Create a clip from live stream
clip, err := client.CreateClip(ctx, &helix.CreateClipParams{
BroadcasterID: broadcasterID,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Clip created!\n")
fmt.Printf(" ID: %s\n", clip.Data[0].ID)
fmt.Printf(" Edit URL: %s\n", clip.Data[0].EditURL)
// Wait for clip to be processed
fmt.Println("Waiting for clip to process...")
time.Sleep(15 * time.Second)
// Get the processed clip
processedClip, err := client.GetClips(ctx, &helix.GetClipsParams{
IDs: []string{clip.Data[0].ID},
})
if err == nil && len(processedClip.Data) > 0 {
fmt.Printf("Clip ready: %s\n", processedClip.Data[0].URL)
}
// Create clip with delay (captures content from 30 seconds ago)
delayedClip, err := client.CreateClip(ctx, &helix.CreateClipParams{
BroadcasterID: broadcasterID,
HasDelay: true, // Adds a delay to capture content from slightly before
})
}
Create Clip from VOD
package main
import (
"context"
"fmt"
"log"
"github.com/Its-donkey/kappopher/helix"
)
func main() {
ctx := context.Background()
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)
// Create a clip from a VOD
clip, err := client.CreateClipFromVOD(ctx, &helix.CreateClipFromVODParams{
VideoID: "video-id",
OffsetSeconds: 3600, // Start at 1 hour mark
Duration: 30, // 30 second clip (5-60 seconds allowed)
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("VOD clip created!\n")
fmt.Printf(" ID: %s\n", clip.Data[0].ID)
fmt.Printf(" Edit URL: %s\n", clip.Data[0].EditURL)
}
Stream Markers
package main
import (
"context"
"fmt"
"log"
"github.com/Its-donkey/kappopher/helix"
)
func main() {
ctx := context.Background()
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)
broadcasterID := "12345"
// Create a stream marker
marker, err := client.CreateStreamMarker(ctx, &helix.CreateStreamMarkerParams{
UserID: broadcasterID,
Description: "Epic moment!",
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Marker created at %d seconds\n", marker.Data[0].PositionSeconds)
// Get stream markers
markers, err := client.GetStreamMarkers(ctx, &helix.GetStreamMarkersParams{
UserID: broadcasterID,
First: 20,
})
if err != nil {
log.Fatal(err)
}
for _, video := range markers.Data {
fmt.Printf("\nVideo: %s\n", video.VideoID)
for _, m := range video.Markers {
fmt.Printf(" [%d:%02d] %s (ID: %s)\n",
m.PositionSeconds/60, m.PositionSeconds%60,
m.Description, m.ID)
}
}
// Get markers for specific video
videoMarkers, err := client.GetStreamMarkers(ctx, &helix.GetStreamMarkersParams{
VideoID: "video-id",
})
}
Clip Compilation Bot
Auto-clip highlights based on chat activity:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"sync"
"time"
"github.com/Its-donkey/kappopher/helix"
)
type ClipBot struct {
client *helix.Client
broadcasterID string
chatActivity int
threshold int
cooldown time.Duration
lastClip time.Time
mu sync.Mutex
}
func NewClipBot(client *helix.Client, broadcasterID string) *ClipBot {
return &ClipBot{
client: client,
broadcasterID: broadcasterID,
threshold: 50, // Clip when 50 messages in 10 seconds
cooldown: 30 * time.Second,
}
}
func (b *ClipBot) TrackMessage() {
b.mu.Lock()
defer b.mu.Unlock()
b.chatActivity++
}
func (b *ClipBot) CheckAndClip(ctx context.Context) {
b.mu.Lock()
activity := b.chatActivity
b.chatActivity = 0 // Reset counter
canClip := time.Since(b.lastClip) > b.cooldown
b.mu.Unlock()
if activity >= b.threshold && canClip {
fmt.Printf("High activity detected (%d messages)! Creating clip...\n", activity)
clip, err := b.client.CreateClip(ctx, &helix.CreateClipParams{
BroadcasterID: b.broadcasterID,
HasDelay: true, // Capture the moment that just happened
})
if err != nil {
log.Printf("Failed to create clip: %v", err)
return
}
b.mu.Lock()
b.lastClip = time.Now()
b.mu.Unlock()
fmt.Printf("Clip created: %s\n", clip.Data[0].EditURL)
}
}
func main() {
ctx := context.Background()
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)
broadcasterID := "12345"
bot := NewClipBot(client, broadcasterID)
ws := helix.NewEventSubWebSocket(client)
if err := ws.Connect(ctx); err != nil {
log.Fatal(err)
}
defer ws.Close()
// Track chat messages
ws.Subscribe(ctx, helix.EventSubTypeChannelChatMessage, "1",
map[string]string{
"broadcaster_user_id": broadcasterID,
"user_id": broadcasterID,
},
func(event json.RawMessage) {
bot.TrackMessage()
},
)
// Check activity every 10 seconds
ticker := time.NewTicker(10 * time.Second)
go func() {
for range ticker.C {
bot.CheckAndClip(ctx)
}
}()
fmt.Println("Clip bot running...")
select {}
}
Video Stats Dashboard
package main
import (
"context"
"fmt"
"log"
"sort"
"github.com/Its-donkey/kappopher/helix"
)
func main() {
ctx := context.Background()
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
client := helix.NewClient("your-client-id", authClient)
broadcasterID := "12345"
// Get all videos
var allVideos []helix.Video
cursor := ""
for {
resp, err := client.GetVideos(ctx, &helix.GetVideosParams{
UserID: broadcasterID,
First: 100,
After: cursor,
})
if err != nil {
log.Fatal(err)
}
allVideos = append(allVideos, resp.Data...)
if resp.Pagination == nil || resp.Pagination.Cursor == "" {
break
}
cursor = resp.Pagination.Cursor
}
// Get all clips
var allClips []helix.Clip
cursor = ""
for {
resp, err := client.GetClips(ctx, &helix.GetClipsParams{
BroadcasterID: broadcasterID,
First: 100,
After: cursor,
})
if err != nil {
log.Fatal(err)
}
allClips = append(allClips, resp.Data...)
if resp.Pagination == nil || resp.Pagination.Cursor == "" {
break
}
cursor = resp.Pagination.Cursor
}
// Calculate stats
fmt.Println("=== Video & Clip Statistics ===\n")
// Video stats
totalVideoViews := 0
videoTypes := map[string]int{}
for _, v := range allVideos {
totalVideoViews += v.ViewCount
videoTypes[v.Type]++
}
fmt.Printf("Videos: %d\n", len(allVideos))
fmt.Printf(" Archives: %d\n", videoTypes["archive"])
fmt.Printf(" Highlights: %d\n", videoTypes["highlight"])
fmt.Printf(" Uploads: %d\n", videoTypes["upload"])
fmt.Printf(" Total Views: %d\n", totalVideoViews)
// Top videos
sort.Slice(allVideos, func(i, j int) bool {
return allVideos[i].ViewCount > allVideos[j].ViewCount
})
fmt.Printf("\nTop 5 Videos:\n")
for i := 0; i < 5 && i < len(allVideos); i++ {
fmt.Printf(" %d. %s (%d views)\n", i+1, allVideos[i].Title, allVideos[i].ViewCount)
}
// Clip stats
totalClipViews := 0
for _, c := range allClips {
totalClipViews += c.ViewCount
}
fmt.Printf("\nClips: %d\n", len(allClips))
fmt.Printf(" Total Views: %d\n", totalClipViews)
// Top clips
sort.Slice(allClips, func(i, j int) bool {
return allClips[i].ViewCount > allClips[j].ViewCount
})
fmt.Printf("\nTop 5 Clips:\n")
for i := 0; i < 5 && i < len(allClips); i++ {
fmt.Printf(" %d. %s (%d views) - by %s\n",
i+1, allClips[i].Title, allClips[i].ViewCount, allClips[i].CreatorName)
}
}