Update dependencies, update ktlint rules

This commit is contained in:
Mikołaj Pich 2023-12-22 13:39:47 +01:00
parent 29af590dd8
commit f0c7093521
22 changed files with 151 additions and 91 deletions

View file

@ -9,7 +9,17 @@ indent_size=4
indent_size=2
[*.{kt,kts}]
disabled_rules=import-ordering,no-wildcard-imports
max_line_length=177
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_allow_trailing_comma=true
ktlint_standard_no-empty-first-line-in-class-body=disabled
ktlint_standard_function-signature=disabled
ktlint_standard_string-template-indent=disabled
ktlint_standard_string-template-indent-expression-wrapping=disabled
ktlint_standard_multiline-expression-wrapping=disabled
ktlint_standard_max-line-length=disabled
ktlint_standard_no-blank-line-in-list=disabled
ktlint_standard_parameter-list-wrapping=disabled
ktlint_standard_discouraged-comment-location=disabled
ktlint_standard_if-else-wrapping=disabled

View file

@ -2,7 +2,7 @@ plugins {
alias(libs.plugins.kotlin) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.serialization)
id "org.jlleitschuh.gradle.ktlint" version "11.6.1"
id "org.jlleitschuh.gradle.ktlint" version "12.0.2"
id "io.github.gradle-nexus.publish-plugin" version "1.3.0"
id 'ru.vyarus.mkdocs' version '3.0.0'
}
@ -13,7 +13,7 @@ ext {
GIT_URL = 'https://github.com/wulkanowy/sdk.git'
jspoon = "1.3.2"
jsoup = "1.16.2"
jsoup = "1.17.1"
slf4j = "2.0.9"
moshi = "1.13.0"
}
@ -112,7 +112,7 @@ subprojects {
apply plugin: 'jacoco'
ktlint {
additionalEditorconfigFile = file(".editorconfig")
// additionalEditorconfigFile.set(file(".editorconfig"))
disabledRules = [
"no-wildcard-imports",
"import-ordering",
@ -134,7 +134,7 @@ subprojects {
implementation libs.coroutines.core
implementation "com.squareup.okhttp3:logging-interceptor"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
testImplementation "junit:junit:4.13.2"
testImplementation "com.squareup.okhttp3:mockwebserver"

View file

@ -70,9 +70,10 @@ internal class RepositoryManager(
private fun getRetrofitBuilder(isJson: Boolean = true, signInterceptor: Boolean): Retrofit.Builder {
return Retrofit.Builder()
.apply {
if (isJson) {
addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
} else addConverterFactory(ScalarsConverterFactory.create())
when {
isJson -> addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
else -> addConverterFactory(ScalarsConverterFactory.create())
}
}
.client(
OkHttpClient().newBuilder()
@ -81,8 +82,10 @@ internal class RepositoryManager(
addInterceptor(SignInterceptor(keyId, privatePem, deviceModel))
}
interceptors.forEach {
if (it.second) addNetworkInterceptor(it.first)
else addInterceptor(it.first)
when {
it.second -> addNetworkInterceptor(it.first)
else -> addInterceptor(it.first)
}
}
}
.build(),

View file

@ -1,6 +1,5 @@
package io.github.wulkanowy.sdk.hebe
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import okhttp3.logging.HttpLoggingInterceptor
import org.junit.Assert.assertTrue
@ -10,7 +9,6 @@ import org.junit.Test
import java.time.LocalDate
@Ignore
@OptIn(ExperimentalCoroutinesApi::class)
class HebeRemoteTest {
private val hebe = Hebe()

View file

@ -300,15 +300,17 @@ class Scrapper {
}
suspend fun getGrades(semester: Int): Grades {
if (diaryId == 0) return Grades(
details = emptyList(),
summary = emptyList(),
descriptive = emptyList(),
isAverage = false,
isPoints = false,
isForAdults = false,
type = -1,
)
if (diaryId == 0) {
return Grades(
details = emptyList(),
summary = emptyList(),
descriptive = emptyList(),
isAverage = false,
isPoints = false,
isForAdults = false,
type = -1,
)
}
return student.getGrades(semester)
}
@ -344,11 +346,13 @@ class Scrapper {
suspend fun getMenu(date: LocalDate): List<Menu> = student.getMenu(date)
suspend fun getTimetable(startDate: LocalDate, endDate: LocalDate? = null): Timetable {
if (diaryId == 0) return Timetable(
headers = emptyList(),
lessons = emptyList(),
additional = emptyList(),
)
if (diaryId == 0) {
return Timetable(
headers = emptyList(),
lessons = emptyList(),
additional = emptyList(),
)
}
return student.getTimetable(startDate, endDate)
}

View file

@ -39,8 +39,10 @@ internal fun getGradeShortValue(value: String?): String {
}
internal fun String.getEmptyIfDash(): String {
return if (this == "-") ""
else this
return when {
this == "-" -> ""
else -> this
}
}
internal fun String.getGradePointPercent(): String {
@ -77,7 +79,7 @@ fun String.getNormalizedSymbol(): String = this
internal fun List<Recipient>.normalizeRecipients() = map { it.parseName() }
internal fun Recipient.parseName(): Recipient {
val typeSeparatorPosition = fullName.indexOfAny(RecipientType.values().map { " - ${it.letter} - " })
val typeSeparatorPosition = fullName.indexOfAny(RecipientType.entries.map { " - ${it.letter} - " })
if (typeSeparatorPosition == -1) return copy(userName = fullName)
@ -88,7 +90,7 @@ internal fun Recipient.parseName(): Recipient {
return copy(
userName = userName,
studentName = studentName.takeIf { it != "($schoolName)" } ?: userName,
type = typeLetter.let { letter -> RecipientType.values().first { it.letter == letter } },
type = typeLetter.let { letter -> RecipientType.entries.first { it.letter == letter } },
schoolNameShort = schoolName,
)
}

View file

@ -17,9 +17,10 @@ internal object GradeDateDeserializer : KSerializer<LocalDate> {
private val formatter = DateTimeFormatter.ofPattern("[$SERVER_FORMAT][$SERVER_FORMAT_2]")
override fun deserialize(decoder: Decoder): LocalDate {
val date = if (decoder.decodeNotNullMark()) {
decoder.decodeString()
} else "01.01.1970"
val date = when {
decoder.decodeNotNullMark() -> decoder.decodeString()
else -> "01.01.1970"
}
return LocalDate.parse(date, formatter)
}

View file

@ -5,7 +5,7 @@ private val gradeMinus = "-[0-6]|[0-6]-".toRegex()
private val gradePlus = "[+][0-6]|[0-6][+]".toRegex()
private val gradeDoublePlus = "[+]{2}[0-6]|[0-6][+]{2}".toRegex()
private val gradeDoubleMinus = "[-|=]{1,2}[0-6]|[0-6][-|=]{1,2}".toRegex()
private const val modifierWeight = .33
private const val MODIFIER_WEIGHT = .33
fun isGradeValid(grade: String): Boolean {
return grade.matches(validGrade)
@ -19,8 +19,8 @@ fun getGradeValueWithModifier(grade: String): Pair<Int?, Double?> {
return gradeValue.run {
when {
matches(gradeMinus) -> replace("-", "").toInt() to -modifierWeight
matches(gradePlus) -> replace("+", "").toInt() to modifierWeight
matches(gradeMinus) -> replace("-", "").toInt() to -MODIFIER_WEIGHT
matches(gradePlus) -> replace("+", "").toInt() to MODIFIER_WEIGHT
matches(gradeDoublePlus) -> replace("++", "").toInt() to .5
matches(gradeDoubleMinus) -> replace("[-|=]{1,2}".toRegex(), "").toInt() to -.5
else -> (toIntOrNull() ?: 0) to .0

View file

@ -43,5 +43,6 @@ data class Homework(
lateinit var teacherSymbol: String
@Transient
@Suppress("PropertyName")
var _attachments: List<Pair<String, String>> = emptyList()
}

View file

@ -149,7 +149,9 @@ internal class AutoLoginInterceptor(
.body(
body = response()?.errorBody() ?: object : ResponseBody() {
override fun contentLength() = 0L
override fun contentType(): MediaType? = null
override fun source(): BufferedSource = Buffer()
},
)

View file

@ -8,16 +8,20 @@ import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
import io.github.wulkanowy.sdk.scrapper.exception.VulcanException
internal fun <T> ApiResponse<T>.handleErrors(): ApiResponse<T> {
return if (!success && feedback != null) throw feedback.run {
when {
message.contains("niespójność danych") -> ScrapperException(message)
message.contains("Brak uprawnień") -> AuthorizationRequiredException(message)
message.contains("wyłączony") -> FeatureDisabledException(message)
message.contains("DB_ERROR") -> VulcanException(message)
message.contains("błąd") -> VulcanException(message)
message.contains("The controller for path") -> InvalidPathException(message)
message.contains("The parameters dictionary contains a null entry for parameter") -> InvalidPathException(message)
else -> VulcanException(message)
return when {
!success && feedback != null -> throw feedback.run {
when {
message.contains("niespójność danych") -> ScrapperException(message)
message.contains("Brak uprawnień") -> AuthorizationRequiredException(message)
message.contains("wyłączony") -> FeatureDisabledException(message)
message.contains("DB_ERROR") -> VulcanException(message)
message.contains("błąd") -> VulcanException(message)
message.contains("The controller for path") -> InvalidPathException(message)
message.contains("The parameters dictionary contains a null entry for parameter") -> InvalidPathException(message)
else -> VulcanException(message)
}
}
} else this
else -> this
}
}

View file

@ -54,10 +54,13 @@ internal class ErrorInterceptor(
doc.select(".ErrorMessage, #ErrorTextLabel, #loginArea #errorText").takeIf { it.isNotEmpty() }?.let {
val errorMessage = it.text().trimEnd('.')
if (doc.select(SELECTOR_ADFS).isNotEmpty()) {
if (errorMessage.isNotBlank()) throw BadCredentialsException(errorMessage)
else logger.warn("Unexpected login page!")
} else throw BadCredentialsException(errorMessage)
when {
doc.select(SELECTOR_ADFS).isNotEmpty() -> when {
errorMessage.isNotBlank() -> throw BadCredentialsException(errorMessage)
else -> logger.warn("Unexpected login page!")
}
else -> throw BadCredentialsException(errorMessage)
}
}
doc.select(".app-error-container").takeIf { it.isNotEmpty() }?.let {

View file

@ -84,14 +84,17 @@ internal class AccountRepository(private val account: AccountService) {
else -> throw ScrapperException("Nieznany dziennik $url")
}
return if (unlockUrl.first == AUTO) {
val loginType = getLoginType(UrlGenerator(url, domainSuffix, symbol, ""))
loginType to when (loginType) {
STANDARD -> "https://cufs$domainSuffix.vulcan.net.pl/$symbol/AccountManage/UnlockAccount"
ADFSLightScoped -> "https://adfslight.vulcan.net.pl/$symbol/AccountManage/UnlockAccountRequest"
else -> throw ScrapperException("Nieznany dziennik $registerBaseUrl, $loginType")
return when (unlockUrl.first) {
AUTO -> {
val loginType = getLoginType(UrlGenerator(url, domainSuffix, symbol, ""))
loginType to when (loginType) {
STANDARD -> "https://cufs$domainSuffix.vulcan.net.pl/$symbol/AccountManage/UnlockAccount"
ADFSLightScoped -> "https://adfslight.vulcan.net.pl/$symbol/AccountManage/UnlockAccountRequest"
else -> throw ScrapperException("Nieznany dziennik $registerBaseUrl, $loginType")
}
}
} else unlockUrl
else -> unlockUrl
}
}
private suspend fun getLoginType(urlGenerator: UrlGenerator): Scrapper.LoginType {

View file

@ -120,18 +120,20 @@ internal class MessagesRepository(private val api: MessagesService) {
val appGuid = getScriptParam("appGuid", startPage)
val appVersion = getScriptParam("version", startPage)
if (!removeForever) {
api.moveMessageToTrash(
when {
!removeForever -> api.moveMessageToTrash(
token = token,
appGuid = appGuid,
appVersion = appVersion,
body = globalKeys,
)
} else api.deleteMessage(
token = token,
appGuid = appGuid,
appVersion = appVersion,
body = globalKeys,
)
else -> api.deleteMessage(
token = token,
appGuid = appGuid,
appVersion = appVersion,
body = globalKeys,
)
}
}
}

View file

@ -246,9 +246,12 @@ internal class RegisterRepository(
val startPage = runCatching {
student.getStart(url.generate(UrlGenerator.Site.STUDENT) + "App")
}.recoverCatching {
if (it is ScrapperException && it.code == HttpURLConnection.HTTP_NOT_FOUND) {
student.getStart(url.generate(UrlGenerator.Site.STUDENT) + "Start")
} else throw it
when {
it is ScrapperException && it.code == HttpURLConnection.HTTP_NOT_FOUND -> {
student.getStart(url.generate(UrlGenerator.Site.STUDENT) + "Start")
}
else -> throw it
}
}.getOrThrow()
return student.getUserCache(

View file

@ -304,9 +304,10 @@ internal class StudentRepository(
return runCatching {
api.getStart("App")
}.recoverCatching {
if (it is ScrapperException && it.code == HTTP_NOT_FOUND) {
api.getStart("Start")
} else throw it
when {
it is ScrapperException && it.code == HTTP_NOT_FOUND -> api.getStart("Start")
else -> throw it
}
}.getOrThrow()
}
}

View file

@ -205,8 +205,10 @@ internal class ServiceManager(
.client(client.build())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(
if (json) this.json.asConverterFactory("application/json".toMediaType())
else JspoonConverterFactory.create(),
when {
json -> this.json.asConverterFactory("application/json".toMediaType())
else -> JspoonConverterFactory.create()
},
)
.build()
@ -235,8 +237,10 @@ internal class ServiceManager(
if (it.first is AutoLoginInterceptor && loginIntercept) addInterceptor(it.first)
if (it.first is ErrorInterceptor && errIntercept) addInterceptor(it.first)
} else {
if (it.second) addNetworkInterceptor(it.first)
else addInterceptor(it.first)
when {
it.second -> addNetworkInterceptor(it.first)
else -> addInterceptor(it.first)
}
}
}
}

View file

@ -20,8 +20,8 @@ import io.github.wulkanowy.sdk.scrapper.grades.GradesStatisticsRequest
import io.github.wulkanowy.sdk.scrapper.grades.GradesStatisticsSemester
import io.github.wulkanowy.sdk.scrapper.homework.HomeworkDay
import io.github.wulkanowy.sdk.scrapper.homework.HomeworkRequest
import io.github.wulkanowy.sdk.scrapper.menu.MenuRequest
import io.github.wulkanowy.sdk.scrapper.menu.Menu
import io.github.wulkanowy.sdk.scrapper.menu.MenuRequest
import io.github.wulkanowy.sdk.scrapper.mobile.Device
import io.github.wulkanowy.sdk.scrapper.mobile.TokenResponse
import io.github.wulkanowy.sdk.scrapper.mobile.UnregisterDeviceRequest

View file

@ -30,8 +30,10 @@ internal class TimetableParser {
else -> null
}?.let {
td.select(".uwaga-panel").getOrNull(0)?.let { warn ->
if (it.info.isBlank()) it.copy(info = warn.text())
else it.copy(info = "${it.info}: ${warn.text()}")
when {
it.info.isBlank() -> it.copy(info = warn.text())
else -> it.copy(info = "${it.info}: ${warn.text()}")
}
} ?: it
}
}
@ -39,7 +41,7 @@ internal class TimetableParser {
private fun getLessonInfoForDuoDivs(lesson: Lesson, divs: Elements) = when {
divs.has(1, CLASS_MOVED_OR_CANCELED) -> {
when {
divs[1]?.selectFirst("span")?.hasClass(CLASS_PLANNED) == true -> getLessonInfo(lesson, divs[0]).run {
divs[1].selectFirst("span")?.hasClass(CLASS_PLANNED) == true -> getLessonInfo(lesson, divs[0]).run {
val old = getLessonInfo(lesson, divs[1])
copy(
changes = true,
@ -49,9 +51,11 @@ internal class TimetableParser {
info = stripLessonInfo("${getFormattedLessonInfo(info)}, ${old.info}").replace("$subject ", "").capitalise(),
)
}
else -> getLessonInfo(lesson, divs[1])
}
}
divs.has(1, CLASS_CHANGES) -> getLessonInfo(lesson, divs[1]).run {
val old = getLessonInfo(lesson, divs[0])
copy(
@ -62,6 +66,7 @@ internal class TimetableParser {
roomOld = old.room,
)
}
divs.has(0, CLASS_MOVED_OR_CANCELED) && divs.has(0, CLASS_PLANNED) && divs.has(1, null) -> {
getLessonInfo(lesson, divs[1]).run {
val old = getLessonInfo(lesson, divs[0])
@ -75,19 +80,23 @@ internal class TimetableParser {
)
}
}
divs.has(0, CLASS_CHANGES) -> {
val oldLesson = getLessonInfo(lesson, divs[0])
val newLesson = getLessonInfo(lesson, divs[1])
val isNewLessonEmpty = divs[1]?.select("span").isNullOrEmpty()
if (!isNewLessonEmpty && oldLesson.teacher == newLesson.teacher) {
newLesson.copy(
val isNewLessonEmpty = divs[1].select("span").isNullOrEmpty()
when {
!isNewLessonEmpty && oldLesson.teacher == newLesson.teacher -> newLesson.copy(
subjectOld = oldLesson.subject,
roomOld = oldLesson.room,
teacherOld = oldLesson.teacherOld,
changes = true,
)
} else oldLesson
else -> oldLesson
}
}
else -> getLessonInfo(lesson, divs[0])
}
@ -104,6 +113,7 @@ internal class TimetableParser {
)
}
}
divs.has(0, CLASS_MOVED_OR_CANCELED) && divs.has(1, CLASS_MOVED_OR_CANCELED) && divs.has(2, CLASS_CHANGES) -> {
getLessonInfo(lesson, divs[2]).run {
val old = getLessonInfo(lesson, divs[0])
@ -116,6 +126,7 @@ internal class TimetableParser {
)
}
}
divs.has(0, CLASS_MOVED_OR_CANCELED) && divs.has(1, CLASS_CHANGES) && divs.has(1, CLASS_MOVED_OR_CANCELED) && divs.has(2, null) -> {
val oldLesson = getLessonInfo(lesson, divs[0])
getLessonInfo(lesson, divs[2]).copy(
@ -124,11 +135,12 @@ internal class TimetableParser {
roomOld = oldLesson.room,
)
}
else -> getLessonInfo(lesson, divs[1])
}
private fun Elements.has(index: Int, className: String?): Boolean {
return this[index]?.selectFirst("span").let {
return this[index].selectFirst("span").let {
when (className) {
null -> it?.attr("class").isNullOrBlank()
else -> it?.hasClass(className) == true
@ -145,12 +157,14 @@ internal class TimetableParser {
offset = 0,
changes = div.ownText(),
)
size == 3 && div.ownText().contains(INFO_REPLACEMENT_ROOM, true) -> getSimpleLessonWithNewReplacementRoom(
lesson = lesson,
spans = this,
offset = 0,
changes = div.ownText(),
)
size == 3 -> getSimpleLesson(lesson, this, changes = div.ownText())
size == 4 && div.ownText().contains(INFO_REPLACEMENT_TEACHER, true) -> getSimpleLessonWithNewReplacementTeacher(
lesson = lesson,
@ -158,12 +172,14 @@ internal class TimetableParser {
offset = 1,
changes = div.ownText(),
)
size == 4 && div.ownText().contains(INFO_REPLACEMENT_ROOM, true) -> getSimpleLessonWithNewReplacementRoom(
lesson = lesson,
spans = this,
offset = 1,
changes = div.ownText(),
)
size == 4 && last()?.hasClass(CLASS_REALIZED) == true -> getSimpleLesson(lesson, this, changes = div.ownText())
size == 4 -> getGroupLesson(lesson, this, changes = div.ownText())
size == 5 && first()?.hasClass(CLASS_CHANGES) == true && select(".$CLASS_REALIZED").size == 2 -> getSimpleLesson(
@ -172,11 +188,13 @@ internal class TimetableParser {
infoExtraOffset = 1,
changes = div.ownText(),
)
size == 5 && last()?.hasClass(CLASS_REALIZED) == true -> getGroupLesson(
lesson = lesson,
spans = this,
changes = div.ownText(),
)
size == 7 -> getSimpleLessonWithReplacement(lesson, spans = this)
size == 9 -> getGroupLessonWithReplacement(lesson, spans = this)
else -> lesson
@ -279,6 +297,7 @@ internal class TimetableParser {
private fun getRoomFromInfo(info: String?) = info?.substringAfter("(zmieniono salę z ")?.substringBefore(" na").orEmpty()
private fun getTeacherChangesWithoutSubstitution(changes: String?) = changes?.substringBefore("(zastępstwo: ").orEmpty()
private fun getRoomChangesWithoutSubstitution(changes: String?) = changes?.substringBefore("(zmieniono salę z ").orEmpty()
private fun stripLessonInfo(info: String) = info

View file

@ -77,8 +77,10 @@ abstract class BaseLocalTest : BaseTest() {
.client(okHttp)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(
if (!html) json.asConverterFactory("application/json".toMediaType())
else JspoonConverterFactory.create(),
when {
!html -> json.asConverterFactory("application/json".toMediaType())
else -> JspoonConverterFactory.create()
},
)
.baseUrl(url)
.build()

View file

@ -1,9 +1,8 @@
package io.github.wulkanowy.sdk.scrapper.attendance
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.Assert.*
class AttendanceCategoryTest {
@Test

View file

@ -1,9 +1,8 @@
package io.github.wulkanowy.sdk.mapper
import io.github.wulkanowy.sdk.pojo.Exam
import io.github.wulkanowy.sdk.toLocalDate
import io.github.wulkanowy.sdk.scrapper.exams.Exam as ScrapperExam
import io.github.wulkanowy.sdk.hebe.models.Exam as HebeExam
import io.github.wulkanowy.sdk.scrapper.exams.Exam as ScrapperExam
@JvmName("mapScrapperExams")
internal fun List<ScrapperExam>.mapExams() = map {