From dd6a2c0979c2f01a96bea39726d57d523a7a6f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 9 Sep 2021 23:14:24 +0200 Subject: [PATCH] [API/Mobidziennik] Add Web timetable scrapper. (#66) * [API] Add utils for getting teacher, subject and team by name. * [API/Mobidziennik] Add Web timetable scrapper. * [API/Mobidziennik] Add missing Regexes. --- .../edziennik/data/api/Regexes.kt | 11 + .../edziennik/edudziennik/DataEdudziennik.kt | 31 -- .../data/web/EdudziennikWebExams.kt | 2 +- .../data/web/EdudziennikWebGrades.kt | 2 +- .../data/web/EdudziennikWebHomework.kt | 2 +- .../data/web/EdudziennikWebStart.kt | 2 +- .../data/web/EdudziennikWebTimetable.kt | 3 +- .../mobidziennik/MobidziennikFeatures.kt | 7 + .../mobidziennik/data/MobidziennikData.kt | 4 + .../data/web/MobidziennikWebTimetable.kt | 333 ++++++++++++++++++ .../api/edziennik/podlasie/DataPodlasie.kt | 35 -- .../data/api/PodlasieApiFinalGrades.kt | 2 +- .../podlasie/data/api/PodlasieApiGrades.kt | 2 +- .../podlasie/data/api/PodlasieApiMain.kt | 8 +- .../podlasie/data/api/PodlasieApiTimetable.kt | 11 +- .../edziennik/data/api/models/Data.kt | 105 ++++++ 16 files changed, 484 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt index 5d847e3f..2bbf1894 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -117,6 +117,17 @@ object Regexes { } + val MOBIDZIENNIK_TIMETABLE_TOP by lazy { + """
.+?
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_TIMETABLE_CELL by lazy { + """
.+?style="(.+?)".+?title="(.+?)".+?>\s+(.+?)\s+
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_TIMETABLE_LEFT by lazy { + """
.+?
""".toRegex(DOT_MATCHES_ALL) + } + + val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { """""".toRegex(DOT_MATCHES_ALL) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt index 40601249..49a9d4a0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt @@ -111,37 +111,6 @@ class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Dat val courseStudentEndpoint: String get() = "Course/$studentId/" - fun getSubject(longId: String, name: String): Subject { - val id = longId.crc32() - return subjectList.singleOrNull { it.id == id } ?: run { - val subject = Subject(profileId, id, name, name) - subjectList.put(id, subject) - subject - } - } - - fun getTeacher(firstName: String, lastName: String, longId: String? = null): Teacher { - val name = "$firstName $lastName".fixName() - val id = name.crc32() - return teacherList.singleOrNull { it.id == id }?.also { - if (longId != null && it.loginId == null) it.loginId = longId - } ?: run { - val teacher = Teacher(profileId, id, firstName, lastName, longId) - teacherList.put(id, teacher) - teacher - } - } - - fun getTeacherByFirstLast(nameFirstLast: String, longId: String? = null): Teacher { - val nameParts = nameFirstLast.split(" ") - return getTeacher(nameParts[0], nameParts[1], longId) - } - - fun getTeacherByLastFirst(nameLastFirst: String, longId: String? = null): Teacher { - val nameParts = nameLastFirst.split(" ") - return getTeacher(nameParts[1], nameParts[0], longId) - } - fun getEventType(longId: String, name: String): EventType { val id = longId.crc16().toLong() return eventTypes.singleOrNull { it.id == id } ?: run { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt index f6abf637..c700100c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt @@ -40,7 +40,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik, val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) ?: return@forEach val subjectName = subjectElement.text().trim() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) val dateString = examElement.child(2).text().trim() if (dateString.isBlank()) return@forEach diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt index 459f13aa..6a247290 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt @@ -53,7 +53,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, val subjectId = subjectElement.id().trim() val subjectName = subjectElement.child(0).text().trim() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) val gradeType = when { subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt index 169a5127..b843abe9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt @@ -41,7 +41,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik, val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) ?: return@forEach val subjectName = subjectElement.text() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt index 66cb7a59..3fdd6e0e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt @@ -73,7 +73,7 @@ class EdudziennikWebStart(override val data: DataEdudziennik, EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach { val id = it[1].trim() val name = it[2].trim() - data.getSubject(id, name) + data.getSubject(id.crc32(), name) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt index 94db15ef..33feb75e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.crc32 import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik @@ -89,7 +90,7 @@ class EdudziennikWebTimetable(override val data: DataEdudziennik, val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) ?: return@forEachIndexed val subjectName = subjectElement.text().trim() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) /* Getting teacher */ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt index 7396ac52..7b39e455 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt @@ -18,6 +18,7 @@ const val ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE = 2050 const val ENDPOINT_MOBIDZIENNIK_WEB_MANUALS = 2100 const val ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL = 2200 const val ENDPOINT_MOBIDZIENNIK_WEB_HOMEWORK = 2300 // not used as an endpoint +const val ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE = 2400 const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000 val MobidziennikFeatures = listOf( @@ -38,6 +39,12 @@ val MobidziennikFeatures = listOf( + /** + * Timetable - web scraping - does nothing if the API_MAIN timetable is enough. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_TIMETABLE, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), /** * Agenda - "API" + web scraping. */ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt index 09e1e9fb..b82f6355 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt @@ -84,6 +84,10 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) MobidziennikWebManuals(data, lastSync, onSuccess) }*/ + ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE-> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + MobidziennikWebTimetable(data, lastSync, onSuccess) + } else -> onSuccess(endpointId) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt new file mode 100644 index 00000000..221c50aa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt @@ -0,0 +1,333 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-9-8. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web + +import android.annotation.SuppressLint +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week +import kotlin.collections.set +import kotlin.text.replace + +class MobidziennikWebTimetable( + override val data: DataMobidziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : MobidziennikWeb(data, lastSync) { + companion object { + private const val TAG = "MobidziennikWebTimetable" + } + + private val rangesH = mutableMapOf, Date>() + private val hoursV = mutableMapOf>() + private var startDate: Date + + private fun parseCss(css: String): Map { + return css.split(";").mapNotNull { + val spl = it.split(":") + if (spl.size != 2) + return@mapNotNull null + return@mapNotNull spl[0].trim() to spl[1].trim() + }.toMap() + } + + private fun getRangeH(h: Float): Date? { + return rangesH.entries.firstOrNull { + h in it.key + }?.value + } + + private fun stringToDate(date: String): Date? { + val items = date.split(" ") + val day = items.getOrNull(0)?.toIntOrNull() ?: return null + val year = items.getOrNull(2)?.toIntOrNull() ?: return null + val month = when (items.getOrNull(1)) { + "stycznia" -> 1 + "lutego" -> 2 + "marca" -> 3 + "kwietnia" -> 4 + "maja" -> 5 + "czerwca" -> 6 + "lipca" -> 7 + "sierpnia" -> 8 + "września" -> 9 + "października" -> 10 + "listopada" -> 11 + "grudnia" -> 12 + else -> return null + } + return Date(year, month, day) + } + + init { + val currentWeekStart = Week.getWeekStart() + val nextWeekEnd = Week.getWeekEnd().stepForward(0, 0, 7) + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + startDate = data.arguments?.getString("weekStart")?.let { + Date.fromY_m_d(it) + } ?: currentWeekStart + + val syncFutureDate = startDate > nextWeekEnd + // TODO: 2021-09-09 make DataRemoveModel keep extra lessons + val syncExtraLessons = false && System.currentTimeMillis() - (lastSync ?: 0) > 2 * DAY * MS + if (!syncFutureDate && !syncExtraLessons) { + onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE) + } + else { + val types = when { + syncFutureDate -> mutableListOf("podstawowy")//, "pozalekcyjny") + syncExtraLessons -> mutableListOf("pozalekcyjny") + else -> mutableListOf() + } + + syncTypes(types, startDate) { + // set as synced now only when not syncing future date + // (to avoid waiting 2 days for normal sync after future sync) + if (syncExtraLessons) + data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) + onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE) + } + } + } + + private fun syncTypes(types: MutableList, startDate: Date, onSuccess: () -> Unit) { + if (types.isEmpty()) { + onSuccess() + return + } + val type = types.removeAt(0) + webGet(TAG, "/dziennik/planlekcji?typ=$type&tydzien=${startDate.stringY_m_d}") { html -> + MobidziennikLuckyNumberExtractor(data, html) + readRangesH(html) + readRangesV(html) + readLessons(html) + syncTypes(types, startDate, onSuccess) + } + } + + private fun readRangesH(html: String) { + val htmlH = Regexes.MOBIDZIENNIK_TIMETABLE_TOP.find(html) ?: return + val docH = Jsoup.parse(htmlH.value) + + var posH = 0f + for (el in docH.select("div > div")) { + val css = parseCss(el.attr("style")) + val width = css["width"] + ?.trimEnd('%') + ?.toFloatOrNull() + ?: continue + val value = stringToDate(el.attr("title")) + ?: continue + + val range = posH.rangeTo(posH + width) + posH += width + + rangesH[range] = value + } + } + + private fun readRangesV(html: String) { + val htmlV = Regexes.MOBIDZIENNIK_TIMETABLE_LEFT.find(html) ?: return + val docV = Jsoup.parse(htmlV.value) + + for (el in docV.select("div > div")) { + val css = parseCss(el.attr("style")) + val top = css["top"] + ?.trimEnd('%') + ?.toFloatOrNull() + ?: continue + val values = el.text().split(" ") + + val time = values.getOrNull(0)?.let { + Time.fromH_m(it) + } ?: continue + val num = values.getOrNull(1)?.toIntOrNull() + + hoursV[(top * 100).toInt()] = time to num + } + } + + private val whitespaceRegex = "\\s+".toRegex() + private val classroomRegex = "\\((.*)\\)".toRegex() + private fun cleanup(str: String): List { + return str + .replace(whitespaceRegex, " ") + .replace("\n", "") + .replace("<small>", "$") + .replace("</small>", "$") + .replace("<br />", "\n") + .replace("<br/>", "\n") + .replace("<br>", "\n") + .replace("
", "\n") + .replace("
", "\n") + .replace("
", "\n") + .replace("", "%") + .replace("", "%") + .replace("", "") + .replace("", "") + .split("\n") + .map { it.trim() } + } + + @SuppressLint("LongLogTag", "LogNotTimber") + private fun readLessons(html: String) { + val matches = Regexes.MOBIDZIENNIK_TIMETABLE_CELL.findAll(html) + + val noLessonDays = mutableListOf() + for (i in 0..6) { + noLessonDays.add(startDate.clone().stepForward(0, 0, i)) + } + + for (match in matches) { + val css = parseCss("${match[1]};${match[2]}") + val left = css["left"]?.trimEnd('%')?.toFloatOrNull() ?: continue + val top = css["top"]?.trimEnd('%')?.toFloatOrNull() ?: continue + val width = css["width"]?.trimEnd('%')?.toFloatOrNull() ?: continue + val height = css["height"]?.trimEnd('%')?.toFloatOrNull() ?: continue + + val posH = left + width / 2f + val topInt = (top * 100).toInt() + val bottomInt = ((top + height) * 100).toInt() + + val lessonDate = getRangeH(posH) ?: continue + val (startTime, lessonNumber) = hoursV[topInt] ?: continue + val endTime = hoursV[bottomInt]?.first ?: continue + + noLessonDays.remove(lessonDate) + + var typeName: String? = null + var subjectName: String? = null + var teacherName: String? = null + var classroomName: String? = null + var teamName: String? = null + val items = (cleanup(match[3]) + cleanup(match[4])).toMutableList() + + var length = 0 + while (items.isNotEmpty() && length != items.size) { + length = items.size + val toRemove = mutableListOf() + items.forEachIndexed { i, item -> + when { + item.isEmpty() -> + toRemove.add(item) + item.contains(":") && item.contains(" - ") -> + toRemove.add(item) + + item.startsWith("%") -> { + subjectName = item.trim('%') + toRemove.add(item) + toRemove.add(items[0]) + } + + item.startsWith("&") -> { + typeName = item.trim('&') + toRemove.add(item) + } + typeName != null && (item.contains(typeName!!) || item.contains("")) -> { + toRemove.add(item) + } + + item.contains("(") && item.contains(")") -> { + classroomName = classroomRegex.find(item)?.get(1) + items[i] = item.replace("($classroomName)", "").trim() + } + classroomName != null && item.contains(classroomName!!) -> { + items[i] = item.replace("($classroomName)", "").trim() + } + + item.contains("class=\"wyjatek tooltip\"") -> + toRemove.add(item) + } + } + items.removeAll(toRemove) + } + + if (items.size == 2 && items[0].contains(" - ")) { + val parts = items[0].split(" - ") + teamName = parts[0] + teacherName = parts[1] + } + else if (items.size == 2 && typeName?.contains("odwołana") == true) { + teamName = items[0] + } + else if (items.size == 4) { + teamName = items[0] + teacherName = items[1] + } + + val type = when (typeName) { + "zastępstwo" -> Lesson.TYPE_CHANGE + "lekcja odwołana", "odwołana" -> Lesson.TYPE_CANCELLED + else -> Lesson.TYPE_NORMAL + } + val subject = subjectName?.let { data.getSubject(null, it) } + val teacher = teacherName?.let { data.getTeacherByLastFirst(it) } + val team = teamName?.let { data.getTeam( + id = null, + name = it, + schoolCode = data.loginServerName ?: return@let null, + isTeamClass = false + ) } + + Lesson(data.profileId, -1).also { + it.type = type + if (type == Lesson.TYPE_CANCELLED) { + it.oldDate = lessonDate + it.oldLessonNumber = lessonNumber + it.oldStartTime = startTime + it.oldEndTime = endTime + it.oldSubjectId = subject?.id ?: -1 + it.oldTeamId = team?.id ?: -1 + } + else { + it.date = lessonDate + it.lessonNumber = lessonNumber + it.startTime = startTime + it.endTime = endTime + it.subjectId = subject?.id ?: -1 + it.teacherId = teacher?.id ?: -1 + it.teamId = team?.id ?: -1 + it.classroom = classroomName + } + + it.id = it.buildId() + + val seen = profile?.empty == false || lessonDate < Date.getToday() + + if (it.type != Lesson.TYPE_NORMAL) { + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_LESSON_CHANGE, + it.id, + seen, + seen + ) + ) + } + data.lessonList += it + } + } + + for (date in noLessonDays) { + data.lessonList += Lesson(data.profileId, date.value.toLong()).also { + it.type = Lesson.TYPE_NO_LESSONS + it.date = date + } + } + } +} + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt index d117c139..7962de7e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt @@ -81,39 +81,4 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a val loginShort: String? get() = studentLogin?.split('@')?.get(0) - - fun getSubject(name: String): Subject { - val id = name.crc32() - return subjectList.singleOrNull { it.id == id } ?: run { - val subject = Subject(profileId, id, name, name) - subjectList.put(id, subject) - subject - } - } - - fun getTeacher(firstName: String, lastName: String): Teacher { - val name = "$firstName $lastName".fixName() - return teacherList.singleOrNull { it.fullName == name } ?: run { - val id = name.crc32() - val teacher = Teacher(profileId, id, firstName, lastName) - teacherList.put(id, teacher) - teacher - } - } - - fun getTeam(name: String? = null): Team { - if (name == "cała klasa" || name == null) return teamClass ?: run { - val id = className!!.crc32() - val teamCode = "$schoolShortName:$className" - val team = Team(profileId, id, className, Team.TYPE_CLASS, teamCode, -1) - teamList.put(id, team) - return team - } else { - val id = name.crc32() - val teamCode = "$schoolShortName:$name" - val team = Team(profileId, id, name, Team.TYPE_VIRTUAL, teamCode, -1) - teamList.put(id, team) - return team - } - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt index 7c4575aa..bb4f696c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt @@ -36,7 +36,7 @@ class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List) } val subjectName = grade.getString("SchoolSubject") ?: return@forEach - val subject = data.getSubject(subjectName) + val subject = data.getSubject(null, subjectName) val addedDate = if (profile.empty) profile.getSemesterStart(semester).inMillis else System.currentTimeMillis() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt index d5696567..c3946c76 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt @@ -34,7 +34,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List) { val teacher = data.getTeacher(teacherFirstName, teacherLastName) val subjectName = grade.getString("SchoolSubject") ?: return@forEach - val subject = data.getSubject(subjectName) + val subject = data.getSubject(null, subjectName) val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt index 6fe77b5c..f108d35e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt @@ -22,7 +22,13 @@ class PodlasieApiMain(override val data: DataPodlasie, init { apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json -> - data.getTeam() // Save the class team when it doesn't exist. + // Save the class team when it doesn't exist. + data.getTeam( + id = null, + name = data.className ?: "", + schoolCode = data.schoolShortName ?: "", + isTeamClass = true + ) json.getInt("LuckyNumber")?.let { PodlasieApiLuckyNumber(data, it) } json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(data, it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt index a9a12d5d..13cff861 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt @@ -43,14 +43,21 @@ class PodlasieApiTimetable(val data: DataPodlasie, rows: List) { val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) } ?: return@forEach val endTime = lesson.getString("TimeTo")?.let { Time.fromH_m_s(it) } ?: return@forEach - val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(it) } + val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(null, it) } ?: return@forEach val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach val teacher = data.getTeacher(teacherFirstName, teacherLastName) - val team = lesson.getString("Group")?.let { data.getTeam(it) } ?: return@forEach + val team = lesson.getString("Group")?.let { + data.getTeam( + id = null, + name = it, + schoolCode = data.schoolShortName ?: "", + isTeamClass = it == "cała klasa" + ) + } ?: return@forEach val classroom = lesson.getString("Room") Lesson(data.profileId, -1).also { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt index 8dda9543..2b7bdf1a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt @@ -2,6 +2,7 @@ package pl.szczodrzynski.edziennik.data.api.models import android.util.LongSparseArray import android.util.SparseArray +import androidx.core.util.set import androidx.core.util.size import androidx.room.OnConflictStrategy import com.google.gson.JsonObject @@ -376,4 +377,108 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt fun startProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + + /* _ _ _ _ _ + | | | | | (_) | + | | | | |_ _| |___ + | | | | __| | / __| + | |__| | |_| | \__ \ + \____/ \__|_|_|__*/ + fun getSubject(id: Long?, name: String, shortName: String = name): Subject { + var subject = subjectList.singleOrNull { it.id == id } + if (subject == null) + subject = subjectList.singleOrNull { it.longName == name } + if (subject == null) + subject = subjectList.singleOrNull { it.shortName == name } + + if (subject == null) { + subject = Subject( + profileId, + id ?: name.crc32(), + name, + shortName + ) + subjectList[subject.id] = subject + } + return subject + } + + fun getTeam(id: Long?, name: String, schoolCode: String, isTeamClass: Boolean = false): Team { + if (isTeamClass && teamClass != null) + return teamClass as Team + var team = teamList.singleOrNull { it.id == id } + + val namePlain = name.replace(" ", "") + if (team == null) + team = teamList.singleOrNull { it.name.replace(" ", "") == namePlain } + + if (team == null) { + team = Team( + profileId, + id ?: name.crc32(), + name, + if (isTeamClass) Team.TYPE_CLASS else Team.TYPE_VIRTUAL, + "$schoolCode:$name", + -1 + ) + teamList[team.id] = team + } + return team + } + + fun getTeacher(firstName: String, lastName: String, loginId: String? = null): Teacher { + val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" } + return validateTeacher(teacher, firstName, lastName, loginId) + } + + fun getTeacher(firstNameChar: Char, lastName: String, loginId: String? = null): Teacher { + val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" } + return validateTeacher(teacher, firstNameChar.toString(), lastName, loginId) + } + + fun getTeacherByLastFirst(nameLastFirst: String, loginId: String? = null): Teacher { + val nameParts = nameLastFirst.split(" ") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[1], nameParts[0], loginId) + } + + fun getTeacherByFirstLast(nameFirstLast: String, loginId: String? = null): Teacher { + val nameParts = nameFirstLast.split(" ") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[0], nameParts[1], loginId) + } + + fun getTeacherByFDotLast(nameFDotLast: String, loginId: String? = null): Teacher { + val nameParts = nameFDotLast.split(".") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[0][0], nameParts[1], loginId) + } + + fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String, loginId: String? = null): Teacher { + val nameParts = nameFDotSpaceLast.split(".") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[0][0], nameParts[1], loginId) + } + + private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String, loginId: String?): Teacher { + val obj = teacher ?: Teacher(profileId, -1, firstName, lastName, loginId).apply { + id = fullName.crc32() + teacherList[id] = this + } + return obj.also { + if (loginId != null && it.loginId != null) + it.loginId = loginId + if (firstName.length > 1) + it.name = firstName + it.surname = lastName + } + } }