#!/bin/bash # This is a shell that makes it possible to use Twitch # by typing commands. The licence is CC0. # # -- Compatibility -- # # This shell should work with OpenBSD's ksh too. # All you need to change is: # - "#!/bin/bash" -> "#!/bin/sh" # - The "while read" line near the bottom # # -- Features and tips -- # # * You can do most things without logging in, except for set_* commands. # * You can either run commands from within the shell or pass them as # arguments, like `./twitch-shell search xyz`. # * One letter shortcuts for common commands (like `s` for `search`) # * Check whether your favourite channels are live by running # `twitch-shell live channel1 channel2`... # # -- Example usage -- # # $ ./twitch-shell # twitch> search programming # HappyLittleRat playing Creative # [Silent C++] Workbench #programming #C++ #gamedev # dylan_landry playing Creative # Relaxing Programming: Help, chill, learn, and relax. Day 11 # twitch> info # Robalni playing Creative # Doing something boring # twitch> set_title "Programming a Twitch shell" # Robalni playing Creative # Programming a Twitch shell # # -- Commands -- # # "your channel/stream" means the one you have specified in the # variable `username` at the top of the script. A slash in the # command name like "live/l" means that there is an alias. # Arguments written like [x] are optional. Arguments written like # are # required. # Three dots means that you can specify multiple of that argument. # # Command: Description: # info/i [channel...] Shows info about channels (default: your channel) # live/l [channel...] See whether the channels are live # (default: your channel) # search/s [page] Search for streams # set_game Set your game # set_title Set your stream title # videos/v [channel] Shows a list of videos from the channel # (default: your channel) # watch/w [channel] Watch a stream (using mpv) (default: your stream) # Default username for when you change things or don't specify username. username='' # A read-only token. If you need to do more, you need to run 'login'. oauth='nmkctp79q37iqsz6bhgl6jm1ty3w26' if [ -e ~/.config/twitch-shell/commands.sh ]; then # In this file you can put your tokens and extra functions. . ~/.config/twitch-shell/commands.sh fi videoplayer=mpv # Must be able to play m3u8 playlists. use_ytdl=0 # Change to 1 if watching streams doesn't work and you have youtube-dl or yt-dlp installed. # These two are needed for this script to work. You should not need to change them. clientid='dl1xe55lg2y26u8njj769lxhq3i47r' clientsecret='39pxzfwymvpmaqoovj6fyj41idov1j' # API reference: https://dev.twitch.tv/docs/api/reference/ get_request() { curl -s -X GET "https://api.twitch.tv/helix/$1" -H "Authorization: Bearer $oauth" -H "Client-Id: $clientid" } patch_request() { curl -s -X PATCH "https://api.twitch.tv/helix/$1" -H "Authorization: Bearer $oauth" -H "Client-Id: $clientid" -H 'Content-Type: application/json' --data-raw "$2" } urlencode() { sed -e 's/%/%25/g' -e 's/ /+/g' -e 's/&/%26/g' -e 's/\\//g' -e 's/"/%22/g' -e 's/{/%7B/g' -e 's/}/%7D/g' -e 's/\[/%5B/g' -e 's/\]/%5D/g' -e 's/,/%2C/g' -e 's/:/%3A/g' } jsonencode() { sed -e 's/"/\\"/g' } play_video() { if [ $# -eq 1 ]; then $videoplayer "$1" elif [ $# -ge 2 ]; then $videoplayer --force-media-title="$2" "$1" fi } # Commands START cmd_set_title() { title="$1" userid=`get_request "users?login=$username" | jq -r '.data[0].id'` patch_request "channels?broadcaster_id=$userid" "{\"title\":\"`echo "$title" | jsonencode`\"}" cmd_info } cmd_set_game() { game="$1" gameurl=`echo "$game" | sed 's/ /%20/g'` userid=`get_request "users?login=$username" | jq -r '.data[0].id'` gameid='' if [ -n "$game" ]; then gameid=`get_request "games?name=$gameurl" | jq -r '.data[0].id'` fi patch_request "channels?broadcaster_id=$userid" "{\"game_id\":\"$gameid\"}" cmd_info } # Usage: set_tags tag1 tag2 ... # Tags are not case sensitive. cmd_set_tags() { json='[' first_iteration=1 while [ $# -ne 0 ]; do if [ $first_iteration -eq 0 ]; then json="$json," fi json="$json\"`echo $1 | jsonencode`\"" first_iteration=0 shift done json="$json]" userid=`get_request "users?login=$username" | jq -r '.data[0].id'` patch_request "channels?broadcaster_id=$userid" "{\"tags\":$json}" cmd_info } cmd_live() { params='' if [ $# -eq 0 ]; then params="$params&user_login=$username" fi while [ $# -ne 0 ]; do params="$params&user_login=$1" shift done get_request "streams?$params" | jq -r '.data[] | ("\u001b[1m"+.user_login+"\u001b[m playing \u001b[32m"+.game_name+"\u001b[m\n "+.title)' } cmd_streams() { cmd_live "$@" } cmd_l() { cmd_live "$@" } cmd_login() { echo "Open this in a browser:" echo "https://id.twitch.tv/oauth2/authorize?client_id=$clientid&redirect_uri=http://localhost%3A49814&response_type=code&scope=channel%3Amanage%3Abroadcast+chat%3Aread+chat%3Aedit" if type nc &> /dev/null; then echo "Running a server on localhost:49814 to receive token..." echo "If this server solution doesn't work, here is how to do it manually:" echo " You will find a code in the URL that the login page redirects to." echo " Make this request, replacing CODE with that code:" echo " curl -X POST 'https://id.twitch.tv/oauth2/token' --data 'client_id=$clientid&client_secret=$clientsecret&code=CODE&grant_type=authorization_code&redirect_uri=http://localhost%3A49814'" echo " You will find your oauth token in the response. Put it in the oauth variable at the top of this script." code=`nc -lp 49814 | sed -n 's/.*code=\([^&]*\)&.*/\1/p'` token='' if `echo "$code" | grep '^.....' > /dev/null`; then token=`curl -s -X POST 'https://id.twitch.tv/oauth2/token' --data "client_id=$clientid&client_secret=$clientsecret&code=$code&grant_type=authorization_code&redirect_uri=http://localhost%3A49814" | jq -r '.access_token'` fi if `echo "$token" | grep '^.....' > /dev/null`; then echo "Success! Your oauth token is $token" echo "Put it in the oauth variable at the top of this script." else echo "Something went wrong. Follow the steps described above." fi else echo "When you have logged in, you will find a code in the URL it redirects to." echo "Make this request, replacing CODE with that code:" echo "curl -X POST 'https://id.twitch.tv/oauth2/token' --data 'client_id=$clientid&client_secret=$clientsecret&code=CODE&grant_type=authorization_code&redirect_uri=http://localhost%3A49814'" echo "You will find your oauth token in the response. Put it in the oauth variable at the top of this script." fi } cmd_search() { page_size=10 query=`echo "$1" | urlencode` page=1 if [ $# -ge 2 ]; then page="$2" fi after=`printf $((($page - 1) * $page_size)) | base64` get_request "search/channels?live_only=true&first=$page_size&after=$after&query=$query" | jq -r '.data[] | ("\u001b[1m"+.broadcaster_login+"\u001b[m playing \u001b[32m"+.game_name+"\u001b[m\n "+.title)' } cmd_s() { cmd_search "$@" } cmd_info() { params='' if [ $# -eq 0 ]; then userid=`get_request "users?login=$username" | jq -r '.data[0].id'` params="$params&broadcaster_id=$userid" fi while [ $# -ne 0 ]; do userid=`get_request "users?login=$1" | jq -r '.data[0].id'` params="$params&broadcaster_id=$userid" shift done get_request "channels?$params" | jq -r '.data[] | ("\u001b[1m"+.broadcaster_login+"\u001b[m playing \u001b[32m"+.game_name+"\u001b[m\n "+.title+"\n Tags: "+(.tags|tostring))' } cmd_i() { cmd_info "$@" } cmd_videos() { page_size=10 name="$1" if [ -z "$name" ]; then name="$username" fi userid=`get_request "users?login=$name" | jq -r '.data[0].id'` page=1 if [ $# -ge 2 ]; then page="$2" fi after=`printf '{"b":null,"a":{"Offset":'$((($page - 1) * $page_size))'}}' | base64` get_request "videos?first=$page_size&after=$after&user_id=$userid" | jq -r '.data[] | ("\u001b[36m"+.type+"\u001b[m: "+.url+" - "+.published_at+"\n "+.title)' } cmd_v() { cmd_videos "$@" } cmd_vods() { cmd_videos "$@" } cmd_watch() { arg="$1" if [ -z "$arg" ]; then arg="$username" fi if [ "$use_ytdl" -eq 0 ]; then vod_id=`echo "$arg" | sed -n 's;^\(https://\)\?\(www\.\)\?twitch\.tv/videos/\([^/]*\);\3;p'` if [ -n "$vod_id" ]; then # Using yt-dlp's client-id for now. I don't know why our client-id doesn't work. resp=`curl -H "Client-ID: kimne78kx3ncx6brgo4mv6wki5h1ko" "https://gql.twitch.tv/gql" --data '{"query":"{videoPlaybackAccessToken(id:\"'"$vod_id"'\",params:{platform:\"web\",playerBackend:\"mediaplayer\",playerType:\"site\"}){value signature}}"}'` value_urlencoded=`echo "$resp" | jq -r '.data.videoPlaybackAccessToken.value' | urlencode` sig=`echo "$resp" | jq -r '.data.videoPlaybackAccessToken.signature'` play_video "https://usher.ttvnw.net/vod/$vod_id.m3u8?nauth=$value_urlencoded&nauthsig=$sig&allow_source=true" "Twitch video" else name=`echo "$arg" | sed -n 's;^\(https://\)\?\(www\.\)\?twitch\.tv/\([^/]*\)$;\3;p'` if [ -z "$name" ]; then name="$arg" fi # Using yt-dlp's client-id for now. I don't know why our client-id doesn't work. resp=`curl -H "Client-ID: kimne78kx3ncx6brgo4mv6wki5h1ko" "https://gql.twitch.tv/gql" --data '{"query":"{streamPlaybackAccessToken(channelName:\"'"$name"'\",params:{platform:\"web\",playerBackend:\"mediaplayer\",playerType:\"site\"}){value signature}}"}'` value_urlencoded=`echo "$resp" | jq -r '.data.streamPlaybackAccessToken.value' | urlencode` sig=`echo "$resp" | jq -r '.data.streamPlaybackAccessToken.signature'` play_video "https://usher.ttvnw.net/api/channel/hls/$name.m3u8?token=$value_urlencoded&sig=$sig&allow_source=true" "$name" fi else if ( echo "$arg" | grep -q '/' ); then play_video "$arg" else play_video "https://twitch.tv/$arg" fi fi } cmd_w() { cmd_watch "$@" } cmd_chat() { irssi --connect='-tls irc.chat.twitch.tv' --port=6697 --password="oauth:$oauth" --nick="$username" } # Commands END if [ $# -gt 0 ]; then "cmd_$@" else while read -ep $'\33[1m\33[38;2;100;65;165mtwitch>\33[m ' cmd; do eval "cmd_$cmd" done fi