Current scope: tibiakt-core| all classes
|
com.galarzaa.tibiakt.core.section.community.leaderboard.parser
Coverage Summary for Class: LeaderboardParser (com.galarzaa.tibiakt.core.section.community.leaderboard.parser)
| Class | Class, % | Method, % | Branch, % | Line, % | Instruction, % |
|---|---|---|---|---|---|
| LeaderboardParser | 100% (1/1) | 100% (3/3) | 71.7% (33/46) | 93.3% (42/45) | 90.8% (324/357) |
/*
* Copyright © 2025 Allan Galarza
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.galarzaa.tibiakt.core.section.community.leaderboard.parser
import com.galarzaa.tibiakt.core.collections.offsetStart
import com.galarzaa.tibiakt.core.exceptions.ParsingException
import com.galarzaa.tibiakt.core.html.PaginationData
import com.galarzaa.tibiakt.core.html.TABLE_SELECTOR
import com.galarzaa.tibiakt.core.html.cells
import com.galarzaa.tibiakt.core.html.cleanText
import com.galarzaa.tibiakt.core.html.formData
import com.galarzaa.tibiakt.core.html.getLinkInformation
import com.galarzaa.tibiakt.core.html.parsePagination
import com.galarzaa.tibiakt.core.html.rows
import com.galarzaa.tibiakt.core.parser.Parser
import com.galarzaa.tibiakt.core.section.community.leaderboard.builder.LeaderboardBuilder
import com.galarzaa.tibiakt.core.section.community.leaderboard.builder.leaderboard
import com.galarzaa.tibiakt.core.section.community.leaderboard.model.Leaderboard
import com.galarzaa.tibiakt.core.section.community.leaderboard.model.LeaderboardEntry
import com.galarzaa.tibiakt.core.section.community.leaderboard.model.LeaderboardRotation
import com.galarzaa.tibiakt.core.text.clean
import com.galarzaa.tibiakt.core.text.remove
import com.galarzaa.tibiakt.core.time.parseTibiaDateTime
import org.jsoup.nodes.Element
import kotlin.time.Clock
import kotlin.time.Duration.Companion.minutes
/** Parses content from the leaderboards. */
public object LeaderboardParser : Parser<Leaderboard?> {
private val rotationEndPattern = Regex("""ends on ([^)]+)""")
override fun fromContent(content: String): Leaderboard? {
val boxContent = boxContent(content)
val tables = boxContent.select(TABLE_SELECTOR)
return leaderboard {
val formData = tables[1].selectFirst("form")?.formData() ?: throw ParsingException("form not found")
val rotationOptions = tables[1].select("select[name=rotation] > option")
.associate { it.attr("value").toInt() to it.cleanText() }
for ((rotationId, label) in rotationOptions) {
var cleanLabel = label
var isCurrent = false
if ("Current" in label) {
cleanLabel = rotationEndPattern.find(label)?.groupValues?.last()
?: throw ParsingException("rotation option label doesn't match expected format")
isCurrent = true
}
val rotationEnd = parseTibiaDateTime(cleanLabel)
val rotation = LeaderboardRotation(
rotationId = rotationId, isCurrent = isCurrent, endsAt = rotationEnd
)
if (isCurrent) {
this.rotation = rotation
}
addAvailableRotation(rotation)
}
world = formData.values["world"] ?: if ("world" in formData.availableOptions) return null
else throw ParsingException("world form parameter not found")
if (tables.size == 4) {
val lastUpdateString = tables[2].text()
val minutes =
Regex("""(\d+)""").find(lastUpdateString)?.groups?.get(0)?.value?.toInt() ?: throw ParsingException(
"unexpected last update text: $lastUpdateString"
)
lastUpdatedAt = Clock.System.now().minus(minutes.minutes)
}
val entriesTable = tables.last()
parseLeaderboardEntries(entriesTable)
val paginationData =
boxContent.selectFirst("small")?.parsePagination() ?: PaginationData.Companion.default()
currentPage = paginationData.currentPage
resultsCount = paginationData.resultsCount
totalPages = paginationData.totalPages
}
}
private fun LeaderboardBuilder.parseLeaderboardEntries(entriesTable: Element?) {
for (row in entriesTable.rows().offsetStart(1)) {
val cells = row.cells()
if (cells.size != 3) continue
val name = cells[1].selectFirst("a")?.getLinkInformation()?.title?.clean()
val rank = cells[0].text().remove(".").toInt()
val dromeLevel = cells[2].text().toInt()
addEntry(
if (name != null) LeaderboardEntry.Character(rank, dromeLevel, name) else LeaderboardEntry.Deleted(
rank, dromeLevel
)
)
}
}
}