diff --git a/cmd/vanity-tester-backend/config.go b/cmd/vanity-tester-backend/config.go index 669a615..eb8334f 100644 --- a/cmd/vanity-tester-backend/config.go +++ b/cmd/vanity-tester-backend/config.go @@ -15,18 +15,18 @@ type Redis struct { DB int `json:"db"` } -type Gql struct { +type GqlConfig struct { ClientID string `json:"clientId"` } type Config struct { HTTPServer HTTPServer `json:"http_server"` Redis Redis `json:"redis"` - Gql Gql `json:"gql"` + Gql GqlConfig `json:"gql"` + DbPath string `json:"dbpath"` } func loadConfig(configPath string) Config { - // Default config in case a value is missing in the config file var config = Config{ HTTPServer: HTTPServer{ @@ -37,9 +37,10 @@ func loadConfig(configPath string) Config { Addr: "127.0.0.1:6379", DB: 0, }, - Gql: Gql{ + Gql: GqlConfig{ ClientID: "ue6666qo983tsx6so1t0vnawi233wa", }, + DbPath: "./database.sqlite3", } file, err := os.ReadFile(configPath) diff --git a/cmd/vanity-tester-backend/database.go b/cmd/vanity-tester-backend/database.go index c83367b..b8fb873 100644 --- a/cmd/vanity-tester-backend/database.go +++ b/cmd/vanity-tester-backend/database.go @@ -2,15 +2,14 @@ package main import ( "database/sql" - "log" ) -func createDb(db *sql.DB) { +func createDb(db *sql.DB) error { var exists bool // rows := db.QueryRow("SELECT EXISTS (SELECT 1 FROM sqlite_schema WHERE type='table' AND name='seventv_ids') AND EXISTS (SELECT 1 FROM sqlite_schema WHERE type='table' AND name='seventv_ids');") err := db.QueryRow(`SELECT EXISTS (SELECT 1 FROM sqlite_schema WHERE type='table' AND name='seventv_ids') AND EXISTS (SELECT 1 FROM sqlite_schema WHERE type='table' AND name='seventv_ids');`).Scan(&exists) if err != nil { - log.Fatal("Query failed: ", err) + return err } if !exists { @@ -20,10 +19,11 @@ func createDb(db *sql.DB) { } for _, query := range queries { - _, err := db.Exec(query) + _, err = db.Exec(query) if err != nil { - log.Fatalf("Failed to create table: %s\nError: %v", query, err) + return err } } } + return nil } diff --git a/cmd/vanity-tester-backend/endpoints.go b/cmd/vanity-tester-backend/endpoints.go index 6b4095a..923cf89 100644 --- a/cmd/vanity-tester-backend/endpoints.go +++ b/cmd/vanity-tester-backend/endpoints.go @@ -5,6 +5,43 @@ import ( "net/http" ) -func root(w http.ResponseWriter, req *http.Request) { +func root(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "WIP Vanity Tester Backend") } + +func getAllBadges(w http.ResponseWriter, r *http.Request) { + parseAllBadges() + io.WriteString(w, "test") +} + +// func sevenTvPaints(w http.ResponseWriter, r *http.Request) { +// svt := &SevenTv{} +// badges, err := svt.getPaints() +// if err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// io.WriteString(w, badges) +// } + +// func sevenTvBadges(w http.ResponseWriter, r *http.Request) { +// svt := &SevenTv{} +// badges, err := svt.getBadges() +// if err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// io.WriteString(w, badges) +// } + +// func sevenTvUserCosmetics(w http.ResponseWriter, r *http.Request) { +// svt := &SevenTv{} +// user := strings.TrimPrefix(r.URL.Path, "/7tv/cosmetics/") +// logger.Trace().Msg("sevenTvUserCosmetics: user: " + user) +// badges, err := svt.getUserCosmetics(user) +// if err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// io.WriteString(w, badges) +// } diff --git a/cmd/vanity-tester-backend/gql.go b/cmd/vanity-tester-backend/gql.go index f6955cf..84b4ff4 100644 --- a/cmd/vanity-tester-backend/gql.go +++ b/cmd/vanity-tester-backend/gql.go @@ -1,35 +1,57 @@ package main import ( + "bytes" + "encoding/json" + "fmt" "io" "net/http" ) -type GQL struct{} +type Gql struct{} -func getUserID(username string) { +func (s *Gql) getUserID(username string) (string, error) { headers := map[string]string{ "Content-Type": "application/json", "Client-Id": config.Gql.ClientID, + // The headers from bellow are not really necessary, but since it's GQL, + // it's better to fake the headers to make it more like a real browser + // and not just an scrapper. + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0", + "Accept": "*/*", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate, br", + "Access-Control-Request-Method": "POST", + "Access-Control-Request-Headers": "authorization,client-id,client-session-id,client-version,x-device-id", + "Referer": "https://www.twitch.tv/", + "Origin": "https://www.twitch.tv", + "Connection": "keep-alive", } - // TODO: Send the data! - request, err := http.Post("https://gql.twitch.tv/gql", "application/json", data) + query := map[string]string{"query": fmt.Sprintf(`query { user(login: "%s") { id } }`, username)} + + data, err := json.Marshal(query) if err != nil { - logger.Error().Msg(err.Error()) + return "", logger.Error().GetCtx().Err() } + logger.Trace().Msg(string(data)) - for key, value := range headers { - request.Header.Set(key, value) - } - - res, err := client.Do(request.Request) + request, err := http.NewRequest("POST", "https://gql.twitch.tv/gql", bytes.NewBuffer(data)) if err != nil { - logger.Error().Msg(err.Error()) + return "", err + } + + for k, v := range headers { + request.Header.Add(k, v) + } + + res, err := client.Do(request) + if err != nil { + return "", err } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body) + return string(body), nil } diff --git a/cmd/vanity-tester-backend/main.go b/cmd/vanity-tester-backend/main.go index f75442b..3bfed66 100644 --- a/cmd/vanity-tester-backend/main.go +++ b/cmd/vanity-tester-backend/main.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "net/http" + "net/url" "os" _ "github.com/mattn/go-sqlite3" @@ -11,34 +12,63 @@ import ( "github.com/rs/zerolog/log" ) -var client *http.Client var rctx = context.Background() var config = loadConfig("./config.json") var logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr}) +var client = &http.Client{ + Transport: &http.Transport{ + Proxy: func(r *http.Request) (*url.URL, error) { + if os.Getenv("PROXY") != "" { + return url.Parse(os.Getenv("PROXY")) + } + return nil, nil + }, + }, +} + +// Just a small handler to add headers on all responses +func middleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + next(w, r) + } +} + func main() { + // s := &SevenTv{} + // xd, _ := s.getPaints() + // logger.Trace().Msg(xd) // redisDb := redis.NewClient(&redis.Options{ // Addr: config.Redis.Addr, // DB: config.Redis.DB, // }) - db, err := sql.Open("sqlite3", "./database.db") + db, err := sql.Open("sqlite3", config.DbPath) if err != nil { logger.Fatal().Msg(err.Error()) } defer db.Close() - createDb(db) + err = createDb(db) + if err != nil { + logger.Fatal().Msgf("Failed to create DB: '%s'", err.Error()) + } mux := http.NewServeMux() mux.HandleFunc("/", root) - // mux.HandleFunc("/health", health()) - // mux.HandleFunc("/stats", stats()) + mux.HandleFunc("/badges", middleware(getAllBadges)) + // mux.HandleFunc("/7tv/paints", middleware(sevenTvPaints)) + // mux.HandleFunc("/7tv/badges", middleware(sevenTvBadges)) + // // CONSIDERATION: github.com/gorilla/mux may be an option to make use of something like this: + // // mux.HandleFunc("/7tv/cosmetics/{user}", middleware(sevenTvUserCosmetics)) + // // For now I will just trim the path + // mux.HandleFunc("/7tv/cosmetics/", middleware(sevenTvUserCosmetics)) srv := &http.Server{ - Handler: mux, Addr: config.HTTPServer.Addr + ":" + config.HTTPServer.Port, + Handler: mux, } logger.Info().Msgf("Starting server at http://%s:%s", config.HTTPServer.Addr, config.HTTPServer.Port) diff --git a/cmd/vanity-tester-backend/parser.go b/cmd/vanity-tester-backend/parser.go new file mode 100644 index 0000000..62e66c8 --- /dev/null +++ b/cmd/vanity-tester-backend/parser.go @@ -0,0 +1,26 @@ +package main + +import "encoding/json" + +type Parser struct { + SevenTV string `json:"SevenTV"` + Bttv string `json:"BTTV"` + Ffz string `json:"FFZ"` + Chatterino string `json:"Chatterino"` + Chatty string `json:"Chatty"` + Homies string `json:"Homies"` + Ptv string `json:"PurpleTV"` +} + +func parseAllBadges() { + stv := &SevenTv{} + stv.getBadges() + json, _ := json.Marshal(stv.Badges) + logger.Trace().Msg(string(json)) + // bttv := &Bttv{} + // ffz := &FrankerFz{} + // chatterino := &Chatterino{} + // chatty := &Chatty{} + // homies := &Homies{} + // ptv := &PurpleTV{} +} diff --git a/cmd/vanity-tester-backend/providers.go b/cmd/vanity-tester-backend/providers.go index 5198a9a..08a7d4b 100644 --- a/cmd/vanity-tester-backend/providers.go +++ b/cmd/vanity-tester-backend/providers.go @@ -1,11 +1,40 @@ package main import ( + "bytes" + "encoding/json" "io" "net/http" ) -type SevenTv struct{} +type Variables struct { + ID string `json:"id"` +} +type GqlQuery struct { + OperationName string `json:"operationName"` + Variables Variables `json:"variables"` + Query string `json:"query"` +} + +type SevenTv struct { + Query GqlQuery `json:"query"` + Badges struct { + Data struct { + Cosmetics struct { + Badges []struct { + ID string `json:"id"` + Kind string `json:"kind"` + Name string `json:"name"` + Tooltip string `json:"tooltip"` + Tag string `json:"tag"` + Typename string `json:"__typename"` + } + Typename string `json:"__typename"` + } `json:"cosmetics"` + } `json:"data"` + } `json:"sevenTvBadges"` +} + type Bttv struct{} type FrankerFz struct{} type Chatterino struct{} @@ -14,12 +43,12 @@ type DankChat struct{} type Homies struct{} type PurpleTV struct{} -func doGetRequest(url string) string { - request, err := http.Get(url) +func doGetRequest(url string) []byte { + request, err := http.NewRequest("GET", url, nil) if err != nil { logger.Error().Msg(err.Error()) } - res, err := client.Do(request.Request) + res, err := client.Do(request) if err != nil { logger.Error().Msg(err.Error()) } @@ -27,35 +56,168 @@ func doGetRequest(url string) string { body, err := io.ReadAll(res.Body) - return string(body) + return body } -func (s *SevenTv) getUserID(userId string) string { +func doPostRequest(url string, data []byte) []byte { + request, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) + if err != nil { + logger.Error().Msg(err.Error()) + } + res, err := client.Do(request) + if err != nil { + logger.Error().Msg(err.Error()) + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + + return body +} + +func (s *SevenTv) getUserID(userId string) []byte { req := doGetRequest("https://7tv.io/v3/users/twitch/" + userId) return req } -func (s *Bttv) getBadges() string { - req := doGetRequest("https://api.betterttv.net/3/cached/badges/twitch") - return req +func (s *SevenTv) getBadges() *SevenTv { + query := ` + query GetCosmestics($list: [ObjectID!]) { + cosmetics(list: $list) { + badges { + id + kind + name + tooltip + tag + __typename + } + __typename + } + }` + + s.Query = GqlQuery{ + OperationName: "GetCosmestics", + Query: query, + } + + data, _ := json.Marshal(s.Query) + // if err != nil { + // return "", err + // } + + res := doPostRequest("https://7tv.io/v3/gql", data) + + logger.Info().Msg(string(res)) + + err := json.Unmarshal(res, &s.Badges) + if err != nil { + logger.Error().Msg(err.Error()) + } + + return s } -func (s *FrankerFz) getBadges() string { - req := doGetRequest("https://api.frankerfacez.com/v1/badges/ids") - return req -} +// func (s *SevenTv) getPaints() (string, error) { +// query := ` +// query GetCosmestics($list: [ObjectID!]) { +// cosmetics(list: $list) { +// paints { +// id +// kind +// name +// function +// color +// angle +// shape +// image_url +// repeat +// stops { +// at +// color +// __typename +// } +// shadows { +// x_offset +// y_offset +// radius +// color +// __typename +// } +// __typename +// } +// } +// }` -func (s *Chatty) getBadges() string { - req := doGetRequest("https://api.betterttv.net/3/cached/badges/twitch") - return req -} +// s.Query = GqlQuery{ +// OperationName: "GetCosmestics", +// Query: query, +// } -func (s *DankChat) getBadges() string { - req := doGetRequest("https://flxrs.com/api/badges") - return req -} +// data, err := json.Marshal(s.Query) +// if err != nil { +// return "", err +// } +// logger.Trace().Msg(string(data)) -func (s *Chatterino) getBadges() string { - req := doGetRequest("https://api.chatterino.com/badges") - return req -} +// res := doPostRequest("https://7tv.io/v3/gql", data) +// return res, nil +// } + +// func (s *SevenTv) getUserCosmetics(userId string) (string, error) { +// query := ` +// query GetUserCosmetics($id: ObjectID!) { +// user(id: $id) { +// id +// cosmetics { +// id +// kind +// selected +// __typename +// } +// __typename +// } +// }` + +// s.Query = GqlQuery{ +// OperationName: "GetUserCosmetics", +// Variables: Variables{ +// ID: userId, +// }, +// Query: query, +// } + +// data, err := json.Marshal(s.Query) +// if err != nil { +// return "", err +// } +// logger.Trace().Msg(string(data)) + +// res := doPostRequest("https://7tv.io/v3/gql", data) +// return res, nil +// } + +// func (s *Bttv) getBadges() string { +// req := doGetRequest("https://api.betterttv.net/3/cached/badges/twitch") +// return req +// } + +// func (s *FrankerFz) getBadges() string { +// req := doGetRequest("https://api.frankerfacez.com/v1/badges/ids") +// return req +// } + +// func (s *Chatty) getBadges() string { +// req := doGetRequest("https://api.betterttv.net/3/cached/badges/twitch") +// return req +// } + +// func (s *DankChat) getBadges() string { +// req := doGetRequest("https://flxrs.com/api/badges") +// return req +// } + +// func (s *Chatterino) getBadges() string { +// req := doGetRequest("https://api.chatterino.com/badges") +// return req +// }