go-discord-bot / internal / tft / api / api.go
api.go
Raw
package api

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/mr1hm/go-discord-bot/internal/config"
	"github.com/mr1hm/go-discord-bot/internal/tft"
)

type Friends struct {
	Friendlies []tft.RiotAccountResponse
}

const (
	riot_accounts       = "https://americas.api.riotgames.com/riot/account/v1/accounts/by-riot-id/"
	riot_tft_matches    = "https://americas.api.riotgames.com/tft/match/v1/matches/by-puuid/"
	riot_tft_match_info = "https://americas.api.riotgames.com/tft/match/v1/matches/"
)

var (
	InfoLogger = log.New(os.Stdout, "[ API ] INFO:\t", log.Ldate|log.Ltime)
	ErrLogger  = log.New(os.Stdout, "[ API ] ERROR:\t", log.Ldate|log.Ltime)

	FriendsList = Friends{
		Friendlies: []tft.RiotAccountResponse{
			{
				GameName: "defter",
				Tagline:  "1337",
			},
			{
				GameName: "lavem",
				Tagline:  "8996",
			},
			{
				GameName: "sammypoon",
				Tagline:  "NA1",
			},
			// The following users are commented out since fetching data for additional users can easily exceed Riot's API rate limitations
			// {
			// 	Username: "Ugoff",
			// 	Tagline:  "NA1",
			// },
			// {
			// 	Username: "nukleas",
			// 	Tagline:  "nukle",
			// },
			// {
			// 	Username: "Sicario",
			// 	Tagline: "4122",
			// },
		},
	}

	start    = time.Now()
	SyncData sync.Map
)

func GetAccounts(ctx context.Context) {
	ctx, cancelCtx := context.WithCancel(ctx)
	accounts_ch := make(chan tft.RiotAccountResponse)

	go GetAccount(ctx, accounts_ch)
	for _, acc := range FriendsList.Friendlies {
		accounts_ch <- tft.RiotAccountResponse{
			GameName: acc.GameName,
			Tagline:  acc.Tagline,
		}
	}

	cancelCtx()
}

func GetAccount(ctx context.Context, accounts_ch <-chan tft.RiotAccountResponse) {
	for {
		select {
		case <-ctx.Done():
			if err := ctx.Err(); err != nil {
				fmt.Printf("GetAccount error: %s\n", err)
			}
			fmt.Printf("GetAccount: finished\n")
			elapsed := time.Since(start)
			InfoLogger.Printf("Time Elapsed from GetAccount(): %v", elapsed.Milliseconds())
			return
		case acc := <-accounts_ch:
			go func() {
				req, err := http.NewRequest(http.MethodGet, riot_accounts+acc.GameName+"/"+acc.Tagline, nil)
				if err != nil {
					ErrLogger.Printf("GetAccount() - Failed to create new request: %v", err)
				}
				req.Header.Add("X-Riot-Token", config.RiotAPIKey)

				resp, err := http.DefaultClient.Do(req)
				if err != nil {
					ErrLogger.Printf("GetAccount() - Request failed: %v", err)
				}
				defer resp.Body.Close()

				if resp.StatusCode != 200 {
					ErrLogger.Printf("GetAccount() - Request status was not 200: %v", resp.Status)

					b, err := io.ReadAll(resp.Body)
					if err != nil {
						ErrLogger.Fatalln(err)
					}
					ErrLogger.Printf("GetAccount() - Error: %v\n", string(b))
				}

				var resp_payload tft.RiotAccountResponse
				if err := json.NewDecoder(resp.Body).Decode(&resp_payload); err != nil {
					ErrLogger.Printf("GetAccount() - Decode failed: %v", err)
				}

				// Set new values in UserData.Data
				set_err := tft.UserData.Set("account", resp_payload.PUUID, resp_payload)
				if set_err != nil {
					ErrLogger.Printf("GetAccount() - Set failed: %v", set_err)
				}

				return
			}()
		}
	}
}

func GetTFTMatches(ctx context.Context) {
	fmt.Println("GetTFTMatches()...")
	ctx, cancelCtx := context.WithCancel(ctx)
	pid_ch := make(chan string)

	go GetTFTMatchIDsByPUUID(ctx, pid_ch)
	tft.UserData.Mtx.RLock()
	for puuid := range tft.UserData.Data {
		pid_ch <- puuid
	}
	tft.UserData.Mtx.RUnlock()

	cancelCtx()
}

func GetTFTMatchIDsByPUUID(ctx context.Context, pid_ch <-chan string) {
	for {
		select {
		case <-ctx.Done():
			if err := ctx.Err(); err != nil {
				ErrLogger.Printf("GetTFTMatchIDsByPUUID() - err: %v", err)
			}
			InfoLogger.Printf("GetTFTMatchIDsByPUUID() - finished %+v", tft.UserData.Data)
			elapsed := time.Since(start)
			InfoLogger.Printf("Time Elapsed from GetTFTMatchIDsByPUUID(): %vms", elapsed.Milliseconds())
			return
		case puuid := <-pid_ch:
			go func() {
				req, err := http.NewRequest(http.MethodGet, riot_tft_matches+string(puuid)+"/ids?start=0&count=2", nil)
				if err != nil {
					ErrLogger.Printf("GetTFTMatchIDsByPUUID() - Failed to create new request: %v", err)
				}
				req.Header.Add("X-Riot-Token", config.RiotAPIKey)

				resp, err := http.DefaultClient.Do(req)
				if err != nil {
					ErrLogger.Printf("GetMatchIDsByPUUID() - Request failed: %v", err)
				}
				defer resp.Body.Close()

				if resp.StatusCode != 200 {
					ErrLogger.Printf("GetMatchIDsByPUUID() - Request failed, status was not 200: %v", resp.Status)
				}

				var resp_payload tft.RiotMatchesResponse
				if err := json.NewDecoder(resp.Body).Decode(&resp_payload); err != nil {
					ErrLogger.Printf("GetMatchIDsByPUUID() - Decode failed: %v", err)
				}

				set_err := tft.UserData.Set("matches", puuid, resp_payload)
				if set_err != nil {
					ErrLogger.Printf("GetTFTMatchIDsByPUUID() - Set failed: %v", set_err)
				}

				return
			}()
		}
	}
}

func GetTFTMatchDetails(ctx context.Context) {
	fmt.Println("GetTFTMatchDetails()...")
	ctx, cancelCtx := context.WithCancel(ctx)
	mid_ch := make(chan string)

	go GetTFTMatchDetailsByMatchID(ctx, mid_ch)
	match_ids := []string{}

	// Read from data set and loop through new slice to prevent locking for too long
	tft.UserData.Mtx.RLock()
	for puuid := range tft.UserData.Data {
		match_ids = append(match_ids, puuid)
	}
	tft.UserData.Mtx.RUnlock()

	for _, puuid := range match_ids {
		for _, mid := range tft.UserData.Data[puuid].Matches {
			mid_ch <- mid
		}
	}

	cancelCtx()
}

func GetTFTMatchDetailsByMatchID(ctx context.Context, mid_ch <-chan string) {
	for {
		select {
		case <-ctx.Done():
			if err := ctx.Err(); err != nil {
				ErrLogger.Printf("GetTFTMatchDetailsByMatchID() - err: %v", err)
			}
			InfoLogger.Printf("GetTFTMatchDetailsByMatchID() - finished %+v", tft.UserData.Data)
			elapsed := time.Since(start)
			InfoLogger.Printf("Time elapsed from GetTFTMatchDetailsByMatchID(): %vms", elapsed.Milliseconds())
			return
		case mid := <-mid_ch:
			go func() {
				req, err := http.NewRequest(http.MethodGet, riot_tft_match_info+mid, nil)
				if err != nil {
					ErrLogger.Printf("GetTFTMatchDetailsByMatchID() - Failed to create new request: %v", err)
				}
				req.Header.Add("X-Riot-Token", config.RiotAPIKey)

				resp, err := http.DefaultClient.Do(req)
				if err != nil {
					ErrLogger.Printf("GetTFTMatchDetailsByMatchID() - Request failed: %v", err)
				}
				defer resp.Body.Close()

				if resp.StatusCode != 200 {
					ErrLogger.Printf("GetMatchDetailsByMatchID() - Request failed, status was not 200: %v", resp.Status)
				}

				var resp_payload tft.RiotMatchResponse
				if err := json.NewDecoder(resp.Body).Decode(&resp_payload); err != nil {
					ErrLogger.Printf("GetMatchDetailsByMatchID() - Decode failed: %v", err)
				}

				set_err := tft.UserData.Set("match_details", resp_payload.Metadata.MatchID, resp_payload)
				if set_err != nil {
					ErrLogger.Printf("GetMatchDetailsByMatchID() - Error in Set(): %v", set_err)
				}

				return
			}()
		}
	}
}