-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathtwitch_tools.own
185 lines (174 loc) · 6.62 KB
/
twitch_tools.own
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// Twitch Tools
use std, math, http, json, date, types, functional
match ARGS {
case (): usage()
case ("status", _): status(ARGS[1])
case ("liveinfo", _): liveInfo(ARGS[1])
case ("chat", _): chat(ARGS[1])
case ("list", "past", _): listPastBroadcasts(ARGS[2])
case ("m3u", "live", _): m3uPlaylistLive(ARGS[2])
case ("m3u", _): m3uPlaylist(ARGS[1])
case ("url", "live", _): urlPlaylistLive("source", ARGS[2])
case ("url", "live", _, _): urlPlaylistLive(ARGS[2], ARGS[3])
case ("url", _): urlPlaylist("source", ARGS[1])
case ("url", _, _): urlPlaylist(ARGS[1], ARGS[2])
case _: usage()
}
def usage() {
println "Usage: twitch_tools [mode] [args*]
mode:
status [channel] - prints channel status (online or offline)
liveinfo [channel] - prints live stream info
chat [vod id] - shows chat
list past [channel] - prints past broadcasts of channel
m3u [vod id] - gets m3u playlist for past broadcast
m3u live [channel] - gets m3u playlist for live channel
url [vod id] - gets url for past broadcast with source quality
url [quality] [vod id] - gets url for past broadcast with given quality
url live [channel] - gets url for live channel with source quality
url live [quality] [vod id] - gets playlist url for live channel with given quality
"
}
def status(channel) {
extract (status, ) = getStreamInfo(channel)
print status
}
def liveInfo(channel) {
extract (status, info) = getStreamInfo(channel)
if (status == "offline") {
println "This channel is offline"
return 0
}
stream = info.stream
channel = stream.channel
// Stream
print (arrayKeyExists("mature", channel) && channel.mature) ? "[NSFW] " : ""
println channel.status
println "Date: " + formatTzDate(stream.created_at)
println "Game: " + (arrayKeyExists("game", stream) ? stream.game : "unknown")
println "Viewers: " + stream.viewers
println sprintf("Language: %s, broadcaster: %s", channel.language, channel.broadcaster_language)
// Previews
preview = stream.preview
println "Previews:"
for resolution : ["small", "medium", "large"] {
if (!arrayKeyExists(resolution, preview)) continue
println sprintf("%8s %s", resolution, preview[resolution])
}
template = preview.template
def formatResolution(w, h) = replace(replace(template, "{width}", w), "{height}", h)
println sprintf("%8s %s", "HD", formatResolution(1280, 720))
println sprintf("%8s %s", "Full HD", formatResolution(1920, 1080))
println sprintf("height: %d, %d fps, %d delay", stream.video_height, int(round(stream.average_fps)), stream.delay)
}
def listPastBroadcasts(channel) {
url = sprintf("https://api.twitch.tv/kraken/channels/%s/videos?limit=100&broadcasts=true", channel)
pastVods = getJsonSync(url)
pastVods = pastVods.videos
for vod : pastVods {
println "=" * 30
println vod._id
if arrayKeyExists("title", vod) {
println vod.title
}
println "Date: " + formatTzDate(vod.recorded_at)
println "Game: " + (arrayKeyExists("game", vod) ? vod.game : "unknown")
println "Duration: " + lengthToTime(vod.length)
println "Views: " + vod.views
println "Previews:"
println vod.preview
if arrayKeyExists("animated_preview", vod) {
println vod.animated_preview
}
for quality : ["chunked", "high", "medium", "low", "mobile"] {
if (!arrayKeyExists(quality, vod.resolutions)
|| vod.resolutions[quality] == "0x0") continue
println sprintf("%s: %s %d fps", quality, vod.resolutions[quality], round(vod.fps[quality]))
}
}
}
def m3uPlaylist(vodId) {
print m3uPlaylistString(vodId)
}
def urlPlaylist(quality, vodId) {
print searchInPlaylist(quality, m3uPlaylistString(vodId))
}
def m3uPlaylistString(vodId) {
id = (indexOf(vodId, "v") == 0) ? substring(vodId, 1) : vodId
// Get token
url = sprintf("https://api.twitch.tv/api/vods/%s/access_token", id)
token = getJsonSync(url)
// Get playlist
url = sprintf("http://usher.twitch.tv/vod/%s?player=twitchweb&allow_source=true"
+ "&nauthsig=%s&nauth=%s", id, token.sig, urlencode(token.token))
return sync(def(ret) = http(url, ret))
}
def m3uPlaylistLive(channel) {
print m3uPlaylistLiveString(channel)
}
def urlPlaylistLive(quality, channel) {
print searchInPlaylist(quality, m3uPlaylistLiveString(channel))
}
def m3uPlaylistLiveString(channel) {
// Get token
url = sprintf("http://api.twitch.tv/api/channels/%s/access_token", channel)
token = getJsonSync(url)
// Get playlist
url = sprintf("http://usher.twitch.tv/api/channel/hls/%s.m3u8?player=twitchweb"
+ "&token=%s&sig=%s&allow_audio_only=true&allow_source=true&type=any&p=%d",
channel, token.token, token.sig, rand(1000000))
return sync(def(ret) = http(url, ret))
}
def chat(vodId) {
id = (indexOf(vodId, "v") == 0) ? substring(vodId, 1) : vodId
url = sprintf("https://api.twitch.tv/kraken/videos/v%s", id)
vodInfo = getJsonSync(url)
if (arrayKeyExists("error", vodInfo)) {
println "Error: " + vodInfo.message
return 0
}
recordDate = parseTzDate(vodInfo.recorded_at)
startTime = toTimestamp(recordDate) / 1000
startTime += (3 * 60 * 60)
endTime = long(startTime + ceil(vodInfo.length / 30.0) * 30)
maxIterations = (endTime - startTime) / 30
for i = 0, i < maxIterations, i++ {
timestamp = startTime + i * 30
url = sprintf("http://rechat.twitch.tv/rechat-messages?start=%d&video_id=v%s", timestamp, id)
chunksString = sync(def(ret) = http(url, ret))
chunksObject = jsondecode(chunksString)
for chunk : chunksObject.data {
attr = chunk.attributes
formattedDate = formatDate(newDate(attr.timestamp))
println sprintf("%s (%s)\n%s\n\n====\n",
attr.tags["display-name"], formattedDate, attr.message)
}
}
}
def searchInPlaylist(quality, m3u) {
quality = toLowerCase(quality)
lines = split(m3u, "\n")
found = false
for line : lines {
lower = toLowerCase(line)
if (contains(line, "#EXT-X-MEDIA") && contains(lower, quality)) {
found = true
} else if (found && contains(lower, "http")) {
return line
}
}
return ""
}
def getStreamInfo(channel) {
url = sprintf("https://api.twitch.tv/kraken/streams/%s", channel)
stream = getJsonSync(url)
return try(def() {
arrayKeyExists("_id", stream.stream)
return ["online", stream]
}, def(type, message) = ["offline", stream])
}
def contains(what, where) = indexOf(what, where) >= 0
def formatTzDate(str) = formatDate(parseTzDate(str), newFormat("yyyy-MM-dd HH:mm:ss"))
def parseTzDate(str) = parseDate(str, newFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"))
def lengthToTime(len) = sprintf("%02d:%02d:%02d", len / 3600, len / 60 % 60, len % 60)
def getJsonSync(url) = sync(def(ret) = http(url, def(r) = ret(jsondecode(r)) ))