main
Raw Download raw file
  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}