From a301e870910b6e53352bcce352f7d3258f480a85 Mon Sep 17 00:00:00 2001 From: Flashover Date: Wed, 4 Mar 2020 00:00:47 +0100 Subject: [PATCH] Extra CombatReportSummary and FullCombatReport --- cmd/ogamed/main.go | 3 + extractor_v6.go | 6 ++ extractor_v71.go | 11 ++++ extracts.go | 1 + extracts_v6.go | 53 ++++++++++++++++++ extracts_v71.go | 98 +++++++++++++++++++++++++++++++++ handlers.go | 50 +++++++++++++++++ ogame.go | 13 +++++ ogame_test.go | 133 ++++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 367 insertions(+), 1 deletion(-) diff --git a/cmd/ogamed/main.go b/cmd/ogamed/main.go index 3090ee3d..8fc3e4b3 100644 --- a/cmd/ogamed/main.go +++ b/cmd/ogamed/main.go @@ -256,6 +256,9 @@ func start(c *cli.Context) error { e.POST("/bot/delete-report/:messageID", ogame.DeleteMessageHandler) e.POST("/bot/delete-all-espionage-reports", ogame.DeleteEspionageMessagesHandler) e.POST("/bot/delete-all-reports/:tabIndex", ogame.DeleteMessagesFromTabHandler) + e.GET("/bot/combat-report/:msgid", ogame.GetCombatReportHandler) // returns FullCombatReport, it returns the JSON from inside the CombatReport. There is no JSON in EspionageReport. + e.GET("/bot/combat-report/:galaxy/:system/:position", ogame.GetCombatReportForHandler) // returns []CombatReportSummary + e.GET("/bot/combat-report", ogame.GetCombatReportMessagesHandler) // returns []CombatReportSummary e.GET("/bot/attacks", ogame.GetAttacksHandler) e.GET("/bot/get-auction", ogame.GetAuctionHandler) e.POST("/bot/do-auction", ogame.DoAuctionHandler) diff --git a/extractor_v6.go b/extractor_v6.go index ba73e9e0..23406d52 100644 --- a/extractor_v6.go +++ b/extractor_v6.go @@ -781,3 +781,9 @@ func (e ExtractorV6) ExtractDMCosts(pageHTML []byte) (DMCosts, error) { func (e ExtractorV6) ExtractBuffActivation(pageHTML []byte) (string, []Item, error) { panic("not implemented") } + +// ExtractFullCombatReport ... +func (e ExtractorV6) ExtractFullCombatReport(pageHTML []byte, msgID int64) (FullCombatReport, error) { + doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(pageHTML)) + return extractFullCombatReportFromDoc(doc, msgID) +} diff --git a/extractor_v71.go b/extractor_v71.go index 13997412..e6552d87 100644 --- a/extractor_v71.go +++ b/extractor_v71.go @@ -160,3 +160,14 @@ func (e ExtractorV71) ExtractBuffActivation(pageHTML []byte) (string, []Item, er func (e ExtractorV71) ExtractBuffActivationFromDoc(doc *goquery.Document) (string, []Item, error) { return extractBuffActivationFromDocV71(doc) } + +// ExtractCombatReportMessagesSummary ... +func (e ExtractorV71) ExtractCombatReportMessagesSummary(pageHTML []byte) ([]CombatReportSummary, int64) { + doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(pageHTML)) + return e.ExtractCombatReportMessagesSummaryFromDoc(doc) +} + +// ExtractCombatReportMessagesSummaryFromDoc ... +func (e ExtractorV71) ExtractCombatReportMessagesSummaryFromDoc(doc *goquery.Document) ([]CombatReportSummary, int64) { + return extractCombatReportMessagesSummaryFromDocV71(doc) +} diff --git a/extracts.go b/extracts.go index 530515f7..c07a9242 100644 --- a/extracts.go +++ b/extracts.go @@ -149,6 +149,7 @@ type Extractor interface { ExtractAllResources(pageHTML []byte) (map[CelestialID]Resources, error) ExtractDMCosts(pageHTML []byte) (DMCosts, error) ExtractBuffActivation(pageHTML []byte) (string, []Item, error) + ExtractFullCombatReport(pageHTML []byte, msgID int64) (FullCombatReport, error) } // Compile time checks to ensure type satisfies Extractor interface diff --git a/extracts_v6.go b/extracts_v6.go index f02111a0..ffc7549b 100644 --- a/extracts_v6.go +++ b/extracts_v6.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "log" + "github.com/PuerkitoBio/goquery" "github.com/alaingilbert/clockwork" lua "github.com/yuin/gopher-lua" @@ -662,6 +664,8 @@ func extractCombatReportMessagesFromDocV6(doc *goquery.Document) ([]CombatReport if idStr, exists := s.Attr("data-msg-id"); exists { if id, err := strconv.ParseInt(idStr, 10, 64); err == nil { report := CombatReportSummary{ID: id} + + // Find destination coordinate report.Destination = extractCoordV6(s.Find("div.msg_head a").Text()) if s.Find("div.msg_head figure").HasClass("planet") { report.Destination.Type = PlanetType @@ -670,6 +674,8 @@ func extractCombatReportMessagesFromDocV6(doc *goquery.Document) ([]CombatReport } else { report.Destination.Type = PlanetType } + + // Find looted resources {Metal, Crystal, Deuterium} resTitle := s.Find("span.msg_content div.combatLeftSide span").Eq(1).AttrOr("title", "") m := regexp.MustCompile(`([\d.]+)
[^\d]*([\d.]+)
[^\d]*([\d.]+)`).FindStringSubmatch(resTitle) if len(m) == 4 { @@ -677,16 +683,41 @@ func extractCombatReportMessagesFromDocV6(doc *goquery.Document) ([]CombatReport report.Crystal = ParseInt(m[2]) report.Deuterium = ParseInt(m[3]) } + + // Find debris field debrisFieldTitle := s.Find("span.msg_content div.combatLeftSide span").Eq(2).AttrOr("title", "0") report.DebrisField = ParseInt(debrisFieldTitle) + + // Find Loot (%) or Total Looted resources TODO resText := s.Find("span.msg_content div.combatLeftSide span").Eq(1).Text() m = regexp.MustCompile(`[\d.]+[^\d]*([\d.]+)`).FindStringSubmatch(resText) if len(m) == 2 { report.Loot = ParseInt(m[1]) } + + // Find Date msgDate, _ := time.Parse("02.01.2006 15:04:05", s.Find("span.msg_date").Text()) report.CreatedAt = msgDate + // Find: attacker name + report.AttackerName = "" + attacker := s.Find("span.msg_content div.combatLeftSide span").Eq(0).Text() + m = regexp.MustCompile(`\((.*)\)`).FindStringSubmatch(attacker) + + if len(m) > 0 { + report.AttackerName = m[1] + } + + // Find: Defender name + report.DefenderName = "" + defender := s.Find("span.msg_content div.combatRightSide span").Eq(0).Text() + m = regexp.MustCompile(`\((.*)\)`).FindStringSubmatch(defender) + + if len(m) > 0 { + report.DefenderName = m[1] + } + + // Find origin coordinate link := s.Find("div.msg_actions a span.icon_attack").Parent().AttrOr("href", "") m = regexp.MustCompile(`page=fleet1&galaxy=(\d+)&system=(\d+)&position=(\d+)&type=(\d+)&`).FindStringSubmatch(link) if len(m) != 5 { @@ -697,6 +728,7 @@ func extractCombatReportMessagesFromDocV6(doc *goquery.Document) ([]CombatReport position, _ := strconv.ParseInt(m[3], 10, 64) planetType, _ := strconv.ParseInt(m[4], 10, 64) report.Origin = &Coordinate{galaxy, system, position, CelestialType(planetType)} + if report.Origin.Equal(report.Destination) { report.Origin = nil } @@ -2122,3 +2154,24 @@ func extractAuctionFromDoc(doc *goquery.Document) (Auction, error) { return auction, nil } + +func extractFullCombatReportFromDoc(doc *goquery.Document, msgID int64) (FullCombatReport, error) { + log.SetFlags(log.LstdFlags | log.Lshortfile) + + report := FullCombatReport{} + report.ID = msgID + + scriptHTMLText := doc.Find("script").First().Text() + + m := regexp.MustCompile(`parseJSON\(\'(.*)\'\);`).FindStringSubmatch(scriptHTMLText) + + if len(m) == 2 { + if err := json.Unmarshal([]byte(m[1]), &report.Content); err != nil { + return FullCombatReport{}, err + } + + return report, nil + } + + return FullCombatReport{}, errors.New("Unable to find JSON in this CombatReport") +} diff --git a/extracts_v71.go b/extracts_v71.go index 3fa7e368..091aa0d2 100644 --- a/extracts_v71.go +++ b/extracts_v71.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "log" + "github.com/PuerkitoBio/goquery" "github.com/alaingilbert/clockwork" "golang.org/x/net/html" @@ -806,3 +808,99 @@ func extractBuffActivationFromDocV71(doc *goquery.Document) (token string, items } return } + +func extractCombatReportMessagesSummaryFromDocV71(doc *goquery.Document) ([]CombatReportSummary, int64) { + log.SetFlags(log.LstdFlags | log.Lshortfile) + msgs := make([]CombatReportSummary, 0) + nbPage, _ := strconv.ParseInt(doc.Find("ul.pagination li").Last().AttrOr("data-page", "1"), 10, 64) + doc.Find("li.msg").Each(func(i int, s *goquery.Selection) { + if idStr, exists := s.Attr("data-msg-id"); exists { + if id, err := strconv.ParseInt(idStr, 10, 64); err == nil { + report := CombatReportSummary{ID: id} + + // Find destination coordinate + report.Destination = extractCoordV6(s.Find("div.msg_head a").Text()) + if s.Find("div.msg_head figure").HasClass("planet") { + report.Destination.Type = PlanetType + } else if s.Find("div.msg_head figure").HasClass("moon") { + report.Destination.Type = MoonType + } else { + report.Destination.Type = PlanetType + } + + // Find looted resources {Metal, Crystal, Deuterium} + resTitle := s.Find("span.msg_content div.combatLeftSide span").Eq(1).AttrOr("title", "") + m := regexp.MustCompile(`([\d.]+)
[^\d]*([\d.]+)
[^\d]*([\d.]+)`).FindStringSubmatch(resTitle) + if len(m) == 4 { + report.Metal = ParseInt(m[1]) + report.Crystal = ParseInt(m[2]) + report.Deuterium = ParseInt(m[3]) + } + + // Find debris field + report.DebrisField = ParseInt(s.Find("span.msg_content div.combatLeftSide span").Eq(2).AttrOr("title", "0")) + + // Find Loot (%) + resText := s.Find("span.msg_content div.combatLeftSide span").Eq(1).Text() + m = regexp.MustCompile(`(\d+)\%`).FindStringSubmatch(resText) + if len(m) > 0 { + // NL = Grondstoffen: 4,883M, buit: 50% + // EN = Resources: 1.958Mn, Loot: 50% + report.Loot = ParseInt(m[len(m)-1]) // Find last item , as NL language uses "," as thousand-separator instead of "." in EN. + } + + // Find Date + msgDate, _ := time.Parse("02.01.2006 15:04:05", s.Find("span.msg_date").Text()) + report.CreatedAt = msgDate + + // Find: AttackerName and AttackerLostValue + attacker := s.Find("span.msg_content div.combatLeftSide span").Eq(0) + report.AttackerLostValue = ParseInt(attacker.AttrOr("title", "0")) + m = regexp.MustCompile(`\((.*)\)`).FindStringSubmatch(attacker.Text()) + if len(m) == 2 { + report.AttackerName = m[1] + } + + // Find: DefenderName and DefenderLostValue + defender := s.Find("span.msg_content div.combatRightSide span").Eq(0) + report.DefenderLostValue = ParseInt(defender.AttrOr("title", "0")) + m = regexp.MustCompile(`\((.*)\)`).FindStringSubmatch(defender.Text()) + if len(m) == 2 { + report.DefenderName = m[1] + } + + // Find origin coordinate + link := s.Find("div.msg_actions a span.icon_attack").Parent().AttrOr("href", "") + m = regexp.MustCompile(`page=ingame&component=fleetdispatch&galaxy=(\d+)&system=(\d+)&position=(\d+)&type=(\d+)&`).FindStringSubmatch(link) + if len(m) == 5 { + galaxy := ParseInt(m[1]) + system := ParseInt(m[2]) + position := ParseInt(m[3]) + planetType := ParseInt(m[4]) + report.Origin = &Coordinate{galaxy, system, position, CelestialType(planetType)} + + if report.Origin.Equal(report.Destination) { + report.Origin = nil + } + } + + // Find APIKey + apikey := s.Find("span.icon_apikey").AttrOr("title", "") + if len(apikey) > 0 { + apiDoc, _ := goquery.NewDocumentFromReader(strings.NewReader(apikey)) + report.APIKey = apiDoc.Find("input").First().AttrOr("value", "") + } + + // Find Repaired + // Actually repaired: 0 + repaired := s.Find("div.combatRightSide").Find("span.msg_ctn3").First().AttrOr("title", "") + if len(repaired) > 0 { + report.Repaired = ParseInt(repaired) + } + + msgs = append(msgs, report) + } + } + }) + return msgs, nbPage +} diff --git a/handlers.go b/handlers.go index 6c40c05e..7f5f29bb 100644 --- a/handlers.go +++ b/handlers.go @@ -1160,3 +1160,53 @@ func PhalanxHandler(c echo.Context) error { } return c.JSON(http.StatusOK, SuccessResp(fleets)) } + +// GetCombatReportMessagesHandler ... +func GetCombatReportMessagesHandler(c echo.Context) error { + bot := c.Get("bot").(*OGame) + + report, err := bot.getCombatReportMessages() + if err != nil { + return c.JSON(http.StatusInternalServerError, ErrorResp(500, err.Error())) + } + return c.JSON(http.StatusOK, SuccessResp(report)) +} + +// FullCombatReport, takes JSON from CR message. There is no JSON in EspionageReport. +func GetCombatReportHandler(c echo.Context) error { + bot := c.Get("bot").(*OGame) + + msgID, err := strconv.ParseInt(c.Param("msgid"), 10, 64) + if err != nil { + return c.JSON(http.StatusBadRequest, ErrorResp(400, "invalid msgid id")) + } + combatReport, err := bot.GetFullCombatReport(msgID) + if err != nil { + return c.JSON(http.StatusInternalServerError, ErrorResp(500, err.Error())) + } + return c.JSON(http.StatusOK, SuccessResp(combatReport)) +} + +// GetCombatReportForHandler ... +func GetCombatReportForHandler(c echo.Context) error { + bot := c.Get("bot").(*OGame) + + galaxy, err := strconv.ParseInt(c.Param("galaxy"), 10, 64) + if err != nil { + return c.JSON(http.StatusBadRequest, ErrorResp(400, "invalid galaxy")) + } + system, err := strconv.ParseInt(c.Param("system"), 10, 64) + if err != nil { + return c.JSON(http.StatusBadRequest, ErrorResp(400, "invalid system")) + } + position, err := strconv.ParseInt(c.Param("position"), 10, 64) + if err != nil { + return c.JSON(http.StatusBadRequest, ErrorResp(400, "invalid position")) + } + + planet, err := bot.getCombatReportFor(Coordinate{Type: PlanetType, Galaxy: galaxy, System: system, Position: position}) + if err != nil { + return c.JSON(http.StatusInternalServerError, ErrorResp(500, err.Error())) + } + return c.JSON(http.StatusOK, SuccessResp(planet)) +} diff --git a/ogame.go b/ogame.go index 36d5e950..e189e986 100644 --- a/ogame.go +++ b/ogame.go @@ -3344,11 +3344,14 @@ type CombatReportSummary struct { Destination Coordinate AttackerName string DefenderName string + AttackerLostValue int64 + DefenderLostValue int64 Loot int64 Metal int64 Crystal int64 Deuterium int64 DebrisField int64 + Repaired int64 CreatedAt time.Time } @@ -4381,6 +4384,16 @@ func (b *OGame) GetEmpire(nbr int64) (interface{}, error) { return b.WithPriority(Normal).GetEmpire(nbr) } +type FullCombatReport struct { + ID int64 + Content interface{} +} + +func (b *OGame) GetFullCombatReport(msgID int64) (FullCombatReport, error) { + pageHTML, _ := b.getPageContent(url.Values{"page": {"messages"}, "messageId": {strconv.FormatInt(msgID, 10)}, "tabid": {"21"}, "ajax": {"1"}}) + return b.extractor.ExtractFullCombatReport(pageHTML, msgID) +} + // CharacterClass returns the bot character class func (b *OGame) CharacterClass() CharacterClass { return b.characterClass diff --git a/ogame_test.go b/ogame_test.go index 09d9ff19..c4bd9f74 100644 --- a/ogame_test.go +++ b/ogame_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + //"log" + "github.com/PuerkitoBio/goquery" "github.com/alaingilbert/clockwork" "github.com/stretchr/testify/assert" @@ -921,7 +923,7 @@ func TestExtractCombatReportMessagesV71(t *testing.T) { func TestExtractCombatReportMessagesV71_lossContact(t *testing.T) { pageHTMLBytes, _ := ioutil.ReadFile("samples/v7.1/en/combat_reports_loss_contact.html") msgs, _ := NewExtractorV71().ExtractCombatReportMessagesSummary(pageHTMLBytes) - assert.Equal(t, 8, len(msgs)) + assert.Equal(t, 10, len(msgs)) } func TestExtractCombatReportMessages(t *testing.T) { @@ -943,6 +945,11 @@ func TestExtractCombatReportAttackingMessages(t *testing.T) { assert.Equal(t, int64(25200), msgs[1].DebrisField) assert.Equal(t, int64(0), msgs[2].DebrisField) assert.Equal(t, "08.09.2018 09:33:18", msgs[0].CreatedAt.Format("02.01.2006 15:04:05")) + + + assert.Equal(t, "Commodore Nomad", msgs[0].AttackerName) + assert.Equal(t, "Commodore Nomad", msgs[1].AttackerName) + assert.Equal(t, "Du ZiMei", msgs[2].AttackerName) } func TestExtractCombatReportMessagesSummary(t *testing.T) { @@ -2827,3 +2834,127 @@ func TestExtractBuffActivation(t *testing.T) { assert.Equal(t, "081876002bf5791011097597836d3f5c", token) assert.Equal(t, 31, len(items)) } + +func TestV71ExtractCombatReportSummaries(t *testing.T) { + pageHTMLBytes, _ := ioutil.ReadFile("samples/v7.1/en/combat_reports.html") + msgs, nbPages := NewExtractorV71().ExtractCombatReportMessagesSummary(pageHTMLBytes) + + assert.Equal(t, 10, len(msgs)) + assert.Equal(t, int64(4), nbPages) + + /* + type CombatReportSummary struct + ID int64 + APIKey string + Origin *Coordinate + Destination Coordinate + AttackerName string + DefenderName string + AttackerLostValue int64 + DefenderLostValue int64 + Loot int64 + Metal int64 + Crystal int64 + Deuterium int64 + DebrisField int64 + Repaired int64 + CreatedAt time.Time + } + */ + +// for k, v := range msgs { +// log.Print(k, v.ID) +// } + + assert.Equal(t, int64(3566422), msgs[0].ID) //int64 + assert.Equal(t, int64(3566421), msgs[1].ID) //int64 + assert.Equal(t, int64(3566420), msgs[2].ID) //int64 + assert.Equal(t, int64(3566416), msgs[3].ID) //int64 + assert.Equal(t, int64(3566413), msgs[4].ID) //int64 + assert.Equal(t, int64(3566403), msgs[5].ID) //int64 + assert.Equal(t, int64(3566399), msgs[6].ID) //int64 + assert.Equal(t, int64(3566394), msgs[7].ID) //int64 + assert.Equal(t, int64(3565518), msgs[8].ID) //int64 + assert.Equal(t, int64(3565516), msgs[9].ID) //int64 + + assert.Equal(t, &Coordinate{Galaxy:5, System:42, Position: 8, Type:3}, msgs[0].Origin) //*Coordinate + + assert.Equal(t, Coordinate{Galaxy:1, System:430, Position: 4, Type:1}, msgs[0].Destination) //Coordinate + assert.Equal(t, "Czar Celestial", msgs[0].AttackerName) //string + assert.Equal(t, "Notriv", msgs[0].DefenderName) //string + assert.Equal(t, int64(50), msgs[0].Loot) //int64 + assert.Equal(t, int64(50), msgs[1].Loot) //int64 + + assert.Equal(t, int64(1638541), msgs[0].Metal) //int64 + assert.Equal(t, int64(64575), msgs[0].Crystal) //int64 + assert.Equal(t, int64(728721), msgs[0].Deuterium) //int64 + + assert.Equal(t, int64(396581), msgs[5].Metal) //int64 + assert.Equal(t, int64(223442), msgs[5].Crystal) //int64 + assert.Equal(t, int64(424575), msgs[5].Deuterium) //int64 + + assert.Equal(t, int64(3566403), msgs[5].ID) //int64 + assert.Equal(t, int64(748800), msgs[5].DebrisField) //int64 + + assert.Equal(t, int64(3566394), msgs[7].ID) //int64 + assert.Equal(t, int64(70021600), msgs[7].DebrisField) //int64 + + assert.Equal(t, "cr-us-149-fe449460902860455db7ef57a522ae341f931a59", msgs[0].APIKey) //string + assert.Equal(t, "cr-us-149-9f9b7ba4b7fc6355e976446cecbd5a825858e34c", msgs[1].APIKey) //string + assert.Equal(t, "23.02.2020 12:02:39", msgs[0].CreatedAt.Format("02.01.2006 15:04:05")) //time.Time +} + +func TestV71ExtractCombatReportSummaries2(t *testing.T) { + pageHTMLBytes, _ := ioutil.ReadFile("samples/v7.1/en/combat_reports_loss_contact.html") + msgs,_ := NewExtractorV71().ExtractCombatReportMessagesSummary(pageHTMLBytes) + + /* + type CombatReportSummary struct + ID int64 + APIKey string + Origin *Coordinate + Destination Coordinate + AttackerName string + DefenderName string + AttackerLostValue int64 + DefenderLostValue int64 + Loot int64 + Metal int64 + Crystal int64 + Deuterium int64 + DebrisField int64 + Repaired int64 + CreatedAt time.Time + } + */ + +// for k, v := range msgs { +// log.Print(k, v.ID, v.Loot, v.Repaired) +// } + + /* +

+ Attacker: (threeflipskat3r): 3.896Mn +
+ Resources: 0, Loot: 50% +
+ Debris field (newly created): 2.217Mn +
+
+ +
+
+ Defender: (Notriv): 42.938Mn +
+ Actually repaired: 9.040 +
+
+ */ + + assert.Equal(t, int64(12502200), msgs[5].ID) //int64 + assert.Equal(t, int64(3896000), msgs[5].AttackerLostValue) //int64 + assert.Equal(t, int64(50), msgs[5].Loot) //int64 + assert.Equal(t, int64(2217600), msgs[5].DebrisField) //int64 + assert.Equal(t, int64(42938000), msgs[5].DefenderLostValue) //int64 + assert.Equal(t, int64(9040), msgs[5].Repaired) //int64 +}