ivr-api-crystal/src/datahandlers/twitch.cr

640 lines
23 KiB
Crystal

module TwAPI::Datahandlers::Twitch
extend self
private macro error(message)
env.response.content_type = "application/json"
env.response.status_code = 403
error_message = {"error" => {{message}}, "twitchResponse" => gql}.to_json
return error_message
end
private macro twitchError
env.response.content_type = "application/json"
env.response.status_code = 403
error_message = {"error" => ex.message, "twitchResponse" => gql}.to_json
return error_message
end
private macro skipCache
env.params.query["skipCache"]?
end
def isBannedLogin(gql)
if gql["userResultByLogin"]["reason"]?
return true
else
return false
end
end
def isBannedId(gql)
if gql["userResultByID"]["reason"]?
return true
else
return false
end
end
def banReason(gql)
if gql["userResultByLogin"] == nil
return false
end
return true
end
def userLogin(env)
if skipCache != "true"
if (json = Utils::Redis.retrieveFromCache("user-login_#{env.params.query["login"].try &.split(',').to_s}"))
json = JSON.parse(json)
return json.to_json
end
end
begin
gql = GqlAPI.getUserByLogin(env.params.query)
rescue ex
twitchError
end
json_data = JSON.build do |json|
json.array do
gql.each do |gql|
gql = gql["data"]
if (gql["user"] == nil)
# v3 could add some feedback error message instead of an empty array.
nil # Do not add anything to the array. Just like api.ivr.fi does.
else
panels = gql["user"]["panels"]
banned = isBannedLogin(gql)
json.object do
json.field "banned", banned
if banned == true
json.field "banReason", gql["userResultByLogin"]["reason"]
end
json.field "displayName", gql["user"]["displayName"]
json.field "login", gql["user"]["login"]
json.field "id", gql["user"]["id"]
json.field "bio", gql["user"]["description"]
json.field "follows", nil
json.field "followers", gql["user"]["followers"]["totalCount"]
json.field "profileViewCount", nil # Always null
json.field "panelCount", panels.size
json.field "chatColor", gql["user"]["chatColor"]
json.field "logo", gql["user"]["profileImageURL"]
json.field "banner", gql["user"]["bannerImageURL"]
json.field "verifiedBot", nil # Deprecated by twitch
json.field "createdAt", gql["user"]["createdAt"]
json.field "updatedAt", gql["user"]["updatedAt"]
json.field "deletedAt", gql["user"]["deletedAt"]
json.field "emotePrefix", gql["user"]["emoticonPrefix"]["name"]
if gql["user"]["primaryTeam"] != nil
json.field "team", gql["user"]["primaryTeam"]["displayName"]
else
json.field "team", nil
end
if gql["user"]["primaryTeam"] != nil
json.field "contract", gql["user"]["programAgreement"]["type"]
else
json.field "contract", nil
end
json.field "roles", gql["user"]["roles"]
json.field "badges", gql["user"]["displayBadges"]
json.field "chatterCount", gql["user"]["channel"]["chatters"]["count"]
json.field "chatSettings", gql["user"]["chatSettings"]
json.field "stream", gql["user"]["stream"]
json.field "lastBroadcast", gql["user"]["lastBroadcast"]
json.field "panels", panels
end
end
end
end
end
begin
Utils::Redis.saveJson("user-login_#{env.params.query["login"].try &.split(',')}", json_data)
rescue ex
puts ex.message
end
return json_data
end
def userId(env)
if skipCache != "true"
if (json = Utils::Redis.retrieveFromCache("user-id_#{env.params.query["id"].try &.split(',').to_s}"))
json = JSON.parse(json)
return json.to_json
end
end
begin
gql = GqlAPI.getUserById(env.params.query)
rescue ex
twitchError
end
json_data = JSON.build do |json|
json.array do
gql.each do |gql|
pp gql = gql["data"]
pp typeof(gql)
if (gql["user"] == nil)
# v3 could add some feedback error message instead of an empty array.
# Do not add anything to the array. Just like api.ivr.fi does.
nil
else
panels = gql["user"]["panels"]
banned = isBannedId(gql)
json.object do
json.field "banned", banned
if banned == true
json.field "banReason", gql["userResultByID"]["reason"]
end
json.field "displayName", gql["user"]["displayName"]
json.field "login", gql["user"]["login"]
json.field "id", gql["user"]["id"]
json.field "bio", gql["user"]["description"]
json.field "follows", nil
json.field "followers", gql["user"]["followers"]["totalCount"]
json.field "profileViewCount", nil # Always null
json.field "panelCount", panels.size
json.field "chatColor", gql["user"]["chatColor"]
json.field "logo", gql["user"]["profileImageURL"]
json.field "banner", gql["user"]["bannerImageURL"]
json.field "verifiedBot", nil # Deprecated by twitch
json.field "createdAt", gql["user"]["createdAt"]
json.field "updatedAt", gql["user"]["updatedAt"]
json.field "deletedAt", gql["user"]["deletedAt"]
json.field "emotePrefix", gql["user"]["emoticonPrefix"]["name"]
# failed attempts
# pp "1: #{gql["user"]["primaryTeam"]? || nil}"
# pp "2: #{gql["user"].try &.["primaryTeam"]?.try &.["displayName"]? || nil}"
# json.field "team", gql["user"]["primaryTeam"].try &.["displayName"]?
#
if gql["user"]["primaryTeam"] != nil
json.field "team", gql["user"]["primaryTeam"]["displayName"]
else
json.field "team", nil
end
if gql["user"]["primaryTeam"] != nil
json.field "contract", gql["user"]["programAgreement"]["type"]
else
json.field "contract", nil
end
json.field "roles", gql["user"]["roles"]
json.field "badges", gql["user"]["displayBadges"]
json.field "chatterCount", gql["user"]["channel"]["chatters"]["count"]
json.field "chatSettings", gql["user"]["chatSettings"]
json.field "stream", gql["user"]["stream"]
json.field "lastBroadcast", gql["user"]["lastBroadcast"]
json.field "panels", panels
end
end
end
end
end
begin
Utils::Redis.saveJson("user-id_#{env.params.query["id"].try &.split(',')}", json_data)
rescue ex
puts ex.message
end
return json_data
end
def modvip(env)
channel = env.params.url["channel"]
if (json = Utils::Redis.retrieveFromCache("modvip-#{channel}")) && skipCache != "true"
json = JSON.parse(json)
json.as_h["cache"].as_h["isCached"] = JSON::Any.new(true)
json.as_h["cache"].as_h["expireTime"] = JSON::Any.new(Utils::Redis.getExpireTime("modvip-#{channel}"))
return json.to_json
end
begin
gql = GqlAPI.getModsVips(channel)
gql = gql["data"]["user"]
rescue ex
twitchError
end
if (gql == nil)
error("Specified channel does no exist")
end
json_data = JSON.build do |json|
json.object do
json.field "cache" do
json.object do
json.field "isCached", false
json.field "expireTime", nil
end
end
json.field "modCount", gql["mods"]["edges"].size
json.field "vipCount", gql["vips"]["edges"].size
json.field "mods" do
json.array do
gql["mods"]["edges"]?.try &.as_a.each do |edges|
json.object do
if edges.try &.["node"]? == nil
json.field "id", nil
json.field "login", nil
json.field "displayName", nil
json.field "grantedAt", nil
else
json.field "id", edges["node"]["id"]?
json.field "login", edges["node"]["login"]?
json.field "displayName", edges["node"]["displayName"]?
json.field "grantedAt", edges["grantedAt"]?
end
end
end
end
json.field "vips" do
json.array do
gql["vips"]["edges"]?.try &.as_a.each do |edges|
json.object do
if edges.try &.["node"]? == nil
json.field "id", nil
json.field "login", nil
json.field "displayName", nil
json.field "grantedAt", nil
else
json.field "id", edges["node"]["id"]
json.field "login", edges["node"]["login"]
json.field "displayName", edges["node"]["displayName"]
json.field "grantedAt", edges["grantedAt"]
end
end
end
end
end
end
end
end
Utils::Redis.saveJson("modvip-#{channel}", json_data)
return json_data
end
# TODO: Add error message if query doesn't exist at all
def founders(env)
login = env.params.url["login"]
if (json = Utils::Redis.retrieveFromCache("founders-#{login}")) && skipCache != "true"
json = JSON.parse(json)
json.as_h["cache"].as_h["isCached"] = JSON::Any.new(true)
json.as_h["cache"].as_h["expireTime"] = JSON::Any.new(Utils::Redis.getExpireTime("founders-#{login}"))
return json.to_json
end
begin
gql = GqlAPI.getFounders(login)
gql = gql["data"]["user"]
rescue ex
twitchError
end
# TODO: use begin rescue for this too!
if (gql == nil)
error("Specified channel does no exist")
end
json_data = JSON.build do |json|
json.object do
json.field "cache" do
json.object do
json.field "isCached", false
json.field "expireTime", nil
end
end
json.field "foundersCount", gql["channel"]["founders"].size
json.field "founders" do
json.array do
gql["channel"]["founders"]?.try &.as_a.each do |founders|
json.object do
json.field "isSubscribed", founders["isSubscribed"]?
json.field "entitlementStart", founders["entitlementStart"]?
if founders.try &.["user"]? == nil
json.field "id", nil
json.field "login", nil
json.field "displayName", nil
else
json.field "id", founders["user"]["id"]
json.field "login", founders["user"]["login"]
json.field "displayName", founders["user"]["displayName"]
end
end
end
end
end
end
end
Utils::Redis.saveJson("founders-#{login}", json_data)
return json_data
end
def clip(env)
slug = env.params.url["slug"]
if (json = Utils::Redis.retrieveFromCache("clip-#{slug}")) && skipCache != "true"
json = JSON.parse(json)
json.as_h["cache"].as_h["isCached"] = JSON::Any.new(true)
json.as_h["cache"].as_h["expireTime"] = JSON::Any.new(Utils::Redis.getExpireTime("clip-#{slug}"))
return json.to_json
end
begin
gql = GqlAPI.getClips(slug)
gql = gql["data"]["clip"]
rescue ex
twitchError
end
# TODO: use begin rescue for this too! (again)
if (gql == nil)
error("Slug does not exist")
end
json_data = JSON.build do |json|
json.object do
json.field "cache" do
json.object do
json.field "isCached", false
json.field "expireTime", nil
end
end
json.field "clip" do
json.object do
json.field "durationSeconds", gql["durationSeconds"]
json.field "id", gql["id"]
json.field "slug", gql["slug"]
json.field "url", gql["url"]
# TODO: Add tiny, small and medium previews
# "tiny": "https://clips-media-assets2.twitch.tv/MEtZsD4gcXcJb1kGQRbUYg/42368009784-offset-19328-preview-86x45.jpg",
# "small": "https://clips-media-assets2.twitch.tv/MEtZsD4gcXcJb1kGQRbUYg/42368009784-offset-19328-preview-260x147.jpg",
# "medium": "https://clips-media-assets2.twitch.tv/MEtZsD4gcXcJb1kGQRbUYg/42368009784-offset-19328-preview-480x272.jpg",
json.field "createdAt", gql["createdAt"]
json.field "title", gql["title"]
json.field "viewCount", gql["viewCount"]
# Game
json.field "game" do
json.object do
json.field "id", gql["game"]["id"]
json.field "name", gql["game"]["name"]
end
end
# Broadcaster
json.field "broadcaster" do
json.object do
json.field "id", gql["broadcaster"]["id"]
json.field "displayName", gql["broadcaster"]["displayName"]
end
end
# Curator
json.field "curator" do
json.object do
json.field "id", gql["curator"]["id"]
json.field "displayName", gql["curator"]["displayName"]
end
end
# Array of available video qualities
json.field "videoQualities" do
json.array do
gql["videoQualities"]?.try &.as_a.each do |videoQualities|
json.object do
if videoQualities.try &.["frameRate"]? == nil
json.field "error", "No data"
else
json.field "frameRate", videoQualities["frameRate"]
json.field "quality", videoQualities["quality"]
json.field "sourceURL", videoQualities["sourceURL"]
end
end
end
end
end
# TODO: get ClipKey
# "clipKey": "?sig=5cc70f40c61313db289a132ab58c7b27d16220ab&token=%7B%22authorization%22:%7B%22forbidden%22:false,%22reason%22:%22%22%7D,%22clip_uri%22:%22https://production.assets.clips.twitchcdn.net/WclEPrSCziCGI7bKo8Md-w/AT-cm%257CWclEPrSCziCGI7bKo8Md-w.mp4%22,%22clip_slug%22:%22AltruisticSarcasticQuailSoBayed-tILOIIYjtsqjwx37%22,%22device_id%22:null,%22expires%22:1716487243,%22user_id%22:%22%22,%22version%22:2%7D"
json.field "clipKey", "How the fuck I get this shit"
end
end
end
end
Utils::Redis.saveJson("clip-#{slug}", json_data)
return json_data
end
def subage(env)
user = env.params.url["user"]
channel = env.params.url["channel"]
if (json = Utils::Redis.retrieveFromCache("subage-user_#{user}_channel_#{channel}")) && skipCache != "true"
json = JSON.parse(json)
json.as_h["cache"].as_h["isCached"] = JSON::Any.new(true)
json.as_h["cache"].as_h["expireTime"] = JSON::Any.new(Utils::Redis.getExpireTime("subage-user_#{user}_channel_#{channel}"))
return json.to_json
end
begin
gql = GqlAPI.getSubage(user, channel)
gql = gql["data"]
rescue ex
if (gql == nil)
error("Channel does not exist")
end
twitchError
end
json_data = JSON.build do |json|
json.object do
json.field "cache" do
json.object do
json.field "isCached", false
json.field "expireTime", nil
end
end
json.field "user" do
json.object do
json.field "id", gql["userData"]["id"]?
json.field "login", gql["userData"]["login"]?
json.field "displayName", gql["userData"]["displayName"]?
end
end
json.field "channel" do
json.object do
json.field "id", gql["channelData"]["id"]?
json.field "login", gql["channelData"]["login"]?
json.field "displayName", gql["channelData"]["displayName"]?
end
end
json.field "statusHidden", false # TODO: what is this? I need to get it NOW!
json.field "followedAt", gql["info"]["relationship"]["followedAt"]
if (gql["info"]["relationship"]["streak"] == nil) || (gql["info"]["relationship"]["streak"]["elapsedDays"]? == 0)
json.field "streak", nil
else
json.field "streak" do
json.object do
json.field "elapsedDays", gql["info"]["relationship"]["streak"]["elapsedDays"]?
json.field "daysRemaining", gql["info"]["relationship"]["streak"]["daysRemaining"]?
json.field "months", gql["info"]["relationship"]["streak"]["months"]?
json.field "end", gql["info"]["relationship"]["streak"]["end"]?
json.field "start", gql["info"]["relationship"]["streak"]["elapsedDays"]?
end
end
end
if (gql["info"]["relationship"]["cumulative"] == nil) || (gql["info"]["relationship"]["cumulative"]["elapsedDays"]? == 0)
json.field "cumulative", nil
else
json.field "cumulative" do
json.object do
json.field "elapsedDays", gql["info"]["relationship"]["cumulative"]["elapsedDays"]?
json.field "daysRemaining", gql["info"]["relationship"]["cumulative"]["daysRemaining"]?
json.field "months", gql["info"]["relationship"]["cumulative"]["months"]?
json.field "end", gql["info"]["relationship"]["cumulative"]["end"]?
json.field "start", gql["info"]["relationship"]["cumulative"]["elapsedDays"]?
end
end
end
if (gql["info"]["relationship"]["meta"] == nil)
json.field "meta", nil
else
json.field "meta" do
json.object do
json.field "type", nil # TODO: get the type of the sub, could be paid or something else gql["info"]["relationship"]["meta"][""]
json.field "tier", (gql["info"]["relationship"]["meta"]["tier"].to_s.to_i / 1000)
json.field "endsAt", gql["info"]["relationship"]["meta"]["endsAt"]
json.field "renewsAt", gql["info"]["relationship"]["meta"]["renewsAt"]
json.field "giftMeta", nil # TODO: fill giftMeta is sub is gifted
end
end
end
end
end
Utils::Redis.saveJson("subage-user_#{user}_channel_#{channel}", json_data)
return json_data
end
def emotes(env)
emoteName = env.params.url["emote"]
if (json = Utils::Redis.retrieveFromCache("emotes-#{emoteName}")) && skipCache != "true"
json = JSON.parse(json)
json.as_h["cache"].as_h["isCached"] = JSON::Any.new(true)
json.as_h["cache"].as_h["expireTime"] = JSON::Any.new(Utils::Redis.getExpireTime("emotes-#{emoteName}"))
return json.to_json
end
begin
gql = GqlAPI.getEmotes(emoteName)
gql = gql["data"]["emote"]
if gql == nil
raise "Emote does not exist"
end
rescue ex
twitchError
end
json_data = JSON.build do |json|
json.object do
json.field "cache" do
json.object do
json.field "isCached", false
json.field "expireTime", nil
end
end
json.field "channelName", gql["owner"]["displayName"]
json.field "channelLogin", gql["owner"]["login"]
json.field "channelID", gql["owner"]["id"]
json.field "artist", nil # TODO: ADD SUPPORT FOR ARTIST OBJECT
json.field "emoteID", gql["id"]
json.field "emoteCode", gql["text"]
json.field "emoteSetID", gql["setID"]
json.field "emoteAssetType", gql["assetType"]
json.field "emoteState", gql["state"]
json.field "emoteType", gql["type"]
json.field "emoteTier", gql["subscriptionTier"]
end
end
Utils::Redis.saveJson("emotes-#{emoteName}", json_data)
return json_data
end
def channelEmotes(env)
gql = GqlAPI.getChannelEmotes(env.params.query["channel"]) # TODO: Add support for IDs an not just the channel name
json_data = JSON.build do |json|
json.object do
json.field "cache" do
json.object do
json.field "isCached", false
json.field "expireTime", nil
end
end
json.field "subProducts" do
json.array do
gql["user"]["subEmotes"]?.try &.as_a.each do |subEmotes|
json.object do
json.field "displayName", subEmotes["displayName"]
json.field "emoteSetID", subEmotes["id"]
json.field "tier", subEmotes["tier"]
json.field "emotes" do
json.array do
subEmotes["emotes"]?.try &.as_a.each do |emotes|
json.object do
json.field "id", emotes["id"]
json.field "setID", emotes["setID"]
json.field "code", emotes["text"]
json.field "type", emotes["type"]
json.field "assetType", emotes["assetType"]
end
end
end
end
end
end
end
# TODO: Emotes available by donating bits
# # # # # #
# json.field "bitEmotes" do
# json.array do
# end
json.field "localEmotes" do
json.array do
json.object do
json.field "id", gql["user"]["channel"]["localEmoteSets"][0]["id"] # TODO: Detect if channel has more than one localEmoteSets (since it's a fucking array)
json.field "emotes" do
json.array do
gql["user"]["channel"]["localEmoteSets"][0]["localEmotes"]?.try &.as_a.each do |localEmotes|
json.object do
json.field "artist", localEmotes["artist"] # TODO: Add support for artist (again)
json.field "id", localEmotes["id"]
json.field "type", localEmotes["type"]
json.field "assetType", localEmotes["assetType"]
json.field "code", localEmotes["text"]
end
end
end
end
end
end
end
end
end
end
end
end