-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
304 lines (280 loc) · 13.2 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
const axios = require('axios');
const cheerio = require('cheerio');
const moment = require('moment');
const fs = require('fs');
const BASEURL = "https://genshin.honeyhunterworld.com"
const HHW_LANG = process.env.HHW_LANG
const HEADERS = {
params: { lang: HHW_LANG ? HHW_LANG : 'EN' },
timeout: 60000,
headers: {
"accept": "text/html",
"user-agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36"
},
// proxy: {
// protocol: 'http',
// host: '127.0.0.1',
// port: 7890,
// }
}
fetchData()
function fetchData() {
// 请求 Genshin Honey Hunter World
axios.get(new URL('/d_1001/', BASEURL), HEADERS).then((betaResp) => {
const latestLiveUrl = getLatestLive(betaResp.data)
axios.get(latestLiveUrl, HEADERS).then((liveResp) => {
extractData(liveResp.data, "Latest Live")
}).catch((err) => {
console.error("解析 Last Live 深渊数据出错")
console.error(err)
process.exit(1)
}).then(() => {
bestResult()
}).catch((err) => {
console.error("处理最优深渊数据出错")
console.error(err)
process.exit(1)
})
}).catch((err) => {
console.error("解析 Last Beta 深渊数据出错")
console.error(err)
process.exit(1)
})
}
function bestResult() {
// 判断 Last Beta 深渊数据的所有时间节点是否全部正确
// Last Beta 深渊数据存在异常的时间节点则使用 Last Live 深渊数据
const betaContent = fs.readFileSync("assets/abyss.beta.json", 'utf-8')
const liveContent = fs.readFileSync("assets/abyss.live.json", 'utf-8')
const betaData = JSON.parse(betaContent)
const LiveData = JSON.parse(liveContent)
const betaDateKeys = Object.keys(betaData.Schedule).reverse()
const timeFormat = 'YYYY-MM-DD HH:mm:ss'
let startTime = moment('2020-07-16 04:00:00', timeFormat);
const isValid = betaDateKeys.every((key, i) => {
if (i === 0) {
return key === "2020-07-01 00:00:00"
}
const thisTime = startTime.format(timeFormat)
if (startTime.date() === 1) {
// 当前 key 为某月 1 日,将 startTime 向后推迟 15 天到当月 16 日
startTime = startTime.add(15, 'days')
} else {
// 当前 key 为某月 16 日,将 startTime 向后推迟直到下月 1 日
startTime = startTime.endOf('month').add(1, 'days').set('hour', 4).set('minute', 0).set('second', 0)
}
return key === thisTime
})
result = isValid ? betaData : LiveData
fs.writeFileSync('assets/abyss.json', JSON.stringify(result));
fs.writeFileSync('assets/abyss.beautify.json', JSON.stringify(result, null, 2));
}
function getLatestLive(htmlStr) {
extractData(htmlStr, "Latest Beta")
const $ = cheerio.load(htmlStr)
const options = $('div.version_select > select.version_selector > option')
const latestLiveParams = options.filter(function () {
return $(this).text() === "Latest Live"
}).attr("value")
return new URL('/d_1001/' + latestLiveParams, BASEURL)
}
function extractData(htmlStr, pageType) {
console.log(`===============\n ${pageType} \n===============`)
const data = { "Floor": {}, "Schedule": {} }
const $ = cheerio.load(htmlStr)
const floors = $('#abyss_floors > div > div > section')
console.assert(floors.length === 13);
// 获取 Variant 表格元素
const tables = floors.slice(0, 12).find('div.scroll_wrap > table > tbody')
// 每个 Variant 包含一个层通用数据表格、三个间数据表格
console.assert(tables.length % 4 === 0)
// 每 4 个表格分割至一组
const variants = []
for (var i = 0; i < tables.length; i += 4) {
variants.push(tables.slice(i, i + 4))
}
// 遍历解析 Variant
variants.map(function (tableGroup) {
// 解析 Floor ID
findFloorSection = tableGroup.closest('section.tab-panel-2')
console.assert(findFloorSection.length === 1)
const fId = /Floor \#(\d*)/gm.exec(findFloorSection[0].attribs.id)[1]
// 解析 Variant ID
findVariantSection = tableGroup.closest('section.tab-panel-3')
console.assert(findVariantSection.length === 1)
const vId = /Variant \#(\d*)/gm.exec(findVariantSection[0].attribs.id)[1]
console.log(fId, vId)
const vData = {}
const commonTable = tableGroup.eq(0)
const chamberTables = tableGroup.slice(1, 4)
// 解析层通用数据表格
const commonRows = commonTable.children('tr')
console.assert(commonRows.length === 5)
commonRows.map(function (rowIdx) {
const tagTds = $(this).children('td')
const key = tagTds.eq(-2).text().replace(/(\s|\(|\))/g, "")
const _value = tagTds.eq(-1)
if (rowIdx === 0) {
// 第一行共三列,其中第一列为该层图像
const vImgElem = tagTds.children('img')[0]
vData["Icon"] = BASEURL + vImgElem.attribs.src
}
if (key === "Unlock") {
// 渊星总数,提取为 number
value = /⭐x(\d*)/gm.exec(_value.text())[1] * 1
} else if (key === "Disorders") {
// 地脉异常,替换 <br> 标签
value = _value.html().split('<br><br>')
} else if (key === "Reward") {
// 星之秘宝,解析嵌套表格
value = []
const rewardRows = _value.children('table').children('tbody').children('tr')
console.assert(rewardRows.length === 3)
rewardRows.map(function () {
const starText = $(this).children('td').eq(0).text()
const rewardLoaded = $(this).children('td').eq(-1)
// const needStarCount = /⭐x(\d*)/gm.exec(starText)[1]
const _reward = []
rewardLoaded.children('a').map(function () {
const iconDivLoaded = $(this).children('div')
const iconImgElem = iconDivLoaded.children('img')[0]
_reward.push({
'Icon': BASEURL + iconImgElem.attribs.src,
'Id': /i_(\d*)/gm.exec(iconDivLoaded[0].parent.attribs.href)[1] * 1,
'Rarity': /rar_bg_(\d)/gm.exec(iconDivLoaded[0].attribs.class)[1] * 1,
'Name': iconImgElem.attribs.alt,
'Count': $(this).text() * 1,
})
})
value.push(_reward)
})
} else {
// 其余数据先尝试转为 number 再写入
value = (_value.text() * 1) ? (_value.text() * 1) : _value.text()
}
// 写入层通用数据的一对键值
vData[key] = value
})
// 解析间数据表格
vData['Chambers'] = []
chamberTables.map(function () {
const cData = {}
$(this).children('tr').map(function () {
const tagTds = $(this).children('td')
const _key = tagTds.eq(0).text().replace(/(\s|\(|\)|\#)/g, "")
const _value = tagTds.eq(-1)
if (_key === 'Conditions') {
// 挑战目标,解析嵌套表格
key = _key
value = []
const conditionRows = _value.children('table').children('tbody').children('tr')
console.assert(conditionRows.length === 3)
conditionRows.map(function () {
value.push($(this).children('td').eq(-1).text())
})
} else if (_key.startsWith('PossibleBuff')) {
// 深秘祝福,解析嵌套表格
key = "PossibleBuff"
// const innerKey = /PossibleBuff(\d)/gm.exec(_key)[1]
const innerValue = []
const buffRows = _value.children('table').children('tbody').children('tr')
buffRows.map(function () {
const buffTds = $(this).children('td')
const buffText = buffTds.eq(-1).text()
const buffRegex = /(.*) \((Single Chamber|Whole Floor)\)/gm
const regMatched = buffRegex.exec(buffText)
const iconImgElem = buffTds.eq(0).children('img')[0]
innerValue.push({
'Icon': BASEURL + iconImgElem.attribs.src,
'Buff': regMatched[1],
'Time': regMatched[2],
})
})
cData[key] = cData[key] ? cData[key] : []
cData[key].push(innerValue)
return
} else if (_key.startsWith('Monsters')) {
// 讨伐列表,解析嵌套表格
key = "Monsters"
const innerKey = /Monsters(\w*)/gm.exec(_key)[1]
const innerValue = []
_value.children('a').map(function (_, aElem) {
const iconDivLoaded = $(this).children('div')
const iconImgElem = iconDivLoaded.children('img')[0]
innerValue.push({
'Icon': BASEURL + iconImgElem.attribs.src,
'Id': /m_(\d*)/gm.exec(aElem.attribs.href)[1] * 1,
'Rarity': /rar_bg_(.*)/gm.exec(iconDivLoaded[0].attribs.class)[1] * 1,
'Name': $(this).next().text(),
})
})
cData[key] = cData[key] ? cData[key] : {}
cData[key][innerKey] = innerValue
return
} else if (_key === 'Reward') {
// 间之秘宝,解析嵌套表格
key = _key
value = []
_value.children('a').map(function (_, aElem) {
const iconDivLoaded = $(this).children('div')
const iconImgElem = iconDivLoaded.children('img')[0]
value.push({
'Icon': BASEURL + iconImgElem.attribs.src,
'Id': /i_(\d*)/gm.exec(aElem.attribs.href)[1] * 1,
'Rarity': /rar_bg_(.*)/gm.exec(iconDivLoaded[0].attribs.class)[1] * 1,
'Name': iconImgElem.attribs.alt,
'Count': $(this).text() * 1,
})
})
} else {
key = _key
value = (_value.text() * 1) ? (_value.text() * 1) : _value.text()
}
// 写入间数据的一对键值
cData[key] = value
})
// 压入层数据的间数据
vData['Chambers'].push(cData)
})
// 解析完毕填入 data
data["Floor"][fId] = data["Floor"][fId] ? data["Floor"][fId] : {}
data["Floor"][fId][vId] = vData
})
// 获取 Floor Schedule 表格元素
const schedule = floors.eq(-1).children("div.menu_item_scrollable").children('table')
// 获取 Blessings 表格元素
const blessing = $('section#abyss_blessing > div.menu_item_scrollable > table');
console.assert(schedule.length === blessing.length)
// 遍历解析 Floor Schedule & Blessings
schedule.map(function (sIdx) {
const time = $(this).eq(0).parent().prev().text()
console.log(time)
// 解析 Floor Variant 安排数据
const scheduleRows = $(this).children('tbody').children('tr')
const arrangeData = {}
scheduleRows.map(function () {
const arrangement = $(this).children('td')
const fIdx = /Floor \#(\d*)/gm.exec(arrangement.eq(0).text())[1]
const vIdx = /Variant \#(\d*)/gm.exec(arrangement.eq(-1).text())[1]
arrangeData[fIdx] = vIdx
})
// 解析渊月祝福数据,根据 sIdx 寻找
const blessTableLoaded = blessing.eq(sIdx).children('tbody')
console.assert(blessTableLoaded.parent().parent().prev().text() === time)
const blessTagTds = blessTableLoaded.children('tr').children('td')
console.assert(blessTagTds.length === 3)
const blessData = {
'Icon': BASEURL + blessTagTds.children('img')[0].attribs.src,
'Name': blessTagTds.eq(1).text(),
'Detail': blessTagTds.eq(2).text(),
'ColorfulDetail': blessTagTds.eq(2).html(),
}
// 解析完毕填入 data
data["Schedule"][time] = { 'arrangement': arrangeData, 'blessing': blessData }
})
// 生成 JSON 文件
data["Type"] = pageType
fileName = `abyss.${pageType.split(" ")[1].toLowerCase()}.json`
// fs.writeFileSync('assets/abyss.beautify.json', JSON.stringify(data, null, 2));
fs.writeFileSync(`assets/${fileName}`, JSON.stringify(data, null));
}