Skip to content

Commit 00c6776

Browse files
committed
Feature: add Tibia Forums Section details (v4/forum/section/{name})
New endpoint is added: `v3/forum/section/{name}` that returns all the data available on sites: - https://www.tibia.com/forum/?subtopic=worldboards - https://www.tibia.com/forum/?subtopic=tradeboards - https://www.tibia.com/forum/?subtopic=communityboards - https://www.tibia.com/forum/?subtopic=supportboards Similar to TibiaData#215 but for v4
1 parent c05ab61 commit 00c6776

File tree

7 files changed

+1316
-280
lines changed

7 files changed

+1316
-280
lines changed

src/TibiaDataUtils.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,19 @@ func TibiaDataDatetime(date string) string {
3737
loc, _ := time.LoadLocation("Europe/Berlin")
3838

3939
// format used in datetime on html: Jan 02 2007, 19:20:30 CET
40-
formatting := "Jan 02 2006, 15:04:05 MST"
40+
var formatting string
41+
42+
// parsing and setting format of return
43+
switch dateLength := len(date); {
44+
case dateLength > 19:
45+
// format used in datetime on html: Jan 02 2007, 19:20:30 CET
46+
formatting = "Jan 02 2006, 15:04:05 MST"
47+
case dateLength == 19:
48+
// format used in datetime on html: 03.06.2023 01:19:00
49+
formatting = "02.01.2006 15:04:05"
50+
default:
51+
log.Printf("Weird format detected: %s", date)
52+
}
4153

4254
// parsing html in time with location set in loc
4355
returnDate, err = time.ParseInLocation(formatting, date, loc)
@@ -299,3 +311,19 @@ func TibiaDataGetNewsType(data string) string {
299311
return "unknown"
300312
}
301313
}
314+
315+
// TibiaDataForumNameValidator func - return valid forum string
316+
func TibiaDataForumNameValidator(name string) string {
317+
switch strings.ToLower(name) {
318+
case "world boards", "world", "worldboards":
319+
return "worldboards"
320+
case "trade boards", "trade", "tradeboards":
321+
return "tradeboards"
322+
case "community boards", "community", "communityboards":
323+
return "communityboards"
324+
case "support boards", "support", "supportboards":
325+
return "supportboards"
326+
default:
327+
return "worldboards"
328+
}
329+
}

src/TibiaDataUtils_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ func TestTibiaUTCDateFormat(t *testing.T) {
1818
assert.Equal(t, "2021-12-24T09:52:16Z", TibiaDataDatetime("Dec 24 2021, 09:52:16 UTC"))
1919
}
2020

21+
func TestTibiaForumDateFormat(t *testing.T) {
22+
assert.Equal(t, "2023-06-20T13:55:36Z", TibiaDataDatetime("20.06.2023 15:55:36"))
23+
}
24+
2125
func TestEnvFunctions(t *testing.T) {
2226
assert := assert.New(t)
2327

@@ -72,6 +76,28 @@ func TestTibiaDataGetNewsType(t *testing.T) {
7276
assert.Equal("unknown", TibiaDataGetNewsType("TibiaData"))
7377
}
7478

79+
func TestTibiaDataForumNameValidator(t *testing.T) {
80+
assert := assert.New(t)
81+
82+
assert.Equal("worldboards", TibiaDataForumNameValidator("world boards"))
83+
assert.Equal("worldboards", TibiaDataForumNameValidator("world"))
84+
assert.Equal("worldboards", TibiaDataForumNameValidator("worldboards"))
85+
86+
assert.Equal("tradeboards", TibiaDataForumNameValidator("trade boards"))
87+
assert.Equal("tradeboards", TibiaDataForumNameValidator("trade"))
88+
assert.Equal("tradeboards", TibiaDataForumNameValidator("tradeboards"))
89+
90+
assert.Equal("communityboards", TibiaDataForumNameValidator("community boards"))
91+
assert.Equal("communityboards", TibiaDataForumNameValidator("community"))
92+
assert.Equal("communityboards", TibiaDataForumNameValidator("communityboards"))
93+
94+
assert.Equal("supportboards", TibiaDataForumNameValidator("support boards"))
95+
assert.Equal("supportboards", TibiaDataForumNameValidator("support"))
96+
assert.Equal("supportboards", TibiaDataForumNameValidator("supportboards"))
97+
98+
assert.Equal("worldboards", TibiaDataForumNameValidator("def"))
99+
}
100+
75101
func TestHTMLLineBreakRemover(t *testing.T) {
76102
const str = "a\nb\nc\nd\ne\nf\ng\nh\n"
77103

src/TibiaForumSection.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/PuerkitoBio/goquery"
10+
)
11+
12+
type SectionBoardLastPost struct {
13+
ID int `json:"id"`
14+
PostedAt string `json:"posted_at"`
15+
CharacterName string `json:"character_name"`
16+
}
17+
18+
type SectionBoard struct {
19+
ID int `json:"id"`
20+
Name string `json:"name"`
21+
Description string `json:"description"`
22+
Posts int `json:"posts"`
23+
Threads int `json:"threads"`
24+
LastPost SectionBoardLastPost `json:"last_post"`
25+
}
26+
27+
type ForumSectionResponse struct {
28+
Boards []SectionBoard `json:"boards"`
29+
Information Information `json:"information"`
30+
}
31+
32+
var (
33+
boardInformationRegex = regexp.MustCompile(`.*boardid=(.*)">(.*)<\/a><br\/><font class="ff_info">(.*)<\/font><\/td><td class="TextRight">(.*)<\/td><td class="TextRight">(.*)<\/td><td><span class="LastPostInfo">`)
34+
lastPostIdRegex = regexp.MustCompile(`.*postid=(.*)#post`)
35+
lastPostPostedAtRegex = regexp.MustCompile(`.*height="9"\/><\/a>(.*)<\/span><span><font class="ff_info">`)
36+
lastPostCharacterNameRegex = regexp.MustCompile(`.*subtopic=characters&amp;name=.*\">(.*)<\/a><\/span>`)
37+
)
38+
39+
// TibiaForumSectionImpl func
40+
func TibiaForumSectionImpl(BoxContentHTML string) (*ForumSectionResponse, error) {
41+
// Loading HTML data into ReaderHTML for goquery with NewReader
42+
ReaderHTML, err := goquery.NewDocumentFromReader(strings.NewReader(BoxContentHTML))
43+
if err != nil {
44+
return nil, fmt.Errorf("[error] TibiaForumSectionImpl failed at goquery.NewDocumentFromReader, err: %s", err)
45+
}
46+
47+
var (
48+
BoardsData []SectionBoard
49+
LastPostId int
50+
LastPostPostedAt, LastPostCharacterName string
51+
52+
insideError error
53+
)
54+
55+
// Running query over each div
56+
ReaderHTML.Find(".TableContentContainer .TableContent tbody tr:not(.LabelH)").EachWithBreak(func(index int, s *goquery.Selection) bool {
57+
// Storing HTML into CreatureDivHTML
58+
BoardsDivHTML, err := s.Html()
59+
if err != nil {
60+
insideError = fmt.Errorf("[error] TibiaForumSectionImpl failed at BoardsDivHTML, err := s.Html(), err: %s", err)
61+
return false
62+
}
63+
64+
subma1 := boardInformationRegex.FindAllStringSubmatch(BoardsDivHTML, -1)
65+
if len(subma1) == 0 {
66+
return false
67+
}
68+
69+
subma2 := lastPostIdRegex.FindAllStringSubmatch(BoardsDivHTML, -1)
70+
if len(subma2) > 0 {
71+
LastPostId = TibiaDataStringToInteger(subma2[0][1])
72+
}
73+
74+
subma3 := lastPostPostedAtRegex.FindAllStringSubmatch(BoardsDivHTML, -1)
75+
if len(subma3) > 0 {
76+
LastPostPostedAt = TibiaDataDatetime(strings.Trim(TibiaDataSanitizeStrings(subma3[0][1]), " "))
77+
}
78+
79+
subma4 := lastPostCharacterNameRegex.FindAllStringSubmatch(BoardsDivHTML, -1)
80+
if len(subma4) > 0 {
81+
LastPostCharacterName = TibiaDataSanitizeStrings(subma4[0][1])
82+
}
83+
84+
BoardsData = append(BoardsData, SectionBoard{
85+
ID: TibiaDataStringToInteger(subma1[0][1]),
86+
Name: subma1[0][2],
87+
Description: subma1[0][3],
88+
Posts: TibiaDataStringToInteger(subma1[0][4]),
89+
Threads: TibiaDataStringToInteger(subma1[0][5]),
90+
LastPost: SectionBoardLastPost{
91+
ID: LastPostId,
92+
PostedAt: LastPostPostedAt,
93+
CharacterName: LastPostCharacterName,
94+
},
95+
})
96+
97+
return true
98+
})
99+
100+
if insideError != nil {
101+
return nil, insideError
102+
}
103+
104+
//
105+
// Build the data-blob
106+
return &ForumSectionResponse{
107+
Boards: BoardsData,
108+
Information: Information{
109+
APIDetails: TibiaDataAPIDetails,
110+
Timestamp: TibiaDataDatetime(""),
111+
Status: Status{
112+
HTTPCode: http.StatusOK,
113+
},
114+
},
115+
}, nil
116+
}

src/TibiaForumSection_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package main
2+
3+
import (
4+
"github.com/TibiaData/tibiadata-api-go/src/static"
5+
"github.com/stretchr/testify/assert"
6+
"io"
7+
"testing"
8+
)
9+
10+
func TestForumsSectionWorldboard(t *testing.T) {
11+
file, err := static.TestFiles.Open("testdata/forums/section/worldboard.html")
12+
if err != nil {
13+
t.Fatalf("file opening error: %s", err)
14+
}
15+
defer file.Close()
16+
17+
data, err := io.ReadAll(file)
18+
if err != nil {
19+
t.Fatalf("File reading error: %s", err)
20+
}
21+
22+
boardsJson, err := TibiaForumSectionImpl(string(data))
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
27+
assert := assert.New(t)
28+
29+
assert.Equal(90, len(boardsJson.Boards))
30+
31+
adra := boardsJson.Boards[0]
32+
assert.Equal(146482, adra.ID)
33+
assert.Equal("Adra", adra.Name)
34+
assert.Equal("This board is for general discussions related to the game world Adra.", adra.Description)
35+
assert.Equal(388, adra.Threads)
36+
assert.Equal(1158, adra.Posts)
37+
assert.Equal(39395612, adra.LastPost.ID)
38+
assert.Equal("Rcdohl", adra.LastPost.CharacterName)
39+
assert.Equal("2023-06-02T23:19:00Z", adra.LastPost.PostedAt)
40+
41+
alumbra := boardsJson.Boards[1]
42+
assert.Equal(147016, alumbra.ID)
43+
assert.Equal("Alumbra", alumbra.Name)
44+
assert.Equal("This board is for general discussions related to the game world Alumbra.", alumbra.Description)
45+
assert.Equal(563, alumbra.Threads)
46+
assert.Equal(1011, alumbra.Posts)
47+
assert.Equal(39395777, alumbra.LastPost.ID)
48+
assert.Equal("Mad Mustazza", alumbra.LastPost.CharacterName)
49+
assert.Equal("2023-06-04T15:51:13Z", alumbra.LastPost.PostedAt)
50+
}

src/static/testdata/forums/section/worldboard.html

Lines changed: 780 additions & 0 deletions
Large diffs are not rendered by default.

src/webserver.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ func runWebServer() {
199199
// Tibia worlds
200200
v4.GET("/world/:name", tibiaWorldsWorld)
201201
v4.GET("/worlds", tibiaWorldsOverview)
202+
203+
// Tibia forums
204+
v4.GET("/forum/section/:name", tibiaForumSection)
202205
}
203206

204207
// Container version details endpoint
@@ -1061,6 +1064,36 @@ func tibiaWorldsWorld(c *gin.Context) {
10611064
"TibiaWorldsWorld")
10621065
}
10631066

1067+
// Forum godoc
1068+
// @Summary Show one section
1069+
// @Description Show all information about one section
1070+
// @Tags forums
1071+
// @Accept json
1072+
// @Produce json
1073+
// @Param name path string true "The name of section" extensions(x-example=worldboards)
1074+
// @Success 200 {object} ForumSectionResponse
1075+
// @Failure 400 {object} Information
1076+
// @Failure 404 {object} Information
1077+
// @Failure 503 {object} Information
1078+
// @Router /v4/forum/section/{name} [get]
1079+
func tibiaForumSection(c *gin.Context) {
1080+
// getting params from URL
1081+
name := c.Param("name")
1082+
1083+
tibiadataRequest := TibiaDataRequestStruct{
1084+
Method: resty.MethodGet,
1085+
URL: "https://www.tibia.com/forum/?subtopic=" + TibiaDataForumNameValidator(name),
1086+
}
1087+
1088+
tibiaDataRequestHandler(
1089+
c,
1090+
tibiadataRequest,
1091+
func(BoxContentHTML string) (interface{}, error) {
1092+
return TibiaForumSectionImpl(BoxContentHTML)
1093+
},
1094+
"TibiaForumSectionImpl")
1095+
}
1096+
10641097
func TibiaDataErrorHandler(c *gin.Context, err error, httpCode int) {
10651098
if err == nil {
10661099
panic(errors.New("TibiaDataErrorHandler called with nil err"))

0 commit comments

Comments
 (0)