From 60ad2e81f3370e7866663acfc202e64d5f9ec564 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz <kapi2289@gmail.com> Date: Tue, 24 Dec 2019 15:39:22 +0100 Subject: [PATCH] [API/Edudziennik] Add getting attendance. --- .../edziennik/data/api/Regexes.kt | 56 ++++++----- .../edudziennik/EdudziennikFeatures.kt | 5 + .../edudziennik/data/EdudziennikData.kt | 9 +- .../data/web/EdudziennikWebAttendance.kt | 94 +++++++++++++++++++ .../data/web/EdudziennikWebTimetable.kt | 8 ++ 5 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.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 4bd50da0..ccfd2301 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 @@ -4,6 +4,8 @@ package pl.szczodrzynski.edziennik.data.api +import kotlin.text.RegexOption.DOT_MATCHES_ALL + object Regexes { val STYLE_CSS_COLOR by lazy { """color: \w+?;?"?""".toRegex() @@ -12,69 +14,69 @@ object Regexes { val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy { - """<div.*?>\n*\s*(.+?)\s*\n*(?:<.*?)??</div>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<div.*?>\n*\s*(.+?)\s*\n*(?:<.*?)??</div>""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_GRADES_COLOR by lazy { - """background-color:([#A-Fa-f0-9]+);""".toRegex(RegexOption.DOT_MATCHES_ALL) + """background-color:([#A-Fa-f0-9]+);""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_GRADES_CATEGORY by lazy { - """> (.+?):</span>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """> (.+?):</span>""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_GRADES_CLASS_AVERAGE by lazy { - """Średnia ocen:.*<strong>([0-9]*\.?[0-9]*)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """Średnia ocen:.*<strong>([0-9]*\.?[0-9]*)</strong>""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_GRADES_ADDED_DATE by lazy { - """Wpisano:.*<strong>.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """Wpisano:.*<strong>.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)</strong>""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_GRADES_COUNT_TO_AVG by lazy { - """Liczona do średniej:.*?<strong>nie<br/?></strong>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """Liczona do średniej:.*?<strong>nie<br/?></strong>""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_GRADES_DETAILS by lazy { - """<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?(?:<small>\((.+?)\)</small>.*?)?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>.*?(?:Komentarz:.*?<strong>(.+?)</strong>)?</span>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?(?:<small>\((.+?)\)</small>.*?)?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>.*?(?:Komentarz:.*?<strong>(.+?)</strong>)?</span>""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_EVENT_TYPE by lazy { - """\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(RegexOption.DOT_MATCHES_ALL) + """\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_LUCKY_NUMBER by lazy { - """class="szczesliwy_numerek".*>0*([0-9]+)(?:/0*[0-9]+)*</a>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """class="szczesliwy_numerek".*>0*([0-9]+)(?:/0*[0-9]+)*</a>""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_CLASS_CALENDAR by lazy { """events: (.+),$""".toRegex(RegexOption.MULTILINE) } val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy { - """czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL) + """czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy { - """.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL) + """.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL) } val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy { - """href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(RegexOption.DOT_MATCHES_ALL) + """href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { - """<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_ERROR by lazy { - """id="spanErrorMessage">(.*?)</""".toRegex(RegexOption.DOT_MATCHES_ALL) + """id="spanErrorMessage">(.*?)</""".toRegex(DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME by lazy { - """Imię i nazwisko:.+?">(.+?)</div>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """Imię i nazwisko:.+?">(.+?)</div>""".toRegex(DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy { """id="ctl00_CzyRodzic" value="([01])" />""".toRegex() } val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy { - """name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9/]+)<""".toRegex(RegexOption.DOT_MATCHES_ALL) + """name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9/]+)<""".toRegex(DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy { - """<select.*?name="ctl00\${"$"}dxComboUczniowie".*?</select>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<select.*?name="ctl00\${"$"}dxComboUczniowie".*?</select>""".toRegex(DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { - """<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(DOT_MATCHES_ALL) } @@ -98,6 +100,16 @@ object Regexes { """<span id='user_dn'>(.*?)</span>""".toRegex() } + val EDUDZIENNIK_ATTENDANCE_ENTRIES by lazy { + """<td id="([\d-]+?):(\d+?)".*?>(.+?)</td>""".toRegex() + } + val EDUDZIENNIK_ATTENDANCE_TYPES by lazy { + """<div class="info">.*?<p>(.*?)</p>""".toRegex(DOT_MATCHES_ALL) + } + val EDUDZIENNIK_ATTENDANCE_TYPE by lazy { + """\((.+?)\) (.+)""".toRegex() + } + val EDUDZIENNIK_SUBJECT_ID by lazy { """/Courses/([\w-_]+?)/""".toRegex() } @@ -112,15 +124,15 @@ object Regexes { } val EDUDZIENNIK_SCHOOL_DETAIL_ID by lazy { - """<a id="School_detail".*?/School/([\w-_]+?)/""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<a id="School_detail".*?/School/([\w-_]+?)/""".toRegex(DOT_MATCHES_ALL) } val EDUDZIENNIK_SCHOOL_DETAIL_NAME by lazy { - """</li>.*?<p>(.*?)</p>.*?<li>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """</li>.*?<p>(.*?)</p>.*?<li>""".toRegex(DOT_MATCHES_ALL) } val EDUDZIENNIK_CLASS_DETAIL_ID by lazy { - """<a id="Klass_detail".*?/Klass/([\w-_]+?)/""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<a id="Klass_detail".*?/Klass/([\w-_]+?)/""".toRegex(DOT_MATCHES_ALL) } val EDUDZIENNIK_CLASS_DETAIL_NAME by lazy { - """<a id="Klass_detail".*?>Klasa (.*?)</a>""".toRegex(RegexOption.DOT_MATCHES_ALL) + """<a id="Klass_detail".*?>Klasa (.*?)</a>""".toRegex(DOT_MATCHES_ALL) } } 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 25835d12..33c524fc 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 @@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.models.Feature 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_LUCKY_NUMBER = 1010 val EdudziennikFeatures = listOf( @@ -29,6 +30,10 @@ val EdudziennikFeatures = listOf( ENDPOINT_EDUDZIENNIK_WEB_EXAMS to LOGIN_METHOD_EDUDZIENNIK_WEB ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_ATTENDANCE, listOf( + ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER to LOGIN_METHOD_EDUDZIENNIK_WEB ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)) 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 9567d27b..c48d622c 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 @@ -6,10 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.* -import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.EdudziennikWebExams -import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.EdudziennikWebLuckyNumber -import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.EdudziennikWebStart -import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.EdudziennikWebTimetable +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.* import pl.szczodrzynski.edziennik.utils.Utils class EdudziennikData(val data: DataEdudziennik, val onSuccess: () -> Unit) { @@ -51,6 +48,10 @@ class EdudziennikData(val data: DataEdudziennik, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_exams) EdudziennikWebExams(data, onSuccess) } + ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + EdudziennikWebAttendance(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/EdudziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt new file mode 100644 index 00000000..18df0181 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt @@ -0,0 +1,94 @@ +/* + * 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.Regexes.EDUDZIENNIK_ATTENDANCE_ENTRIES +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPE +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE +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.db.modules.attendance.Attendance +import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class EdudziennikWebAttendance(override val data: DataEdudziennik, + val onSuccess: () -> Unit) : EdudziennikWeb(data) { + companion object { + private const val TAG = "EdudziennikWebAttendance" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.studentEndpoint + "Presence") { text -> + + val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map { + val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim()) + val symbol = type?.get(1)?.trim() + val name = type?.get(2)?.trim() + return@map Triple( + symbol, + name, + when (name?.toLowerCase(Locale.ROOT)) { + "obecność" -> Attendance.TYPE_PRESENT + "nieobecność" -> Attendance.TYPE_ABSENT + "spóźnienie" -> Attendance.TYPE_BELATED + "nieobecność usprawiedliwiona" -> Attendance.TYPE_ABSENT_EXCUSED + "dzień wolny" -> Attendance.TYPE_RELEASED + "brak zajęć" -> Attendance.TYPE_RELEASED + "oddelegowany" -> Attendance.TYPE_RELEASED + else -> Attendance.TYPE_CUSTOM + } + ) + } ?: emptyList() + + EDUDZIENNIK_ATTENDANCE_ENTRIES.findAll(text).forEach { attendanceElement -> + val date = Date.fromY_m_d(attendanceElement[1]) + val lessonNumber = attendanceElement[2].toInt() + val attendanceSymbol = attendanceElement[3] + + val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lesson = lessons.firstOrNull { it.lessonNumber == lessonNumber } + + val id = "${date.stringY_m_d}:$lessonNumber:$attendanceSymbol".crc32() + + val (_, name, type) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol } + ?: return@forEach + + val startTime = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }?.startTime + ?: return@forEach + + val attendanceObject = Attendance( + profileId, + id, + lesson?.displayTeacherId ?: -1, + lesson?.displaySubjectId ?: -1, + profile.currentSemester, + name, + date, + lesson?.displayStartTime ?: startTime, + type + ) + + data.attendanceList.add(attendanceObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ATTENDANCE, + id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS) + onSuccess() + } + } ?: onSuccess() } +} 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 ef06c58e..8ac973f4 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 @@ -13,10 +13,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.Edudzienni import pl.szczodrzynski.edziennik.data.api.models.ApiError 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.lessons.LessonRange import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.get import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.singleOrNull import pl.szczodrzynski.edziennik.splitName import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.models.Date @@ -70,6 +72,12 @@ class EdudziennikWebTimetable(override val data: DataEdudziennik, val startTime = Time.fromH_m(times[0].trim()) val endTime = Time.fromH_m(times[1].trim()) + data.lessonRanges.singleOrNull { + it.lessonNumber == lessonNumber && it.startTime == startTime && it.endTime == endTime + } ?: run { + data.lessonRanges.put(lessonNumber, LessonRange(profileId, lessonNumber, startTime, endTime)) + } + rowElements.subList(2, rowElements.size).forEachIndexed { index, lesson -> val course = lesson.select(".course").firstOrNull() ?: return@forEachIndexed val info = course.select("span > span")