Skip to content

Commit 5147947

Browse files
committed
SA server support
1 parent cf89a16 commit 5147947

File tree

12 files changed

+133
-20
lines changed

12 files changed

+133
-20
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# BDO-REST-API
2-
A collector for Black Desert Online player in-game data that provides an unofficial REST API.
2+
A collector for Black Desert Online player in-game data that provides an unofficial REST API. It currently supports European, North American and South American servers. (Korean server support will be added in the future).
33

44
## Projects using this API
55
- [BDO Leaderboards](https://bdo.hemlo.cc/leaderboards/) ([GitHub](https://github.com/octoman90/BDO-Leaderboards)): web-based leaderboards for Black Desert Online.
66

77
## How to start using it
88
There are two ways to use this API in your apps:
9-
1. https://bdo-rest-api.herokuapp.com/v1 is an instance that I host. Keep in mind that it may be slow to respond, and might just stop working one day. It's also rolling-release: if the API changes in the master branch on GitHub, this instance will reflect it immediately and your app may break. The API documentation can be viewed [here](https://gitlab.com/man90/black-desert-social-rest-api/-/tree/master/doc/api/openapi.json).
10-
2. Host it yourself. The server may cost some money, but the process is trivial. This approach will give you more stability and freedom. There are four easy steps to it:
9+
* https://bdo-rest-api.herokuapp.com/v1 is an instance that I host. Be aware that if the API changes in the master branch on GitHub, this instance will reflect it immediately and your app may break. The API documentation can be viewed [here](https://gitlab.com/man90/black-desert-social-rest-api/-/tree/master/doc/api/openapi.json).
10+
* Host it yourself. Renting a VPS may cost some money, but the process is trivial. This approach will give you more stability and freedom. There are four easy steps to it:
1111
1. Build the server from the source code following [this guide](doc/buildingFromSource.md) or download a prebuilt Linux binary from [here](https://gitlab.com/man90/black-desert-social-rest-api/-/pipelines).
1212
2. Set the environment variables if you want. The list is in a section below.
1313
3. Run the binary. Possible flags are described in a section below.

doc/api/openapi.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
"region": {
1818
"name": "region",
1919
"in": "query",
20-
"description": "Only Eropean and North American servers are supported. If you omit this parameter, it's assumed that you want to search on the European server.",
20+
"description": "Only Eropean, North American and South American servers are supported. If you omit this parameter, it's assumed that you want to search on the European server.",
2121
"schema": {
2222
"type": "string",
23-
"enum": ["EU", "NA"],
23+
"enum": ["EU", "NA", "SA"],
2424
"example": "EU"
2525
}
2626
}
@@ -52,6 +52,16 @@
5252
"schema": {
5353
"type": "string"
5454
}
55+
},
56+
{
57+
"name": "region",
58+
"in": "query",
59+
"description": "Only Eropean, North American and South American servers are supported. You may omit this parameter for the European and North American servers.",
60+
"schema": {
61+
"type": "string",
62+
"enum": ["EU", "NA", "SA"],
63+
"example": "EU"
64+
}
5565
}
5666
],
5767
"responses": {
@@ -262,6 +272,10 @@
262272
"type": "string",
263273
"example": "Ninja"
264274
},
275+
"main": {
276+
"type": "boolean",
277+
"example": true
278+
},
265279
"level": {
266280
"type": "number",
267281
"example": 56

handlers/GetAdventurer.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@ func GetAdventurer(w http.ResponseWriter, r *http.Request) {
1313
setHeaders(w)
1414

1515
profileTargetParams, profileTargetProvided := r.URL.Query()["profileTarget"]
16+
regionParams, regionProvided := r.URL.Query()["region"]
1617

1718
// Return status 400 if a required parameter is invalid
1819
if !profileTargetProvided || !validators.ValidateProfileTarget(&profileTargetParams[0]) {
1920
w.WriteHeader(http.StatusBadRequest)
2021
return
2122
}
2223

24+
// Set defaults for optional parameters
25+
region := defaultRegion
26+
27+
if regionProvided && validators.ValidateRegion(&regionParams[0]) {
28+
region = regionParams[0]
29+
}
30+
2331
// Run the scraper
24-
if data, status := scrapers.ScrapeAdventurer(url.QueryEscape(profileTargetParams[0])); status == http.StatusOK {
32+
if data, status := scrapers.ScrapeAdventurer(region, url.QueryEscape(profileTargetParams[0])); status == http.StatusOK {
2533
json.NewEncoder(w).Encode(data)
2634
} else {
2735
w.WriteHeader(status)

scrapers/ScrapeAdventurer.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import (
1111
"github.com/gocolly/colly/v2"
1212

1313
"bdo-rest-api/models"
14+
"bdo-rest-api/translators"
1415
)
1516

16-
func ScrapeAdventurer(profileTarget string) (profile models.Profile, status int) {
17+
func ScrapeAdventurer(region string, profileTarget string) (profile models.Profile, status int) {
1718
c := collyFactory()
1819

1920
profile.ProfileTarget = profileTarget
21+
profile.Region = region
2022
status = http.StatusNotFound
2123

2224
c.OnHTML(closetimeSelector, func(e *colly.HTMLElement) {
@@ -63,6 +65,10 @@ func ScrapeAdventurer(profileTarget string) (profile models.Profile, status int)
6365
Class: e.ChildText(".character_info .character_symbol em:last-child"),
6466
}
6567

68+
if region == "SA" {
69+
translators.TranslateClassName(&character.Class)
70+
}
71+
6672
e.ForEach(`.selected_label`, func(ind int, el *colly.HTMLElement) {
6773
character.Main = true
6874
})
@@ -88,9 +94,13 @@ func ScrapeAdventurer(profileTarget string) (profile models.Profile, status int)
8894
e.ForEach(".character_spec:not(.lock) .spec_level", func(ind int, el *colly.HTMLElement) {
8995
// "Beginner1" → "Beginner 1"
9096
i := regexp.MustCompile(`[0-9]`).FindStringIndex(el.Text)[0]
91-
level := el.Text[:i] + " " + el.Text[i:]
97+
wordLevel := el.Text[:i]
98+
99+
if region == "SA" {
100+
translators.TranslateSpecLevel(&wordLevel)
101+
}
92102

93-
specLevels[ind] = level
103+
specLevels[ind] = wordLevel + " " + el.Text[i:]
94104
})
95105

96106
if len(specLevels[0]) > 0 {
@@ -117,7 +127,7 @@ func ScrapeAdventurer(profileTarget string) (profile models.Profile, status int)
117127
profile.Privacy = profile.Privacy | models.PrivateSpecs
118128
})
119129

120-
c.Visit(fmt.Sprintf("https://www.naeu.playblackdesert.com/en-US/Adventure/Profile?profileTarget=%v", profileTarget))
130+
c.Visit(fmt.Sprintf("%v/Adventure/Profile?profileTarget=%v", getSiteRoot(region), profileTarget))
121131

122132
return
123133
}

scrapers/ScrapeAdventurerSearch.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import (
88
"github.com/gocolly/colly/v2"
99

1010
"bdo-rest-api/models"
11+
"bdo-rest-api/translators"
1112
)
1213

13-
func ScrapeAdventurerSearch(region, query string, searchType uint8, page uint16) (profiles []models.Profile, status int) {
14+
func ScrapeAdventurerSearch(region string, query string, searchType uint8, page uint16) (profiles []models.Profile, status int) {
1415
c := collyFactory()
1516
closetime := false
1617

@@ -20,20 +21,29 @@ func ScrapeAdventurerSearch(region, query string, searchType uint8, page uint16)
2021

2122
c.OnHTML(`.box_list_area li:not(.no_result)`, func(e *colly.HTMLElement) {
2223
profile := models.Profile{
23-
Region: e.ChildText(".region_info"),
24+
Region: region,
2425
FamilyName: e.ChildText(".title a"),
2526
ProfileTarget: extractProfileTarget(e.ChildAttr(".title a", "href")),
2627
Characters: make([]models.Character, 1),
2728
}
2829

29-
if e.ChildAttr(".state a", "href") != "javscript:void(0)" {
30+
if region != "SA" {
31+
profile.Region = e.ChildText(".region_info")
32+
}
33+
34+
if len(e.ChildAttr(".state a", "href")) > 0 {
3035
profile.Guild = &models.GuildProfile{
3136
Name: e.ChildText(".state a"),
3237
}
3338
}
3439

35-
profile.Characters[0].Name = e.ChildText(".text")
3640
profile.Characters[0].Class = e.ChildText(".name")
41+
profile.Characters[0].Main = true
42+
profile.Characters[0].Name = e.ChildText(".text")
43+
44+
if region == "SA" {
45+
translators.TranslateClassName(&profile.Characters[0].Class)
46+
}
3747

3848
if level, err := strconv.Atoi(e.ChildText(".level")[3:]); err == nil {
3949
profile.Characters[0].Level = uint8(level)
@@ -42,7 +52,7 @@ func ScrapeAdventurerSearch(region, query string, searchType uint8, page uint16)
4252
profiles = append(profiles, profile)
4353
})
4454

45-
c.Visit(fmt.Sprintf("https://www.naeu.playblackdesert.com/en-US/Adventure?region=%v&searchType=%v&searchKeyword=%v&Page=%v", region, searchType, query, page))
55+
c.Visit(fmt.Sprintf("%v/Adventure?region=%v&searchType=%v&searchKeyword=%v&Page=%v", getSiteRoot(region), region, searchType, query, page))
4656

4757
if closetime {
4858
status = http.StatusServiceUnavailable

scrapers/ScrapeGuild.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func ScrapeGuild(region, name string) (guildProfile models.GuildProfile, status
4848

4949
c.OnHTML(`.line_list:not(.mob_none) li:last-child .desc`, func(e *colly.HTMLElement) {
5050
text := dry(e.Text)
51-
if text != "None" {
51+
if text != "None" && text != "N/A" {
5252
guildProfile.Occupying = text
5353
}
5454
})
@@ -62,7 +62,7 @@ func ScrapeGuild(region, name string) (guildProfile models.GuildProfile, status
6262
guildProfile.Members = append(guildProfile.Members, member)
6363
})
6464

65-
c.Visit(fmt.Sprintf("https://www.naeu.playblackdesert.com/en-US/Adventure/Guild/GuildProfile?guildName=%v&region=%v", name, region))
65+
c.Visit(fmt.Sprintf("%v/Adventure/Guild/GuildProfile?guildName=%v&region=%v", getSiteRoot(region), name, region))
6666

6767
return
6868
}

scrapers/ScrapeGuildSearch.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/gocolly/colly/v2"
1010

1111
"bdo-rest-api/models"
12+
"bdo-rest-api/translators"
1213
)
1314

1415
func ScrapeGuildSearch(region, query string, page uint16) (guildProfiles []models.GuildProfile, status int) {
@@ -24,7 +25,7 @@ func ScrapeGuildSearch(region, query string, page uint16) (guildProfiles []model
2425

2526
guildProfile := models.GuildProfile{
2627
Name: e.ChildText(".guild_title a"),
27-
Region: e.ChildText(".region_info"),
28+
Region: region,
2829
Kind: e.ChildText(".tag_label.guild_label"),
2930
Master: &models.Profile{
3031
FamilyName: e.ChildText(".guild_info a"),
@@ -33,6 +34,14 @@ func ScrapeGuildSearch(region, query string, page uint16) (guildProfiles []model
3334
CreatedOn: &createdOn,
3435
}
3536

37+
if region != "SA" {
38+
guildProfile.Region = e.ChildText(".region_info")
39+
}
40+
41+
if region == "SA" {
42+
translators.TranslateGuildKind(&guildProfile.Kind)
43+
}
44+
3645
if membersStr := e.ChildText(".member"); true {
3746
population, _ := strconv.Atoi(membersStr)
3847
guildProfile.Population = uint8(population)
@@ -41,7 +50,7 @@ func ScrapeGuildSearch(region, query string, page uint16) (guildProfiles []model
4150
guildProfiles = append(guildProfiles, guildProfile)
4251
})
4352

44-
c.Visit(fmt.Sprintf("https://www.naeu.playblackdesert.com/en-US/Adventure/Guild?region=%v&page=%v&searchText=%v", region, page, query))
53+
c.Visit(fmt.Sprintf("%v/Adventure/Guild?region=%v&page=%v&searchText=%v", getSiteRoot(region), region, page, query))
4554

4655
if closetime {
4756
status = http.StatusServiceUnavailable

scrapers/scraper.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ func extractProfileTarget(link string) string {
3030
return url.QueryEscape(m["profileTarget"][0])
3131
}
3232

33+
func getSiteRoot(region string) string {
34+
if "SA" == region {
35+
return "https://www.sa.playblackdesert.com/pt-BR"
36+
}
37+
38+
return "https://www.naeu.playblackdesert.com/en-US"
39+
}
40+
3341
func collyFactory() (c *colly.Collector) {
3442
c = colly.NewCollector()
3543
c.SetRequestTimeout(time.Minute / 2)

translators/TranslateClassName.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package translators
2+
3+
var classNameTranslationMap = map[string]string{
4+
"Cavaleira Negra": "Dark Knight",
5+
"Corsária": "Corsair",
6+
"Domadora": "Tamer",
7+
"Feiticeira": "Sorceress",
8+
"Guardiã": "Guardian",
9+
"Guerreiro": "Warrior",
10+
"Lutador": "Striker",
11+
"Maga": "Witch",
12+
"Mago": "Wizard",
13+
"Mística": "Mystic",
14+
"Musah": "Musa",
15+
"Sábio": "Sage",
16+
"Sagitário": "Archer",
17+
"Valquíria": "Valkyrie",
18+
}
19+
20+
func TranslateClassName(className *string) {
21+
if val, ok := classNameTranslationMap[*className]; ok {
22+
*className = val
23+
}
24+
}

translators/TranslateGuildKind.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package translators
2+
3+
var guildKindTranslationMap = map[string]string{
4+
"Clã": "Clan",
5+
"Guilda": "Guild",
6+
}
7+
8+
func TranslateGuildKind(kind *string) {
9+
if val, ok := guildKindTranslationMap[*kind]; ok {
10+
*kind = val
11+
}
12+
}

0 commit comments

Comments
 (0)