From 04103d1c845ecb45890dc4c101f2ae14ace91b5d Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Wed, 25 Dec 2019 01:49:37 +0100 Subject: [PATCH] [API/Edudziennik] Move getting grades to other endpoint and add getting subjects. --- .../edziennik/data/api/Regexes.kt | 3 + .../edudziennik/EdudziennikFeatures.kt | 3 +- .../edudziennik/data/EdudziennikData.kt | 4 + .../data/web/EdudziennikWebGrades.kt | 196 ++++++++++++++++++ .../data/web/EdudziennikWebStart.kt | 61 +++++- .../data/web/EdudziennikWebStartGrades.kt | 185 ----------------- .../data/web/EdudziennikWebStartInfo.kt | 54 ----- 7 files changed, 259 insertions(+), 247 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartGrades.kt delete mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartInfo.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 952ca176..a4f20f9f 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 @@ -99,6 +99,9 @@ object Regexes { val EDUDZIENNIK_ACCOUNT_NAME_START by lazy { """(.*?)""".toRegex() } + val EDUDZIENNIK_SUBJECTS_START by lazy { + """(.+?)""".toRegex() + } val EDUDZIENNIK_ATTENDANCE_ENTRIES by lazy { """(.+?)""".toRegex() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt index 33c524fc..f98192c6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt @@ -11,6 +11,7 @@ const val ENDPOINT_EDUDZIENNIK_WEB_START = 1000 const val ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE = 1001 const val ENDPOINT_EDUDZIENNIK_WEB_EXAMS = 1002 const val ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE = 1003 +const val ENDPOINT_EDUDZIENNIK_WEB_GRADES = 1004 const val ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER = 1010 val EdudziennikFeatures = listOf( @@ -23,7 +24,7 @@ val EdudziennikFeatures = listOf( ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_GRADES, listOf( - ENDPOINT_EDUDZIENNIK_WEB_START to LOGIN_METHOD_EDUDZIENNIK_WEB + ENDPOINT_EDUDZIENNIK_WEB_GRADES to LOGIN_METHOD_EDUDZIENNIK_WEB ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_AGENDA, listOf( diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt index c48d622c..1d94b459 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt @@ -52,6 +52,10 @@ class EdudziennikData(val data: DataEdudziennik, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_attendance) EdudziennikWebAttendance(data, onSuccess) } + ENDPOINT_EDUDZIENNIK_WEB_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + EdudziennikWebGrades(data, onSuccess) + } ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER -> { data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) EdudziennikWebLuckyNumber(data, onSuccess) 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 new file mode 100644 index 00000000..8e6e92fe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import android.graphics.Color +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.colorFromCssName +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade +import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.* +import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebGrades(override val data: DataEdudziennik, + val onSuccess: () -> Unit) : EdudziennikWeb(data) { + companion object { + private const val TAG = "EdudziennikWebGrades" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.studentEndpoint + "start") { text -> + val doc = Jsoup.parse(text) + + val subjects = doc.select("#student_grades tbody").firstOrNull()?.children() + + subjects?.forEach { subjectElement -> + if (subjectElement.id().isBlank()) return@forEach + + val subjectId = subjectElement.id().trim() + val subjectName = subjectElement.child(0).text().trim() + val subject = data.getSubject(subjectId, subjectName) + + val grades = subjectElement.select(".grade") + val gradesInfo = subjectElement.select(".grade-tip") + + val gradeValues = subjects.select(".avg-$subjectId .grade-tip > p").first() + .text().split('+').map { + val split = it.split('*') + val weight = split[0].trim().toFloat() + val value = split[1].trim().toFloat() + + Pair(value, weight) + } + + grades.forEachIndexed { index, gradeElement -> + val id = Regexes.EDUDZIENNIK_GRADE_ID.find(gradeElement.attr("href"))?.get(1)?.crc32() + ?: return@forEachIndexed + val (value, weight) = gradeValues[index] + val name = gradeElement.text().trim().let { + if (it.contains(',') || it.contains('.')) { + val replaced = it.replace(',', '.') + val float = replaced.toFloatOrNull() + + if (float != null && float % 1 == 0f) float.toInt().toString() + else it + } else it + } + + val info = gradesInfo[index] + val category = info.child(4).text().trim() + + val (teacherLastName, teacherFirstName) = info.child(1).text().split(' ') + val teacher = data.getTeacher(teacherFirstName, teacherLastName) + + val addedDate = info.child(2).text().split(' ').let { + val day = it[0].toInt() + val month = Utils.monthFromName(it[1]) + val year = it[2].toInt() + + Date(year, month, day).inMillis + } + + val color = Regexes.STYLE_CSS_COLOR.find(gradeElement.attr("style"))?.get(1)?.let { + if (it.startsWith('#')) Color.parseColor(it) + else colorFromCssName(it) + } ?: -1 + + val gradeObject = Grade( + profileId, + id, + category, + color, + "", + name, + value, + weight, + profile.currentSemester, + teacher.id, + subject.id + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + val proposed = subjectElement.select(".proposal").firstOrNull()?.text()?.trim() + + if (proposed != null && proposed.isNotBlank()) { + val proposedGradeObject = Grade( + profileId, + (-1 * subject.id) - 1, + "", + -1, + "", + proposed, + proposed.toFloatOrNull() ?: 0f, + 0f, + profile.currentSemester, + -1, + subject.id + ).apply { + type = when (semester) { + 1 -> TYPE_SEMESTER1_PROPOSED + else -> TYPE_SEMESTER2_PROPOSED + } + } + + data.gradeList.add(proposedGradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + proposedGradeObject.id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + + val final = subjectElement.select(".final").firstOrNull()?.text()?.trim() + + if (final != null && final.isNotBlank()) { + val finalGradeObject = Grade( + profileId, + (-1 * subject.id) - 2, + "", + -1, + "", + final, + final.toFloatOrNull() ?: 0f, + 0f, + profile.currentSemester, + -1, + subject.id + ).apply { + type = when (semester) { + 1 -> TYPE_SEMESTER1_FINAL + else -> TYPE_SEMESTER2_FINAL + } + } + + data.gradeList.add(finalGradeObject) + data.metadataList.add(Metadata( + data.profileId, + Metadata.TYPE_GRADE, + finalGradeObject.id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + } + + if (!subjects.isNullOrEmpty()) { + data.toRemove.addAll(listOf( + TYPE_NORMAL, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_PROPOSED, + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER2_FINAL + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_GRADES, SYNC_ALWAYS) + onSuccess() + } + }} +} 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 bcaba702..e21d85da 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 @@ -4,11 +4,18 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web -import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.MONTH +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.ERROR_EDUDZIENNIK_WEB_TEAM_MISSING +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECTS_START import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_START import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb -import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.modules.teams.Team +import pl.szczodrzynski.edziennik.firstLettersName +import pl.szczodrzynski.edziennik.get class EdudziennikWebStart(override val data: DataEdudziennik, val onSuccess: () -> Unit) : EdudziennikWeb(data) { @@ -18,13 +25,53 @@ class EdudziennikWebStart(override val data: DataEdudziennik, init { webGet(TAG, data.studentEndpoint + "start") { text -> - val doc = Jsoup.parse(text) + getSchoolAndTeam(text) + getSubjects(text) - EdudziennikWebStartInfo(data, text) - EdudziennikWebStartGrades(data, doc) - - data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_START, SYNC_ALWAYS) + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_START, MONTH) onSuccess() } } + + private fun getSchoolAndTeam(text: String) { + val schoolId = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_ID.find(text)?.get(1)?.trim() + val schoolLongName = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_NAME.find(text)?.get(1)?.trim() + data.schoolId = schoolId + + val classId = Regexes.EDUDZIENNIK_CLASS_DETAIL_ID.find(text)?.get(1)?.trim() + val className = Regexes.EDUDZIENNIK_CLASS_DETAIL_NAME.find(text)?.get(1)?.trim() + data.classId = classId + + if (classId == null || className == null || schoolId == null || schoolLongName == null) { + data.error(ApiError(TAG, ERROR_EDUDZIENNIK_WEB_TEAM_MISSING) + .withApiResponse(text)) + return + } + + val schoolName = schoolId.crc32().toString() + schoolLongName.firstLettersName + "_edu" + data.schoolName = schoolName + + val teamId = classId.crc32() + val teamCode = "$schoolName:$className" + + val teamObject = Team( + data.profileId, + teamId, + className, + Team.TYPE_CLASS, + teamCode, + -1 + ) + + data.teamClass = teamObject + data.teamList.put(teamObject.id, teamObject) + } + + private fun getSubjects(text: String) { + EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach { + val id = it[1].trim() + val name = it[2].trim() + data.getSubject(id, name) + } + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartGrades.kt deleted file mode 100644 index 974e2332..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartGrades.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-12-24 - */ - -package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web - -import android.graphics.Color -import org.jsoup.nodes.Document -import pl.szczodrzynski.edziennik.colorFromCssName -import pl.szczodrzynski.edziennik.crc32 -import pl.szczodrzynski.edziennik.data.api.Regexes -import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik -import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade -import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.* -import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata -import pl.szczodrzynski.edziennik.get -import pl.szczodrzynski.edziennik.utils.Utils -import pl.szczodrzynski.edziennik.utils.models.Date - -class EdudziennikWebStartGrades(val data: DataEdudziennik, val doc: Document) { - companion object { - const val TAG = "EdudziennikWebStartGrades" - } - - init {data.profile?.also { profile -> - val subjects = doc.select("#student_grades tbody").firstOrNull()?.children() - - if (subjects.isNullOrEmpty()) return@also - - subjects.forEach { subjectElement -> - if (subjectElement.id().isBlank()) return@forEach - - val subjectId = subjectElement.id().trim() - val subjectName = subjectElement.child(0).text().trim() - val subject = data.getSubject(subjectId, subjectName) - - val grades = subjectElement.select(".grade") - val gradesInfo = subjectElement.select(".grade-tip") - - val gradeValues = subjects.select(".avg-$subjectId .grade-tip > p").first() - .text().split('+').map { - val split = it.split('*') - val weight = split[0].trim().toFloat() - val value = split[1].trim().toFloat() - - Pair(value, weight) - } - - grades.forEachIndexed { index, gradeElement -> - val id = Regexes.EDUDZIENNIK_GRADE_ID.find(gradeElement.attr("href"))?.get(1)?.crc32() - ?: return@forEachIndexed - val (value, weight) = gradeValues[index] - val name = gradeElement.text().trim().let { - if (it.contains(',') || it.contains('.')) { - val replaced = it.replace(',', '.') - val float = replaced.toFloatOrNull() - - if (float != null && float % 1 == 0f) float.toInt().toString() - else it - } else it - } - - val info = gradesInfo[index] - val category = info.child(4).text().trim() - - val (teacherLastName, teacherFirstName) = info.child(1).text().split(' ') - val teacher = data.getTeacher(teacherFirstName, teacherLastName) - - val addedDate = info.child(2).text().split(' ').let { - val day = it[0].toInt() - val month = Utils.monthFromName(it[1]) - val year = it[2].toInt() - - Date(year, month, day).inMillis - } - - val color = Regexes.STYLE_CSS_COLOR.find(gradeElement.attr("style"))?.get(1)?.let { - if (it.startsWith('#')) Color.parseColor(it) - else colorFromCssName(it) - } ?: -1 - - val gradeObject = Grade( - data.profileId, - id, - category, - color, - "", - name, - value, - weight, - profile.currentSemester, - teacher.id, - subject.id - ) - - data.gradeList.add(gradeObject) - data.metadataList.add(Metadata( - data.profileId, - Metadata.TYPE_GRADE, - id, - profile.empty, - profile.empty, - addedDate - )) - } - - val proposed = subjectElement.select(".proposal").firstOrNull()?.text()?.trim() - - if (proposed != null && proposed.isNotBlank()) { - val proposedGradeObject = Grade( - data.profileId, - (-1 * subject.id) - 1, - "", - -1, - "", - proposed, - proposed.toFloatOrNull() ?: 0f, - 0f, - profile.currentSemester, - -1, - subject.id - ).apply { - type = when (semester) { - 1 -> TYPE_SEMESTER1_PROPOSED - else -> TYPE_SEMESTER2_PROPOSED - } - } - - data.gradeList.add(proposedGradeObject) - data.metadataList.add(Metadata( - data.profileId, - Metadata.TYPE_GRADE, - proposedGradeObject.id, - profile.empty, - profile.empty, - System.currentTimeMillis() - )) - } - - val final = subjectElement.select(".final").firstOrNull()?.text()?.trim() - - if (final != null && final.isNotBlank()) { - val finalGradeObject = Grade( - data.profileId, - (-1 * subject.id) - 2, - "", - -1, - "", - final, - final.toFloatOrNull() ?: 0f, - 0f, - profile.currentSemester, - -1, - subject.id - ).apply { - type = when (semester) { - 1 -> TYPE_SEMESTER1_FINAL - else -> TYPE_SEMESTER2_FINAL - } - } - - data.gradeList.add(finalGradeObject) - data.metadataList.add(Metadata( - data.profileId, - Metadata.TYPE_GRADE, - finalGradeObject.id, - profile.empty, - profile.empty, - System.currentTimeMillis() - )) - } - } - - data.toRemove.addAll(listOf( - TYPE_NORMAL, - TYPE_SEMESTER1_PROPOSED, - TYPE_SEMESTER2_PROPOSED, - TYPE_SEMESTER1_FINAL, - TYPE_SEMESTER2_FINAL - ).map { - DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) - }) - }} -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartInfo.kt deleted file mode 100644 index c9f82efe..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStartInfo.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2019-12-24 - */ - -package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web - -import pl.szczodrzynski.edziennik.crc32 -import pl.szczodrzynski.edziennik.data.api.ERROR_EDUDZIENNIK_WEB_TEAM_MISSING -import pl.szczodrzynski.edziennik.data.api.Regexes -import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik -import pl.szczodrzynski.edziennik.data.api.models.ApiError -import pl.szczodrzynski.edziennik.data.db.modules.teams.Team -import pl.szczodrzynski.edziennik.firstLettersName -import pl.szczodrzynski.edziennik.get - -class EdudziennikWebStartInfo(val data: DataEdudziennik, val text: String) { - companion object { - const val TAG = "EdudziennikWebStartInfo" - } - - init { run { - val schoolId = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_ID.find(text)?.get(1)?.trim() - val schoolLongName = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_NAME.find(text)?.get(1)?.trim() - data.schoolId = schoolId - - val classId = Regexes.EDUDZIENNIK_CLASS_DETAIL_ID.find(text)?.get(1)?.trim() - val className = Regexes.EDUDZIENNIK_CLASS_DETAIL_NAME.find(text)?.get(1)?.trim() - data.classId = classId - - if (classId == null || className == null || schoolId == null || schoolLongName == null) { - data.error(ApiError(TAG, ERROR_EDUDZIENNIK_WEB_TEAM_MISSING) - .withApiResponse(text)) - return@run - } - - val schoolName = schoolId.crc32().toString() + schoolLongName.firstLettersName + "_edu" - data.schoolName = schoolName - - val teamId = classId.crc32() - val teamCode = "$schoolName:$className" - - val teamObject = Team( - data.profileId, - teamId, - className, - Team.TYPE_CLASS, - teamCode, - -1 - ) - - data.teamClass = teamObject - data.teamList.put(teamObject.id, teamObject) - }} -}