diff --git a/extractor_v6.go b/extractor_v6.go index 04404aae..1f3dda39 100644 --- a/extractor_v6.go +++ b/extractor_v6.go @@ -166,7 +166,7 @@ func (e ExtractorV6) ExtractAttacks(pageHTML []byte, ownCoords []Coordinate) ([] func (e ExtractorV6) extractAttacks(pageHTML []byte, clock clockwork.Clock, ownCoords []Coordinate) ([]AttackEvent, error) { doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(pageHTML)) - return e.ExtractAttacksFromDoc(doc, clock, ownCoords) + return e.extractAttacksFromDoc(doc, clock, ownCoords) } // ExtractOfferOfTheDay ... @@ -414,7 +414,11 @@ func (e ExtractorV6) ExtractOGameSessionFromDoc(doc *goquery.Document) string { } // ExtractAttacksFromDoc ... -func (e ExtractorV6) ExtractAttacksFromDoc(doc *goquery.Document, clock clockwork.Clock, ownCoords []Coordinate) ([]AttackEvent, error) { +func (e ExtractorV6) ExtractAttacksFromDoc(doc *goquery.Document, ownCoords []Coordinate) ([]AttackEvent, error) { + return e.extractAttacksFromDoc(doc, clockwork.NewRealClock(), ownCoords) +} + +func (e ExtractorV6) extractAttacksFromDoc(doc *goquery.Document, clock clockwork.Clock, ownCoords []Coordinate) ([]AttackEvent, error) { return extractAttacksFromDocV6(doc, clock, ownCoords) } diff --git a/extractor_v71.go b/extractor_v71.go index e3ed9b6a..84a1f2b1 100644 --- a/extractor_v71.go +++ b/extractor_v71.go @@ -114,8 +114,8 @@ func (e ExtractorV71) ExtractAllResources(pageHTML []byte) (map[CelestialID]Reso } // ExtractAttacksFromDoc ... -func (e ExtractorV71) ExtractAttacksFromDoc(doc *goquery.Document, clock clockwork.Clock, ownCoords []Coordinate) ([]AttackEvent, error) { - return extractAttacksFromDocV71(doc, clock, ownCoords) +func (e ExtractorV71) ExtractAttacksFromDoc(doc *goquery.Document, ownCoords []Coordinate) ([]AttackEvent, error) { + return e.extractAttacksFromDoc(doc, clockwork.NewRealClock(), ownCoords) } // ExtractAttacks ... @@ -125,7 +125,11 @@ func (e ExtractorV71) ExtractAttacks(pageHTML []byte, ownCoords []Coordinate) ([ func (e ExtractorV71) extractAttacks(pageHTML []byte, clock clockwork.Clock, ownCoords []Coordinate) ([]AttackEvent, error) { doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(pageHTML)) - return e.ExtractAttacksFromDoc(doc, clock, ownCoords) + return e.extractAttacksFromDoc(doc, clock, ownCoords) +} + +func (e ExtractorV71) extractAttacksFromDoc(doc *goquery.Document, clock clockwork.Clock, ownCoords []Coordinate) ([]AttackEvent, error) { + return extractAttacksFromDocV71(doc, clock, ownCoords) } // DMCost ... diff --git a/fetcher.go b/fetcher.go index b0e00e77..00fe8cf5 100644 --- a/fetcher.go +++ b/fetcher.go @@ -105,6 +105,7 @@ func getAjaxPage[T AjaxPagePages](b *OGame, vals url.Values, opts ...Option) (T, case MissileAttackLayerAjaxPage: case FetchTechsAjaxPage: case RocketlayerAjaxPage: + case PhalanxAjaxPage: default: panic("not implemented") } diff --git a/interfaces.go b/interfaces.go index 57e77bee..471244d1 100644 --- a/interfaces.go +++ b/interfaces.go @@ -7,7 +7,6 @@ import ( "time" "github.com/PuerkitoBio/goquery" - "github.com/alaingilbert/clockwork" ) // Prioritizable ... @@ -348,7 +347,7 @@ type Extractor interface { ExtractFacilitiesFromDoc(doc *goquery.Document) (Facilities, error) ExtractResearchFromDoc(doc *goquery.Document) Researches ExtractOGameSessionFromDoc(doc *goquery.Document) string - ExtractAttacksFromDoc(doc *goquery.Document, clock clockwork.Clock, ownCoords []Coordinate) ([]AttackEvent, error) + ExtractAttacksFromDoc(doc *goquery.Document, ownCoords []Coordinate) ([]AttackEvent, error) ExtractOfferOfTheDayFromDoc(doc *goquery.Document) (price int64, importToken string, planetResources PlanetResources, multiplier Multiplier, err error) ExtractProductionFromDoc(doc *goquery.Document) ([]Quantifiable, error) ExtractOverviewProductionFromDoc(doc *goquery.Document) ([]Quantifiable, error) diff --git a/ogame.go b/ogame.go index 18c53e1a..09585335 100644 --- a/ogame.go +++ b/ogame.go @@ -447,7 +447,11 @@ func (b *OGame) loginWithBearerToken(token string) (bool, error) { } } b.debug("login using existing cookies") - if err := b.loginPart3(userAccount, pageHTML); err != nil { + page, err := ParsePage[OverviewPage](b, pageHTML) + if err != nil { + return false, err + } + if err := b.loginPart3(userAccount, page); err != nil { return false, err } if err := b.client.Jar.(*cookiejar.Jar).Save(); err != nil { @@ -461,7 +465,11 @@ func (b *OGame) loginWithBearerToken(token string) (bool, error) { return false, err } b.debug("login using existing cookies") - if err := b.loginPart3(userAccount, pageHTML); err != nil { + page, err := ParsePage[OverviewPage](b, pageHTML) + if err != nil { + return false, err + } + if err := b.loginPart3(userAccount, page); err != nil { return false, err } return true, nil @@ -643,7 +651,11 @@ func (b *OGame) login() error { if err := b.loginPart2(server); err != nil { return err } - if err := b.loginPart3(userAccount, pageHTML); err != nil { + page, err := ParsePage[OverviewPage](b, pageHTML) + if err != nil { + return err + } + if err := b.loginPart3(userAccount, page); err != nil { return err } @@ -714,7 +726,7 @@ func (b *OGame) loginPart2(server Server) error { return nil } -func (b *OGame) loginPart3(userAccount Account, pageHTML []byte) error { +func (b *OGame) loginPart3(userAccount Account, page OverviewPage) error { if ogVersion, err := version.NewVersion(b.serverData.Version); err == nil { if ogVersion.GreaterThanOrEqual(version.Must(version.NewVersion("9.0.0"))) { b.extractor = NewExtractorV9() @@ -736,24 +748,20 @@ func (b *OGame) loginPart3(userAccount Account, pageHTML []byte) error { b.debug("logged in as " + userAccount.Name + " on " + b.Universe + "-" + b.language) b.debug("extract information from html") - doc, err := goquery.NewDocumentFromReader(bytes.NewReader(pageHTML)) - if err != nil { - return err - } - b.ogameSession = b.extractor.ExtractOGameSessionFromDoc(doc) + b.ogameSession = page.ExtractOGameSession() if b.ogameSession == "" { return ErrBadCredentials } - serverTime, _ := b.extractor.ExtractServerTime(pageHTML) + serverTime, _ := page.ExtractServerTime() b.location = serverTime.Location() - b.cacheFullPageInfo(OverviewPageName, pageHTML) + b.cacheFullPageInfo(page) _, _ = b.getPage(PreferencesPageName) // Will update preferences cached values // Extract chat host and port - m := regexp.MustCompile(`var nodeUrl\s?=\s?"https:\\/\\/([^:]+):(\d+)\\/socket.io\\/socket.io.js"`).FindSubmatch(pageHTML) + m := regexp.MustCompile(`var nodeUrl\s?=\s?"https:\\/\\/([^:]+):(\d+)\\/socket.io\\/socket.io.js"`).FindSubmatch(page.content) chatHost := string(m[1]) chatPort := string(m[2]) @@ -780,27 +788,26 @@ func (b *OGame) loginPart3(userAccount Account, pageHTML []byte) error { return nil } -func (b *OGame) cacheFullPageInfo(page string, pageHTML []byte) { - doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(pageHTML)) +func (b *OGame) cacheFullPageInfo(page IFullPage) { b.planetsMu.Lock() - b.planets = b.extractor.ExtractPlanetsFromDoc(doc, b) + b.planets = page.ExtractPlanets() b.planetsMu.Unlock() - b.isVacationModeEnabled = b.extractor.ExtractIsInVacationFromDoc(doc) - b.ajaxChatToken, _ = b.extractor.ExtractAjaxChatToken(pageHTML) - b.characterClass, _ = b.extractor.ExtractCharacterClassFromDoc(doc) - b.hasCommander = b.extractor.ExtractCommanderFromDoc(doc) - b.hasAdmiral = b.extractor.ExtractAdmiralFromDoc(doc) - b.hasEngineer = b.extractor.ExtractEngineerFromDoc(doc) - b.hasGeologist = b.extractor.ExtractGeologistFromDoc(doc) - b.hasTechnocrat = b.extractor.ExtractTechnocratFromDoc(doc) - - switch page { - case OverviewPageName: - b.Player, _ = b.extractor.ExtractUserInfos(pageHTML, b.language) - case PreferencesPageName: - b.CachedPreferences = b.extractor.ExtractPreferencesFromDoc(doc) - case ResearchPageName: - researches := b.extractor.ExtractResearchFromDoc(doc) + b.isVacationModeEnabled = page.ExtractIsInVacation() + b.ajaxChatToken, _ = page.ExtractAjaxChatToken() + b.characterClass, _ = page.ExtractCharacterClass() + b.hasCommander = page.ExtractCommander() + b.hasAdmiral = page.ExtractAdmiral() + b.hasEngineer = page.ExtractEngineer() + b.hasGeologist = page.ExtractGeologist() + b.hasTechnocrat = page.ExtractTechnocrat() + + switch castedPage := page.(type) { + case OverviewPage: + b.Player, _ = castedPage.ExtractUserInfos() + case PreferencesPage: + b.CachedPreferences = castedPage.ExtractPreferences() + case ResearchPage: + researches := castedPage.ExtractResearch() b.researches = &researches } } @@ -1437,10 +1444,7 @@ func isLogged(pageHTML []byte) bool { // IsKnowFullPage ... func IsKnowFullPage(vals url.Values) bool { - page := vals.Get("page") - if page == "ingame" { - page = vals.Get("component") - } + page := getPageName(vals) return page == OverviewPageName || page == TraderOverviewPageName || page == ResearchPageName || @@ -1463,12 +1467,13 @@ func IsKnowFullPage(vals url.Values) bool { page == FleetdispatchPageName } +func IsEmpirePage(vals url.Values) bool { + return vals.Get("page") == "standalone" && vals.Get("component") == "empire" +} + // IsAjaxPage either the requested page is a partial/ajax page func IsAjaxPage(vals url.Values) bool { - page := vals.Get("page") - if page == "ingame" { - page = vals.Get("component") - } + page := getPageName(vals) ajax := vals.Get("ajax") asJson := vals.Get("asJson") return page == FetchEventboxAjaxPageName || @@ -1562,49 +1567,91 @@ func (b *OGame) execRequest(method, finalURL string, payload, vals url.Values) ( return by, nil } -func (b *OGame) getPageContent(vals url.Values, opts ...Option) ([]byte, error) { - var cfg options - for _, opt := range opts { - opt(&cfg) +func getPageName(vals url.Values) string { + page := vals.Get("page") + component := vals.Get("component") + if page == "ingame" || + (page == "componentOnly" && component == FetchEventboxAjaxPageName) || + (page == "componentOnly" && component == EventListAjaxPageName && vals.Get("action") != FetchEventboxAjaxPageName) { + page = component } + return page +} - if err := b.preRequestChecks(); err != nil { - return []byte{}, err +func getOptions(opts ...Option) (out options) { + for _, opt := range opts { + opt(&out) } + return +} +func setCPParam(b *OGame, vals url.Values, cfg options) { if vals.Get("cp") == "" && cfg.ChangePlanet != 0 && b.getCachedCelestial(cfg.ChangePlanet) != nil { vals.Set("cp", FI64(cfg.ChangePlanet)) } +} +func detectLoggedOut(page string, vals url.Values, pageHTML []byte) bool { + if vals.Get("allianceId") != "" { + return false + } + return (page != LogoutPageName && (IsKnowFullPage(vals) || page == "") && !IsAjaxPage(vals) && !isLogged(pageHTML)) || + (page == EventListAjaxPageName && !bytes.Contains(pageHTML, []byte("eventListWrap"))) || + (page == FetchEventboxAjaxPageName && !canParseEventBox(pageHTML)) || + (page == GalaxyContentAjaxPageName && !canParseSystemInfos(pageHTML)) +} + +func constructFinalURL(b *OGame, vals url.Values) string { finalURL := b.serverURL + "/game/index.php?" + vals.Encode() allianceID := vals.Get("allianceId") if allianceID != "" { finalURL = b.serverURL + "/game/allianceInfo.php?allianceID=" + allianceID } + return finalURL +} + +func retryPolicyFromConfig(b *OGame, cfg options) func(func() error) error { + retryPolicy := b.withRetry + if cfg.SkipRetry { + retryPolicy = b.withoutRetry + } + return retryPolicy +} - page := vals.Get("page") - if page == "ingame" || - (page == "componentOnly" && vals.Get("component") == FetchEventboxAjaxPageName) || - (page == "componentOnly" && vals.Get("component") == EventListAjaxPageName && vals.Get("action") != FetchEventboxAjaxPageName) { - page = vals.Get("component") +func (b *OGame) getPageContent(vals url.Values, opts ...Option) ([]byte, error) { + var payload url.Values + method := http.MethodGet + + cfg := getOptions(opts...) + + if err := b.preRequestChecks(); err != nil { + return []byte{}, err } + + setCPParam(b, vals, cfg) + + alterPayload(method, b, vals, payload) + + finalURL := constructFinalURL(b, vals) + + page := getPageName(vals) var pageHTMLBytes []byte clb := func() (err error) { - pageHTMLBytes, err = b.execRequest(http.MethodGet, finalURL, nil, vals) + // Needs to be inside the withRetry, so if we need to re-login the redirect is back for the login call + // Prevent redirect (301) https://stackoverflow.com/a/38150816/4196220 + b.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } + defer func() { b.client.CheckRedirect = nil }() + + pageHTMLBytes, err = b.execRequest(method, finalURL, payload, vals) if err != nil { return err } - if allianceID != "" { - return nil - } - if (page != LogoutPageName && (IsKnowFullPage(vals) || page == "") && !IsAjaxPage(vals) && !isLogged(pageHTMLBytes)) || - (page == EventListAjaxPageName && !bytes.Contains(pageHTMLBytes, []byte("eventListWrap"))) || - (page == FetchEventboxAjaxPageName && !canParseEventBox(pageHTMLBytes)) { + if detectLoggedOut(page, vals, pageHTMLBytes) { b.error("Err not logged on page : ", page) atomic.StoreInt32(&b.isConnectedAtom, 0) return ErrNotLogged @@ -1613,32 +1660,20 @@ func (b *OGame) getPageContent(vals url.Values, opts ...Option) ([]byte, error) return nil } - var err error - if cfg.SkipRetry { - err = clb() - } else { - err = b.withRetry(clb) - } - if err != nil { + retryPolicy := retryPolicyFromConfig(b, cfg) + if err := retryPolicy(clb); err != nil { b.error(err) return []byte{}, err } - if !IsAjaxPage(vals) && isLogged(pageHTMLBytes) { - page := vals.Get("page") - component := vals.Get("component") - if page != "standalone" && component != "empire" { - if page == "ingame" { - page = component - } - b.cacheFullPageInfo(page, pageHTMLBytes) - } + if err := processResponseHTML(method, b, pageHTMLBytes, page, payload, vals); err != nil { + return []byte{}, err } if !cfg.SkipInterceptor { go func() { for _, fn := range b.interceptorCallbacks { - fn(http.MethodGet, finalURL, vals, nil, pageHTMLBytes) + fn(method, finalURL, vals, payload, pageHTMLBytes) } }() } @@ -1647,70 +1682,57 @@ func (b *OGame) getPageContent(vals url.Values, opts ...Option) ([]byte, error) } func (b *OGame) postPageContent(vals, payload url.Values, opts ...Option) ([]byte, error) { - var cfg options - for _, opt := range opts { - opt(&cfg) - } + method := http.MethodPost + + cfg := getOptions(opts...) if err := b.preRequestChecks(); err != nil { return []byte{}, err } - if vals.Get("cp") == "" && - cfg.ChangePlanet != 0 && - b.getCachedCelestial(cfg.ChangePlanet) != nil { - vals.Set("cp", FI64(cfg.ChangePlanet)) - } + setCPParam(b, vals, cfg) - if vals.Get("page") == "ajaxChat" && payload.Get("mode") == "1" { - payload.Set("token", b.ajaxChatToken) - } + alterPayload(method, b, vals, payload) - finalURL := b.serverURL + "/game/index.php?" + vals.Encode() - page := vals.Get("page") - if page == "ingame" { - page = vals.Get("component") - } + finalURL := constructFinalURL(b, vals) + + page := getPageName(vals) var pageHTMLBytes []byte - if err := b.withRetry(func() (err error) { + clb := func() (err error) { // Needs to be inside the withRetry, so if we need to re-login the redirect is back for the login call // Prevent redirect (301) https://stackoverflow.com/a/38150816/4196220 b.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } defer func() { b.client.CheckRedirect = nil }() - pageHTMLBytes, err = b.execRequest(http.MethodPost, finalURL, payload, vals) + pageHTMLBytes, err = b.execRequest(method, finalURL, payload, vals) if err != nil { return err } - if page == "galaxyContent" && !canParseSystemInfos(pageHTMLBytes) { + if detectLoggedOut(page, vals, pageHTMLBytes) { b.error("Err not logged on page : ", page) - b.error(string(pageHTMLBytes)) atomic.StoreInt32(&b.isConnectedAtom, 0) return ErrNotLogged } return nil - }); err != nil { + } + + retryPolicy := retryPolicyFromConfig(b, cfg) + if err := retryPolicy(clb); err != nil { b.error(err) return []byte{}, err } - if page == "preferences" { - b.CachedPreferences = b.extractor.ExtractPreferences(pageHTMLBytes) - } else if page == "ajaxChat" && (payload.Get("mode") == "1" || payload.Get("mode") == "3") { - var res ChatPostResp - if err := json.Unmarshal(pageHTMLBytes, &res); err != nil { - return []byte{}, err - } - b.ajaxChatToken = res.NewToken + if err := processResponseHTML(method, b, pageHTMLBytes, page, payload, vals); err != nil { + return []byte{}, err } if !cfg.SkipInterceptor { go func() { for _, fn := range b.interceptorCallbacks { - fn(http.MethodPost, finalURL, vals, payload, pageHTMLBytes) + fn(method, finalURL, vals, payload, pageHTMLBytes) } }() } @@ -1718,6 +1740,44 @@ func (b *OGame) postPageContent(vals, payload url.Values, opts ...Option) ([]byt return pageHTMLBytes, nil } +func alterPayload(method string, b *OGame, vals, payload url.Values) { + switch method { + case http.MethodPost: + if vals.Get("page") == "ajaxChat" && payload.Get("mode") == "1" { + payload.Set("token", b.ajaxChatToken) + } + } +} + +func processResponseHTML(method string, b *OGame, pageHTML []byte, page string, payload, vals url.Values) error { + switch method { + case http.MethodGet: + if !IsAjaxPage(vals) && !IsEmpirePage(vals) && isLogged(pageHTML) { + parsedFullPage := AutoParseFullPage(b, pageHTML) + b.cacheFullPageInfo(parsedFullPage) + } + + case http.MethodPost: + if page == PreferencesPageName { + b.CachedPreferences = b.extractor.ExtractPreferences(pageHTML) + } else if page == "ajaxChat" && (payload.Get("mode") == "1" || payload.Get("mode") == "3") { + if err := extractNewChatToken(b, pageHTML); err != nil { + return err + } + } + } + return nil +} + +func extractNewChatToken(b *OGame, pageHTMLBytes []byte) error { + var res ChatPostResp + if err := json.Unmarshal(pageHTMLBytes, &res); err != nil { + return err + } + b.ajaxChatToken = res.NewToken + return nil +} + func (b *OGame) getAlliancePageContent(vals url.Values) ([]byte, error) { if err := b.preRequestChecks(); err != nil { return []byte{}, err @@ -1732,6 +1792,10 @@ type eventboxResp struct { Friendly int } +func (b *OGame) withoutRetry(fn func() error) error { + return fn() +} + func (b *OGame) withRetry(fn func() error) error { maxRetry := 10 retryInterval := 1 @@ -2253,16 +2317,16 @@ func (b *OGame) getUnsafePhalanx(moonID MoonID, coord Coordinate) ([]Fleet, erro return nil, errors.New("cannot scan own planet") } - pageHTML, _ := b.getPageContent(url.Values{ - "page": {"phalanx"}, + vals := url.Values{ + "page": {PhalanxAjaxPageName}, "galaxy": {FI64(coord.Galaxy)}, "system": {FI64(coord.System)}, "position": {FI64(coord.Position)}, "ajax": {"1"}, - "cp": {FI64(moonID)}, "token": {planetInfos.OverlayToken}, - }) - return b.extractor.ExtractPhalanx(pageHTML) + } + page, _ := getAjaxPage[PhalanxAjaxPage](b, vals, ChangePlanet(moonID.Celestial())) + return page.ExtractPhalanx() } func moonIDInSlice(needle MoonID, haystack []MoonID) bool { @@ -2950,10 +3014,7 @@ func (b *OGame) getAttacks(opts ...Option) (out []AttackEvent, err error) { } func (b *OGame) galaxyInfos(galaxy, system int64, opts ...Option) (SystemInfos, error) { - var cfg options - for _, opt := range opts { - opt(&cfg) - } + cfg := getOptions(opts...) var res SystemInfos if galaxy < 1 || galaxy > b.server.Settings.UniverseSize { return res, fmt.Errorf("galaxy must be within [1, %d]", b.server.Settings.UniverseSize) diff --git a/ogame_test.go b/ogame_test.go index cd40a172..8a0ccec4 100644 --- a/ogame_test.go +++ b/ogame_test.go @@ -3394,5 +3394,5 @@ func TestParsePage(t *testing.T) { fmt.Println(reflect.TypeOf(page), err, planets) def, err := page.ExtractDefense() fmt.Println(def, err) - assert.Equal(t, 1, 2) + assert.Equal(t, 1, 1) } diff --git a/page.go b/page.go index bdf8f998..c36adcaa 100644 --- a/page.go +++ b/page.go @@ -1,19 +1,34 @@ package ogame -import "time" +import ( + "bytes" + "github.com/PuerkitoBio/goquery" + "time" +) type Page struct { b *OGame + doc *goquery.Document content []byte } +func (p *Page) GetDoc() *goquery.Document { + if p.doc == nil { + doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(p.content)) + p.doc = doc + } + return p.doc +} + type EventListAjaxPage struct{ Page } type MissileAttackLayerAjaxPage struct{ Page } type FetchTechsAjaxPage struct{ Page } type RocketlayerAjaxPage struct{ Page } +type PhalanxAjaxPage struct{ Page } type FullPage struct{ Page } type OverviewPage struct{ FullPage } +type PreferencesPage struct{ FullPage } type SuppliesPage struct{ FullPage } type ResourcesSettingsPage struct{ FullPage } @@ -40,48 +55,102 @@ type MovementPage struct{ FullPage } //type BuddiesPageContent struct{ FullPageContent } //type HighScorePageContent struct{ FullPageContent } +type IFullPage interface { + ExtractOGameSession() string + ExtractIsInVacation() bool + ExtractPlanets() []Planet + ExtractAjaxChatToken() (string, error) + ExtractCharacterClass() (CharacterClass, error) + ExtractCommander() bool + ExtractAdmiral() bool + ExtractEngineer() bool + ExtractGeologist() bool + ExtractTechnocrat() bool + ExtractServerTime() (time.Time, error) +} + +func (p PhalanxAjaxPage) ExtractPhalanx() ([]Fleet, error) { + return p.b.extractor.ExtractPhalanx(p.content) +} + func (p RocketlayerAjaxPage) ExtractDestroyRockets() (int64, int64, string, error) { return p.b.extractor.ExtractDestroyRockets(p.content) } +func (p FullPage) ExtractOGameSession() string { + return p.b.extractor.ExtractOGameSessionFromDoc(p.GetDoc()) +} + +func (p FullPage) ExtractIsInVacation() bool { + return p.b.extractor.ExtractIsInVacationFromDoc(p.GetDoc()) +} + +func (p FullPage) ExtractAjaxChatToken() (string, error) { + return p.b.extractor.ExtractAjaxChatToken(p.content) +} + +func (p FullPage) ExtractCharacterClass() (CharacterClass, error) { + return p.b.extractor.ExtractCharacterClassFromDoc(p.GetDoc()) +} + +func (p FullPage) ExtractCommander() bool { + return p.b.extractor.ExtractCommanderFromDoc(p.GetDoc()) +} + +func (p FullPage) ExtractAdmiral() bool { + return p.b.extractor.ExtractAdmiralFromDoc(p.GetDoc()) +} + +func (p FullPage) ExtractEngineer() bool { + return p.b.extractor.ExtractEngineerFromDoc(p.GetDoc()) +} + +func (p FullPage) ExtractGeologist() bool { + return p.b.extractor.ExtractGeologistFromDoc(p.GetDoc()) +} + +func (p FullPage) ExtractTechnocrat() bool { + return p.b.extractor.ExtractTechnocratFromDoc(p.GetDoc()) +} + func (p FullPage) ExtractServerTime() (time.Time, error) { - return p.b.extractor.ExtractServerTime(p.content) + return p.b.extractor.ExtractServerTimeFromDoc(p.GetDoc()) } func (p FullPage) ExtractPlanets() []Planet { - return p.b.extractor.ExtractPlanets(p.content, p.b) + return p.b.extractor.ExtractPlanetsFromDoc(p.GetDoc(), p.b) } func (p FullPage) ExtractPlanet(v any) (Planet, error) { - return p.b.extractor.ExtractPlanet(p.content, p.b, v) + return p.b.extractor.ExtractPlanetFromDoc(p.GetDoc(), p.b, v) } func (p FullPage) ExtractMoons() []Moon { - return p.b.extractor.ExtractMoons(p.content, p.b) + return p.b.extractor.ExtractMoonsFromDoc(p.GetDoc(), p.b) } func (p FullPage) ExtractMoon(v any) (Moon, error) { - return p.b.extractor.ExtractMoon(p.content, p.b, v) + return p.b.extractor.ExtractMoonFromDoc(p.GetDoc(), p.b, v) } func (p FullPage) ExtractCelestials() ([]Celestial, error) { - return p.b.extractor.ExtractCelestials(p.content, p.b) + return p.b.extractor.ExtractCelestialsFromDoc(p.GetDoc(), p.b) } func (p FullPage) ExtractCelestial(v any) (Celestial, error) { - return p.b.extractor.ExtractCelestial(p.content, p.b, v) + return p.b.extractor.ExtractCelestialFromDoc(p.GetDoc(), p.b, v) } func (p ResearchPage) ExtractResearch() Researches { - return p.b.extractor.ExtractResearch(p.content) + return p.b.extractor.ExtractResearchFromDoc(p.GetDoc()) } func (p SuppliesPage) ExtractResourcesBuildings() (ResourcesBuildings, error) { - return p.b.extractor.ExtractResourcesBuildings(p.content) + return p.b.extractor.ExtractResourcesBuildingsFromDoc(p.GetDoc()) } func (p DefensesPage) ExtractDefense() (DefensesInfos, error) { - return p.b.extractor.ExtractDefense(p.content) + return p.b.extractor.ExtractDefenseFromDoc(p.GetDoc()) } func (p OverviewPage) ExtractDMCosts() (DMCosts, error) { @@ -105,7 +174,7 @@ func (p OverviewPage) ExtractCancelBuildingInfos() (token string, techID, listID } func (p FacilitiesPage) ExtractFacilities() (Facilities, error) { - return p.b.extractor.ExtractFacilities(p.content) + return p.b.extractor.ExtractFacilitiesFromDoc(p.GetDoc()) } func (p ShipyardPage) ExtractProduction() ([]Quantifiable, int64, error) { @@ -113,19 +182,19 @@ func (p ShipyardPage) ExtractProduction() ([]Quantifiable, int64, error) { } func (p ShipyardPage) ExtractShips() (ShipsInfos, error) { - return p.b.extractor.ExtractShips(p.content) + return p.b.extractor.ExtractShipsFromDoc(p.GetDoc()) } func (p ResourcesSettingsPage) ExtractResourceSettings() (ResourceSettings, error) { - return p.b.extractor.ExtractResourceSettings(p.content) + return p.b.extractor.ExtractResourceSettingsFromDoc(p.GetDoc()) } func (p MovementPage) ExtractFleets() []Fleet { - return p.b.extractor.ExtractFleets(p.content, p.b.location) + return p.b.extractor.ExtractFleetsFromDoc(p.GetDoc(), p.b.location) } func (p MovementPage) ExtractSlots() Slots { - return p.b.extractor.ExtractSlots(p.content) + return p.b.extractor.ExtractSlotsFromDoc(p.GetDoc()) } func (p MovementPage) ExtractCancelFleetToken(fleetID FleetID) (string, error) { @@ -133,19 +202,24 @@ func (p MovementPage) ExtractCancelFleetToken(fleetID FleetID) (string, error) { } func (p EventListAjaxPage) ExtractAttacks(ownCoords []Coordinate) ([]AttackEvent, error) { - return p.b.extractor.ExtractAttacks(p.content, ownCoords) + return p.b.extractor.ExtractAttacksFromDoc(p.GetDoc(), ownCoords) } func (p MissileAttackLayerAjaxPage) ExtractIPM() (int64, int64, string) { - return p.b.extractor.ExtractIPM(p.content) + return p.b.extractor.ExtractIPMFromDoc(p.GetDoc()) } func (p FetchTechsAjaxPage) ExtractTechs() (ResourcesBuildings, Facilities, ShipsInfos, DefensesInfos, Researches, error) { return p.b.extractor.ExtractTechs(p.content) } +func (p PreferencesPage) ExtractPreferences() Preferences { + return p.b.extractor.ExtractPreferencesFromDoc(p.GetDoc()) +} + type FullPagePages interface { OverviewPage | + PreferencesPage | SuppliesPage | ResourcesSettingsPage | FacilitiesPage | @@ -171,5 +245,6 @@ type AjaxPagePages interface { EventListAjaxPage | MissileAttackLayerAjaxPage | FetchTechsAjaxPage | - RocketlayerAjaxPage + RocketlayerAjaxPage | + PhalanxAjaxPage } diff --git a/parser.go b/parser.go index 6228a742..a0a99f29 100644 --- a/parser.go +++ b/parser.go @@ -7,32 +7,48 @@ import ( var ErrParsePageType = errors.New("failed to parse requested page type") +func AutoParseFullPage(b *OGame, pageHTML []byte) IFullPage { + fullPage := FullPage{Page{b: b, content: pageHTML}} + if bytes.Contains(pageHTML, []byte(`currentPage = "overview";`)) { + return OverviewPage{fullPage} + } else if bytes.Contains(pageHTML, []byte(`currentPage = "preferences";`)) { + return PreferencesPage{fullPage} + } else if bytes.Contains(pageHTML, []byte(`currentPage = "research";`)) { + return ResearchPage{fullPage} + } + return fullPage +} + // ParsePage given a pageHTML and an extractor for the game version this html represent, // returns a page of type T func ParsePage[T FullPagePages](b *OGame, pageHTML []byte) (T, error) { var zero T + fullPage := FullPage{Page{b: b, content: pageHTML}} switch any(zero).(type) { case OverviewPage: if bytes.Contains(pageHTML, []byte(`currentPage = "overview";`)) { - return T(OverviewPage{FullPage{Page{b: b, content: pageHTML}}}), nil + return T(OverviewPage{fullPage}), nil } case DefensesPage: - c, err := ParseDefensesPageContent(b, pageHTML) - return T(c), err + if isDefensesPage(b.extractor, pageHTML) { + return T(DefensesPage{fullPage}), nil + } case ShipyardPage: if bytes.Contains(pageHTML, []byte(`currentPage = "shipyard";`)) { - return T(ShipyardPage{FullPage{Page{b: b, content: pageHTML}}}), nil + return T(ShipyardPage{fullPage}), nil } case ResearchPage: - return T(ResearchPage{FullPage{Page{b: b, content: pageHTML}}}), nil + return T(ResearchPage{fullPage}), nil case FacilitiesPage: - return T(FacilitiesPage{FullPage{Page{b: b, content: pageHTML}}}), nil + return T(FacilitiesPage{fullPage}), nil case SuppliesPage: - return T(SuppliesPage{FullPage{Page{b: b, content: pageHTML}}}), nil + return T(SuppliesPage{fullPage}), nil case ResourcesSettingsPage: - return T(ResourcesSettingsPage{FullPage{Page{b: b, content: pageHTML}}}), nil + return T(ResourcesSettingsPage{fullPage}), nil + case PreferencesPage: + return T(PreferencesPage{fullPage}), nil case MovementPage: - return T(MovementPage{FullPage{Page{b: b, content: pageHTML}}}), nil + return T(MovementPage{fullPage}), nil default: return zero, errors.New("page type not implemented") } @@ -41,29 +57,29 @@ func ParsePage[T FullPagePages](b *OGame, pageHTML []byte) (T, error) { func ParseAjaxPage[T AjaxPagePages](b *OGame, pageHTML []byte) (T, error) { var zero T + page := Page{b: b, content: pageHTML} switch any(zero).(type) { case EventListAjaxPage: - return T(EventListAjaxPage{Page{b: b, content: pageHTML}}), nil + return T(EventListAjaxPage{page}), nil case MissileAttackLayerAjaxPage: - return T(MissileAttackLayerAjaxPage{Page{b: b, content: pageHTML}}), nil + return T(MissileAttackLayerAjaxPage{page}), nil case RocketlayerAjaxPage: - return T(RocketlayerAjaxPage{Page{b: b, content: pageHTML}}), nil + return T(RocketlayerAjaxPage{page}), nil + case PhalanxAjaxPage: + return T(PhalanxAjaxPage{page}), nil case FetchTechsAjaxPage: - return T(FetchTechsAjaxPage{Page{b: b, content: pageHTML}}), nil + return T(FetchTechsAjaxPage{page}), nil } return zero, ErrParsePageType } -func ParseDefensesPageContent(b *OGame, pageHTML []byte) (out DefensesPage, err error) { +func isDefensesPage(e Extractor, pageHTML []byte) bool { var target string - switch b.extractor.(type) { + switch e.(type) { case *ExtractorV6: target = `currentPage="defense";` default: target = `currentPage = "defenses";` } - if bytes.Contains(pageHTML, []byte(target)) { - return DefensesPage{FullPage{Page{b: b, content: pageHTML}}}, nil - } - return out, ErrParsePageType + return bytes.Contains(pageHTML, []byte(target)) } diff --git a/parser_test.go b/parser_test.go index 9885d604..4759c062 100644 --- a/parser_test.go +++ b/parser_test.go @@ -18,8 +18,8 @@ func parserErr(_ any, err error) error { return err } -func TestParseDefensesPageContent(t *testing.T) { - assert.NoError(t, parserErr(ParseDefensesPageContent(&OGame{extractor: NewExtractorV6()}, MustReadFile("samples/unversioned/defence.html")))) - assert.NoError(t, parserErr(ParseDefensesPageContent(&OGame{extractor: NewExtractorV7()}, MustReadFile("samples/v7/defenses.html")))) - assert.Error(t, parserErr(ParseDefensesPageContent(&OGame{extractor: NewExtractorV7()}, MustReadFile("samples/v7/overview.html")))) +func TestParsePageContent(t *testing.T) { + assert.NoError(t, parserErr(ParsePage[DefensesPage](&OGame{extractor: NewExtractorV6()}, MustReadFile("samples/unversioned/defence.html")))) + assert.NoError(t, parserErr(ParsePage[DefensesPage](&OGame{extractor: NewExtractorV7()}, MustReadFile("samples/v7/defenses.html")))) + assert.Error(t, parserErr(ParsePage[DefensesPage](&OGame{extractor: NewExtractorV7()}, MustReadFile("samples/v7/overview.html")))) }