This commit is contained in:
Fijxu 2024-05-17 16:16:37 -04:00
commit 9e3e7b5397
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
7 changed files with 340 additions and 0 deletions

18
shard.lock Normal file
View file

@ -0,0 +1,18 @@
version: 2.0
shards:
backtracer:
git: https://github.com/sija/backtracer.cr.git
version: 1.2.2
exception_page:
git: https://github.com/crystal-loot/exception_page.git
version: 0.4.1
kemal:
git: https://github.com/kemalcr/kemal.git
version: 1.5.0
radix:
git: https://github.com/luislavena/radix.git
version: 0.4.1

28
shard.yml Normal file
View file

@ -0,0 +1,28 @@
name: ivr-api
version: 0.1.0
targets:
invidious:
main: src/main.cr
dependencies:
kemal:
github: kemalcr/kemal
# authors:
# - name <email@example.com>
# description: |
# Short description of ivr-api
# dependencies:
# pg:
# github: will/crystal-pg
# version: "~> 0.5"
# development_dependencies:
# webmock:
# github: manastech/webmock.cr
# license: MIT

61
src/api/users.cr Normal file
View file

@ -0,0 +1,61 @@
require "json"
require "../twitchapi/*"
module Users
def self.parseData(params)
helixUsers = JSON.parse(HelixAPI.users(params))
login = helixUsers["data"][0]["login"]
id = helixUsers["data"][0]["id"]
# gqlUser = JSON.parse(GqlAPI.user(idto))
gqlUser = GqlAPI.user(id.to_s)
gqlChannel = GqlAPI.channel(id.to_s)
helixChatColor = JSON.parse(HelixAPI.chatColor(id))
# gqlCA = JSON.parse(GqlAPI.gqlReq("ChannelAvatar", "#{login}"))
# gqlCS = JSON.parse(GqlAPI.gqlReq("ChannelShell", "#{login}"))
# gqlUser["chatSettings"]["rules"].to_json.each do |rule|
# puts rule
# end
json_data = [
{
"banned" => false,
"displayName" => helixUsers["data"][0]["display_name"],
"login" => helixUsers["data"][0]["login"],
"id" => helixUsers["data"][0]["id"],
"bio" => helixUsers["data"][0]["description"],
# "followers" => gqlUser[0]["data"]["user"]["followers"]["totalCount"],
"profileViewCount" => nil, # Always null
"panelCount" => "",
"chatColor" => helixChatColor["data"][0]["color"],
"logo" => gqlUser["profileImageURL"],
"banner" => gqlUser["bannerImageURL"],
"verifiedBot" => nil, # Deprecated by twitch
"createdAt" => helixUsers["data"][0]["created_at"],
"updatedAt" => nil,
"emotePrefix" => "lol",
"chatterCount" => gqlChannel["chatters"]["count"],
"roles" => {
"isAffiliate" => gqlUser["roles"]["isAffiliate"],
"isPartner" => gqlUser["roles"]["isPartner"],
"isStaff" => gqlUser["roles"]["isStaff"],
},
"badges" => [
{
"setID" => "game-developer",
"title" => "Game Developer",
"description" => "Game Developer for:",
"version" => "1",
},
],
"chatSettings" => { gqlUser["chatSettings"] },
"stream" => gqlUser["stream"],
"lastBroadcast" => gqlUser["lastBroadcast"]
},
]
return json_data.to_json
end
end

39
src/config.cr Normal file
View file

@ -0,0 +1,39 @@
require "yaml"
class Config
include YAML::Serializable
property oauthToken : String?
property clientID : String?
property apiEndpoint : String?
property gqlEndpoint : String?
def self.load
config_file = "config/config.yml"
config_yaml = File.read(config_file)
config = Config.from_yaml(config_yaml)
if config.oauthToken.to_s.empty?
puts "Config: 'oauthToken' is required/can't be empty"
exit(1)
end
if config.clientID.to_s.empty?
puts "Config: 'oauthToken' is required/can't be empty"
exit(1)
end
if config.apiEndpoint.to_s.empty?
puts "Config: 'apiEndpoint' is required/can't be empty"
exit(1)
end
if config.gqlEndpoint.to_s.empty?
puts "Config: 'apiEndpoint' is required/can't be empty"
exit(1)
end
return config
end
end

49
src/main.cr Normal file
View file

@ -0,0 +1,49 @@
require "http/server"
require "kemal"
require "json"
require "uri"
require "./config"
require "./api/*"
require "./twitchapi/*"
CONFIG = Config.load
# puts("oauthToken: #{CONFIG.oauthToken}")
# puts("clientID: #{CONFIG.clientID}")
before_all "/twitch/*" do |env|
env.response.content_type = "application/json"
end
get "/twitch/user" do |env|
query = env.request.query
if query
params = URI::Params.parse(query)
if params.has_key?("login")
begin
Users.parseData(params)
rescue ex
env.response.status_code = 401
err = { "error" => "#{ex.message}" }
err.to_json
end
elsif params.has_key?("id")
begin
Users.parseData(params)
rescue ex
env.response.status_code = 401
err = { "error" => "#{ex.message}" }
err.to_json
end
else
env.response.status_code = 401
err = { "error" => "Parameter 'login' or 'id' is missing" }
err.to_json
end
else
env.response.status_code = 401
err = { "error" => "No query parameters found" }
err.to_json
end
end
Kemal.run

110
src/twitchapi/gql.cr Normal file
View file

@ -0,0 +1,110 @@
require "json"
require "uri"
module GqlAPI
@@headers = HTTP::Headers{
"Content-Type" => "application/json",
# "Client-Id" => "kimne78kx3ncx6brgo4mv6wki5h1ko", # Can cause problems due to Client-Integrity
"Client-Id" => "ue6666qo983tsx6so1t0vnawi233wa",
}
def self.gqlOperation(operation : String, login : String)
case operation
when "ChannelShell"
data = [
{
"operationName" => "#{operation}",
"variables" => {
"login" => "#{login}",
},
"extensions" => {
"persistedQuery" => {
"version" => 1,
"sha256Hash" => "580ab410bcd0c1ad194224957ae2241e5d252b2c5173d8e0cce9d32d5bb14efe",
},
},
},
]
when "ChannelAvatar"
data = [
{
"operationName" => "#{operation}",
"variables" => {
"channelLogin" => "#{login}",
},
"extensions" => {
"persistedQuery" => {
"version" => 1,
"sha256Hash" => "84ed918aaa9aaf930e58ac81733f552abeef8ac26c0117746865428a7e5c8ab0",
},
},
},
]
end
return data.to_json
end
def self.gqlOperation2()
data = { "query" => "{}" }
return data.to_json
end
def self.user(id : String)
data = { "query" => "{user(id:#{id}){
bannerImageURL,
profileImageURL(width: 600),
roles{isStaff,isAffiliate,isPartner,isExtensionsDeveloper},
chatSettings{blockLinks,chatDelayMs,slowModeDurationSeconds,followersOnlyDurationMinutes,isBroadcasterLanguageModeEnabled,isEmoteOnlyModeEnabled,isFastSubsModeEnabled,isSubscribersOnlyModeEnabled,isUniqueChatModeEnabled,requireVerifiedAccount,rules},
stream{averageFPS,bitrate,codec,createdAt,width,height,id,viewersCount,type,game{displayName}},
lastBroadcast{game{displayName},id,startedAt,title}
}}" }
return JSON.parse(gqlReq(id, data.to_json))["data"]["user"]
end
def self.channel(id : String)
data = { "query" => "{channel(id:#{id}){
chatters{count,moderators{login},vips{login}}
}}" }
return JSON.parse(gqlReq(id, data.to_json))["data"]["channel"]
end
def self.gqlReq(id : String, data : String)
response = HTTP::Client.post(CONFIG.gqlEndpoint.to_s, headers: @@headers, body: data)
if response.success?
return (response.body)
else
raise "GQL Twitch API returned #{response.status_code}: #{response.body.to_s}"
end
end
# def self.gqlReq(operation : String, login : String)
# gqlRequest = gqlOperation2()
# puts gqlRequest
# response = HTTP::Client.post(CONFIG.gqlEndpoint.to_s, headers: @@headers, body: gqlRequest)
# if response.success?
# return (response.body)
# else
# raise "GQL Twitch API returned #{response.status_code}: #{response.body.to_s}"
# end
# end
end
{
"4": {
"extensions": {
"persistedQuery": {
"sha256Hash": "84ed918aaa9aaf930e58ac81733f552abeef8ac26c0117746865428a7e5c8ab0",
"version": 1
}
},
"operationName": "ChannelAvatar",
"variables": {
"channelLogin": "fijxu"
}
}
}

35
src/twitchapi/helix.cr Normal file
View file

@ -0,0 +1,35 @@
module HelixAPI
@@headers = HTTP::Headers{
"Content-Type" => "application/json",
"Authorization" => "Bearer #{CONFIG.oauthToken}",
"Client-Id" => "#{CONFIG.clientID}",
}
def self.users(params)
if params.has_key?("login")
endpoint = "#{CONFIG.apiEndpoint}/users?login=#{params["login"]}"
else
endpoint = "#{CONFIG.apiEndpoint}/users?id=#{params["id"]}"
end
response = HTTP::Client.get(endpoint, headers: @@headers)
if response.success?
return (response.body)
else
raise "Helix Twitch API returned #{response.status_code}: #{response.body.to_s}"
end
end
def self.chatColor(id)
endpoint = "#{CONFIG.apiEndpoint}/chat/color?user_id=#{id}"
response = HTTP::Client.get(endpoint, headers: @@headers)
if response.success?
return (response.body)
else
raise "Helix Twitch API returned #{response.status_code}: #{response.body.to_s}"
end
end
end