summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/twitcr/client.cr60
-rw-r--r--src/twitcr/rest.cr159
2 files changed, 185 insertions, 34 deletions
diff --git a/src/twitcr/client.cr b/src/twitcr/client.cr
index 51dedad..1a02a40 100644
--- a/src/twitcr/client.cr
+++ b/src/twitcr/client.cr
@@ -2,7 +2,6 @@ require "./rest"
require "./mappings/*"
class Twitcr::Client
- include REST
getter token : String?
getter client_id : String
@@ -10,19 +9,68 @@ class Twitcr::Client
def initialize(@client_secret, @client_id)
@token = nil
+
+ include REST
+
+ def initialize( @settings : Hash( String, String ) )
end
- def user?(name : String)
- true if get_user_by_login(name)
+ def user?( name : String )
+ true if parse_user(name)
rescue EMPTY_RESULT
false
end
+ # FIXME: move into a class
def user(name : String)
- get_user_by_login(name)
+ parse_user( name )
+ end
+
+ def user(id : UInt64 )
+ parse_user( id )
+ end
+
+ def user_id( name : String )
+ name.to_u64{ user(name).id }
+ end
+
+ def user_id( id : UInt64 )
+
+ end
+
+ # FIXME: move into a class
+ def game ( name : String )
+ parse_game( name )
end
- def user(id : Int64)
- get_user_by_id(id)
+ def game ( id : UInt64 )
+ parse_game( id )
end
+
+ def game_id( name : String )
+ name.to_u64{ game(name).id }
+ end
+
+ def game_id( id : UInt64 )
+
+ end
+
+ def unfollow( unfollower : ( String | UInt64 ), unfollowee : ( String | UInt64 ) )
+ unfollower_id = user_id(unfollower)
+ unfollowee_id = user_id(unfollowee)
+ return unfollow!( unfollower_id, unfollowee_id )
+ end
+
+ def follow( follower : ( String | UInt64 ), followee : ( String | UInt64 ) )
+ follower_id = user_id(follower)
+ followee_id = user_id(followee)
+ return follow!( follower_id, followee_id )
+ end
+
+ def friend( friender : ( String | UInt64 ), friendee : ( String | UInt64 ) )
+ friender_id = user_id(friender)
+ friendee_id = user_id(friendee)
+ return friend!( friender_id, friendee_id )
+ end
+
end
diff --git a/src/twitcr/rest.cr b/src/twitcr/rest.cr
index cb2c1a0..f3af85c 100644
--- a/src/twitcr/rest.cr
+++ b/src/twitcr/rest.cr
@@ -1,65 +1,168 @@
require "http"
+require "uri"
require "json"
require "./mappings/*"
module Twitcr::REST
# Mixin for interacting with Twitch's REST API
SSL_CONTEXT = OpenSSL::SSL::Context::Client.new
- API_BASE = "https://api.twitch.tv/helix"
+ API_BASE = "https://api.twitch.tv/"
EMPTY_RESULT = Exception.new("Empty Result")
- # Executes an HTTP request against the API_BASE url
- def request(method : String, route : String, version = "5", headers = HTTP::Headers.new, body : String? = nil)
- get_token unless @token
+ enum Api
+ Api # typically undocumented APIs go here
+ Kraken # v5, "deprecated" with no real replacement for half of the functions
+ Helix # "new api"
+ PubSub # FIXME: put this in another module, probably
+ end
+
+ # Execute HTTP request
+ def request( method : String, twitchapi : Api, route : String, body : String? = nil, *, contenttype : String? = nil )
+
+ headers = HTTP::Headers.new
+ headers["Client-ID"] = @settings["client_id"].not_nil!
+
+ api = twitchapi.to_s.downcase
+ case api
+ when "helix"
+ # FIXME: maybe don't send this every time?
+ headers["Authorization"] = "Bearer #{@settings["access_token"].not_nil!}"
+ when "kraken"
+ headers["Authorization"] = "OAuth #{@settings["access_token"].not_nil!}"
+ headers["Accept"] = "application/vnd.twitchtv.v5+json"
+ when "api"
+ # typically twitch restricts these to the twitch client id
+ headers["Client-ID"] = @settings["client_id_twitch"].not_nil!
+ else
+ end
+
+ if contenttype
+ headers["Content-Type"] = contenttype
+ end
- headers["Authorization"] = @token || "test"
- headers["Client-ID"] = @client_id
+ begin
+ ENV["DEBUG"]
+ rescue KeyError
+ else
+ puts method + " " + API_BASE + api + route
+ #pp headers# probably too sensitive
+ pp body
+ end
response = HTTP::Client.exec(
method,
- API_BASE + route,
+ API_BASE + api + route,
headers,
+ body,
tls: SSL_CONTEXT
)
- if response.status_code == 401
- get_token
- puts "Had to get new token"
- return request(method, route, version, headers, body)
+ begin
+ ENV["DEBUG"]
+ rescue KeyError
+ else
+ pp response.body
end
response.body
end
- def get_user_by_login(login : String)
- response = request(
- "GET",
- "/users?login=" + login
- )
-
- list = UserList.from_json(response)
+ # "!" denotes state mutation. "REST" is a bit of a misnomer.
+ def get_user( login : String ); request( "GET", Api::Helix, "/users?login=#{login}" ) end
+ def get_user( id : UInt64 ); request( "GET", Api::Helix, "/users?id=#{id.to_s}" ) end
+ def get_user( id : Array( UInt64 ) ); request( "GET", Api::Helix, "/users?id=#{id.map { |name| name.to_s }.join("&id=")}" ) end
+ def get_user( id : Array( String ) ); request( "GET", Api::Helix, "/users?login=#{login.join("&login=")}" ) end
+ def follow!( from : UInt64, to : UInt64 ); request( "PUT", Api::Kraken, "/users/#{from.to_s}/follows/channels/#{to.to_s}" ) end
+ def unfollow!( from : UInt64, to : UInt64 ); request( "DELETE", Api::Kraken, "/users/#{from.to_s}/follows/channels/#{to.to_s}" ) end
+ def get_user_follows( *, from : UInt64, after : String? = nil )
+ params = Hash( String, String ).new
+ params["first"] = "100"
+ params["from_id"] = from.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, "/users/follows" + query_string )
+ end
+ def get_user_follows( *, to : UInt64, after : String? = nil )
+ params = Hash( String, String ).new
+ params["first"] = "100"
+ params["to_id"] = to.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, "/users/follows" + query_string )
+ end
+ # FIXME: support array of mixed UInt64/String
+ def get_games( name : String ); request( "GET", Api::Helix, "/games?name=#{URI.encode_www_form( name )}" ) end
+ def get_games( id : UInt64 ); request( "GET", Api::Helix, "/games?id=#{id.to_s}" ) end
+ def get_streams( user_ids : Array( UInt64 ) ); request( "GET", Api::Helix, "/streams?user_id=#{user_ids.join( "&user_id=" )}" ) end
+ def get_streams( user_id : UInt64 ); request( "GET", Api::Helix, "/streams?user_id=#{user_id}" ) end
+ def get_streams( game_id : UInt64 ); request( "GET", Api::Helix, "/streams?first=100&game_id=#{game_id}" ) end
+ # Kraken returns more information
+ def get_streams_v5( game : String ); request( "GET", Api::Kraken, "/streams?limit=100&game=#{URI.encode_www_form( game )}" ) end
+ def get_streams_v5( *, channel : ( Array( UInt64 ) | UInt64 | Nil ) = nil, game : String? = nil, language : String? = nil, stream_type : String? = nil );
+ params = Hash( String, String ).new
+ case channel
+ when Nil
+ when UInt64
+ params["channel"] = channel.to_s
+ when Array( UInt64 )
+ params["channel"] = channel.map { |id| id.to_s }.join(",")
+ else
+ end
+ if game ; params["game"] = game end
+ if language ; params["language"] = language end
+ if stream_type ; params["stream_type"] = stream_type end
+ params["limit"] = "100"
+ query_string = HTTP::Params.encode( params )
+ 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
+ # 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_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 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
+
+ # 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
+ # https://github.com/justintv/Twitch-API/issues/545 requires weird scopes that normal client_ids can't get
+ def friend!( friender : UInt64, friendee : UInt64 ); request( "PUT", Api::Kraken, "/users/#{friender.to_s}/friends/#{friendee.to_s}" ) end
+ 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 )
+ list = GameList.from_json( get_games( game ) )
raise EMPTY_RESULT if list.data.empty?
-
list.data.first
end
- def get_user_by_id(id : Int64)
- response = request(
- "GET",
- "/users?id=" + id.to_s
- )
-
- list = UserList.from_json(response)
+ def parse_user( user : UInt64 | String )
+ list = UserList.from_json( get_user( user ) )
raise EMPTY_RESULT if list.data.empty?
-
list.data.first
end
def get_token
response = HTTP::Client.exec(
"POST",
- "https://id.twitch.tv/oauth2/token?client_id=#{@client_id}&client_secret=#{@client_secret}&grant_type=client_credentials",
+ "https://id.twitch.tv/oauth2/token?client_id=#{@settings["client_id"].not_nil!}&client_secret=#{@settings["client_secret"].not_nil!}&grant_type=client_credentials",
tls: SSL_CONTEXT
)