Skip to content

Commit 2f820d3

Browse files
Feature: Tibia Houses endpoints (#26)
Adds endpoint for both house-list and house-view Co-authored-by: Pedro Pessoa <pedro_santos_40@hotmail.com>
1 parent c2bba4e commit 2f820d3

File tree

7 files changed

+434
-0
lines changed

7 files changed

+434
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ Those are the current existing endpoints.
106106
- GET `/v3/highscores/world/:world`
107107
- GET `/v3/highscores/world/:world/:category`
108108
- GET `/v3/highscores/world/:world/:category/:vocation`
109+
- GET `/v3/houses/world/:world/house/:houseid`
110+
- GET `/v3/houses/world/:world/town/:town`
109111
- GET `/v3/killstatistics/world/:world`
110112
- GET `/v3/news/archive`
111113
- GET `/v3/news/archive/:days`

src/HousesMapping.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"log"
6+
"net/http"
7+
"time"
8+
9+
"github.com/go-resty/resty/v2"
10+
)
11+
12+
var (
13+
TibiadataHousesMapping HousesMapping
14+
)
15+
16+
type AssetsHouse struct {
17+
HouseID int `json:"house_id"`
18+
Town string `json:"town"`
19+
HouseType string `json:"type"`
20+
}
21+
type HousesMapping struct {
22+
Houses []AssetsHouse `json:"houses"`
23+
}
24+
25+
// TibiaDataHousesMappingInitiator func - used to load data from local JSON file
26+
func TibiaDataHousesMappingInitiator() {
27+
28+
// Setting up resty client
29+
client := resty.New()
30+
31+
// Set client timeout and retry
32+
client.SetTimeout(5 * time.Second)
33+
client.SetRetryCount(2)
34+
35+
// Set headers for all requests
36+
client.SetHeaders(map[string]string{
37+
"Content-Type": "application/json",
38+
"User-Agent": TibiadataUserAgent,
39+
})
40+
41+
// Enabling Content length value for all request
42+
client.SetContentLength(true)
43+
44+
// Disable redirection of client (so we skip parsing maintenance page)
45+
client.SetRedirectPolicy(resty.NoRedirectPolicy())
46+
47+
TibiadataAssetsURL := "https://raw.githubusercontent.com/TibiaData/tibiadata-api-assets/main/data/houses_mapping.json"
48+
res, err := client.R().Get(TibiadataAssetsURL)
49+
50+
switch res.StatusCode() {
51+
case http.StatusOK:
52+
// adding response into the data field
53+
data := HousesMapping{}
54+
err = json.Unmarshal([]byte(res.Body()), &data)
55+
56+
if err != nil {
57+
log.Println("[error] TibiaData API failed to parse content from houses_mapping.json")
58+
} else {
59+
// storing data so it's accessible from other places
60+
TibiadataHousesMapping = data
61+
}
62+
63+
default:
64+
log.Printf("[error] TibiaData API failed to load houses mapping. %s", err)
65+
}
66+
}
67+
68+
// TibiaDataHousesMapResolver func - used to return both town and type
69+
func TibiaDataHousesMapResolver(houseid int) (town string, housetype string) {
70+
for _, value := range TibiadataHousesMapping.Houses {
71+
if houseid == value.HouseID {
72+
return value.Town, value.HouseType
73+
}
74+
}
75+
return "", ""
76+
}

src/TibiaDataUtils.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ func getEnvAsInt(name string, defaultVal int) int {
222222
}
223223
*/
224224

225+
// TibiaDataConvertValuesWithK func - convert price strings that contain k, kk or more to 3x0
226+
func TibiaDataConvertValuesWithK(data string) int {
227+
return TibiadataStringToIntegerV3(strings.ReplaceAll(data, "k", "") + strings.Repeat("000", strings.Count(data, "k")))
228+
}
229+
225230
// TibiaDataVocationValidator func - return valid vocation string and vocation id
226231
func TibiaDataVocationValidator(vocation string) (string, string) {
227232
// defining return vars

src/TibiaHousesHouseV3.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/PuerkitoBio/goquery"
10+
"github.com/gin-gonic/gin"
11+
)
12+
13+
// Child of Status
14+
type HouseRental struct {
15+
Owner string `json:"owner"`
16+
OwnerSex string `json:"owner_sex"`
17+
PaidUntil string `json:"paid_until"`
18+
MovingDate string `json:"moving_date"`
19+
TransferReceiver string `json:"transfer_receiver"`
20+
TransferPrice int `json:"transfer_price"`
21+
TransferAccept bool `json:"transfer_accept"`
22+
}
23+
24+
// Child of Status
25+
type HouseAuction struct {
26+
CurrentBid int `json:"current_bid"`
27+
CurrentBidder string `json:"current_bidder"`
28+
AuctionOngoing bool `json:"auction_ongoing"`
29+
AuctionEnd string `json:"auction_end"`
30+
}
31+
32+
// Child of House
33+
type HouseStatus struct {
34+
IsAuctioned bool `json:"is_auctioned"`
35+
IsRented bool `json:"is_rented"`
36+
IsMoving bool `json:"is_moving"`
37+
IsTransfering bool `json:"is_transfering"`
38+
Auction HouseAuction `json:"auction"`
39+
Rental HouseRental `json:"rental"`
40+
Original string `json:"original"`
41+
}
42+
43+
// Child of JSONData
44+
type House struct {
45+
Houseid int `json:"houseid"`
46+
World string `json:"world"`
47+
Town string `json:"town,omitempty"`
48+
Name string `json:"name"`
49+
Type string `json:"type,omitempty"`
50+
Beds int `json:"beds"`
51+
Size int `json:"size"`
52+
Rent int `json:"rent"`
53+
Img string `json:"img"`
54+
Status HouseStatus `json:"status"`
55+
}
56+
57+
//
58+
// The base includes two levels: Houses and Information
59+
type HouseResponse struct {
60+
House House `json:"house"`
61+
Information Information `json:"information"`
62+
}
63+
64+
// TibiaHousesHouseV3 func
65+
func TibiaHousesHouseV3(c *gin.Context) {
66+
67+
// getting params from URL
68+
world := c.Param("world")
69+
houseid := c.Param("houseid")
70+
71+
// Creating empty vars
72+
var HouseData House
73+
74+
// Adding fix for First letter to be upper and rest lower
75+
world = TibiadataStringWorldFormatToTitleV3(world)
76+
77+
// Getting data with TibiadataHTMLDataCollectorV3
78+
TibiadataRequest.URL = "https://www.tibia.com/community/?subtopic=houses&page=view&world=" + TibiadataQueryEscapeStringV3(world) + "&houseid=" + TibiadataQueryEscapeStringV3(houseid)
79+
BoxContentHTML, err := TibiadataHTMLDataCollectorV3(TibiadataRequest)
80+
81+
// return error (e.g. for maintenance mode)
82+
if err != nil {
83+
TibiaDataAPIHandleOtherResponse(c, http.StatusBadGateway, "TibiaHousesHouseV3", gin.H{"error": err.Error()})
84+
return
85+
}
86+
87+
// Loading HTML data into ReaderHTML for goquery with NewReader
88+
ReaderHTML, err := goquery.NewDocumentFromReader(strings.NewReader(BoxContentHTML))
89+
if err != nil {
90+
log.Fatal(err)
91+
}
92+
93+
// Running query over each div
94+
HouseHTML, err := ReaderHTML.Find(".BoxContent table tr").First().Html()
95+
96+
if err != nil {
97+
log.Fatal(err)
98+
}
99+
100+
// Regex to get data for house
101+
regex1 := regexp.MustCompile(`<td.*src="(.*)" width.*<b>(.*)<\/b>.*This (house|guildhall) can.*to ([0-9]+) beds..*<b>([0-9]+) square.*<b>([0-9]+)([k]+).gold<\/b>.*on <b>([A-Za-z]+)<\/b>.(.*)<\/td>`)
102+
subma1 := regex1.FindAllStringSubmatch(HouseHTML, -1)
103+
104+
if len(subma1) > 0 {
105+
HouseData.Houseid = TibiadataStringToIntegerV3(houseid)
106+
HouseData.World = subma1[0][8]
107+
108+
HouseData.Town, HouseData.Type = TibiaDataHousesMapResolver(HouseData.Houseid)
109+
110+
HouseData.Name = TibiaDataSanitizeEscapedString(subma1[0][2])
111+
HouseData.Img = subma1[0][1]
112+
HouseData.Beds = TibiadataStringToIntegerV3(subma1[0][4])
113+
HouseData.Size = TibiadataStringToIntegerV3(subma1[0][5])
114+
HouseData.Rent = TibiaDataConvertValuesWithK(subma1[0][6] + subma1[0][7])
115+
116+
HouseData.Status.Original = TibiaDataSanitizeEscapedString(RemoveHtmlTag(subma1[0][9]))
117+
118+
switch {
119+
case strings.Contains(HouseData.Status.Original, "has been rented by"):
120+
// rented
121+
122+
switch {
123+
case strings.Contains(HouseData.Status.Original, " pass the "+HouseData.Type+" to "):
124+
HouseData.Status.IsTransfering = true
125+
// matching for this: and <wants to|will> pass the <HouseType> to <TransferReceiver> for <TransferPrice> gold
126+
regex2 := regexp.MustCompile(`and (wants to|will) pass the (house|guildhall) to (.*) for ([0-9]+) gold`)
127+
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
128+
// storing values from regex
129+
if subma2[0][1] == "will" {
130+
HouseData.Status.Rental.TransferAccept = true
131+
}
132+
HouseData.Status.Rental.TransferReceiver = subma2[0][3]
133+
HouseData.Status.Rental.TransferPrice = TibiadataStringToIntegerV3(subma2[0][4])
134+
fallthrough
135+
136+
case strings.Contains(HouseData.Status.Original, " will move out on "):
137+
HouseData.Status.IsMoving = true
138+
// matching for this: <OwnerSex> will move out on <MovingDate> (
139+
regex2 := regexp.MustCompile(`(He|She) will move out on (.*?) \(`)
140+
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
141+
// storing values from regex
142+
HouseData.Status.Rental.MovingDate = TibiadataDatetimeV3(subma2[0][2])
143+
fallthrough
144+
145+
default:
146+
HouseData.Status.IsRented = true
147+
// matching for this: The <HouseType> has been rented by <Owner>. <OwnerSex> has paid the rent until <PaidUntil>.
148+
regex2 := regexp.MustCompile(`The (house|guildhall) has been rented by (.*). (He|She) has paid.*until (.*?)\.`)
149+
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
150+
// storing values from regex
151+
HouseData.Status.Rental.Owner = subma2[0][2]
152+
HouseData.Status.Rental.PaidUntil = TibiadataDatetimeV3(subma2[0][4])
153+
switch subma2[0][3] {
154+
case "She":
155+
HouseData.Status.Rental.OwnerSex = "female"
156+
case "He":
157+
HouseData.Status.Rental.OwnerSex = "male"
158+
}
159+
}
160+
161+
case strings.Contains(HouseData.Status.Original, "is currently being auctioned"):
162+
// auctioned
163+
HouseData.Status.IsAuctioned = true
164+
165+
// check if bid is going on
166+
if !strings.Contains(HouseData.Status.Original, "No bid has been submitted so far.") {
167+
regex2 := regexp.MustCompile(`The (house|guildhall) is currently.*The auction (will end|has ended) at (.*)\. The.*is ([0-9]+) gold.*submitted by (.*)\.`)
168+
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
169+
// storing values from regex
170+
HouseData.Status.Auction.AuctionEnd = TibiadataDatetimeV3(subma2[0][3])
171+
HouseData.Status.Auction.CurrentBid = TibiadataStringToIntegerV3(subma2[0][4])
172+
HouseData.Status.Auction.CurrentBidder = subma2[0][5]
173+
if subma2[0][2] == "will end" {
174+
HouseData.Status.Auction.AuctionOngoing = true
175+
}
176+
}
177+
}
178+
179+
}
180+
181+
//
182+
// Build the data-blob
183+
jsonData := HouseResponse{
184+
HouseData,
185+
Information{
186+
APIVersion: TibiadataAPIversion,
187+
Timestamp: TibiadataDatetimeV3(""),
188+
},
189+
}
190+
191+
// return jsonData
192+
TibiaDataAPIHandleSuccessResponse(c, "TibiaHousesHouseV3", jsonData)
193+
}

0 commit comments

Comments
 (0)