main
1package main
2
3import (
4 "bytes"
5 "errors"
6 "fmt"
7 "net/url"
8 "strconv"
9 "strings"
10 "time"
11
12 "github.com/PuerkitoBio/goquery"
13 "golang.org/x/net/html"
14)
15
16var (
17 ErrUpgradeInProgress = errors.New("upgrade in progress")
18 ErrUpgradeNotInProgress = errors.New("upgrade not in progress")
19 ErrNoProductionFound = errors.New("no production found")
20)
21
22func loggedOut(b []byte) bool {
23 if bytes.Contains(b, []byte("Session expired or invalid!")) {
24 return true
25 }
26 return false
27}
28
29func findSessionID(n *html.Node) string {
30 if n.Type == html.ElementNode && n.Data == "a" {
31 for _, attr := range n.Attr {
32 if attr.Key == "href" && strings.Contains(attr.Val, "s=") {
33 parts := strings.Split(attr.Val, "s=")
34 if len(parts) > 1 {
35 sessionID := strings.Split(parts[1], "&")[0] // Split by '&' to handle additional parameters
36 return sessionID
37 }
38 }
39 }
40 }
41 for c := n.FirstChild; c != nil; c = c.NextSibling {
42 if sessionID := findSessionID(c); sessionID != "" {
43 return sessionID
44 }
45 }
46 return ""
47}
48
49func findResources(n *html.Node) *Resoruces {
50 r := &Resoruces{}
51 var extractValue func(*html.Node)
52 extractValue = func(n *html.Node) {
53 if n.Type == html.ElementNode && n.Data == "td" {
54 for c := n.FirstChild; c != nil; c = c.NextSibling {
55 if c.Type == html.TextNode &&
56 c.PrevSibling != nil &&
57 c.PrevSibling.Type == html.ElementNode &&
58 c.PrevSibling.Data == "img" {
59
60 switch c.PrevSibling.Attr[0].Val {
61 case "data/r1.gif":
62 trim := strings.TrimSpace(c.Data)
63 r.Gold, _ = strconv.Atoi(trim)
64 case "data/r2.gif":
65 trim := strings.TrimSpace(c.Data)
66 r.Stone, _ = strconv.Atoi(trim)
67 case "data/r3.gif":
68 trim := strings.TrimSpace(c.Data)
69 r.Lumber, _ = strconv.Atoi(trim)
70 }
71 }
72 }
73 }
74 for c := n.FirstChild; c != nil; c = c.NextSibling {
75 extractValue(c)
76 }
77 }
78 extractValue(n)
79 return r
80}
81
82func findProduction(b []byte) (int, error) {
83 doc, err := goquery.NewDocumentFromReader(bytes.NewReader(b))
84 if err != nil {
85 return 0, fmt.Errorf("failed to parse mine page: %w", err)
86 }
87 var found = false
88 var unitsPerHour int = 0
89
90 doc.Find("tr td b").Each(func(index int, item *goquery.Selection) {
91 if !strings.Contains(item.Text(), "Amount delivered per hour") {
92 return
93 }
94 unitsString := strings.TrimSpace(strings.Split(item.Text(), ":")[1])
95 unitsSplit := strings.Split(unitsString, " ")[0]
96 units, err := strconv.Atoi(unitsSplit)
97 if err == nil {
98 unitsPerHour = units
99 }
100 found = true
101 return
102 })
103
104 if !found {
105 return 0, ErrNoProductionFound
106 }
107 return unitsPerHour, nil
108}
109
110func findBuildings(b []byte) (map[string]*Building, error) {
111
112 doc, err := goquery.NewDocumentFromReader(bytes.NewReader(b))
113 if err != nil {
114 return nil, fmt.Errorf("failed to parse overview page: %w", err)
115 }
116
117 buildings := make(map[string]*Building)
118 doc.Find("td font b").Each(func(index int, item *goquery.Selection) {
119 htmlContent, _ := item.Html()
120 parts := strings.Split(htmlContent, "<br/>")
121 for _, part := range parts {
122 partDoc, err := goquery.NewDocumentFromReader(
123 strings.NewReader(part))
124 if err != nil {
125 return
126 }
127 buildingLine := partDoc.Text()
128 buildingLine = strings.TrimSpace(buildingLine)
129 if buildingLine == "" {
130 return
131 }
132 bs := strings.Split(buildingLine, "(")
133 if len(bs) != 2 {
134 return
135 }
136 building := nameToBuilding(strings.TrimSpace(bs[0]))
137 level := strings.Split(bs[1], " ")[1]
138 level = strings.TrimSuffix(level, ")")
139 building.Level, _ = strconv.Atoi(level)
140 buildings[building.Id] = building
141 }
142 })
143
144 return buildings, nil
145}
146
147func currentBuildingUpgrade(b []byte) (*Building, error) {
148 doc, err := goquery.NewDocumentFromReader(bytes.NewReader(b))
149 if err != nil {
150 return nil, fmt.Errorf("failed to parse fortress page: %w", err)
151 }
152
153 // Look for the timer first
154 timer := doc.Find("b#timer1")
155 timerTitle := doc.Find("b#timer1").AttrOr("title", "")
156 if timerTitle == "" {
157 return nil, ErrUpgradeNotInProgress
158 }
159 timerTitleSplit := strings.Split(timerTitle, "_")
160 if len(timerTitleSplit) != 3 {
161 fmt.Errorf("failed to parse timer: %s", timer)
162 }
163 remaining, _ := strconv.Atoi(timerTitleSplit[1])
164 remainingDuration := time.Second * time.Duration(remaining)
165 buildText := timer.Prev().Text()
166 buildText = strings.TrimPrefix(buildText, "Build: ")
167 buildText = strings.TrimSuffix(buildText, "Duration: ")
168 building := nameToBuilding(buildText)
169 building.Upgrade = &Resoruces{Time: remainingDuration}
170
171 return building, nil
172}
173
174func findBuildingUpgrades(b []byte) ([]Building, error) {
175
176 doc, err := goquery.NewDocumentFromReader(bytes.NewReader(b))
177 if err != nil {
178 return nil, fmt.Errorf("failed to parse fortress page: %w", err)
179 }
180
181 timer := doc.Find("b#timer1").AttrOr("title", "")
182 if timer != "" {
183 return nil, ErrUpgradeInProgress
184 }
185
186 // Iterate through each table row
187 var buildings = []Building{}
188 table := doc.Find("table")
189 row := table.Find("tr")
190 row.Each(func(i int, s *goquery.Selection) {
191 var building Building = Building{Upgrade: &Resoruces{}}
192 s.Find("td").Each(func(j int, td *goquery.Selection) {
193 if td != nil {
194 _, exists := td.Attr("bgcolor")
195 if !exists {
196 return
197 }
198 a := td.Find("a").Text()
199 if a == "" || !strings.Contains(a, "(") {
200 return
201 }
202 name := strings.Split(a, "(")[0]
203 building.Name = strings.TrimSpace(name)
204 levelText := strings.Split(a, "(")[1]
205 levelText = strings.TrimSuffix(levelText, ")")
206 level, _ := strconv.Atoi(strings.Fields(levelText)[1])
207 building.Level = level
208
209 resourcesText := td.Text()
210 resourcesText = resourcesText[strings.Index(resourcesText, "Gold:"):] // Get substring starting with "Gold: "
211 resources := strings.Fields(resourcesText)
212 building.Upgrade.Gold, _ = strconv.Atoi(resources[1])
213 building.Upgrade.Stone, _ = strconv.Atoi(resources[3])
214 building.Upgrade.Lumber, _ = strconv.Atoi(resources[5])
215 building.Upgrade.Time, _ = parseDuration(resources[7])
216 // fmt.Println(building.Name, resources)
217
218 // Extract id from URL
219 foundURL, _ := td.Find("a").Attr("href")
220 u, _ := url.Parse(foundURL)
221 if u.Query().Has("b") {
222 building.Id = u.Query().Get("b")
223 }
224 }
225 })
226 if building.Name != "" {
227 buildings = append(buildings, building)
228 }
229 })
230 return buildings, nil
231}
232
233func parseDuration(input string) (time.Duration, error) {
234 var hours, minutes, seconds int
235 _, err := fmt.Sscanf(input, "%d:%d:%d", &hours, &minutes, &seconds)
236 if err != nil {
237 return 0, err
238 }
239 duration := time.Duration(hours)*time.Hour +
240 time.Duration(minutes)*time.Minute +
241 time.Duration(seconds)*time.Second
242 return duration, nil
243}