Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: unify session initialization #204

Merged
merged 3 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 50 additions & 10 deletions cmd/tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
"errors"
"fmt"
"log"
"net/http"
Expand All @@ -16,7 +17,9 @@ import (
"github.com/williamsjokvist/cfn-tracker/pkg/storage/txt"
"github.com/williamsjokvist/cfn-tracker/pkg/tracker"
"github.com/williamsjokvist/cfn-tracker/pkg/tracker/sf6"
"github.com/williamsjokvist/cfn-tracker/pkg/tracker/sf6/cfn"
"github.com/williamsjokvist/cfn-tracker/pkg/tracker/t8"
"github.com/williamsjokvist/cfn-tracker/pkg/tracker/t8/wavu"
)

type TrackingHandler struct {
Expand Down Expand Up @@ -61,11 +64,50 @@ func (ch *TrackingHandler) SetEventEmitter(eventEmitter EventEmitFn) {
ch.eventEmitter = eventEmitter
}

func (ch *TrackingHandler) StartTracking(userCode string, restore bool) {
func (ch *TrackingHandler) StartTracking(userCode string, restore bool) error {
log.Printf(`Starting tracking for %s, restoring = %v`, userCode, restore)
ticker := time.NewTicker(30 * time.Second)

ctx, cancel := context.WithCancel(context.Background())
ch.cancelPolling = cancel

var session *model.Session
if restore {
sesh, err := ch.sqlDb.GetLatestSession(ctx, userCode)
if err != nil {
return errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf("get latest session: %w", err))
}
session = sesh
} else {
user, err := ch.sqlDb.GetUserByCode(ctx, userCode)
if err != nil && !errors.Is(err, sql.ErrUserNotFound) {
return errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf("get user from db: %w", err))
}

if user == nil {
usr, err := ch.gameTracker.GetUser(ctx, userCode)
if err != nil {
return errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf("get user from api: %w", err))
}
if err := ch.sqlDb.SaveUser(ctx, *usr); err != nil {
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf("save user: %w", err))
}
}

sesh, err := ch.sqlDb.CreateSession(ctx, userCode)
if err != nil {
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf("create session: %w", err))
}
session = sesh
// session.LP = bl.GetLP()
// session.MR = bl.GetMR()
// session.UserName = bl.GetCFN()
}

if session == nil {
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf("session not created"))
}

ticker := time.NewTicker(30 * time.Second)
ch.forcePollChan = make(chan struct{})
defer func() {
ch.eventEmitter("stopped-tracking")
Expand All @@ -75,11 +117,6 @@ func (ch *TrackingHandler) StartTracking(userCode string, restore bool) {
ch.forcePollChan = nil
}()

session, err := ch.gameTracker.Init(ctx, userCode, restore)
if err != nil {
return
}

matchChan := make(chan model.Match)

onNewMatch := func(match model.Match) {
Expand Down Expand Up @@ -139,6 +176,7 @@ func (ch *TrackingHandler) StartTracking(userCode string, restore bool) {
break
}
}
return nil
}

func (ch *TrackingHandler) StopTracking() {
Expand All @@ -150,17 +188,19 @@ func (ch *TrackingHandler) SelectGame(game model.GameType) error {

switch game {
case model.GameTypeT8:
ch.gameTracker = t8.NewT8Tracker(ch.sqlDb)
ch.gameTracker = t8.NewT8Tracker(wavu.NewClient())
case model.GameTypeSF6:
ch.gameTracker = sf6.NewSF6Tracker(ch.browser, ch.sqlDb)
ch.gameTracker = sf6.NewSF6Tracker(cfn.NewClient(ch.browser))
username = ch.cfg.CapIDEmail
password = ch.cfg.CapIDPassword
default:
return errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf(`failed to select game`))
}

authChan := make(chan tracker.AuthStatus)
go ch.gameTracker.Authenticate(username, password, authChan)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
go ch.gameTracker.Authenticate(ctx, username, password, authChan)
for status := range authChan {
if status.Err != nil {
return errorsx.NewFormattedError(http.StatusUnauthorized, status.Err)
Expand Down
54 changes: 29 additions & 25 deletions pkg/tracker/sf6/cfn/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cfn

import (
"context"
"encoding/json"
"errors"
"fmt"
Expand All @@ -15,7 +16,8 @@ import (
)

type CFNClient interface {
GetBattleLog(cfn string) (*BattleLog, error)
GetBattleLog(ctx context.Context, cfn string) (*BattleLog, error)
Authenticate(ctx context.Context, email string, password string, statChan chan tracker.AuthStatus)
}

type Client struct {
Expand All @@ -24,20 +26,21 @@ type Client struct {

var _ CFNClient = (*Client)(nil)

func NewCFNClient(browser *browser.Browser) *Client {
func NewClient(browser *browser.Browser) *Client {
return &Client{browser}
}

func (c *Client) GetBattleLog(cfn string) (*BattleLog, error) {
err := c.browser.Page.Navigate(fmt.Sprintf(`https://www.streetfighter.com/6/buckler/profile/%s/battlelog/rank`, cfn))
func (c *Client) GetBattleLog(ctx context.Context, cfn string) (*BattleLog, error) {
page := c.browser.Page.Context(ctx)
err := page.Navigate(fmt.Sprintf(`https://www.streetfighter.com/6/buckler/profile/%s/battlelog/rank`, cfn))
if err != nil {
return nil, fmt.Errorf(`navigate to cfn: %w`, err)
}
err = c.browser.Page.WaitLoad()
err = page.WaitLoad()
if err != nil {
return nil, fmt.Errorf(`wait for cfn to load: %w`, err)
}
nextData, err := c.browser.Page.Element(`#__NEXT_DATA__`)
nextData, err := page.Element(`#__NEXT_DATA__`)
if err != nil {
return nil, fmt.Errorf(`get next_data element: %w`, err)
}
Expand All @@ -59,22 +62,23 @@ func (c *Client) GetBattleLog(cfn string) (*BattleLog, error) {
return bl, nil
}

func (t *Client) Authenticate(email string, password string, statChan chan tracker.AuthStatus) {
func (c *Client) Authenticate(ctx context.Context, email string, password string, statChan chan tracker.AuthStatus) {
status := &tracker.AuthStatus{Progress: 0, Err: nil}

if t.browser == nil {
if c.browser == nil {
statChan <- *status.WithError(fmt.Errorf("browser not initialized"))
return
}

page := c.browser.Page.Context(ctx)

defer func() {
if r := recover(); r != nil {
log.Println(`Recovered from panic: `, r)
statChan <- *status.WithError(fmt.Errorf(`panic: %v`, r))
}
}()

if strings.Contains(t.browser.Page.MustInfo().URL, `buckler`) {
if strings.Contains(page.MustInfo().URL, `buckler`) {
statChan <- *status.WithProgress(100)
return
}
Expand All @@ -85,39 +89,39 @@ func (t *Client) Authenticate(email string, password string, statChan chan track
}

log.Println(`Logging in`)
t.browser.Page.MustNavigate(`https://cid.capcom.com/ja/login/?guidedBy=web`).MustWaitLoad().MustWaitIdle()
page.MustNavigate(`https://cid.capcom.com/ja/login/?guidedBy=web`).MustWaitLoad().MustWaitIdle()
statChan <- *status.WithProgress(10)

log.Print("Checking if already authed")
if strings.Contains(t.browser.Page.MustInfo().URL, `cid.capcom.com/ja/mypage`) {
if strings.Contains(page.MustInfo().URL, `cid.capcom.com/ja/mypage`) {
log.Print("User already authed")
statChan <- *status.WithProgress(100)
return
}
log.Print("Not authed, continuing with auth process")

// Bypass age check
if strings.Contains(t.browser.Page.MustInfo().URL, `agecheck`) {
t.browser.Page.MustElement(`#country`).MustSelect(COUNTRIES[rand.Intn(len(COUNTRIES))])
t.browser.Page.MustElement(`#birthYear`).MustSelect(strconv.Itoa(rand.Intn(1999-1970) + 1970))
t.browser.Page.MustElement(`#birthMonth`).MustSelect(strconv.Itoa(rand.Intn(12-1) + 1))
t.browser.Page.MustElement(`#birthDay`).MustSelect(strconv.Itoa(rand.Intn(28-1) + 1))
t.browser.Page.MustElement(`form button[type="submit"]`).MustClick()
t.browser.Page.MustWaitLoad().MustWaitRequestIdle()
if strings.Contains(page.MustInfo().URL, `agecheck`) {
page.MustElement(`#country`).MustSelect(COUNTRIES[rand.Intn(len(COUNTRIES))])
page.MustElement(`#birthYear`).MustSelect(strconv.Itoa(rand.Intn(1999-1970) + 1970))
page.MustElement(`#birthMonth`).MustSelect(strconv.Itoa(rand.Intn(12-1) + 1))
page.MustElement(`#birthDay`).MustSelect(strconv.Itoa(rand.Intn(28-1) + 1))
page.MustElement(`form button[type="submit"]`).MustClick()
page.MustWaitLoad().MustWaitRequestIdle()
}
statChan <- *status.WithProgress(30)

// Submit form
t.browser.Page.MustElement(`input[name="email"]`).MustInput(email)
t.browser.Page.MustElement(`input[name="password"]`).MustInput(password)
t.browser.Page.MustElement(`button[type="submit"]`).MustClick()
page.MustElement(`input[name="email"]`).MustInput(email)
page.MustElement(`input[name="password"]`).MustInput(password)
page.MustElement(`button[type="submit"]`).MustClick()
statChan <- *status.WithProgress(50)

// Wait for redirection
var secondsWaited time.Duration = 0
for {
// Break out if we are no longer on Auth0 (redirected to CFN)
if !strings.Contains(t.browser.Page.MustInfo().URL, `auth.cid.capcom.com`) {
if !strings.Contains(page.MustInfo().URL, `auth.cid.capcom.com`) {
break
}

Expand All @@ -127,8 +131,8 @@ func (t *Client) Authenticate(email string, password string, statChan chan track
}
statChan <- *status.WithProgress(65)

t.browser.Page.MustNavigate(`https://www.streetfighter.com/6/buckler/auth/loginep?redirect_url=/`)
t.browser.Page.MustWaitLoad().MustWaitRequestIdle()
page.MustNavigate(`https://www.streetfighter.com/6/buckler/auth/loginep?redirect_url=/`)
page.MustWaitLoad().MustWaitRequestIdle()

statChan <- *status.WithProgress(100)
log.Println(`Authentication passed`)
Expand Down
53 changes: 11 additions & 42 deletions pkg/tracker/sf6/track.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,68 +6,37 @@ import (
"net/http"
"time"

"github.com/williamsjokvist/cfn-tracker/pkg/browser"
"github.com/williamsjokvist/cfn-tracker/pkg/errorsx"
"github.com/williamsjokvist/cfn-tracker/pkg/model"
"github.com/williamsjokvist/cfn-tracker/pkg/storage/sql"
"github.com/williamsjokvist/cfn-tracker/pkg/tracker"
"github.com/williamsjokvist/cfn-tracker/pkg/tracker/sf6/cfn"
)

type SF6Tracker struct {
cfnClient *cfn.Client
sqlDb *sql.Storage
cfnClient cfn.CFNClient
}

var _ tracker.GameTracker = (*SF6Tracker)(nil)

func NewSF6Tracker(browser *browser.Browser, sqlDb *sql.Storage) *SF6Tracker {
func NewSF6Tracker(cfnClient cfn.CFNClient) *SF6Tracker {
return &SF6Tracker{
cfnClient: cfn.NewCFNClient(browser),
sqlDb: sqlDb,
cfnClient,
}
}

// Start will update the tracking state when new matches are played.
func (t *SF6Tracker) Init(ctx context.Context, userCode string, restore bool) (*model.Session, error) {
if restore {
session, err := t.sqlDb.GetLatestSession(ctx, userCode)
if err != nil {
return nil, errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf(`failed to get last session: %w`, err))
}
_, err = t.sqlDb.GetUserByCode(ctx, userCode)
if err != nil {
return nil, errorsx.NewFormattedError(http.StatusNotFound, fmt.Errorf(`failed to get user: %w`, err))
}
return session, nil
}

bl, err := t.cfnClient.GetBattleLog(userCode)
func (t *SF6Tracker) GetUser(ctx context.Context, userCode string) (*model.User, error) {
bl, err := t.cfnClient.GetBattleLog(ctx, userCode)
if err != nil {
return nil, errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf(`failed to fetch battle log: %w`, err))
return nil, errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf("fetch battle log: %w", err))
}

err = t.sqlDb.SaveUser(ctx, model.User{
return &model.User{
DisplayName: bl.GetCFN(),
Code: userCode,
})
if err != nil {
return nil, errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf(`failed to save user: %w`, err))
}
session, err := t.sqlDb.CreateSession(ctx, userCode)
if err != nil {
return nil, errorsx.NewFormattedError(http.StatusInternalServerError, fmt.Errorf(`failed to create session: %w`, err))
}

// set starting LP so we don't count the first polled match
session.LP = bl.GetLP()
session.MR = bl.GetMR()
session.UserName = bl.GetCFN()
return session, nil
}, nil
}

func (t *SF6Tracker) Poll(ctx context.Context, cancel context.CancelFunc, session *model.Session, onNewMatch func(model.Match)) {
bl, err := t.cfnClient.GetBattleLog(session.UserId)
bl, err := t.cfnClient.GetBattleLog(ctx, session.UserId)
if err != nil {
cancel()
}
Expand Down Expand Up @@ -177,6 +146,6 @@ func getLeagueFromLP(lp int) string {
return `Rookie`
}

func (t *SF6Tracker) Authenticate(email string, password string, statChan chan tracker.AuthStatus) {
t.cfnClient.Authenticate(email, password, statChan)
func (t *SF6Tracker) Authenticate(ctx context.Context, email string, password string, statChan chan tracker.AuthStatus) {
t.cfnClient.Authenticate(ctx, email, password, statChan)
}
Loading