Add fallback to eduOne attendance if there is eduOne: true in student start page
This commit is contained in:
parent
f9be659490
commit
773402ceb1
15 changed files with 137 additions and 27 deletions
|
@ -134,15 +134,16 @@ public final class io/github/wulkanowy/sdk/scrapper/attendance/Absent {
|
|||
public final class io/github/wulkanowy/sdk/scrapper/attendance/Attendance {
|
||||
public static final field Companion Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance$Companion;
|
||||
public field category Lio/github/wulkanowy/sdk/scrapper/attendance/AttendanceCategory;
|
||||
public synthetic fun <init> (IILjava/time/LocalDateTime;Ljava/lang/String;ILkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (ILjava/time/LocalDateTime;Ljava/lang/String;I)V
|
||||
public synthetic fun <init> (ILjava/time/LocalDateTime;Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public synthetic fun <init> (IIILjava/time/LocalDateTime;Ljava/lang/String;ILkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (IILjava/time/LocalDateTime;Ljava/lang/String;I)V
|
||||
public synthetic fun <init> (IILjava/time/LocalDateTime;Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()I
|
||||
public final fun component2 ()Ljava/time/LocalDateTime;
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun component4 ()I
|
||||
public final fun copy (ILjava/time/LocalDateTime;Ljava/lang/String;I)Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance;
|
||||
public static synthetic fun copy$default (Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance;ILjava/time/LocalDateTime;Ljava/lang/String;IILjava/lang/Object;)Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance;
|
||||
public final fun component2 ()I
|
||||
public final fun component3 ()Ljava/time/LocalDateTime;
|
||||
public final fun component4 ()Ljava/lang/String;
|
||||
public final fun component5 ()I
|
||||
public final fun copy (IILjava/time/LocalDateTime;Ljava/lang/String;I)Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance;
|
||||
public static synthetic fun copy$default (Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance;IILjava/time/LocalDateTime;Ljava/lang/String;IILjava/lang/Object;)Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getCategory ()Lio/github/wulkanowy/sdk/scrapper/attendance/AttendanceCategory;
|
||||
public final fun getCategoryId ()I
|
||||
|
@ -156,7 +157,6 @@ public final class io/github/wulkanowy/sdk/scrapper/attendance/Attendance {
|
|||
public final fun setCategory (Lio/github/wulkanowy/sdk/scrapper/attendance/AttendanceCategory;)V
|
||||
public final fun setExcusable (Z)V
|
||||
public final fun setExcuseStatus (Lio/github/wulkanowy/sdk/scrapper/attendance/SentExcuseStatus;)V
|
||||
public final fun setNumber (I)V
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final synthetic fun write$Self (Lio/github/wulkanowy/sdk/scrapper/attendance/Attendance;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
|
|
@ -249,7 +249,10 @@ class Scrapper {
|
|||
}
|
||||
|
||||
private val student by resettableLazy(changeManager) {
|
||||
StudentRepository(serviceManager.getStudentService())
|
||||
StudentRepository(
|
||||
api = serviceManager.getStudentService(),
|
||||
studentPlusService = serviceManager.getStudentPlusService(),
|
||||
)
|
||||
}
|
||||
|
||||
private val messages by resettableLazy(changeManager) {
|
||||
|
@ -277,7 +280,7 @@ class Scrapper {
|
|||
suspend fun getAttendance(startDate: LocalDate, endDate: LocalDate? = null): List<Attendance> {
|
||||
if (diaryId == 0) return emptyList()
|
||||
|
||||
return student.getAttendance(startDate, endDate)
|
||||
return student.getAttendance(startDate, endDate, studentId, diaryId)
|
||||
}
|
||||
|
||||
suspend fun getAttendanceSummary(subjectId: Int? = -1): List<AttendanceSummary> {
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.github.wulkanowy.sdk.scrapper
|
|||
import io.github.wulkanowy.sdk.scrapper.messages.Mailbox
|
||||
import io.github.wulkanowy.sdk.scrapper.messages.Recipient
|
||||
import io.github.wulkanowy.sdk.scrapper.messages.RecipientType
|
||||
import org.jsoup.Jsoup.parse
|
||||
import org.jsoup.Jsoup
|
||||
import java.text.Normalizer
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant.ofEpochMilli
|
||||
|
@ -52,7 +52,13 @@ internal fun String.getGradePointPercent(): String {
|
|||
|
||||
internal fun getScriptParam(name: String, content: String, fallback: String = ""): String {
|
||||
return "$name: '(.)*'".toRegex().find(content).let { result ->
|
||||
if (null !== result) parse(result.groupValues[0].substringAfter("'").substringBefore("'")).text() else fallback
|
||||
if (null !== result) Jsoup.parse(result.groupValues[0].substringAfter("'").substringBefore("'")).text() else fallback
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getScriptFlag(name: String, content: String, fallback: Boolean = false): Boolean {
|
||||
return "$name: (false|true)".toRegex().find(content).let { result ->
|
||||
if (null !== result) result.groupValues[1].toBoolean() else fallback
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,38 @@
|
|||
package io.github.wulkanowy.sdk.scrapper.attendance
|
||||
|
||||
import io.github.wulkanowy.sdk.scrapper.adapter.CustomDateAdapter
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
data class Attendance(
|
||||
|
||||
@SerialName("numerLekcji")
|
||||
val number: Int = 0,
|
||||
|
||||
@SerialName("IdPoraLekcji")
|
||||
@JsonNames("idPoraLekcji")
|
||||
val timeId: Int = 0,
|
||||
|
||||
@SerialName("Data")
|
||||
@JsonNames("data")
|
||||
@Serializable(with = CustomDateAdapter::class)
|
||||
val date: LocalDateTime,
|
||||
|
||||
@SerialName("PrzedmiotNazwa")
|
||||
@JsonNames("opisZajec")
|
||||
val subject: String?,
|
||||
|
||||
@SerialName("IdKategoria")
|
||||
@JsonNames("kategoriaFrekwencji")
|
||||
val categoryId: Int = -1,
|
||||
) {
|
||||
|
||||
@Transient
|
||||
var number: Int = 0
|
||||
|
||||
@Transient
|
||||
lateinit var category: AttendanceCategory
|
||||
|
||||
|
|
|
@ -13,8 +13,9 @@ internal fun AttendanceResponse.mapAttendanceList(start: LocalDate, end: LocalDa
|
|||
val endDate = end ?: start.plusDays(4)
|
||||
return lessons.map {
|
||||
val sentExcuse = sentExcuses.firstOrNull { excuse -> excuse.date == it.date && excuse.timeId == it.timeId }
|
||||
it.apply {
|
||||
number = times.single { time -> time.id == it.timeId }.number
|
||||
it.copy(
|
||||
number = times.single { time -> time.id == it.timeId }.number,
|
||||
).apply {
|
||||
category = AttendanceCategory.getCategoryById(categoryId)
|
||||
excusable = excuseActive && (category == ABSENCE_UNEXCUSED || category == UNEXCUSED_LATENESS) && sentExcuse == null
|
||||
if (sentExcuse != null) excuseStatus = SentExcuseStatus.getByValue(sentExcuse.status)
|
||||
|
|
|
@ -1,31 +1,41 @@
|
|||
package io.github.wulkanowy.sdk.scrapper.conferences
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
data class Conference(
|
||||
|
||||
@SerialName("Tytul")
|
||||
@JsonNames("sala")
|
||||
val place: String,
|
||||
|
||||
@SerialName("TematZebrania")
|
||||
@JsonNames("opis")
|
||||
val topic: String,
|
||||
|
||||
@SerialName("Agenda")
|
||||
@JsonNames("opis") // todo
|
||||
val agenda: String,
|
||||
|
||||
@SerialName("ObecniNaZebraniu")
|
||||
@JsonNames("obecniNaZebraniu")
|
||||
val presentOnConference: String,
|
||||
|
||||
@SerialName("ZebranieOnline")
|
||||
@JsonNames("zebranieOnline")
|
||||
val online: String?,
|
||||
|
||||
@SerialName("Id")
|
||||
@JsonNames("id")
|
||||
val id: Int,
|
||||
|
||||
@Transient
|
||||
@JsonNames("dataCzas") // todo
|
||||
val date: LocalDateTime = LocalDateTime.now(),
|
||||
)
|
||||
|
|
|
@ -17,12 +17,17 @@ internal class UrlGenerator(
|
|||
LOGIN,
|
||||
HOME,
|
||||
STUDENT,
|
||||
STUDENT_PLUS,
|
||||
MESSAGES,
|
||||
;
|
||||
|
||||
val isStudent: Boolean
|
||||
get() = this == STUDENT_PLUS || this == STUDENT
|
||||
}
|
||||
|
||||
fun generate(type: Site): String {
|
||||
if (type == Site.BASE) return "$schema://$host"
|
||||
return "$schema://${getSubDomain(type)}$domainSuffix.$host/$symbol/${if (type == Site.STUDENT) "$schoolId/" else ""}"
|
||||
return "$schema://${getSubDomain(type)}$domainSuffix.$host/$symbol/${if (type.isStudent) "$schoolId/" else ""}"
|
||||
}
|
||||
|
||||
private fun getSubDomain(type: Site): String {
|
||||
|
@ -30,6 +35,7 @@ internal class UrlGenerator(
|
|||
Site.LOGIN -> "cufs"
|
||||
Site.HOME -> "uonetplus"
|
||||
Site.STUDENT -> "uonetplus-uczen"
|
||||
Site.STUDENT_PLUS -> "uonetplus-uczenplus"
|
||||
Site.MESSAGES -> "uonetplus-wiadomosciplus"
|
||||
else -> error("unknown")
|
||||
}
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
package io.github.wulkanowy.sdk.scrapper.mobile
|
||||
|
||||
import io.github.wulkanowy.sdk.scrapper.adapter.CustomDateAdapter
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
data class Device(
|
||||
|
||||
@SerialName("Id")
|
||||
@JsonNames("id")
|
||||
val id: Int = 0,
|
||||
|
||||
@SerialName("IdentyfikatorUrzadzenia")
|
||||
val deviceId: String? = null,
|
||||
|
||||
@SerialName("NazwaUrzadzenia")
|
||||
@JsonNames("nazwa")
|
||||
val name: String? = null,
|
||||
|
||||
@SerialName("DataUtworzenia")
|
||||
@JsonNames("dataCertyfikatu")
|
||||
@Serializable(with = CustomDateAdapter::class)
|
||||
val createDate: LocalDateTime? = null,
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import io.github.wulkanowy.sdk.scrapper.exams.mapExamsList
|
|||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.ScrapperException
|
||||
import io.github.wulkanowy.sdk.scrapper.getSchoolYear
|
||||
import io.github.wulkanowy.sdk.scrapper.getScriptFlag
|
||||
import io.github.wulkanowy.sdk.scrapper.getScriptParam
|
||||
import io.github.wulkanowy.sdk.scrapper.grades.GradePointsSummary
|
||||
import io.github.wulkanowy.sdk.scrapper.grades.GradeRequest
|
||||
|
@ -46,6 +47,7 @@ import io.github.wulkanowy.sdk.scrapper.school.School
|
|||
import io.github.wulkanowy.sdk.scrapper.school.Teacher
|
||||
import io.github.wulkanowy.sdk.scrapper.school.mapToSchool
|
||||
import io.github.wulkanowy.sdk.scrapper.school.mapToTeachers
|
||||
import io.github.wulkanowy.sdk.scrapper.service.StudentPlusService
|
||||
import io.github.wulkanowy.sdk.scrapper.service.StudentService
|
||||
import io.github.wulkanowy.sdk.scrapper.student.StudentInfo
|
||||
import io.github.wulkanowy.sdk.scrapper.student.StudentPhoto
|
||||
|
@ -62,14 +64,25 @@ import io.github.wulkanowy.sdk.scrapper.toFormat
|
|||
import org.jsoup.Jsoup
|
||||
import java.net.HttpURLConnection.HTTP_NOT_FOUND
|
||||
import java.time.LocalDate
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
internal class StudentRepository(private val api: StudentService) {
|
||||
internal class StudentRepository(
|
||||
private val api: StudentService,
|
||||
private val studentPlusService: StudentPlusService,
|
||||
) {
|
||||
|
||||
private var isEduOne: Boolean = false
|
||||
|
||||
private fun LocalDate.toISOFormat(): String = toFormat("yyyy-MM-dd'T00:00:00'")
|
||||
|
||||
private suspend fun getCache(): CacheResponse {
|
||||
if (isEduOne) error("Cache unavailable in eduOne compatibility mode")
|
||||
val startPage = getStartPage()
|
||||
|
||||
isEduOne = getScriptFlag("isEduOne", startPage)
|
||||
if (isEduOne) error("Unsupported eduOne detected!")
|
||||
|
||||
val res = api.getUserCache(
|
||||
token = getScriptParam("antiForgeryToken", startPage),
|
||||
appGuid = getScriptParam("appGuid", startPage),
|
||||
|
@ -102,11 +115,20 @@ internal class StudentRepository(private val api: StudentService) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getAttendance(startDate: LocalDate, endDate: LocalDate?): List<Attendance> {
|
||||
val lessonTimes = getCache().times
|
||||
@Suppress("UnnecessaryOptInAnnotation")
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
suspend fun getAttendance(startDate: LocalDate, endDate: LocalDate?, studentId: Int, diaryId: Int): List<Attendance> {
|
||||
val lessonTimes = runCatching { getCache().times }
|
||||
if (lessonTimes.isFailure && isEduOne) {
|
||||
return studentPlusService.getAttendance(
|
||||
key = Base64.encode("$studentId-$diaryId-1".toByteArray()),
|
||||
from = startDate.toISOFormat(),
|
||||
to = endDate?.toISOFormat() ?: startDate.plusDays(7).toISOFormat(),
|
||||
)
|
||||
}
|
||||
return api.getAttendance(AttendanceRequest(startDate.atStartOfDay()))
|
||||
.handleErrors()
|
||||
.data?.mapAttendanceList(startDate, endDate, lessonTimes).orEmpty()
|
||||
.data?.mapAttendanceList(startDate, endDate, lessonTimes.getOrThrow()).orEmpty()
|
||||
}
|
||||
|
||||
suspend fun getAttendanceSummary(subjectId: Int?): List<AttendanceSummary> {
|
||||
|
|
|
@ -154,6 +154,14 @@ internal class ServiceManager(
|
|||
).create()
|
||||
}
|
||||
|
||||
fun getStudentPlusService(withLogin: Boolean = true, studentInterceptor: Boolean = true): StudentPlusService {
|
||||
return getRetrofit(
|
||||
client = prepareStudentService(withLogin, studentInterceptor),
|
||||
baseUrl = urlGenerator.generate(UrlGenerator.Site.STUDENT_PLUS),
|
||||
json = true,
|
||||
).create()
|
||||
}
|
||||
|
||||
private fun prepareStudentService(withLogin: Boolean, studentInterceptor: Boolean): OkHttpClient.Builder {
|
||||
if (withLogin && schoolId.isBlank()) throw ScrapperException("School id is not set")
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package io.github.wulkanowy.sdk.scrapper.service
|
||||
|
||||
import io.github.wulkanowy.sdk.scrapper.attendance.Attendance
|
||||
import io.github.wulkanowy.sdk.scrapper.conferences.Conference
|
||||
import io.github.wulkanowy.sdk.scrapper.mobile.Device
|
||||
import io.github.wulkanowy.sdk.scrapper.timetable.CacheResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.Url
|
||||
|
||||
internal interface StudentPlusService {
|
||||
|
||||
@GET
|
||||
suspend fun getStart(@Url url: String): String
|
||||
|
||||
@GET("api/Cache")
|
||||
suspend fun getUserCache(): CacheResponse
|
||||
|
||||
@GET("api/Frekwencja")
|
||||
suspend fun getAttendance(
|
||||
@Query("key") key: String,
|
||||
@Query("dataOd") from: String,
|
||||
@Query("dataDo") to: String,
|
||||
): List<Attendance>
|
||||
|
||||
@GET("api/ZarejestrowaneUrzadzenia")
|
||||
suspend fun getRegisteredDevices(): List<Device>
|
||||
|
||||
@GET("api/Zebrania")
|
||||
suspend fun getConferences(): List<Conference>
|
||||
}
|
|
@ -1,23 +1,29 @@
|
|||
package io.github.wulkanowy.sdk.scrapper.timetable
|
||||
|
||||
import io.github.wulkanowy.sdk.scrapper.adapter.CustomDateAdapter
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Serializable
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
internal class CacheResponse {
|
||||
|
||||
@SerialName("isParentUser")
|
||||
@JsonNames("isParent")
|
||||
var isParent: Boolean = false
|
||||
|
||||
@SerialName("poryLekcji")
|
||||
var times: List<Time> = emptyList()
|
||||
|
||||
@SerialName("isMenuOn")
|
||||
@JsonNames("isMenu")
|
||||
var isMenu: Boolean = false
|
||||
|
||||
@SerialName("pokazLekcjeZrealizowane")
|
||||
@JsonNames("isPokazLekcjeZrealizowaneOn")
|
||||
var showCompletedLessons: Boolean = false
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.sdk.scrapper.interceptor.HttpErrorInterceptor
|
|||
import io.github.wulkanowy.sdk.scrapper.login.LoginHelper
|
||||
import io.github.wulkanowy.sdk.scrapper.repository.StudentRepository
|
||||
import io.github.wulkanowy.sdk.scrapper.service.LoginService
|
||||
import io.github.wulkanowy.sdk.scrapper.service.StudentPlusService
|
||||
import io.github.wulkanowy.sdk.scrapper.service.StudentService
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
|
@ -49,7 +50,10 @@ abstract class BaseLocalTest : BaseTest() {
|
|||
internal fun getStudentRepo(loginType: Scrapper.LoginType = Scrapper.LoginType.STANDARD, autoLogin: Boolean = false, responses: (MockWebServer) -> Unit): StudentRepository {
|
||||
responses(server)
|
||||
val okHttp = getOkHttp(errorInterceptor = true, autoLoginInterceptorOn = true, loginType = loginType, autoLogin = autoLogin)
|
||||
return StudentRepository(getService(StudentService::class.java, server.url("/").toString(), false, okHttp))
|
||||
return StudentRepository(
|
||||
api = getService(StudentService::class.java, server.url("/").toString(), false, okHttp),
|
||||
studentPlusService = getService(StudentPlusService::class.java, server.url("/").toString(), false, okHttp),
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
|
|
|
@ -128,7 +128,7 @@ class ScrapperRemoteTest : BaseTest() {
|
|||
|
||||
@Test
|
||||
fun attendanceTest() {
|
||||
val attendance = runBlocking { api.getAttendance(getLocalDate(2018, 10, 1)) }
|
||||
val attendance = runBlocking { api.getAttendance(getLocalDate(2023, 5, 1), getLocalDate(2023, 5, 30)) }
|
||||
|
||||
attendance[0].run {
|
||||
assertEquals(1, number)
|
||||
|
|
|
@ -20,7 +20,7 @@ class AttendanceTest : BaseLocalTest() {
|
|||
it.enqueue("UczenCache.json", RegisterTest::class.java)
|
||||
it.enqueue("Frekwencja.json", AttendanceTest::class.java)
|
||||
}
|
||||
runBlocking { repo.getAttendance(getLocalDate(2018, 10, 1), null) }
|
||||
runBlocking { repo.getAttendance(getLocalDate(2018, 10, 1), null, 1, 1) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -162,7 +162,7 @@ class AttendanceTest : BaseLocalTest() {
|
|||
it.enqueue("UczenCache.json", RegisterTest::class.java)
|
||||
it.enqueue("Frekwencja.json", AttendanceTest::class.java)
|
||||
}
|
||||
runBlocking { repo.getAttendance(getLocalDate(2018, 10, 1), null) }
|
||||
runBlocking { repo.getAttendance(getLocalDate(2018, 10, 1), null, 1, 2) }
|
||||
|
||||
server.takeRequest()
|
||||
server.takeRequest()
|
||||
|
|
Loading…
Reference in a new issue