From 8040aea1f2e3b8ca368e3a5e9f9a11b3b470d32a Mon Sep 17 00:00:00 2001 From: Tobias Lindberg Date: Mon, 3 Jan 2022 00:06:49 +0100 Subject: [PATCH 1/2] updating TibiadataQueryEscapeStringV3 function --- src/TibiaCharactersCharacterV3.go | 3 --- src/webserver.go | 8 +++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/TibiaCharactersCharacterV3.go b/src/TibiaCharactersCharacterV3.go index 5859d216..11b8152f 100644 --- a/src/TibiaCharactersCharacterV3.go +++ b/src/TibiaCharactersCharacterV3.go @@ -150,9 +150,6 @@ func TibiaCharactersCharacterV3(c *gin.Context) { var CharacterSection string - // Sanatizing some on the character.. - character = strings.ReplaceAll(character, "+", " ") - // Getting data with TibiadataHTMLDataCollectorV3 BoxContentHTML := TibiadataHTMLDataCollectorV3("https://www.tibia.com/community/?subtopic=characters&name=" + TibiadataQueryEscapeStringV3(character)) diff --git a/src/webserver.go b/src/webserver.go index ec5b27b7..69eea1ba 100644 --- a/src/webserver.go +++ b/src/webserver.go @@ -320,9 +320,15 @@ func TibiadataUnescapeStringV3(data string) string { return html.UnescapeString(data) } -// TibiadataQueryEscapeStringV3 func +// TibiadataQueryEscapeStringV3 func - encode string to be correct formatted func TibiadataQueryEscapeStringV3(data string) string { + // switching "+" to " " + data = strings.ReplaceAll(data, "+", " ") + + // encoding string to latin-1 data, _ = TibiaDataConvertEncodingtoISO88591(data) + + // returning with QueryEscape function return url.QueryEscape(data) } From ad5f05ee33475015ce75a508b34d2c22cc4de920 Mon Sep 17 00:00:00 2001 From: Tobias Lindberg Date: Mon, 3 Jan 2022 00:08:15 +0100 Subject: [PATCH 2/2] adding two new guild endpoints - guild overview (TibiaGuildsOverviewV3) - guild details (TibiaGuildsGuildV3) --- README.md | 2 + src/TibiaGuildsGuildV3.go | 275 +++++++++++++++++++++++++++++++++++ src/TibiaGuildsOverviewV3.go | 109 ++++++++++++++ src/webserver.go | 6 + 4 files changed, 392 insertions(+) create mode 100644 src/TibiaGuildsGuildV3.go create mode 100644 src/TibiaGuildsOverviewV3.go diff --git a/README.md b/README.md index 1f41ac68..be4854ce 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ Those are the current existing endpoints. - GET `/v3/creatures` - GET `/v3/creatures/creature/:creature` - GET `/v3/fansites` +- GET `/v3/guilds/guild/:guild` +- GET `/v3/guilds/world/:world` - GET `/v3/highscores/world/:world` - GET `/v3/highscores/world/:world/:category` - GET `/v3/highscores/world/:world/:category/:vocation` diff --git a/src/TibiaGuildsGuildV3.go b/src/TibiaGuildsGuildV3.go new file mode 100644 index 00000000..599232f8 --- /dev/null +++ b/src/TibiaGuildsGuildV3.go @@ -0,0 +1,275 @@ +package main + +import ( + "log" + "regexp" + "strings" + + "github.com/PuerkitoBio/goquery" + "github.com/gin-gonic/gin" +) + +// TibiaGuildsGuildV3 func +func TibiaGuildsGuildV3(c *gin.Context) { + + // getting params from URL + guild := c.Param("guild") + + // Child of Guild + type Guildhall struct { + Name string `json:"name"` + World string `json:"world"` // Maybe duplicate info? Guild can only be on one world.. + /* + Town string `json:"town"` // We can collect that from cached info? + Status string `json:"status"` // rented (but maybe also auctioned) + Owner string `json:"owner"` // We can collect that from cached info? + HouseID int `json:"houseid"` // We can collect that from cached info? + */ + PaidUntil string `json:"paid_until"` // Paid until date + } + + // Child of Guild + type Members struct { + Name string `json:"name"` + Title string `json:"title"` + Rank string `json:"rank"` + Vocation string `json:"vocation"` + Level int `json:"level"` + Joined string `json:"joined"` + Status string `json:"status"` + } + // Child of Guild + type Invited struct { + Name string `json:"name"` + Date string `json:"date"` + } + + // Child of Guilds + type Guild struct { + Name string `json:"name"` + World string `json:"world"` + LogoURL string `json:"logo_url"` + Description string `json:"description"` + Guildhalls []Guildhall `json:"guildhalls"` + Active bool `json:"active"` + Founded string `json:"founded"` + Applications bool `json:"open_applications"` + Homepage string `json:"homepage"` + InWar bool `json:"in_war"` + DisbandedDate string `json:"disband_date"` + DisbandedCondition string `json:"disband_condition"` + PlayersOnline int `json:"players_online"` + PlayersOffline int `json:"players_offline"` + MembersTotal int `json:"members_total"` + MembersInvited int `json:"members_invited"` + Members []Members `json:"members"` + Invited []Invited `json:"invites"` + } + + // Child of JSONData + type Guilds struct { + Guild Guild `json:"guild"` + } + + // + // The base includes two levels: Guild and Information + type JSONData struct { + Guilds Guilds `json:"guilds"` + Information Information `json:"information"` + } + + // Creating empty vars + var MembersData []Members + var InvitedData []Invited + var GuildGuildhallData []Guildhall + var MembersRank, MembersTitle, MembersStatus, GuildDescription, GuildDisbandedDate, GuildDisbandedCondition, GuildHomepage, GuildWorld, GuildLogoURL, GuildFounded string + var GuildActive, GuildApplications, GuildInWar bool + var MembersCountOnline, MembersCountOffline, MembersCountInvited int + + // Getting data with TibiadataHTMLDataCollectorV3 + BoxContentHTML := TibiadataHTMLDataCollectorV3("https://www.tibia.com/community/?subtopic=guilds&page=view&GuildName=" + TibiadataQueryEscapeStringV3(guild)) + + // Loading HTML data into ReaderHTML for goquery with NewReader + ReaderHTML, err := goquery.NewDocumentFromReader(strings.NewReader(BoxContentHTML)) + if err != nil { + log.Fatal(err) + } + + // Getting data from div.InnerTableContainer and then first p + InnerTableContainerTMPA, err := ReaderHTML.Find(".BoxContent table").Html() + if err != nil { + log.Fatal(err) + } + regex1b := regexp.MustCompile(`.*img src="(.*)" width=.*`) + subma1b := regex1b.FindAllStringSubmatch(InnerTableContainerTMPA, -1) + GuildLogoURL = subma1b[0][1] + + // Getting data from div.InnerTableContainer and then first p + InnerTableContainerTMPB, err := ReaderHTML.Find("#GuildInformationContainer").Html() + if err != nil { + log.Fatal(err) + } + + var GuildDescriptionFinished bool + for _, line := range strings.Split(strings.TrimSuffix(InnerTableContainerTMPB, "\n"), "\n") { + + // Guild information + if !GuildDescriptionFinished { + // First line is the description.. + GuildDescription += strings.ReplaceAll(line+"\n", "

\n", "") + + // Abort loop and continue wiht next section + if strings.Contains(line, "

") { + GuildDescription = TibiadataUnescapeStringV3(GuildDescription) + GuildDescriptionFinished = true + } + + } else if GuildDescriptionFinished { + // The rest of the Guild information + + if strings.Contains(line, "The guild was founded on") { + // Regex to get GuildWorld and GuildFounded + regex1b := regexp.MustCompile(`The guild was founded on (.*) on (.*).
`) + subma1b := regex1b.FindAllStringSubmatch(line, -1) + GuildWorld = subma1b[0][1] + GuildFounded = TibiadataDateV3(subma1b[0][2]) + } + + // If to get GuildActive + if strings.Contains(line, "It is currently active") { + GuildActive = true + } + + // If open for applications + if strings.Contains(line, "Guild is opened for applications.") { + GuildApplications = true + } else if strings.Contains(line, "Guild is closed for applications during war.") { + GuildInWar = true + } + + if strings.Contains(line, "The official homepage is") { + regex1c := regexp.MustCompile(``) + subma1c := regex1c.FindAllStringSubmatch(line, -1) + GuildHomepage = subma1c[0][1] + } + + // If guildhall + if strings.Contains(line, "Their home on "+GuildWorld) { + // Regex to get GuildWorld and GuildFounded + regex1b := regexp.MustCompile(`Their home on ` + GuildWorld + ` is (.*). The rent is paid until (.*).
`) + subma1b := regex1b.FindAllStringSubmatch(line, -1) + + GuildGuildhallData = append(GuildGuildhallData, Guildhall{ + Name: TibiadataUnescapeStringV3(subma1b[0][1]), + World: GuildWorld, + PaidUntil: TibiadataDateV3(subma1b[0][2]), + }) + } + + // If disbanded + if strings.Contains(line, "It will be disbanded on ") { + regex1c := regexp.MustCompile(`It will be disbanded on (.*.[0-9]+.[0-9]+) (.*)\.<\/b>.*`) + subma1c := regex1c.FindAllStringSubmatch(line, -1) + if len(subma1c) > 0 { + GuildDisbandedDate = subma1c[0][1] + GuildDisbandedCondition = subma1c[0][2] + } + } + } + } + + // Running query over each div + ReaderHTML.Find(".TableContentContainer .TableContent tbody tr").Each(func(index int, s *goquery.Selection) { + + // Storing HTML into GuildsDivHTML + GuildsDivHTML, err := s.Html() + if err != nil { + log.Fatal(err) + } + + // Removing linebreaks from HTML + GuildsDivHTML = TibiadataHTMLRemoveLinebreaksV3(GuildsDivHTML) + + // Regex to get data for record values + regex1 := regexp.MustCompile(`(.*)<\/td>(.*)<\/a>(.*)<\/td>(.*)<\/td>([0-9]+)<\/td>(.*)<\/td>(.*)<\/span><\/td>`) + subma1 := regex1.FindAllStringSubmatch(GuildsDivHTML, -1) + + if len(subma1) > 0 { + // Rank name + if len(subma1[0][1]) > 2 { + MembersRank = subma1[0][1] + } + + // Title + MembersTitle = strings.ReplaceAll(strings.ReplaceAll(subma1[0][3], " (", ""), ")", "") + + // Status + if strings.Contains(subma1[0][7], "online") { + MembersStatus = "online" + MembersCountOnline++ + } else { + MembersStatus = "offline" + MembersCountOffline++ + } + + MembersData = append(MembersData, Members{ + Name: TibiadataUnescapeStringV3(subma1[0][2]), + Title: MembersTitle, + Rank: MembersRank, + Vocation: subma1[0][4], + Level: TibiadataStringToIntegerV3(subma1[0][5]), + Joined: TibiadataDateV3(subma1[0][6]), + Status: MembersStatus, + }) + + } else { + + // Regex to get data for record values + regex2 := regexp.MustCompile(`(.*)<\/a><\/td>(.*)<\/td>`) + subma2 := regex2.FindAllStringSubmatch(GuildsDivHTML, -1) + + if len(subma2) > 0 { + MembersCountInvited++ + InvitedData = append(InvitedData, Invited{ + Name: subma2[0][1], + Date: subma2[0][2], + }) + } + } + }) + + // + // Build the data-blob + jsonData := JSONData{ + Guilds{ + Guild{ + Name: guild, + World: GuildWorld, + LogoURL: GuildLogoURL, + Description: GuildDescription, + Guildhalls: GuildGuildhallData, + Active: GuildActive, + Founded: GuildFounded, + Applications: GuildApplications, + Homepage: GuildHomepage, + InWar: GuildInWar, + DisbandedDate: GuildDisbandedDate, + DisbandedCondition: GuildDisbandedCondition, + + PlayersOnline: MembersCountOnline, + PlayersOffline: MembersCountOffline, + MembersTotal: (MembersCountOnline + MembersCountOffline), + MembersInvited: MembersCountInvited, + Members: MembersData, + Invited: InvitedData, + }, + }, + Information{ + APIVersion: TibiadataAPIversion, + Timestamp: TibiadataDatetimeV3(""), + }, + } + + // return jsonData + TibiaDataAPIHandleSuccessResponse(c, "TibiaGuildsGuildV3", jsonData) +} diff --git a/src/TibiaGuildsOverviewV3.go b/src/TibiaGuildsOverviewV3.go new file mode 100644 index 00000000..96fd07a9 --- /dev/null +++ b/src/TibiaGuildsOverviewV3.go @@ -0,0 +1,109 @@ +package main + +import ( + "log" + "regexp" + "strings" + + "github.com/PuerkitoBio/goquery" + "github.com/gin-gonic/gin" +) + +// TibiaGuildsOverviewV3 func +func TibiaGuildsOverviewV3(c *gin.Context) { + + // getting params from URL + world := c.Param("world") + + // Child of Guilds + type Guild struct { + Name string `json:"name"` + LogoURL string `json:"logo_url"` + Description string `json:"description"` + } + + // Child of JSONData + type Guilds struct { + World string `json:"world"` + Active []Guild `json:"active"` + Formation []Guild `json:"formation"` + } + + // + // The base includes two levels: Guilds and Information + type JSONData struct { + Guilds Guilds `json:"guilds"` + Information Information `json:"information"` + } + + // Creating empty vars + var ActiveGuilds, FormationGuilds []Guild + var GuildCategory string + + // Adding fix for First letter to be upper and rest lower + world = TibiadataStringWorldFormatToTitleV3(world) + + // Getting data with TibiadataHTMLDataCollectorV3 + BoxContentHTML := TibiadataHTMLDataCollectorV3("https://www.tibia.com/community/?subtopic=guilds&world=" + TibiadataQueryEscapeStringV3(world)) + + // 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 + ReaderHTML.Find(".Table3 .TableContent tbody tr").Each(func(index int, s *goquery.Selection) { + + // Storing HTML into GuildsDivHTML + GuildsDivHTML, err := s.Html() + if err != nil { + log.Fatal(err) + } + + // Removing linebreaks from HTML + //GuildsDivHTML = TibiadataHTMLRemoveLinebreaksV3(GuildsDivHTML) + + if GuildCategory == "" && strings.Contains(GuildsDivHTML, "Logo") { + GuildCategory = "active" + } else if GuildCategory == "active" && strings.Contains(GuildsDivHTML, "Logo") { + GuildCategory = "formation" + } + + // Regex to get data for record values + regex1 := regexp.MustCompile(`<\/td>(.*)<\/b>()?(.*)<\/td>.*`) + subma1 := regex1.FindAllStringSubmatch(GuildsDivHTML, -1) + + if len(subma1) > 0 { + OneGuild := Guild{ + Name: TibiadataUnescapeStringV3(subma1[0][2]), + LogoURL: subma1[0][1], + Description: TibiadataUnescapeStringV3(strings.TrimSpace(subma1[0][4])), + } + + // Adding OneWorld to correct category + if GuildCategory == "active" { + ActiveGuilds = append(ActiveGuilds, OneGuild) + } else if GuildCategory == "formation" { + FormationGuilds = append(FormationGuilds, OneGuild) + } + } + }) + + // + // Build the data-blob + jsonData := JSONData{ + Guilds{ + World: world, + Active: ActiveGuilds, + Formation: FormationGuilds, + }, + Information{ + APIVersion: TibiadataAPIversion, + Timestamp: TibiadataDatetimeV3(""), + }, + } + + // return jsonData + TibiaDataAPIHandleSuccessResponse(c, "TibiaGuildsOverviewV3", jsonData) +} diff --git a/src/webserver.go b/src/webserver.go index 69eea1ba..867de128 100644 --- a/src/webserver.go +++ b/src/webserver.go @@ -113,6 +113,12 @@ func main() { // Tibia fansites v3.GET("/fansites", TibiaFansitesV3) + // Tibia guilds + v3.GET("/guilds/guild/:guild", TibiaGuildsGuildV3) + //v3.GET("/guilds/guild/:guild/events",TibiaGuildsGuildEventsV3) + //v3.GET("/guilds/guild/:guild/wars",TibiaGuildsGuildWarsV3) + v3.GET("/guilds/world/:world", TibiaGuildsOverviewV3) + // Tibia highscores v3.GET("/highscores/world/:world", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, v3.BasePath()+"/highscores/"+c.Param("world")+"/experience/"+TibiadataDefaultVoc)