[API/Edudziennik] Move getting grades to other endpoint and add getting subjects.

This commit is contained in:
Kacper Ziubryniewicz 2019-12-25 01:49:37 +01:00
parent d20102c3bd
commit 04103d1c84
7 changed files with 259 additions and 247 deletions

View File

@ -99,6 +99,9 @@ object Regexes {
val EDUDZIENNIK_ACCOUNT_NAME_START by lazy {
"""<span id='user_dn'>(.*?)</span>""".toRegex()
}
val EDUDZIENNIK_SUBJECTS_START by lazy {
"""<a class="menu-course" href="/Students/[\w-_]+?/Courses/([\w-_]+)/">(.+?)</a>""".toRegex()
}
val EDUDZIENNIK_ATTENDANCE_ENTRIES by lazy {
"""<td id="([\d-]+?):(\d+?)".*?>(.+?)</td>""".toRegex()

View File

@ -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(

View File

@ -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)

View File

@ -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()
}
}}
}

View File

@ -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)
}
}
}

View File

@ -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)
})
}}
}

View File

@ -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)
}}
}