summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/twitcr/client.cr15
-rw-r--r--src/twitcr/mappings/game.cr13
-rw-r--r--src/twitcr/rest.cr64
-rw-r--r--util/twitch.cr185
4 files changed, 163 insertions, 114 deletions
diff --git a/src/twitcr/client.cr b/src/twitcr/client.cr
index 1a02a40..a0d4be2 100644
--- a/src/twitcr/client.cr
+++ b/src/twitcr/client.cr
@@ -3,12 +3,13 @@ require "./mappings/*"
class Twitcr::Client
- getter token : String?
- getter client_id : String
- getter client_secret : String
+ #getter token : String?
+ #getter client_id : String
+ #getter client_secret : String
- def initialize(@client_secret, @client_id)
- @token = nil
+ #def initialize(@client_secret, @client_id)
+ # @token = nil
+ #end
include REST
@@ -48,11 +49,11 @@ class Twitcr::Client
end
def game_id( name : String )
- name.to_u64{ game(name).id }
+ game(name).id
end
def game_id( id : UInt64 )
-
+ self
end
def unfollow( unfollower : ( String | UInt64 ), unfollowee : ( String | UInt64 ) )
diff --git a/src/twitcr/mappings/game.cr b/src/twitcr/mappings/game.cr
index 2915eb5..d0818cd 100644
--- a/src/twitcr/mappings/game.cr
+++ b/src/twitcr/mappings/game.cr
@@ -2,15 +2,16 @@ require "./converters"
module Twitcr
struct GameList
- JSON.mapping({data: Array(Game)})
+ include JSON::Serializable
+ property data : Array(Game)
end
struct Game
- JSON.mapping({
- id: {type: UInt64, converter: ID::Converter},
- name: String,
- box_art_url: String,
- })
+ include JSON::Serializable
+ @[JSON::Field(converter: ID::Converter)]
+ property id : UInt64
+ property name : String
+ property box_art_url : String
end
end
diff --git a/src/twitcr/rest.cr b/src/twitcr/rest.cr
index 0fc59c1..ae958a7 100644
--- a/src/twitcr/rest.cr
+++ b/src/twitcr/rest.cr
@@ -143,27 +143,43 @@ module Twitcr::REST
if ! query_string.empty?; query_string = "?" + query_string end
request( "GET", Api::Kraken, "/streams" + query_string )
end
- def get_streams_followed( ); request( "GET", Api::Kraken, "/streams/followed?limit=100&stream_type=all" ) end
+ #deprecated
+ #def get_streams_followed( ); request( "GET", Api::Kraken, "/streams/followed?limit=100&stream_type=all" ) end
+ def get_streams_followed( user_id : UInt64 ); request( "GET", Api::Helix, "/streams/followed?user_id=#{user_id}" ) end
# FIXME: Deal with pagination
def get_banned( broadcaster_id : UInt64 ); request( "GET", Api::Helix, "/moderation/banned?broadcaster_id=#{broadcaster_id.to_s}" ) end
def get_banned( broadcaster_id : UInt64, user_id : UInt64 ); request( "GET", Api::Helix, "/moderation/banned?broadcaster_id=#{broadcaster_id.to_s}&user_id=#{user_id}" ) end
def get_banned( broadcaster_id : UInt64, user_id : Array( UInt64 ) ); request( "GET", Api::Helix, "/moderation/banned?broadcaster_id=#{broadcaster_id.to_s}&user_id=#{user_id.join("&user_id=")}" ) end
def get_banned_events( broadcaster_id : UInt64, first : UInt64 = 100 ); request( "GET", Api::Helix, "/moderation/banned/events?broadcaster_id=#{broadcaster_id.to_s}&first=#{first.to_s}" ) end
def get_clips( name : String ); request( "GET", Api::Kraken, "/clips/top?channel=#{name}&first=100&period=all" ) end
+# def get_videos( *, id : UInt64, user_id : UInt64, game_id : UInt64 )
+# params = Hash( String, String ).new
+# params["first"] = "100"
+# params["id"] = id.to_s
+# params["user_id"] = user_id.to_s
+# params["game_id"] = game_id.to_s
+# if after ; params["after"] = after end
+# query_string = HTTP::Params.encode( params )
+# if ! query_string.empty?; query_string = "?" + query_string end
+# request( "GET", Api::Helix, "/videos" + query_string )
+# end
def get_videos( id : UInt64, sort : String = "time", type : String = "all" ); request( "GET", Api::Helix, "/videos?user_id=#{id.to_s}&first=100&sort=#{sort}&type=#{type}" ) end
def get_channels_videos( id : UInt64, sort : String = "time", type : String = "all" ); request( "GET", Api::Kraken, "/channels/#{id.to_s}/videos?limit=100&sort=#{sort}&broadcast_type=#{type}" ) end
- def get_channel( ); request( "GET", Api::Kraken, "/channel" ) end
- def get_channel( id : UInt64 ); request( "GET", Api::Kraken, "/channels/#{id.to_s}" ) end
- def put_channel!( id : UInt64, *, delay : String? = nil, game : String? = nil, status : String? = nil);
- body = Hash( String, Hash( String, String ) ).new
- body["channel"] = Hash( String, String).new
- if game ; body["channel"]["game"] = game end
- if status ; body["channel"]["status"] = status end
- if delay ; body["channel"]["delay"] = delay end
- request( "PUT", Api::Kraken, "/channels/#{id.to_s}", body.to_json, contenttype: "application/json") end
+ #def get_channel( ); request( "GET", Api::Kraken, "/channel" ) end
+ def get_channel( id : UInt64 ); request( "GET", Api::Helix, "/channels/?broadcaster_id=#{id.to_s}" ) end
+ def put_channel!( id : UInt64, *, delay : String? = nil, game : String? = nil, title : String? = nil, lang : String? = nil );
+ #body = Hash( String, Hash( String, String ) ).new
+ body = Hash( String, String ).new
+ #body["channel"] = Hash( String, String).new
+ game && ( body["game_id"] = game_id( game ).to_s )
+ lang && ( body["lang"] = lang )
+ title && ( body["title"] = title )
+ delay && ( body["delay"] = delay )
+ pp body.to_json
+ request( "PATCH", Api::Helix, "/channels/?broadcaster_id=#{id.to_s}", body.to_json, contenttype: "application/json") end
def delete_key!( id : UInt64); request( "DELETE", Api::Kraken, "/channels/#{id.to_s}/stream_key" ) end
def get_hosts( id : UInt64 ); request( "GET", Api::Kraken, "/channels/#{id.to_s}/hosts" ) end
- def get_subs( id : UInt64 ); request( "GET", Api::Kraken, "/channels/#{id.to_s}/subscriptions" ) end
+ def get_subs( id : UInt64 ); request( "GET", Api::Helix, "/subscriptions?first=100&broadcaster_id=#{id}" ) end
def get_eventsubs( ); request( "GET", Api::Helix, "/eventsub/subscriptions" ) end
def delete_eventsubs!( id : String ); request( "DELETE",
Api::Helix, "/eventsub/subscriptions?id=#{id}" ) end
@@ -172,14 +188,11 @@ module Twitcr::REST
varname = "broadcaster_user_id"
case type
when "channel.raid"
- version = "beta"
varname = "to_broadcaster_user_id"
when "user.update"
varname = "user_id"
end
- body = Hash( String, String | Hash( String, String ) ).new
-
- body = {
+ body = Hash( String, Bool | String | Hash( String, String ) ){
"type" => type,
"version" => version,
"condition" => { varname => id.to_s },
@@ -189,8 +202,11 @@ module Twitcr::REST
"secret" => @settings["eventsub_secret"] # FIXME
},
}
+ if type == "drop.entitlement.grant"
+ body["is_batching_enabled"] = true
+ end
request( "POST", Api::Helix, "/eventsub/subscriptions", body.to_json, contenttype: "application/json" ) end
- def get_app_token( ); request( "POST", Api::Oauth2, "/token?client_id=#{@settings["client_id"].not_nil!}&client_secret=#{@settings["client_secret"].not_nil!}&grant_type=client_credentials" ) end
+ def get_app_token( ); request( "POST", Api::Oauth2, "/token?client_id=#{@settings["client_id"].not_nil!}&client_secret=#{@settings["client_secret"].not_nil!}&grant_type=client_credentials&scope=#{ @settings["scopes"].split("\n").join("%20") }" ) end
def get_token_validation( ); request( "GET", Api::Oauth2, "/validate" ) end
# These APIs might require the official Twitch.com web Client-ID now?
def get_panels( login : String ); request( "GET", Api::Api, "/channels/#{login}/panels" ) end
@@ -199,7 +215,7 @@ module Twitcr::REST
def unfriend!( friender : UInt64, friendee : UInt64 ); request( "DELETE", Api::Kraken, "/users/#{friender.to_s}/friends/#{friendee.to_s}" ) end
def get_friends( friender : UInt64 ); request( "GET", Api::Kraken, "/users/#{friender.to_s}/friends" ) end
- def parse_game( game : UInt64 | String )
+ def parse_game( game : UInt64 | String )
list = GameList.from_json( get_games( game ) )
raise EMPTY_RESULT if list.data.empty?
list.data.first
@@ -211,15 +227,15 @@ module Twitcr::REST
list.data.first
end
- def get_token
+ def authorize( )
response = HTTP::Client.exec(
- "POST",
- "https://id.twitch.tv/oauth2/token?client_id=#{@settings["client_id"].not_nil!}&client_secret=#{@settings["client_secret"].not_nil!}&grant_type=client_credentials",
+ "GET",
+ "https://id.twitch.tv/oauth2/authorize?client_id=#{@settings["client_id"].not_nil!}&scope=#{ @settings["scopes"].split("\n").join("%20") }&redirect_uri=#{@settings["redirect_uri"]}&response_type=code",
tls: SSL_CONTEXT
)
-
- access_token = Token.from_json(response.body).access_token
-
- @token = "Bearer #{access_token}"
+ response.headers
+ end
+ def get_token( code : String )
+ request("POST", Api::Oauth2, "/token?client_id=#{@settings["client_id"].not_nil!}&client_secret=#{@settings["client_secret"].not_nil!}&code=#{code}&grant_type=authorization_code&redirect_uri=#{@settings["redirect_uri"]}" )
end
end
diff --git a/util/twitch.cr b/util/twitch.cr
index 1e63736..dea1ef1 100644
--- a/util/twitch.cr
+++ b/util/twitch.cr
@@ -7,7 +7,7 @@ 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|
+["access_token", "channel", "channel_id", "client_id", "client_id_twitch", "client_secret", "app_access_token", "eventsub_secret", "scopes", "redirect_uri"].each do |key|
begin
settings[key] = File.read( settings["home"] + "/.config/twitch/" + key ).chomp
rescue
@@ -20,9 +20,7 @@ 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
+when "videos", "lsvideo", "lsvideos", "videos_helix" # smaller json, less information
list_videos_helix( settings, client )
when "follows", "lsfollow", "lsfollows"
list_user_follows( settings, client )
@@ -37,21 +35,23 @@ when "follow"
when "unfollow"
pp client.unfollow( settings["channel_id"], ARGV[1] )
when "streams_followed"
- list_streams_followed( settings, client )
+ list_streams_followed( settings, client, ARGV[1]? || settings["channel_id"].to_u64 )
when "clips"
pp JSON.parse( client.get_clips( ARGV[1] ) )
when "game"
- pp client.get_games( ARGV[1] )
+ pp JSON.parse( client.get_games( ARGV[1].to_u64? || 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 "streams_kraken"
+ list_streams_v5( settings, client, 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 ) )
+ list_subs( settings, client, ARGV[1] )
+
+ #pp JSON.parse( client.get_subs( client.user( ARGV[1] ).id ) )
when "eventsubs"
pp JSON.parse( client.get_eventsubs( ) )
when "eventsub"
@@ -60,20 +60,20 @@ when "eventunsub"
pp client.delete_eventsubs!( ARGV[1] )
when "panels"
pp JSON.parse( client.get_panels( ARGV[1] ) )
+when "authorize"
+ pp client.authorize()
when "validate"
pp JSON.parse( client.get_token_validation() )
when "app_token"
pp JSON.parse( client.get_app_token() )
+when "token"
+ pp client.get_token( ARGV[1] )
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
+ pp JSON.parse( client.get_channel( client.user_id( ARGV[1]? || settings["channel_id"] ) ) )
else
puts "ARGV[0] must be one of videos, follows"
end
@@ -89,14 +89,18 @@ macro fcell( data )
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]
+ elsif celi != ( celw.size - 1 )
+ celw[( celw.size - 1 ) ]
else
celw[celi] = celw[celi] - ( celw.sum( foh ) - winsz ) - ( {{data}}.bytesize - {{data}}.size )
+ if celw[celi] < 0
+ celw[celi] = 0
+ end
end
end
celi+=1
{ celw[celi-1], celw[celi-1], {{data}} }
+ {{debug()}}
end
macro get_winsz()
@@ -111,6 +115,7 @@ def list_videos_helix( settings, client )
id = client.user( ARGV[1] ).id
response = JSON.parse( client.get_videos( id ) )
+ pp response
winsz = get_winsz()
@@ -129,61 +134,40 @@ def list_videos_helix( settings, client )
celi=0
printf( format,
- *fcell( video["url"].as_s[12..] ),
- *fcell( video["created_at"].as_s.[0..15] ),
+ *fcell( video["url"].as_s ),
+ *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["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
-
+)
+#pp celw
+# if video["title"].as_s.chomp.bytesize > celw[celi]
+# celw[celi] = video["title"].as_s.chomp.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 ) # 33 - 13 = 20
+# elsif celi != ( celw.size - 1 )
+# puts
+# pp foh # 5
+# # sum = 93
+# pp winsz # 80
+# pp celi # 5
+# pp celw # [39, 16, 1, 8, 1, 33]
+# pp celw.size # 6
+# celw[( celw.size-1 )]
+# else
+# celw[celi] = celw[celi] - ( celw.sum( foh ) - winsz ) - ( video["title"].as_s.chomp.bytesize - video["title"].as_s.chomp.size )
+# end
+# end
+# celi+=1
+# printf( "%*.*s\n", celw[celi-1], celw[celi-1], video["title"].as_s.chomp )
+#
+# )
end
@@ -544,7 +528,49 @@ def list_streams( settings, response )
end
-def list_streams_followed( settings, client )
+def list_subs( settings, client, user_id )
+
+ user_id = client.user( user_id ).id
+
+ winsz = get_winsz()
+
+ # cell widths
+ celw=[0,0,0]
+ # cell index
+ celi=0
+
+ format = "%-*.*s %-*.*s %-*.*s\n"
+
+ # format overhead
+ foh = format.delete( "^ " ).size
+
+ response = JSON.parse( client.get_subs( user_id ) )
+
+ pp response
+
+# response["data"].as_a.sort{ |a, b| b["viewer_count"].as_i <=> a["viewer_count"].as_i }.each do |stream|
+ response["data"].as_a.each do |stream|
+
+ celi=0
+
+ begin
+ printf( format,
+ *fcell( stream["plan_name"].as_s[0].to_s ),
+ *fcell( stream["gifter_login"].as_s ),
+ *fcell( stream["user_login"].to_s ),
+ )
+ rescue ex
+ pp ex
+ pp stream
+ end
+
+ end
+ puts response["points"]
+
+end
+def list_streams_followed( settings, client, user_id )
+
+ user_id = client.user( user_id ).id
winsz = get_winsz()
@@ -558,22 +584,25 @@ def list_streams_followed( settings, client )
# format overhead
foh = format.delete( "^ " ).size
- response = JSON.parse( client.get_streams_followed() )
+ response = JSON.parse( client.get_streams_followed( user_id ) )
pp response
- response["streams"].as_a.sort{ |a, b| b["viewers"].as_i <=> a["viewers"].as_i }.each do |stream|
+# response["data"].as_a.sort{ |a, b| b["viewer_count"].as_i <=> a["viewer_count"].as_i }.each do |stream|
+ response["data"].as_a.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 ),
+ #*fcell( stream["user_login"].as_s[12..] ),
+ *fcell( "twitch.tv/#{ stream["user_login"].as_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( "0" ),
+ *fcell( stream["game_name"].as_s ),
+ *fcell( stream["title"].to_s ),
)
rescue ex
pp ex
@@ -586,6 +615,8 @@ end
def update_channel( settings, client, id )
- pp JSON.parse( client.put_channel!( id, game: ARGV[1], status: ARGV[2] ) )
+ response = client.put_channel!( id, game: ARGV[1], title: ARGV[2] )
+ pp response
+ pp JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
end