This commit is contained in:
Fijxu 2024-06-22 02:35:49 -04:00
parent 7fbc39436b
commit c085b71ede
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
17 changed files with 226065 additions and 379 deletions

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
# Binaries
bin
# User config
config/config.yml
# libs
lib
# Syncthing
.stfolder

View file

@ -1,16 +1,24 @@
## How to run
- Clone repo
- Copy the `config/config.example.yml` to `config/config.yml`
- Fill every option
- Compile using `shards build --release`
- Run `./bin/ivr-api-crystal`
### 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!!**)~ (Only done for the user endpoint)
- ~Add other endpoints from api.ivr.fi~ (Almost everything)
- CAPTURE MORE THAN 1 PARAM `?login=fuck,fuck`
- ~~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~~ Done.
- ~~Add other endpoints from api.ivr.fi~~ Almost done.
- ~~Capture more than 1 param: `?login=fijxu,fijxu`~~ Done.
### NOT SO IMPORTANT TODO
- Rate limiting (can be done in the reverse proxy side)
- ~Better shitcode~ Better goodcode
- ~STOP USING KEMAL FOR SIMPLE THINGS!~ Nevermind, kemal is pretty useful and it should stay
- ~~STOP USING KEMAL FOR SIMPLE THINGS~~ Nevermind, kemal is pretty useful and it should stay
- `programAgreement { type }` in the Next commit.
- Guide on how to get the tokens to get it working
- Kick API
### Suggestions
3:45 RyanPotat: add team
~~3:45 RyanPotat: add team~~ Next commit.

View file

@ -1,10 +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
redis_addr: "127.0.0.1:6379"
redis_database: 1
port: 8081

47398
schema.graphql Normal file

File diff suppressed because it is too large Load diff

178032
schema.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,10 @@ shards:
git: https://github.com/sija/backtracer.cr.git
version: 1.2.2
db:
git: https://github.com/crystal-lang/crystal-db.git
version: 0.13.1
exception_page:
git: https://github.com/crystal-loot/exception_page.git
version: 0.4.1
@ -12,15 +16,11 @@ shards:
git: https://github.com/kemalcr/kemal.git
version: 1.5.0
pool:
git: https://github.com/ysbaddaden/pool.git
version: 0.2.4
radix:
git: https://github.com/luislavena/radix.git
version: 0.4.1
redis:
git: https://github.com/stefanwille/crystal-redis.git
version: 2.9.1
git: https://github.com/jgaskins/redis.git
version: 0.9.0

View file

@ -1,30 +1,22 @@
name: ivr-api
version: 0.1.0
name: ivr-crystal
version: 0.2.0
targets:
invidious:
ivr-api-crystal:
main: src/main.cr
dependencies:
kemal:
github: kemalcr/kemal
# redis:
# github: stefanwille/crystal-redis
redis:
github: stefanwille/crystal-redis
github: jgaskins/redis
authors:
- Fijxu
# authors:
# - name <email@example.com>
description: |
Drop-in api.ivr.fi replacement made using Crystal.
# description: |
# Short description of ivr-api
# dependencies:
# pg:
# github: will/crystal-pg
# version: "~> 0.5"
# development_dependencies:
# webmock:
# github: manastech/webmock.cr
# license: MIT
license: Unlicense

View file

@ -3,31 +3,21 @@ require "yaml"
class Config
include YAML::Serializable
property helixOAuth : String?
property helixClientID : String?
property gqlOAuth : String?
property gqlClientID : String?
property apiEndpoint : String?
property gqlEndpoint : String?
property redis_url : String?
property helixOAuth : String
property helixClientID : String
property gqlOAuth : String
property gqlClientID : String
property apiEndpoint : String? = "https://api.twitch.tv/helix"
property gqlEndpoint : String? = "https://gql.twitch.tv/gql"
property redis_addr : String? = "127.0.0.1:6379"
property redis_socket : String?
property redis_database : Int32?
property port : Int32 = 8080
def self.load
config_file = "config/config.yml"
config_yaml = File.read(config_file)
config = Config.from_yaml(config_yaml)
if config.helixOAuth.to_s.empty?
puts "Config: 'helixOAuth' is required/can't be empty"
exit(1)
end
if config.helixClientID.to_s.empty?
puts "Config: 'helixOAuth' is required/can't be empty"
exit(1)
end
return config
end
end

View file

@ -1,159 +1,215 @@
module TwAPI::Routes::Twitch
module TwAPI::Datahandlers::Twitch
extend self
def checkBanned(gql)
if GqlAPI.urb.includes? "login"
if gql["userResultByLogin"]["reason"]?
return gql["userResultByLogin"]["reason"]
else
return nil
end
elsif GqlAPI.urb.includes? "id"
if gql["userResultByID"]["reason"]?
return gql["userResultByID"]["reason"]
else
return nil
end
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 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))
return json
elsif id && (json = REDIS_DB.get(id))
return json
end
# puts REDIS_DB.get(params["login"]?)
# puts REDIS_DB.get(params["id"]?)
# if (json = (REDIS_DB.get(params["login"]?.to_s))) || (json = (REDIS_DB.get(params["id"]?.to_s)))
# if ((json = REDIS_DB.get(id))
# puts json
# return json
# end
gql = GqlAPI.queryUser(params)
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)
# puts(banned)
# Using JSON::Builder will be better I guess
json_data = [
{
"cache" => {
"isCached" => false,
"expireTime" => nil : Int32,
},
"banned" => false,
"reason" => banned,
"displayName" => gql["user"]["displayName"],
"login" => gql["user"]["login"],
"id" => gql["user"]["id"],
"bio" => gql["user"]["description"],
"follows" => nil,
"followers" => gql["user"]["followers"]["totalCount"],
"profileViewCount" => nil, # Always null
"panelCount" => panels.size,
"chatColor" => gql["user"]["chatColor"],
"logo" => gql["user"]["profileImageURL"],
"banner" => gql["user"]["bannerImageURL"],
"verifiedBot" => nil, # Deprecated by twitch
"createdAt" => gql["user"]["createdAt"],
"updatedAt" => gql["user"]["updatedAt"],
"deletedAt" => gql["user"]["deletedAt"],
"emotePrefix" => gql["user"]["emoticonPrefix"]["name"],
"roles" => {
"isAffiliate" => gql["user"]["roles"]["isAffiliate"],
"isPartner" => gql["user"]["roles"]["isPartner"],
"isStaff" => gql["user"]["roles"]["isStaff"],
},
"badges" => gql["user"]["displayBadges"],
"chatterCount" => gql["user"]["channel"]["chatters"]["count"],
"chatSettings" => gql["user"]["chatSettings"],
"stream" => gql["user"]["stream"],
"lastBroadcast" => gql["user"]["lastBroadcast"],
"panels" => panels,
},
]
if banned != nil
json_data[0]["banned"] = true
end
if id
REDIS_DB.set(id, json_data.to_json, ex: 5)
def isBannedId(gql)
if gql["userResultByID"]["reason"]?
return true
else
REDIS_DB.set(login.to_s, json_data.to_json, ex: 5)
return false
end
return json_data.to_json
end
def user(env)
# TODO: Clean this mess shit of else if else if else if
if env.params.query
params = env.params.query
if params.has_key?("login") || params.has_key?("id")
begin
parseData(params)
rescue ex
env.response.status_code = 404
err = {"error" => "#{ex.message}"}
err.to_json
end
elsif params.has_key?("id")
begin
parseData(params)
rescue ex
env.response.status_code = 404
err = {"error" => "#{ex.message}"}
err.to_json
end
else
env.response.status_code = 404
err = {"error" => "Parameter 'login' or 'id' is missing"}
err.to_json
end
else
env.response.status_code = 404
err = {"error" => "No query parameters found"}
err.to_json
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(',')}"))
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"]
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(',')}"))
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|
gql = gql["data"]
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"]
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)
# 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
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
gql = GqlAPI.getModsVips(env.params.url)["user"]
begin
gql = GqlAPI.getModsVips(channel)
gql = gql["data"]["user"]
rescue ex
twitchError
end
if (gql == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
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
@ -196,29 +252,43 @@ module TwAPI::Routes::Twitch
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)
# 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"]
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)
err = {"message" => "Specified user does not exist"}
return err.to_json
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
@ -242,27 +312,41 @@ module TwAPI::Routes::Twitch
end
end
end
Utils::Redis.saveJson("founders-#{login}", json_data)
return json_data
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
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
gql = GqlAPI.getClips(env.params.url)["clip"]
begin
gql = GqlAPI.getClips(slug)
gql = gql["data"]["clip"]
rescue ex
twitchError
end
# TODO: use begin rescue for this too! (again)
if (gql == nil)
err = {"message" => "Specified user does not exist"}
return err.to_json
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"]
@ -321,22 +405,40 @@ module TwAPI::Routes::Twitch
end
end
end
Utils::Redis.saveJson("clip-#{slug}", json_data)
return json_data
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
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
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
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"]?
@ -353,7 +455,7 @@ module TwAPI::Routes::Twitch
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)
if (gql["info"]["relationship"]["streak"] == nil) || (gql["info"]["relationship"]["streak"]["elapsedDays"]? == 0)
json.field "streak", nil
else
json.field "streak" do
@ -366,7 +468,7 @@ module TwAPI::Routes::Twitch
end
end
end
if (gql["info"]["relationship"]["cumulative"] == nil)
if (gql["info"]["relationship"]["cumulative"] == nil) || (gql["info"]["relationship"]["cumulative"]["elapsedDays"]? == 0)
json.field "cumulative", nil
else
json.field "cumulative" do
@ -394,23 +496,39 @@ module TwAPI::Routes::Twitch
end
end
end
Utils::Redis.saveJson("subage-user_#{user}_channel_#{channel}", json_data)
return json_data
end
def emotes(env)
if !env.params.url.has_key?("emote")
err = {"error" => "No query parameters found"}
return err.to_json
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
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
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"]
@ -424,20 +542,22 @@ module TwAPI::Routes::Twitch
json.field "emoteTier", gql["subscriptionTier"]
end
end
Utils::Redis.saveJson("emotes-#{emoteName}", json_data)
return json_data
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 "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|
@ -491,14 +611,3 @@ module TwAPI::Routes::Twitch
end
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

@ -6,57 +6,21 @@ require "redis"
require "./config"
require "./routes/**"
require "./datahandlers/**"
require "./twitchapi/*"
require "./utils/*"
module TwAPI
end
alias TA = TwAPI
CONFIG = Config.load
CONFIG = Config.load
Kemal.config.port = CONFIG.port
REDIS_DB = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil)
if REDIS_DB.ping
puts "Connected to redis"
end
REDIS_UTILS = TwAPI::Utils::Redis
REDIS_DB = Redis::Client.new(URI.parse("redis://#{CONFIG.redis_addr}/#{CONFIG.redis_database}#?keepalive=true"))
puts "Connected to Redis"
before_all "/v2/*" do |env|
env.response.content_type = "application/json"
end
TwAPI::Routing.register_all()
# get "/v2/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
TwAPI::Routes.register_misc
TwAPI::Routes.register_all_twitch
{% if flag?(:release) || flag?(:production) %}
Kemal.config.env = "production" if !ENV.has_key?("KEMAL_ENV")

View file

@ -1,9 +1,8 @@
module TwAPI::Routing
module TwAPI::Routes
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"} %}
macro {{http_method.id}}(path, controller, method = :handle)
unless Kemal::Utils.path_starts_with_slash?(\{{path}})
raise Kemal::Exceptions::InvalidPathStartException.new({{http_method}}, \{{path}})
@ -13,11 +12,23 @@ module TwAPI::Routing
\{{ controller }}.\{{ method.id }}(env)
end
end
{% end %}
def register_all
# get "/"
def register_misc
before_all "/v2/*" do |env|
env.response.content_type = "application/json"
end
get "/" do |env|
env.redirect "/v2"
end
get "/v2" do
{"message": "Welcome to v2, There is no docs!"}.to_json
end
end
def register_all_twitch
get "/v2/twitch/user", TwAPI::Routes::Twitch, :user
get "/v2/twitch/modvip/", TwAPI::Routes::Twitch, :modvip

52
src/routes/twitch.cr Normal file
View file

@ -0,0 +1,52 @@
module TwAPI::Routes::Twitch
extend self
private macro handleData(func)
begin
TwAPI::Datahandlers::Twitch.{{func}}(env)
rescue ex
env.response.status_code = 404
err = {"error" => "#{ex.message}"}
err.to_json
end
end
def user(env)
if env.params.query.has_key?("login")
handleData(userLogin)
else
handleData(userId)
end
end
def modvip(env)
handleData(modvip)
end
def founders(env)
handleData(founders)
end
def clip(env)
handleData(clip)
end
def subage(env)
handleData(subage)
end
def emotes(env)
handleData(emotes)
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,18 +1,6 @@
module GqlAPI
module TwAPI::GqlAPI
extend self
@@headers = HTTP::Headers{
"Content-Type" => "application/json",
# "Client-Id" => "kimne78kx3ncx6brgo4mv6wki5h1ko", # Can cause problems due to Client-Integrity
"Authorization" => "OAuth #{CONFIG.gqlOAuth}",
"Client-Id" => "#{CONFIG.gqlClientID}",
}
@@urb : String = "fuck"
def urb
return @@urb
end
def userResultBy(params)
if params.has_key?("id")
return %(userResultByID(id: "#{params["id"]}"))
@ -37,27 +25,22 @@ module GqlAPI
end
end
def user(params)
if params.has_key?("id")
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
def getUserByLogin(params)
logins = params["login"].try &.split(',')
channel = Channel(JSON::Any).new
responses = [] of JSON::Any
queries = [] of String
def queryUser(params)
@@urb = userResultBy(params)
query = %(
logins.each do |login|
query = %(
query {
#{userResultBy(params)} {
userResultByLogin(login: "#{login}") {
... on UserDoesNotExist {
key
reason
}
}
user(#{user(params)}) {
user(login: "#{login}" lookupType: ALL) {
id
language
login
@ -138,13 +121,140 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
queries << query
end
queries.each do |query|
spawn do
result = JSON.parse(req(query))
channel.send(result)
end
end
queries.each do
responses << channel.receive
end
return responses
end
def getModsVips(env)
def getUserById(params)
ids = params["id"].try &.split(',')
channel = Channel(JSON::Any).new
responses = [] of JSON::Any
queries = [] of String
ids.each do |id|
query = %(
query {
userResultByID(id: "#{id}") {
... on UserDoesNotExist {
key
reason
}
}
user(id: "#{id}" lookupType: ALL) {
id
language
login
displayName
description
bannerImageURL
profileImageURL(width: 600)
createdAt
updatedAt
deletedAt
chatColor
emoticonPrefix {
name
}
panels(hideExtensions: false) {
id
type
}
followers {
totalCount
}
roles {
isStaff
isAffiliate
isPartner
isExtensionsDeveloper
}
displayBadges {
setID
title
description
version
}
chatSettings {
autoModLevel
blockLinks
chatDelayMs
followersOnlyDurationMinutes
isBroadcasterLanguageModeEnabled
isEmoteOnlyModeEnabled
isFastSubsModeEnabled
isOptedOutOfGlobalBannedWordsList
isSubscribersOnlyModeEnabled
isUniqueChatModeEnabled
requireVerifiedAccount
rules
slowModeDurationSeconds
}
stream {
averageFPS
bitrate
codec
createdAt
width
height
id
viewersCount
type
game{displayName}
}
lastBroadcast {
game{displayName}
id
startedAt
title
}
channel {
chatters {
count
moderators {
login
}
vips {
login
}
}
}
}
}
)
queries << query
end
queries.each do |query|
spawn do
result = JSON.parse(req(query))
channel.send(result)
end
end
queries.each do
responses << channel.receive
end
return responses
end
def getModsVips(params)
query = %(
query {
user(#{user(env)}) {
user(login: "#{params}" lookupType: ALL) {
mods(first: 100) {
edges {
grantedAt
@ -169,13 +279,13 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def getFounders(env)
def getFounders(params)
query = %(
query {
user(#{user(env)}) {
user(login: "#{params}" lookupType: ALL) {
id
login
displayName
@ -194,13 +304,13 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def getClips(params)
query = %(
query {
clip(slug: "#{params["slug"]}") {
clip(slug: "#{params}") {
createdAt
creationState
durationSeconds
@ -234,14 +344,14 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def getSubage(user, channel)
channelId = HelixAPI.getId(channel)
query = %(
query {
userData: userResultByLogin (login: "#{user}") {
userData: userResultByLogin (login: "#{user}") {
... on UserDoesNotExist { key reason }
... on UserError { key }
... on User { id displayName login }
@ -287,7 +397,7 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def getEmotes(emote)
@ -318,7 +428,7 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def getChannelEmotes(channel)
@ -352,7 +462,7 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def getEmotes(emote)
@ -383,7 +493,7 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def getChannelBadges(channel)
@ -410,12 +520,18 @@ module GqlAPI
}
}
)
return (JSON.parse(gqlReq(query)))["data"]
return JSON.parse(req(query))
end
def gqlReq(query)
def req(query)
headers = HTTP::Headers{
"Content-Type" => "application/json",
# "Client-Id" => "kimne78kx3ncx6brgo4mv6wki5h1ko", # Can cause problems due to Client-Integrity
"Authorization" => "OAuth #{CONFIG.gqlOAuth}",
"Client-Id" => "#{CONFIG.gqlClientID}",
}
data = {"query" => query}
response = HTTP::Client.post(CONFIG.gqlEndpoint.to_s, headers: @@headers, body: data.to_json)
response = HTTP::Client.post(CONFIG.gqlEndpoint.to_s, headers: headers, body: data.to_json)
if response.success?
return (response.body)

View file

@ -1,40 +0,0 @@
module HelixAPI
extend self
@@headers = HTTP::Headers{
"Content-Type" => "application/json",
"Authorization" => "Bearer #{CONFIG.helixOAuth}",
"Client-Id" => "#{CONFIG.helixClientID}",
}
def 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 getId(login)
request = "#{CONFIG.apiEndpoint.to_s}/users?login=#{login}"
return JSON.parse(helixReq(request))["data"][0]["id"]
end
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
end

23
src/twitchapi/helixapi.cr Normal file
View file

@ -0,0 +1,23 @@
module HelixAPI
extend self
def getId(login)
request = "#{CONFIG.apiEndpoint.to_s}/users?login=#{login}"
return JSON.parse(req(request))["data"][0]["id"]
end
def req(request)
headers = HTTP::Headers{
"Content-Type" => "application/json",
"Authorization" => "Bearer #{CONFIG.helixOAuth}",
"Client-Id" => "#{CONFIG.helixClientID}",
}
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
end

17
src/utils/redis.cr Normal file
View file

@ -0,0 +1,17 @@
module TwAPI::Utils::Redis
extend self
def getExpireTime(key)
return REDIS_DB.ttl(key)
end
def retrieveFromCache(keyName)
if keyName && (json = REDIS_DB.get(keyName))
return json
end
end
def saveJson(keyName, json_data : String)
REDIS_DB.set(keyName, json_data, ex: 60)
end
end

3
src/utils/utils.cr Normal file
View file

@ -0,0 +1,3 @@
module TwAPI::Utils
extend self
end