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 {
-        """>&nbsp;(.+?):</span>""".toRegex(RegexOption.DOT_MATCHES_ALL)
+        """>&nbsp;(.+?):</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")