diff --git a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/JecnaClient.kt b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/JecnaClient.kt index 047a49b5..950b7fb6 100644 --- a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/JecnaClient.kt +++ b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/JecnaClient.kt @@ -2,6 +2,7 @@ package io.github.tomhula.jecnaapi import io.github.tomhula.jecnaapi.data.absence.AbsencesPage import io.github.tomhula.jecnaapi.data.article.NewsPage import io.github.tomhula.jecnaapi.data.attendance.AttendancesPage +import io.github.tomhula.jecnaapi.data.cert.Certificate import io.github.tomhula.jecnaapi.data.grade.GradesPage import io.github.tomhula.jecnaapi.data.notification.Notification import io.github.tomhula.jecnaapi.data.notification.NotificationReference @@ -48,6 +49,7 @@ interface JecnaClient suspend fun getStudentProfile(username: String): Student suspend fun getNotifications(): List suspend fun getNotification(notification: NotificationReference): Notification + suspend fun getStudentCertificates(): List companion object { diff --git a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/WebJecnaClient.kt b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/WebJecnaClient.kt index 3545d033..fbcc80cd 100644 --- a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/WebJecnaClient.kt +++ b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/WebJecnaClient.kt @@ -2,8 +2,7 @@ package io.github.tomhula.jecnaapi import com.fleeksoft.ksoup.Ksoup import com.fleeksoft.ksoup.nodes.Document -import io.ktor.client.statement.* -import io.ktor.http.* +import io.github.tomhula.jecnaapi.data.cert.Certificate import io.github.tomhula.jecnaapi.data.notification.NotificationReference import io.github.tomhula.jecnaapi.parser.parsers.* import io.github.tomhula.jecnaapi.util.JecnaPeriodEncoder @@ -12,6 +11,8 @@ import io.github.tomhula.jecnaapi.util.SchoolYear import io.github.tomhula.jecnaapi.util.SchoolYearHalf import io.github.tomhula.jecnaapi.web.Auth import io.github.tomhula.jecnaapi.web.AuthenticationException +import io.ktor.client.statement.* +import io.ktor.http.* import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.cookies.AcceptAllCookiesStorage @@ -83,6 +84,7 @@ class WebJecnaClient( private val lockerPageParser = LockerPageParser private val roomsPageParser = RoomsPageParser private val roomParser = RoomParser(TimetableParser) + private val certificatePageParser = CertificatePageParser @OptIn(ExperimentalTime::class) override suspend fun login(auth: Auth): Boolean @@ -161,6 +163,16 @@ class WebJecnaClient( override suspend fun getStudentProfile() = autoLoginAuth?.let { getStudentProfile(it.username)} ?: throw AuthenticationException() override suspend fun getNotification(notification: NotificationReference) = notificationParser.getNotification(queryStringBody("${PageWebPath.NOTIFICATION}?userStudentRecordId=${notification.recordId}")) override suspend fun getNotifications() = notificationParser.parse(queryStringBody(PageWebPath.NOTIFICATIONS)) + override suspend fun getStudentCertificates(): List + { + val response = query(PageWebPath.CERTIFICATES, parameters = null) + val locationHeader = response.headers[HttpHeaders.Location] + + if (locationHeader == "$endpoint/neopravneny-pristup") + return emptyList() + + return certificatePageParser.parse(response.bodyAsText()) + } suspend fun setRole(role: Role) { @@ -272,6 +284,7 @@ class WebJecnaClient( const val STUDENT = "/student" const val LOCKER = "/locker/student" const val ROOMS = "/ucebna" + const val CERTIFICATES = "/certification/student" } } diff --git a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/data/cert/Certificate.kt b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/data/cert/Certificate.kt new file mode 100644 index 00000000..1902fb57 --- /dev/null +++ b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/data/cert/Certificate.kt @@ -0,0 +1,9 @@ +package io.github.tomhula.jecnaapi.data.cert + +import kotlinx.datetime.LocalDate + +data class Certificate( + val dateIssued: LocalDate, + val issuer: String, + val label: String, +) diff --git a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/data/schoolStaff/Teacher.kt b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/data/schoolStaff/Teacher.kt index f136d72f..5dbf79f8 100644 --- a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/data/schoolStaff/Teacher.kt +++ b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/data/schoolStaff/Teacher.kt @@ -1,6 +1,7 @@ package io.github.tomhula.jecnaapi.data.schoolStaff import io.github.tomhula.jecnaapi.data.SchoolAttendee +import io.github.tomhula.jecnaapi.data.cert.Certificate import io.github.tomhula.jecnaapi.data.timetable.Timetable class Teacher( @@ -16,7 +17,8 @@ class Teacher( val cabinet: String? = null, val tutorOfClass: String? = null, val consultationHours: String? = null, - val timetable: Timetable? = null + val timetable: Timetable? = null, + val certificates: List = emptyList(), ) : SchoolAttendee(fullName, username, schoolMail, privateMail, phoneNumbers, profilePicturePath) { val tag = tag.trim().lowercase().replaceFirstChar { it.uppercaseChar() } @@ -34,6 +36,7 @@ class Teacher( if (tutorOfClass != other.tutorOfClass) return false if (consultationHours != other.consultationHours) return false if (tag != other.tag) return false + if (certificates != other.certificates) return false return true } @@ -64,6 +67,7 @@ class Teacher( "tutorOfClass=$tutorOfClass, " + "consultationHours=$consultationHours, " + "tag='$tag'" + + "certificates=$certificates" + ")" } } diff --git a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/parser/parsers/CertificatePageParser.kt b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/parser/parsers/CertificatePageParser.kt new file mode 100644 index 00000000..7d373bd4 --- /dev/null +++ b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/parser/parsers/CertificatePageParser.kt @@ -0,0 +1,25 @@ +package io.github.tomhula.jecnaapi.parser.parsers + +import com.fleeksoft.ksoup.Ksoup +import io.github.tomhula.jecnaapi.data.cert.Certificate +import kotlinx.datetime.LocalDate + +object CertificatePageParser +{ + fun parse(html: String): List + { + val document = Ksoup.parse(html) + val lis = document.select("ul.list li") + val certificates = lis.map { li -> + val labelText = li.select("span.label").text().trim() + val parts = labelText.split(" / ", limit = 2) + val issuer = parts[0] + val rest = parts[1] + val dateParts = rest.split(" ze dne ", limit = 2) + val title = dateParts[0] + val dateIssued = LocalDate.parse(dateParts[1], CommonParser.CZECH_DATE_FORMAT_WITH_PADDING) + Certificate(dateIssued, issuer, title) + } + return certificates + } +} diff --git a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/parser/parsers/TeacherParser.kt b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/parser/parsers/TeacherParser.kt index 687d1c76..159167ad 100644 --- a/src/commonMain/kotlin/io/github/tomhula/jecnaapi/parser/parsers/TeacherParser.kt +++ b/src/commonMain/kotlin/io/github/tomhula/jecnaapi/parser/parsers/TeacherParser.kt @@ -1,9 +1,11 @@ package io.github.tomhula.jecnaapi.parser.parsers -import io.github.tomhula.jecnaapi.data.schoolStaff.Teacher -import io.github.tomhula.jecnaapi.parser.ParseException import com.fleeksoft.ksoup.Ksoup import com.fleeksoft.ksoup.nodes.Element +import io.github.tomhula.jecnaapi.data.cert.Certificate +import io.github.tomhula.jecnaapi.data.schoolStaff.Teacher +import io.github.tomhula.jecnaapi.parser.ParseException +import kotlinx.datetime.LocalDate /** https://www.spsejecna.cz/ucitel/{teacher-tag} */ internal class TeacherParser(private val timetableParser: TimetableParser) @@ -14,7 +16,6 @@ internal class TeacherParser(private val timetableParser: TimetableParser) { val document = Ksoup.parse(html) val table = document.selectFirstOrThrow(".userprofile", "data table") - val fullName = getTableValue(table, "Jméno")!! val tag = getTableValue(table, "Zkratka")!! val username = getTableValue(table, "Uživatelské jméno")!! @@ -28,10 +29,26 @@ internal class TeacherParser(private val timetableParser: TimetableParser) val consultationHours = getTableValue(table, "Konzultační hodiny") val tutorOfClass = getTableValue(table, "Třídní učitel") val profilePicturePath = document.selectFirst(".profilephoto .image img")?.attr("src") - val timetableEle = document.selectFirst("table.timetable") val timetable = timetableEle?.let { timetableParser.parse(it.outerHtml()) } + val certificates = mutableListOf() + val certList = document.select("ul.certifications > li") + for (li in certList) + { + val date = LocalDate.parse( + li.selectFirst("span.date")?.text()?.trim().orEmpty(), + CommonParser.CZECH_DATE_FORMAT_WITH_PADDING + ) + val infoSpan = li.selectFirst("span.info") + val label = infoSpan?.selectFirst("span.label")?.text()?.trim().orEmpty() + val institution = infoSpan?.selectFirst("span.institution")?.text()?.trim().orEmpty() + if (institution.isNotEmpty()) + { + certificates.add(Certificate(date, institution, label)) + } + } + return Teacher( fullName = fullName, username = username, @@ -45,7 +62,8 @@ internal class TeacherParser(private val timetableParser: TimetableParser) cabinet = cabinet, tutorOfClass = tutorOfClass, consultationHours = consultationHours, - timetable = timetable + timetable = timetable, + certificates = certificates ) } catch (e: Exception)