Ok everything is working now lol

This commit is contained in:
Fijxu 2024-05-26 02:29:18 -04:00
parent 6d82b13e09
commit 7fbc39436b
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
6 changed files with 682 additions and 74 deletions

View file

@ -1,16 +1,16 @@
### TODO
- Use Redis to cache replies and add a new key to report for how much time the JSON is going to be stored in cache (**Already started, but I need to fix it!!**)
- Add other endpoints from api.ivr.fi
- ~Use Redis to cache replies and add a new key to report for how much time the JSON is going to be stored in cache (**Already started, but I need to fix it!!**)~ (Only done for the user endpoint)
- ~Add other endpoints from api.ivr.fi~ (Almost everything)
- CAPTURE MORE THAN 1 PARAM `?login=fuck,fuck`
### NOT SO IMPORTANT TODO
- Rate limiting (can be done in the reverse proxy side)
- Better shitcode
- STOP USING KEMAL FOR SIMPLE THINGS!
- ~Better shitcode~ Better goodcode
- ~STOP USING KEMAL FOR SIMPLE THINGS!~ Nevermind, kemal is pretty useful and it should stay
### Suggestions
3:45 RyanPotat: add team
3:45 RyanPotat: add team

10
config/config.example.yml Normal file
View file

@ -0,0 +1,10 @@
# Please fill everything
helixOAuth: ""
helixClientID: ""
gqlOAuth: ""
gqlClientID: ""
apiEndpoint: "https://api.twitch.tv/helix"
gqlEndpoint: "https://gql.twitch.tv/gql"
redis_url: "tcp://127.0.0.1:6379"
port: 8080

View file

@ -18,11 +18,15 @@ module TwAPI::Routes::Twitch
end
def parseData(params)
if !params.has_key?("login") && !params.has_key?("id")
err = {"error" => "No query parameters found"}
return err.to_json
end
login = params["login"]?
id = params["id"]?
if login && (json = REDIS_DB.get(login))
puts(JSON.parse(json))
# puts(JSON.parse(json))
return json
elsif id && (json = REDIS_DB.get(id))
return json
@ -37,7 +41,12 @@ module TwAPI::Routes::Twitch
# end
gql = GqlAPI.queryUser(params)
puts gql
if (gql["user"] == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
end
# puts gql
panels = gql["user"]["panels"]
banned = checkBanned(gql)
@ -102,7 +111,7 @@ module TwAPI::Routes::Twitch
begin
parseData(params)
rescue ex
env.response.status_code = 401
env.response.status_code = 404
err = {"error" => "#{ex.message}"}
err.to_json
end
@ -110,57 +119,368 @@ module TwAPI::Routes::Twitch
begin
parseData(params)
rescue ex
env.response.status_code = 401
env.response.status_code = 404
err = {"error" => "#{ex.message}"}
err.to_json
end
else
env.response.status_code = 401
env.response.status_code = 404
err = {"error" => "Parameter 'login' or 'id' is missing"}
err.to_json
end
else
env.response.status_code = 401
env.response.status_code = 404
err = {"error" => "No query parameters found"}
err.to_json
end
end
def modvip(env)
if !env.params.query.has_key?("login") && !env.params.query.has_key?("id")
err = {"error" => "No query parameters found"}
# if !env.params.query.has_key?("login") && !env.params.query.has_key?("id")
# err = {"error" => "No query parameters found"}
# return err.to_json
# else
if !env.params.url.has_key?("channel")
err = {"error" => "Please insert a channel"}
return err.to_json
else
gql = GqlAPI.queryModsVips(env.params.query)["user"]
# puts (gql["user"]["mods"]["edges"].size.times)
i, j = 0, 0
end
json_data = JSON.build do |json|
json.object do
json.field "modCount", gql["mods"]["edges"].size.to_i
json.field "vipCount", gql["vips"]["edges"].size.to_i
json.field "mods" do
json.array do
while i < gql["mods"]["edges"].size # There is a better way to do this?
json.object do
json.field "id", gql["mods"]["edges"][i]["node"]["id"]?
json.field "login", gql["mods"]["edges"][i]["node"]["login"]?
json.field "displayName", gql["mods"]["edges"][i]["node"]["displayName"]?
json.field "grantedAt", gql["mods"]["edges"][i]["grantedAt"]?
i += 1
gql = GqlAPI.getModsVips(env.params.url)["user"]
if (gql == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
end
json_data = JSON.build do |json|
json.object do
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
json.field "vips" do
json.array do
while j < gql["vips"]["edges"].size
json.object do
# puts gql["vips"]["edges"][j]["node"]["id"]
json.field "id", gql["vips"]["edges"][j].try &.["node"]?.try &.["id"]
json.field "login", gql["vips"]["edges"][j].try &.["node"]["login"] || ""
json.field "displayName", gql["vips"]["edges"][j].try &.["node"]["displayName"] || ""
json.field "grantedAt", gql["vips"]["edges"][j]["grantedAt"]?
j += 1
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
return json_data
end
# TODO: Add error message if query doesn't exist at all
def founders(env)
# if !env.params.query.has_key?("login") && !env.params.query.has_key?("id")
# err = {"error" => "No query parameters found"}
# return err.to_json
# else
if !env.params.url.has_key?("login")
err = {"error" => "Please insert a channel"}
return err.to_json
end
gql = GqlAPI.getFounders(env.params.url)["user"]
if (gql == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
end
json_data = JSON.build do |json|
json.object do
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
end
def clip(env)
# if !env.params.query.has_key?("slug")
# err = {"error" => "No query parameters found"}
# return err.to_jso
# else
if !env.params.url.has_key?("slug")
err = {"error" => "Please insert a slug"}
return err.to_json
end
gql = GqlAPI.getClips(env.params.url)["clip"]
if (gql == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
end
json_data = JSON.build do |json|
json.object do
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
end
def subage(env)
if !env.params.url.has_key?("user") || !env.params.url.has_key?("channel")
err = {"error" => "No query parameters found"}
return err.to_json
end
gql = GqlAPI.getSubage(env.params.url["user"], env.params.url["channel"])
if (gql == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
end
json_data = JSON.build do |json|
json.object do
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)
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)
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
end
def emotes(env)
if !env.params.url.has_key?("emote")
err = {"error" => "No query parameters found"}
return err.to_json
end
gql = GqlAPI.getEmotes(env.params.url["emote"])["emote"] # TODO: support if ID or emote name
if (gql == nil)
err = {"message" => "Specified emote does not exist"}
return err.to_json
end
json_data = JSON.build do |json|
json.object do
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
end
def channelEmotes(env)
if !env.params.query.has_key?("channel")
err = {"error" => "No query parameters found"}
return err.to_json
end
gql = GqlAPI.getChannelEmotes(env.params.query["channel"]) # TODO: Add support for IDs an not just the channel name
if (gql == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
end
json_data = JSON.build do |json|
json.object do
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
@ -168,8 +488,17 @@ module TwAPI::Routes::Twitch
end
end
end
return json_data
end
# TODO: Add error message if query doesn't exist at all
end
end
# I'll do this later fuck this shit
# def globalEmotes(env)
# if !env.params.query.has_key?("id") || !env.params.query.has_key?("login")
# err = "[]"
# return err.to_json
# else
# gql = GqlAPI.getChannelEmotes(env.params.query)
# json_data = JSON.build do |json|
# end

View file

@ -1,8 +1,8 @@
module TwAPI::Routing
extend self # To prevent writing self. at the start of every function
extend self # To prevent writing self. at the start of every function
# Thanks Invidious devs
{% for http_method in {"get", "post", "delete", "options", "patch", "put"} %}
# Thanks Invidious devs
{% for http_method in {"get", "post", "delete", "options", "patch", "put"} %}
macro {{http_method.id}}(path, controller, method = :handle)
unless Kemal::Utils.path_starts_with_slash?(\{{path}})
@ -16,9 +16,24 @@ module TwAPI::Routing
{% end %}
def register_all
# get "/"
get "/v2/twitch/user", TwAPI::Routes::Twitch, :user
get "/v2/twitch/modvip", TwAPI::Routes::Twitch, :modvip
end
def register_all
# get "/"
get "/v2/twitch/user", TwAPI::Routes::Twitch, :user
get "/v2/twitch/modvip/", TwAPI::Routes::Twitch, :modvip
get "/v2/twitch/modvip/:channel", TwAPI::Routes::Twitch, :modvip
get "/v2/twitch/founders", TwAPI::Routes::Twitch, :founders
get "/v2/twitch/founders/:login", TwAPI::Routes::Twitch, :founders
get "/v2/twitch/clip", TwAPI::Routes::Twitch, :clip
get "/v2/twitch/clip/:slug", TwAPI::Routes::Twitch, :clip
get "/v2/twitch/subage", TwAPI::Routes::Twitch, :subage
get "/v2/twitch/subage/:user/:channel", TwAPI::Routes::Twitch, :subage
get "/v2/twitch/emotes", TwAPI::Routes::Twitch, :emotes
get "/v2/twitch/emotes/:emote", TwAPI::Routes::Twitch, :emotes
# get "/v2/twitch/emotes/channel", TwAPI::Routes::Twitch, :channelEmotes
end
end

View file

@ -21,19 +21,29 @@ module GqlAPI
end
end
def targetUser(params)
if params.has_key?("id")
return %(targetUserID: "#{params["id"]}" )
else
return %(targetUserLogin: "#{params["login"]}")
end
end
def loginOrID(params)
if params.has_key?("id")
return %(userResultByID(id: "#{params["id"]}")), %(user(id: "#{params["id"]}" lookupType: ALL))
return %(id: "#{params["id"]}")
else
return %(userResultByLogin(login: "#{params["login"]}")), %(user(login: "#{params["login"]}" lookupType: ALL))
return %(login: "#{params["login"]}")
end
end
def user(params)
if params.has_key?("id")
return %(user(id: "#{params["id"]}" lookupType: ALL))
else
return %(user(login: "#{params["login"]}" lookupType: ALL))
return %(id: "#{params["id"]}" lookupType: ALL)
elsif params.has_key?("login")
return %(login: "#{params["login"]}" lookupType: ALL)
else
return %(login: "#{params["channel"]}" lookupType: ALL)
end
end
@ -41,13 +51,13 @@ module GqlAPI
@@urb = userResultBy(params)
query = %(
query {
#{@@urb} {
#{userResultBy(params)} {
... on UserDoesNotExist {
key
reason
}
}
#{user(params)} {
user(#{user(params)}) {
id
language
login
@ -128,13 +138,13 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(params, query)))["data"]
return (JSON.parse(gqlReq(query)))["data"]
end
def queryModsVips(params)
def getModsVips(env)
query = %(
query {
#{user(params)} {
user(#{user(env)}) {
mods(first: 100) {
edges {
grantedAt
@ -159,15 +169,255 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(params, query)))["data"]
return (JSON.parse(gqlReq(query)))["data"]
end
def gqlReq(params, query)
def getFounders(env)
query = %(
query {
user(#{user(env)}) {
id
login
displayName
channel {
founderBadgeAvailability
founders {
isSubscribed
entitlementStart
user {
id
login
displayName
}
}
}
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
end
def getClips(params)
query = %(
query {
clip(slug: "#{params["slug"]}") {
createdAt
creationState
durationSeconds
embedURL
id
isPublished
language
slug
thumbnailURL
title
url
videoOffsetSeconds
viewCount
game {
id
name
}
broadcaster {
id
displayName
}
curator {
id
displayName
}
videoQualities {
frameRate
quality
sourceURL
}
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
end
def getSubage(user, channel)
channelId = HelixAPI.getId(channel)
query = %(
query {
userData: userResultByLogin (login: "#{user}") {
... on UserDoesNotExist { key reason }
... on UserError { key }
... on User { id displayName login }
}
channelData: userResultByLogin (login: "#{channel}") {
... on UserDoesNotExist { key reason }
... on UserError { key }
... on User { id displayName login roles { isAffiliate isPartner } }
}
info: user (login: "#{user}" lookupType: ALL) {
relationship (targetUserID: "#{channelId}") {
followedAt
cumulative: subscriptionTenure(tenureMethod: CUMULATIVE) {
daysRemaining
elapsedDays
end
months
start
}
streak: subscriptionTenure(tenureMethod: STREAK) {
daysRemaining
elapsedDays
end
months
start
}
meta: subscriptionBenefit {
endsAt
purchasedWithPrime
renewsAt
tier
giftMeta: gift {
giftDate
isGift
gifter {
displayName
login
id
}
}
}
}
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
end
def getEmotes(emote)
query = %(
query {
emote(id: "#{emote}") {
setID
assetType
id
state
suffix
text
subscriptionTier
owner {
id
login
displayName
}
artist {
id
login
displayName
}
bitsBadgeTierSummary {
threshold
}
type
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
end
def getChannelEmotes(channel)
query = %(
{
user(login: "#{channel}") {
channel {
localEmoteSets {
id
localEmotes: emotes {
artist { id }
id
type
assetType
text
}
}
}
subEmotes: subscriptionProducts {
displayName
id
tier
emotes {
id
setID
text
type
assetType
}
}
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
end
def getEmotes(emote)
query = %(
query {
emote(id: "#{emote}") {
setID
assetType
id
state
suffix
text
subscriptionTier
owner {
id
login
displayName
}
artist {
id
login
displayName
}
bitsBadgeTierSummary {
threshold
}
type
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
end
def getChannelBadges(channel)
query = %(
query {
user(login: "#{channel}") {
broadcastBadges {
id
normalImageURL: imageURL(size: NORMAL)
doubleImageURL: imageURL(size: DOUBLE)
quadrupleImageURL: imageURL(size: QUADRUPLE)
title
description
onClickAction
clickURL
}
channel {
creatorBadgeFlair {
assets {
tier
}
}
}
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
end
def gqlReq(query)
data = {"query" => query}
response = HTTP::Client.post(CONFIG.gqlEndpoint.to_s, headers: @@headers, body: data.to_json)
if response.success?
# puts (typeof(response.body))
return (response.body)
else
raise "GQL Twitch API returned #{response.status_code}: #{response.body.to_s}"

View file

@ -1,11 +1,13 @@
module HelixAPI
extend self
@@headers = HTTP::Headers{
"Content-Type" => "application/json",
"Authorization" => "Bearer #{CONFIG.helixOAuth}",
"Client-Id" => "#{CONFIG.helixClientID}",
}
def self.users(params)
def users(params)
if params.has_key?("login")
endpoint = "#{CONFIG.apiEndpoint}/users?login=#{params["login"]}"
else
@ -14,7 +16,6 @@ module HelixAPI
response = HTTP::Client.get(endpoint, headers: @@headers)
# puts response.body
if response.success?
return (response.body)
else
@ -22,15 +23,18 @@ module HelixAPI
end
end
# def self.chatColor(id)
# endpoint = "#{CONFIG.apiEndpoint}/chat/color?user_id=#{id}"
def getId(login)
request = "#{CONFIG.apiEndpoint.to_s}/users?login=#{login}"
return JSON.parse(helixReq(request))["data"][0]["id"]
end
# response = HTTP::Client.get(endpoint, headers: @@headers)
def helixReq(request)
response = HTTP::Client.get(request, headers: @@headers)
# if response.success?
# return (response.body)
# else
# raise "Helix Twitch API returned #{response.status_code}: #{response.body.to_s}"
# end
# end
if response.success?
return (response.body)
else
raise "Helix Twitch API returned #{response.status_code}: #{response.body.to_s}"
end
end
end