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