mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-04-01 14:24:28 +02:00
[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.
This commit is contained in:
parent
9fdee6e0c7
commit
dd6a2c0979
@ -117,6 +117,17 @@ object Regexes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val MOBIDZIENNIK_TIMETABLE_TOP by lazy {
|
||||||
|
"""<div class="plansc_top">.+?</div></div>""".toRegex(DOT_MATCHES_ALL)
|
||||||
|
}
|
||||||
|
val MOBIDZIENNIK_TIMETABLE_CELL by lazy {
|
||||||
|
"""<div class="plansc_cnt_w" style="(.+?)">.+?style="(.+?)".+?title="(.+?)".+?>\s+(.+?)\s+</div>""".toRegex(DOT_MATCHES_ALL)
|
||||||
|
}
|
||||||
|
val MOBIDZIENNIK_TIMETABLE_LEFT by lazy {
|
||||||
|
"""<div class="plansc_godz">.+?</div></div>""".toRegex(DOT_MATCHES_ALL)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
|
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
|
||||||
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)
|
"""<input type="hidden".+?name="([A-z0-9_]+)?".+?value="([A-z0-9_+-/=]+)?".+?>""".toRegex(DOT_MATCHES_ALL)
|
||||||
|
@ -111,37 +111,6 @@ class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Dat
|
|||||||
val courseStudentEndpoint: String
|
val courseStudentEndpoint: String
|
||||||
get() = "Course/$studentId/"
|
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 {
|
fun getEventType(longId: String, name: String): EventType {
|
||||||
val id = longId.crc16().toLong()
|
val id = longId.crc16().toLong()
|
||||||
return eventTypes.singleOrNull { it.id == id } ?: run {
|
return eventTypes.singleOrNull { it.id == id } ?: run {
|
||||||
|
@ -40,7 +40,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik,
|
|||||||
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
val subjectName = subjectElement.text().trim()
|
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()
|
val dateString = examElement.child(2).text().trim()
|
||||||
if (dateString.isBlank()) return@forEach
|
if (dateString.isBlank()) return@forEach
|
||||||
|
@ -53,7 +53,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
|
|||||||
|
|
||||||
val subjectId = subjectElement.id().trim()
|
val subjectId = subjectElement.id().trim()
|
||||||
val subjectName = subjectElement.child(0).text().trim()
|
val subjectName = subjectElement.child(0).text().trim()
|
||||||
val subject = data.getSubject(subjectId, subjectName)
|
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||||
|
|
||||||
val gradeType = when {
|
val gradeType = when {
|
||||||
subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM
|
subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM
|
||||||
|
@ -41,7 +41,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik,
|
|||||||
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
val subjectName = subjectElement.text()
|
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 lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date)
|
||||||
val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime
|
val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime
|
||||||
|
@ -73,7 +73,7 @@ class EdudziennikWebStart(override val data: DataEdudziennik,
|
|||||||
EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach {
|
EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach {
|
||||||
val id = it[1].trim()
|
val id = it[1].trim()
|
||||||
val name = it[2].trim()
|
val name = it[2].trim()
|
||||||
data.getSubject(id, name)
|
data.getSubject(id.crc32(), name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
|
package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web
|
||||||
|
|
||||||
import org.jsoup.Jsoup
|
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_SUBJECT_ID
|
||||||
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID
|
import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID
|
||||||
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
|
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)
|
val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1)
|
||||||
?: return@forEachIndexed
|
?: return@forEachIndexed
|
||||||
val subjectName = subjectElement.text().trim()
|
val subjectName = subjectElement.text().trim()
|
||||||
val subject = data.getSubject(subjectId, subjectName)
|
val subject = data.getSubject(subjectId.crc32(), subjectName)
|
||||||
|
|
||||||
/* Getting teacher */
|
/* Getting teacher */
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ const val ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE = 2050
|
|||||||
const val ENDPOINT_MOBIDZIENNIK_WEB_MANUALS = 2100
|
const val ENDPOINT_MOBIDZIENNIK_WEB_MANUALS = 2100
|
||||||
const val ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL = 2200
|
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_HOMEWORK = 2300 // not used as an endpoint
|
||||||
|
const val ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE = 2400
|
||||||
const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
|
const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
|
||||||
|
|
||||||
val MobidziennikFeatures = listOf(
|
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.
|
* Agenda - "API" + web scraping.
|
||||||
*/
|
*/
|
||||||
|
@ -84,6 +84,10 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) {
|
|||||||
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
||||||
MobidziennikWebManuals(data, lastSync, onSuccess)
|
MobidziennikWebManuals(data, lastSync, onSuccess)
|
||||||
}*/
|
}*/
|
||||||
|
ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE-> {
|
||||||
|
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
|
||||||
|
MobidziennikWebTimetable(data, lastSync, onSuccess)
|
||||||
|
}
|
||||||
else -> onSuccess(endpointId)
|
else -> onSuccess(endpointId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<ClosedFloatingPointRange<Float>, Date>()
|
||||||
|
private val hoursV = mutableMapOf<Int, Pair<Time, Int?>>()
|
||||||
|
private var startDate: Date
|
||||||
|
|
||||||
|
private fun parseCss(css: String): Map<String, String> {
|
||||||
|
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<String>, 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<String> {
|
||||||
|
return str
|
||||||
|
.replace(whitespaceRegex, " ")
|
||||||
|
.replace("\n", "")
|
||||||
|
.replace("<small>", "$")
|
||||||
|
.replace("</small>", "$")
|
||||||
|
.replace("<br />", "\n")
|
||||||
|
.replace("<br/>", "\n")
|
||||||
|
.replace("<br>", "\n")
|
||||||
|
.replace("<br />", "\n")
|
||||||
|
.replace("<br/>", "\n")
|
||||||
|
.replace("<br>", "\n")
|
||||||
|
.replace("<b>", "%")
|
||||||
|
.replace("</b>", "%")
|
||||||
|
.replace("<span>", "")
|
||||||
|
.replace("</span>", "")
|
||||||
|
.split("\n")
|
||||||
|
.map { it.trim() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("LongLogTag", "LogNotTimber")
|
||||||
|
private fun readLessons(html: String) {
|
||||||
|
val matches = Regexes.MOBIDZIENNIK_TIMETABLE_CELL.findAll(html)
|
||||||
|
|
||||||
|
val noLessonDays = mutableListOf<Date>()
|
||||||
|
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<String?>()
|
||||||
|
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("</small>")) -> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,39 +81,4 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a
|
|||||||
|
|
||||||
val loginShort: String?
|
val loginShort: String?
|
||||||
get() = studentLogin?.split('@')?.get(0)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List<JsonObject>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
val subjectName = grade.getString("SchoolSubject") ?: return@forEach
|
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
|
val addedDate = if (profile.empty) profile.getSemesterStart(semester).inMillis
|
||||||
else System.currentTimeMillis()
|
else System.currentTimeMillis()
|
||||||
|
@ -34,7 +34,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List<JsonObject>) {
|
|||||||
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
||||||
|
|
||||||
val subjectName = grade.getString("SchoolSubject") ?: return@forEach
|
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 }
|
val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis }
|
||||||
?: System.currentTimeMillis()
|
?: System.currentTimeMillis()
|
||||||
|
@ -22,7 +22,13 @@ class PodlasieApiMain(override val data: DataPodlasie,
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json ->
|
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.getInt("LuckyNumber")?.let { PodlasieApiLuckyNumber(data, it) }
|
||||||
json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(data, it) }
|
json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(data, it) }
|
||||||
|
@ -43,14 +43,21 @@ class PodlasieApiTimetable(val data: DataPodlasie, rows: List<JsonObject>) {
|
|||||||
val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) }
|
val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) }
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
val endTime = lesson.getString("TimeTo")?.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
|
?: return@forEach
|
||||||
|
|
||||||
val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach
|
val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach
|
||||||
val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach
|
val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach
|
||||||
val teacher = data.getTeacher(teacherFirstName, teacherLastName)
|
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")
|
val classroom = lesson.getString("Room")
|
||||||
|
|
||||||
Lesson(data.profileId, -1).also {
|
Lesson(data.profileId, -1).also {
|
||||||
|
@ -2,6 +2,7 @@ package pl.szczodrzynski.edziennik.data.api.models
|
|||||||
|
|
||||||
import android.util.LongSparseArray
|
import android.util.LongSparseArray
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
|
import androidx.core.util.set
|
||||||
import androidx.core.util.size
|
import androidx.core.util.size
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import com.google.gson.JsonObject
|
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) {
|
fun startProgress(stringRes: Int) {
|
||||||
callback.onStartProgress(stringRes)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user