summaryrefslogtreecommitdiff
path: root/util/twitch.cr
diff options
context:
space:
mode:
Diffstat (limited to 'util/twitch.cr')
-rw-r--r--util/twitch.cr591
1 files changed, 591 insertions, 0 deletions
diff --git a/util/twitch.cr b/util/twitch.cr
new file mode 100644
index 0000000..1e63736
--- /dev/null
+++ b/util/twitch.cr
@@ -0,0 +1,591 @@
+require "../src/twitcr"
+
+require "http/client"
+require "json"
+require "pretty_print"
+
+settings = Hash(String, String).new
+
+settings["home"] = Path.home.to_s
+["access_token", "channel", "channel_id", "client_id", "client_id_twitch", "client_secret", "app_access_token", "eventsub_secret"].each do |key|
+ begin
+ settings[key] = File.read( settings["home"] + "/.config/twitch/" + key ).chomp
+ rescue
+# STDERR.puts "Warning: Missing " + settings["home"] + "/.config/twitch/" + key
+ end
+end
+
+command = ARGV[0]
+
+client = Twitcr::Client.new( settings )
+
+case command
+when "videos", "lsvideo", "lsvideos"
+ list_videos_kraken( settings, client )
+when "videos_helix" # smaller json, less information
+ list_videos_helix( settings, client )
+when "follows", "lsfollow", "lsfollows"
+ list_user_follows( settings, client )
+when "followees"
+ list_user_followees( settings, client )
+when "followers"
+ list_user_followers( settings, client )
+when "get_follow"
+ pp JSON.parse( client.get_user_follows( from: client.user_id( ARGV[1] ).to_u64, to: client.user_id( ARGV[2] ).to_u64 ) )
+when "follow"
+ pp JSON.parse( client.follow( settings["channel_id"], ARGV[1] ) )
+when "unfollow"
+ pp client.unfollow( settings["channel_id"], ARGV[1] )
+when "streams_followed"
+ list_streams_followed( settings, client )
+when "clips"
+ pp JSON.parse( client.get_clips( ARGV[1] ) )
+when "game"
+ pp client.get_games( ARGV[1] )
+when "streams"
+ list_streams_v5( settings, client, ARGV[1] )
+when "streams_helix"
+ list_streams( settings, JSON.parse( client.get_streams( game_id = client.game_id( ARGV[1] ) ) ) )
+when "hosts"
+ list_hosts( settings, client )
+when "user"
+ pp JSON.parse( client.get_user( ARGV[1] ) )
+when "subs"
+ pp JSON.parse( client.get_subs( client.user( ARGV[1] ).id ) )
+when "eventsubs"
+ pp JSON.parse( client.get_eventsubs( ) )
+when "eventsub"
+ pp JSON.parse( client.put_eventsubs!( ARGV[1], client.user_id( ARGV[2] ).to_u64, ARGV[3] ) )
+when "eventunsub"
+ pp client.delete_eventsubs!( ARGV[1] )
+when "panels"
+ pp JSON.parse( client.get_panels( ARGV[1] ) )
+when "validate"
+ pp JSON.parse( client.get_token_validation() )
+when "app_token"
+ pp JSON.parse( client.get_app_token() )
+when "reset_key"
+ pp JSON.parse( client.delete_key!( settings["channel_id"].to_u64 ) )
+when "update"
+ update_channel( settings, client, settings["channel_id"].to_u64 )
+when "channel"
+ if ARGV.size > 1
+ pp JSON.parse( client.get_channel( client.user_id( ARGV[1] ) ) )
+ else
+ pp JSON.parse( client.get_channel( ) )
+ end
+else
+ puts "ARGV[0] must be one of videos, follows"
+end
+
+# Format cell: widen column as needed
+# FIXME: not Unicode-aware
+macro fcell( data )
+ if {{data}}.bytesize > celw[celi]
+ celw[celi] = {{data}}.bytesize
+ end
+ if celw.sum( foh ) > winsz
+ # don't exceed winsz, even on screwball wide characters
+ if celi == 0
+ # preserve integrity of primary index at expense of last cell
+ celw[-1] = celw[-1] - ( celw.sum( foh ) - winsz )
+ elsif celi != celw.size
+ celw[celw.size]
+ else
+ celw[celi] = celw[celi] - ( celw.sum( foh ) - winsz ) - ( {{data}}.bytesize - {{data}}.size )
+ end
+ end
+ celi+=1
+ { celw[celi-1], celw[celi-1], {{data}} }
+end
+
+macro get_winsz()
+ # Crystal doesn't support the TIOCGWINSZ ioctl
+ `stty size`.split[1].to_i
+end
+
+def list_videos_helix( settings, client )
+
+ # FIXME: handle UInt32 more gracefully
+ # also: implement Twitcr::User so we can restrict valid strings
+ id = client.user( ARGV[1] ).id
+
+ response = JSON.parse( client.get_videos( id ) )
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%*.*s %-*.*s %*.*s %*.*s %*.*s %-*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ response["data"].as_a.each do |video|
+
+ celi=0
+
+ printf( format,
+ *fcell( video["url"].as_s[12..] ),
+ *fcell( video["created_at"].as_s.[0..15] ),
+ *fcell( video["type"].as_s[0].to_s ),
+ *fcell( video["duration"].as_s[ /([0-9]+[a-z]+)/, 1 ] ),
+ *fcell( video["view_count"].as_i.to_s ),
+ # helix doesn't report game?!
+ # *fcell( video["game"].as_s ),
+ *fcell( video["title"].as_s.chomp ),
+ *fcell( video["description"].as_s.chomp ),
+ )
+
+ end
+
+end
+
+def list_videos_kraken( settings, client )
+
+ # FIXME: handle UInt32 more gracefully
+ # also: implement Twitcr::User so we can restrict valid strings
+ id = client.user( ARGV[1] ).id
+
+ response = JSON.parse( client.get_channels_videos( id ) )
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%*.*s %-*.*s %*.*s %*.*s %*.*s %-*.*s %-*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ response["videos"].as_a.each do |video|
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( video["url"].as_s[12..] ),
+ *fcell( video["created_at"].as_s[0..15] ),
+ *fcell( video["broadcast_type"].as_s[0].to_s ),
+ *fcell( video["length"].as_i.to_s ),
+ *fcell( video["views"].as_i.to_s ),
+ *fcell( video["game"].as_s ),
+ *fcell( video["title"].to_s.chomp ),
+ *fcell( video["description"].to_s.chomp ),
+ )
+ rescue ex
+ puts ex.message
+ pp video
+ end
+
+
+ end
+
+end
+
+def list_user_followees( settings, client )
+
+ id = client.user( ARGV[1] ).id
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%*.*s %-*.*s %*.*s %*.*s %*.*s %-*.*s %*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ follows = JSON.parse( client.get_user_follows( from: id ) )["data"]
+
+ followsuids = follows.as_a.map { |follow| follow["to_id"].as_s.to_u64 }
+
+ streams = Hash( UInt64, JSON::Any ).new
+ channels = Hash( UInt64, JSON::Any ).new
+
+ JSON.parse( client.get_streams_v5( channel: followsuids ) )["streams"].as_a.each do |stream|
+ streams[ stream["channel"]["_id"].as_i64.to_u64 ] = stream
+ channels[ stream["channel"]["_id"].as_i64.to_u64 ] = stream["channel"]
+ end
+
+ follows.as_a.each do |follow|
+
+ uid = follow["to_id"].as_s.to_u64
+
+ channel = ( channels[uid]? || JSON.parse( client.get_channel( uid ) ) )
+ stream = ( streams[uid]? || JSON.parse( "{}" ) )
+
+ celi=0
+
+ begin
+ printf( format,
+ #*fcell( follow["followed_at"]?.to_s.[0..9] ),
+ *fcell( follow["followed_at"]?.to_s ),
+ *fcell( follow["to_name"]?.to_s ),
+ *fcell( stream["viewers"]?.to_s ),
+ *fcell( channel["followers"]?.to_s ),
+ *fcell( channel["views"]?.to_s ),
+ *fcell( channel["game"]?.to_s ),
+ *fcell( channel["broadcaster_language"]?.to_s ),
+ *fcell( channel["status"]?.to_s ),
+ )
+ rescue ex
+ puts( ex )
+ pp follow
+ end
+
+ end
+
+end
+
+def list_user_followers( settings, client )
+ id = client.user( ARGV[1] ).id
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%*.*s %-*.*s %*.*s %*.*s %*.*s %-*.*s %*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ puts( "\nLast 100 followers:\n" )
+
+ if ARGV.size > 2
+ followersjson = JSON.parse( client.get_user_follows( to: id, after: ARGV[2] ) )
+ else
+ followersjson = JSON.parse( client.get_user_follows( to: id ) )
+ end
+ followers = followersjson["data"]
+ followersuids = followers.as_a.map { |follow| follow["from_id"].as_s.to_u64 }
+
+ streams = Hash( UInt64, JSON::Any ).new
+ channels = Hash( UInt64, JSON::Any ).new
+
+
+ JSON.parse( client.get_streams_v5( channel: followersuids ) )["streams"].as_a.each do |stream|
+ streams[ stream["channel"]["_id"].as_i64.to_u64 ] = stream
+ channels[ stream["channel"]["_id"].as_i64.to_u64 ] = stream["channel"]
+ end
+
+ followers.as_a.each do |follow|
+
+ uid = follow["from_id"].as_s.to_u64
+
+ channel = ( channels[uid]? || JSON.parse( client.get_channel( uid ) ) )
+ stream = ( streams[uid]? || JSON.parse( "{}" ) )
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( follow["followed_at"]?.to_s.[0..15] ),
+ *fcell( follow["from_name"]?.to_s ),
+ *fcell( stream["viewers"]?.to_s ),
+ *fcell( channel["followers"]?.to_s ),
+ *fcell( channel["views"]?.to_s ),
+ *fcell( channel["game"]?.to_s ),
+ *fcell( channel["broadcaster_language"]?.to_s ),
+ *fcell( channel["status"]?.to_s ),
+ )
+ rescue ex
+ puts ex
+ pp follow
+ end
+
+ end
+
+ pp followersjson["pagination"]
+
+end
+
+def list_user_follows( settings, client )
+
+ id = client.user( ARGV[1] ).id
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%*.*s %-*.*s %*.*s %*.*s %*.*s %-*.*s %*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ puts( "Last 100 followees:\n" )
+ follows = JSON.parse( client.get_user_follows( from: id ) )["data"]
+
+ followsuids = follows.as_a.map { |follow| follow["to_id"].as_s.to_u64 }
+
+ streams = Hash( UInt64, JSON::Any ).new
+ channels = Hash( UInt64, JSON::Any ).new
+
+ JSON.parse( client.get_streams_v5( channel: followsuids ) )["streams"].as_a.each do |stream|
+ streams[ stream["channel"]["_id"].as_i64.to_u64 ] = stream
+ channels[ stream["channel"]["_id"].as_i64.to_u64 ] = stream["channel"]
+ end
+
+ follows.as_a.each do |follow|
+
+ uid = follow["to_id"].as_s.to_u64
+
+ channel = ( channels[uid]? || JSON.parse( client.get_channel( uid ) ) )
+ stream = ( streams[uid]? || JSON.parse( "{}" ) )
+
+ celi=0
+
+ begin
+ printf( format,
+ #*fcell( follow["followed_at"]?.to_s.[0..9] ),
+ *fcell( follow["followed_at"]?.to_s ),
+ *fcell( follow["to_name"]?.to_s ),
+ *fcell( stream["viewers"]?.to_s ),
+ *fcell( channel["followers"]?.to_s ),
+ *fcell( channel["views"]?.to_s ),
+ *fcell( channel["game"]?.to_s ),
+ *fcell( channel["broadcaster_language"]?.to_s ),
+ *fcell( channel["status"]?.to_s ),
+ )
+ rescue ex
+ puts( ex )
+ pp follow
+ end
+
+ end
+
+ # cell widths
+ celw=[0,0,0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ puts( "\nLast 100 followers:\n" )
+ followers = JSON.parse( client.get_user_follows( to: id ) )["data"]
+
+ followersuids = followers.as_a.map { |follow| follow["from_id"].as_s.to_u64 }
+
+ JSON.parse( client.get_streams_v5( channel: followersuids - followsuids ) )["streams"].as_a.each do |stream|
+ streams[ stream["channel"]["_id"].as_i64.to_u64 ] = stream
+ channels[ stream["channel"]["_id"].as_i64.to_u64 ] = stream["channel"]
+ end
+
+ followers.as_a.each do |follow|
+
+ uid = follow["from_id"].as_s.to_u64
+
+ channel = ( channels[uid]? || JSON.parse( client.get_channel( uid ) ) )
+ stream = ( streams[uid]? || JSON.parse( "{}" ) )
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( follow["followed_at"]?.to_s.[0..15] ),
+ *fcell( follow["from_name"]?.to_s ),
+ *fcell( stream["viewers"]?.to_s ),
+ *fcell( channel["followers"]?.to_s ),
+ *fcell( channel["views"]?.to_s ),
+ *fcell( channel["game"]?.to_s ),
+ *fcell( channel["broadcaster_language"]?.to_s ),
+ *fcell( channel["status"]?.to_s ),
+ )
+ rescue ex
+ puts ex
+ pp follow
+ end
+
+ end
+
+end
+
+def list_streams_v5( settings, client, game : String )
+
+ response = JSON.parse( client.get_streams_v5( game ) )
+
+ pp response
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%-*.*s %-*.*s %*.*s %*.*s %*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ response["streams"].as_a.each do |stream|
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( stream["channel"]["url"].as_s[12..] ),
+ *fcell( stream["broadcast_platform"].as_s[0].to_s ),
+ *fcell( stream["viewers"].as_i.to_s ),
+ *fcell( stream["channel"]["followers"].as_i.to_s ),
+ *fcell( stream["game"].as_s ),
+ *fcell( stream["channel"]["status"].to_s.chomp ),
+ )
+ rescue
+ pp stream
+ end
+
+
+ end
+
+end
+
+def list_hosts( settings, client )
+ if ARGV.size > 1
+ channel_id = client.user( ARGV[1] ).id
+ else
+ channel_id = settings["channel_id"].to_u64
+ end
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,5,6,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%-*.*s %*.*s %*.*s %-*.*s %*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ json = JSON.parse( client.get_hosts( channel_id ) )
+ json["hosts"].as_a.map { | host |
+ host["host_id"].as_s.to_u64
+ }.each do | uid |
+ pp JSON.parse( client.get_channel( uid ) )
+ end
+ json["hosts"].as_a.map { | host |
+ host["host_id"].as_s.to_u64
+ }.each do | uid |
+
+ channel = ( JSON.parse( client.get_channel( uid ) ) )
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( channel["display_name"]?.to_s ),
+ *fcell( channel["followers"]?.to_s ),
+ *fcell( channel["views"]?.to_s ),
+ *fcell( channel["game"]?.to_s ),
+ *fcell( channel["broadcaster_language"]?.to_s ),
+ *fcell( channel["status"]?.to_s ),
+ )
+ rescue ex
+ puts( ex )
+ pp uid
+ pp channel
+ end
+
+ end
+
+# {"hosts" =
+# [{"host_id" => "27345085", "target_id" => "59895482"},
+end
+
+def list_streams( settings, response )
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%-*.*s %*.*s %*.*s %-*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ pp response
+
+ response["data"].as_a.sort{ |a, b| b["viewer_count"].as_i <=> a["viewer_count"].as_i }.each do |stream|
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( "twitch.tv/#{stream["user_name"].to_s}" ),
+ *fcell( stream["type"].as_s[0].to_s ),
+ *fcell( stream["viewer_count"].as_i.to_s ),
+ #*fcell( stream["channel"]["followers"].as_i.to_s ),
+ *fcell( stream["game_id"].as_s ),
+ *fcell( stream["title"].to_s ),
+ )
+ rescue
+ pp stream
+ end
+
+ end
+
+end
+
+def list_streams_followed( settings, client )
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0,0,0,0]
+ # cell index
+ celi=0
+
+ format = "%-*.*s %*.*s %*.*s %*.*s %-*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ response = JSON.parse( client.get_streams_followed() )
+
+ pp response
+
+ response["streams"].as_a.sort{ |a, b| b["viewers"].as_i <=> a["viewers"].as_i }.each do |stream|
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( stream["channel"]["url"].as_s[12..] ),
+ *fcell( stream["stream_type"].as_s[0].to_s ),
+ *fcell( stream["viewers"].as_i.to_s ),
+ *fcell( stream["channel"]["followers"].as_i.to_s ),
+ *fcell( stream["game"].as_s ),
+ *fcell( stream["channel"]["status"].to_s ),
+ )
+ rescue ex
+ pp ex
+ pp stream
+ end
+
+ end
+
+end
+
+def update_channel( settings, client, id )
+
+ pp JSON.parse( client.put_channel!( id, game: ARGV[1], status: ARGV[2] ) )
+
+end