Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9b0fcdf
adding houses endpoints
tobiasehlert Jan 5, 2022
9fc29b1
switching to switch for overview status column
tobiasehlert Jan 5, 2022
c382d84
adding support for house details view
tobiasehlert Jan 5, 2022
e3f96da
renaming and reordering
tobiasehlert Jan 5, 2022
541613c
TibiaDataConvertValuesWithK for pricing with k+
tobiasehlert Jan 5, 2022
aceffab
fetch houses and guildhalls concurrently (#30)
phenpessoa Jan 6, 2022
0110088
updating TibiadataHTMLDataCollectorV3 due to #31
tobiasehlert Jan 6, 2022
14cf8c0
updating due to #36
tobiasehlert Jan 7, 2022
4ed769c
passing gin.Context to houseFetcher
tobiasehlert Jan 7, 2022
6fafd08
properly handle errors on houseFetcher (#38)
phenpessoa Jan 8, 2022
40500c0
fixing TibiaDataConvertValuesWithK function
tobiasehlert Jan 11, 2022
7712923
renaming Houses to OverviewHouses
tobiasehlert Jan 11, 2022
811833a
switching from goroutine to for
tobiasehlert Jan 12, 2022
4bb076f
moving structs outside function for overview
tobiasehlert Jan 12, 2022
46fc536
moving structs outside function for house
tobiasehlert Jan 13, 2022
653bf50
adding ignore for mapping json
tobiasehlert Jan 13, 2022
bba4722
adding HouseMapping file
tobiasehlert Jan 13, 2022
c98ccc1
adding houseid resolver in houseshouse file
tobiasehlert Jan 13, 2022
c7109ab
moving init before start of webserver
tobiasehlert Jan 13, 2022
180836e
adding more logging to mapping init func
tobiasehlert Jan 13, 2022
5f8470f
adding omitempty to town and type
tobiasehlert Jan 13, 2022
3819e3b
switching from local file to dwnload from
tobiasehlert Jan 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ Those are the current existing endpoints.
- GET `/v3/highscores/world/:world`
- GET `/v3/highscores/world/:world/:category`
- GET `/v3/highscores/world/:world/:category/:vocation`
- GET `/v3/houses/world/:world/house/:houseid`
- GET `/v3/houses/world/:world/town/:town`
- GET `/v3/killstatistics/world/:world`
- GET `/v3/news/archive`
- GET `/v3/news/archive/:days`
Expand Down
76 changes: 76 additions & 0 deletions src/HousesMapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package main

import (
"encoding/json"
"log"
"net/http"
"time"

"github.com/go-resty/resty/v2"
)

var (
TibiadataHousesMapping HousesMapping
)

type AssetsHouse struct {
HouseID int `json:"house_id"`
Town string `json:"town"`
HouseType string `json:"type"`
}
type HousesMapping struct {
Houses []AssetsHouse `json:"houses"`
}

// TibiaDataHousesMappingInitiator func - used to load data from local JSON file
func TibiaDataHousesMappingInitiator() {

// Setting up resty client
client := resty.New()

// Set client timeout and retry
client.SetTimeout(5 * time.Second)
client.SetRetryCount(2)

// Set headers for all requests
client.SetHeaders(map[string]string{
"Content-Type": "application/json",
"User-Agent": TibiadataUserAgent,
})

// Enabling Content length value for all request
client.SetContentLength(true)

// Disable redirection of client (so we skip parsing maintenance page)
client.SetRedirectPolicy(resty.NoRedirectPolicy())

TibiadataAssetsURL := "https://raw.githubusercontent.com/TibiaData/tibiadata-api-assets/main/data/houses_mapping.json"
res, err := client.R().Get(TibiadataAssetsURL)

switch res.StatusCode() {
case http.StatusOK:
// adding response into the data field
data := HousesMapping{}
err = json.Unmarshal([]byte(res.Body()), &data)

if err != nil {
log.Println("[error] TibiaData API failed to parse content from houses_mapping.json")
} else {
// storing data so it's accessible from other places
TibiadataHousesMapping = data
}

default:
log.Printf("[error] TibiaData API failed to load houses mapping. %s", err)
}
}

// TibiaDataHousesMapResolver func - used to return both town and type
func TibiaDataHousesMapResolver(houseid int) (town string, housetype string) {
for _, value := range TibiadataHousesMapping.Houses {
if houseid == value.HouseID {
return value.Town, value.HouseType
}
}
return "", ""
}
5 changes: 5 additions & 0 deletions src/TibiaDataUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ func getEnvAsInt(name string, defaultVal int) int {
}
*/

// TibiaDataConvertValuesWithK func - convert price strings that contain k, kk or more to 3x0
func TibiaDataConvertValuesWithK(data string) int {
return TibiadataStringToIntegerV3(strings.ReplaceAll(data, "k", "") + strings.Repeat("000", strings.Count(data, "k")))
}

// TibiaDataVocationValidator func - return valid vocation string and vocation id
func TibiaDataVocationValidator(vocation string) (string, string) {
// defining return vars
Expand Down
193 changes: 193 additions & 0 deletions src/TibiaHousesHouseV3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package main

import (
"log"
"net/http"
"regexp"
"strings"

"github.com/PuerkitoBio/goquery"
"github.com/gin-gonic/gin"
)

// Child of Status
type HouseRental struct {
Owner string `json:"owner"`
OwnerSex string `json:"owner_sex"`
PaidUntil string `json:"paid_until"`
MovingDate string `json:"moving_date"`
TransferReceiver string `json:"transfer_receiver"`
TransferPrice int `json:"transfer_price"`
TransferAccept bool `json:"transfer_accept"`
}

// Child of Status
type HouseAuction struct {
CurrentBid int `json:"current_bid"`
CurrentBidder string `json:"current_bidder"`
AuctionOngoing bool `json:"auction_ongoing"`
AuctionEnd string `json:"auction_end"`
}

// Child of House
type HouseStatus struct {
IsAuctioned bool `json:"is_auctioned"`
IsRented bool `json:"is_rented"`
IsMoving bool `json:"is_moving"`
IsTransfering bool `json:"is_transfering"`
Auction HouseAuction `json:"auction"`
Rental HouseRental `json:"rental"`
Original string `json:"original"`
}

// Child of JSONData
type House struct {
Houseid int `json:"houseid"`
World string `json:"world"`
Town string `json:"town,omitempty"`
Name string `json:"name"`
Type string `json:"type,omitempty"`
Beds int `json:"beds"`
Size int `json:"size"`
Rent int `json:"rent"`
Img string `json:"img"`
Status HouseStatus `json:"status"`
}

//
// The base includes two levels: Houses and Information
type HouseResponse struct {
House House `json:"house"`
Information Information `json:"information"`
}

// TibiaHousesHouseV3 func
func TibiaHousesHouseV3(c *gin.Context) {

// getting params from URL
world := c.Param("world")
houseid := c.Param("houseid")

// Creating empty vars
var HouseData House

// Adding fix for First letter to be upper and rest lower
world = TibiadataStringWorldFormatToTitleV3(world)

// Getting data with TibiadataHTMLDataCollectorV3
TibiadataRequest.URL = "https://www.tibia.com/community/?subtopic=houses&page=view&world=" + TibiadataQueryEscapeStringV3(world) + "&houseid=" + TibiadataQueryEscapeStringV3(houseid)
BoxContentHTML, err := TibiadataHTMLDataCollectorV3(TibiadataRequest)

// return error (e.g. for maintenance mode)
if err != nil {
TibiaDataAPIHandleOtherResponse(c, http.StatusBadGateway, "TibiaHousesHouseV3", gin.H{"error": err.Error()})
return
}

// Loading HTML data into ReaderHTML for goquery with NewReader
ReaderHTML, err := goquery.NewDocumentFromReader(strings.NewReader(BoxContentHTML))
if err != nil {
log.Fatal(err)
}

// Running query over each div
HouseHTML, err := ReaderHTML.Find(".BoxContent table tr").First().Html()

if err != nil {
log.Fatal(err)
}

// Regex to get data for house
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>`)
subma1 := regex1.FindAllStringSubmatch(HouseHTML, -1)

if len(subma1) > 0 {
HouseData.Houseid = TibiadataStringToIntegerV3(houseid)
HouseData.World = subma1[0][8]

HouseData.Town, HouseData.Type = TibiaDataHousesMapResolver(HouseData.Houseid)

HouseData.Name = TibiaDataSanitizeEscapedString(subma1[0][2])
HouseData.Img = subma1[0][1]
HouseData.Beds = TibiadataStringToIntegerV3(subma1[0][4])
HouseData.Size = TibiadataStringToIntegerV3(subma1[0][5])
HouseData.Rent = TibiaDataConvertValuesWithK(subma1[0][6] + subma1[0][7])

HouseData.Status.Original = TibiaDataSanitizeEscapedString(RemoveHtmlTag(subma1[0][9]))

switch {
case strings.Contains(HouseData.Status.Original, "has been rented by"):
// rented

switch {
case strings.Contains(HouseData.Status.Original, " pass the "+HouseData.Type+" to "):
HouseData.Status.IsTransfering = true
// matching for this: and <wants to|will> pass the <HouseType> to <TransferReceiver> for <TransferPrice> gold
regex2 := regexp.MustCompile(`and (wants to|will) pass the (house|guildhall) to (.*) for ([0-9]+) gold`)
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
// storing values from regex
if subma2[0][1] == "will" {
HouseData.Status.Rental.TransferAccept = true
}
HouseData.Status.Rental.TransferReceiver = subma2[0][3]
HouseData.Status.Rental.TransferPrice = TibiadataStringToIntegerV3(subma2[0][4])
fallthrough

case strings.Contains(HouseData.Status.Original, " will move out on "):
HouseData.Status.IsMoving = true
// matching for this: <OwnerSex> will move out on <MovingDate> (
regex2 := regexp.MustCompile(`(He|She) will move out on (.*?) \(`)
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
// storing values from regex
HouseData.Status.Rental.MovingDate = TibiadataDatetimeV3(subma2[0][2])
fallthrough

default:
HouseData.Status.IsRented = true
// matching for this: The <HouseType> has been rented by <Owner>. <OwnerSex> has paid the rent until <PaidUntil>.
regex2 := regexp.MustCompile(`The (house|guildhall) has been rented by (.*). (He|She) has paid.*until (.*?)\.`)
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
// storing values from regex
HouseData.Status.Rental.Owner = subma2[0][2]
HouseData.Status.Rental.PaidUntil = TibiadataDatetimeV3(subma2[0][4])
switch subma2[0][3] {
case "She":
HouseData.Status.Rental.OwnerSex = "female"
case "He":
HouseData.Status.Rental.OwnerSex = "male"
}
}

case strings.Contains(HouseData.Status.Original, "is currently being auctioned"):
// auctioned
HouseData.Status.IsAuctioned = true

// check if bid is going on
if !strings.Contains(HouseData.Status.Original, "No bid has been submitted so far.") {
regex2 := regexp.MustCompile(`The (house|guildhall) is currently.*The auction (will end|has ended) at (.*)\. The.*is ([0-9]+) gold.*submitted by (.*)\.`)
subma2 := regex2.FindAllStringSubmatch(HouseData.Status.Original, -1)
// storing values from regex
HouseData.Status.Auction.AuctionEnd = TibiadataDatetimeV3(subma2[0][3])
HouseData.Status.Auction.CurrentBid = TibiadataStringToIntegerV3(subma2[0][4])
HouseData.Status.Auction.CurrentBidder = subma2[0][5]
if subma2[0][2] == "will end" {
HouseData.Status.Auction.AuctionOngoing = true
}
}
}

}

//
// Build the data-blob
jsonData := HouseResponse{
HouseData,
Information{
APIVersion: TibiadataAPIversion,
Timestamp: TibiadataDatetimeV3(""),
},
}

// return jsonData
TibiaDataAPIHandleSuccessResponse(c, "TibiaHousesHouseV3", jsonData)
}
Loading