Skip to content

Commit 00998d7

Browse files
committed
Experimental support without studygroups
Signed-off-by: koenidv <[email protected]>
1 parent 949fa94 commit 00998d7

File tree

12 files changed

+240
-113
lines changed

12 files changed

+240
-113
lines changed

app/src/main/java/de/koenidv/sph/database/DatabaseHelper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public void onCreate(SQLiteDatabase db) {
6363
"id_course TEXT, id_post TEXT, name TEXT, date INTEGER, url TEXT, size TEXT," +
6464
"type TEXT, pinned INTEGER, lastUse INTEGER)");
6565
// Create tasks table
66-
db.execSQL("CREATE TABLE postTasks(task_id TEXT PRIMARY KEY, id_course TEXT, id_post TEXT," +
66+
db.execSQL("CREATE TABLE tasks(task_id TEXT PRIMARY KEY, id_course TEXT, id_post TEXT," +
6767
"description TEXT, date INTEGER, isdone INTEGER, pinned INTEGER, dueDate INTEGER)");
6868
// Create link attachments table
6969
db.execSQL("CREATE TABLE linkAttachments(attachment_id TEXT PRIMARY KEY," +
@@ -76,7 +76,7 @@ public void onCreate(SQLiteDatabase db) {
7676
//upgrade Database
7777
@Override
7878
public void onUpgrade(SQLiteDatabase db, int oldversion, int newversion) {
79-
if (oldversion < 2) {
79+
if (oldversion == 1 && newversion == 2) {
8080
db.execSQL("ALTER TABLE postTasks RENAME TO tasks");
8181
}
8282
}

app/src/main/java/de/koenidv/sph/networking/CookieStore.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ object CookieStore : CookieJar {
3535
* @return List of cookies for the specified domain
3636
*/
3737
override fun loadForRequest(url: HttpUrl): List<Cookie> {
38-
if (url.host().contains("schulportal.hessen.de"))
39-
return cookies["schulportal.hessen.de"] ?: ArrayList()
38+
return if (url.host().contains("schulportal.hessen.de"))
39+
cookies["schulportal.hessen.de"] ?: ArrayList()
4040
else
41-
return cookies[url.host()] ?: ArrayList()
41+
cookies[url.host()] ?: ArrayList()
4242

4343
}
4444

app/src/main/java/de/koenidv/sph/networking/NetworkManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,13 @@ class NetworkManager {
156156
// Save parsed courses from timetable
157157
coursesDb.save(RawParser().parseCoursesFromTimetable(responseTimetable!!))
158158
// Secondly, load those courses from study groups to find out where the user belongs
159-
loadSiteWithToken("https://start.schulportal.hessen.de/lerngruppen.php", onComplete = { successStudygroups: Int, responseStudygroups: String? ->
159+
loadSiteWithToken(applicationContext().getString(R.string.url_studygroups), onComplete = { successStudygroups: Int, responseStudygroups: String? ->
160160
if (successStudygroups == SUCCESS) {
161161
// Save parsed courses from study groups
162162
coursesDb.setNulledNotFavorite()
163163
coursesDb.save(RawParser().parseCoursesFromStudygroups(responseStudygroups!!))
164164
// Lastly, load courses again from posts overview to get number ids
165-
loadSiteWithToken("https://start.schulportal.hessen.de/meinunterricht.php", onComplete = { successOverview: Int, responseOverview: String? ->
165+
loadSiteWithToken(applicationContext().getString(R.string.url_allposts), onComplete = { successOverview: Int, responseOverview: String? ->
166166
if (successOverview == SUCCESS) {
167167
// Save parsed courses from posts overview
168168
coursesDb.save(RawParser().parseCoursesFromPostsoverview(responseOverview!!))

app/src/main/java/de/koenidv/sph/parsing/IdParser.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.koenidv.sph.parsing
22

3+
import de.koenidv.sph.R
34
import de.koenidv.sph.database.CoursesDb
45
import de.koenidv.sph.objects.Course
56
import java.text.SimpleDateFormat
@@ -156,6 +157,14 @@ class IdParser {
156157
return classType + "_" + teacherId.toLowerCase(Locale.ROOT) + "_" + filteredCourses.size + 1
157158
}
158159

160+
fun getCourseIdPrefixWithNamedId(namedId: String, teacherId: String): String? {
161+
val prefixMap = Utility().parseStringArray(R.array.namedid_course_prefixes)
162+
return if (prefixMap.containsKey(namedId.substring(0, namedId.indexOf(" "))))
163+
(prefixMap[namedId.substring(0, namedId.indexOf(" "))] +
164+
"_" + teacherId).toLowerCase(Locale.ROOT)
165+
else null
166+
}
167+
159168
/**
160169
* Estimates the type of an course id
161170
* @param courseId Id to check

app/src/main/java/de/koenidv/sph/parsing/RawParser.kt

Lines changed: 122 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package de.koenidv.sph.parsing
22

33
import android.annotation.SuppressLint
44
import android.graphics.Color
5+
import android.util.Log
56
import android.widget.Toast
67
import androidx.core.graphics.toColorInt
78
import de.koenidv.sph.R
9+
import de.koenidv.sph.SphPlanner.Companion.TAG
810
import de.koenidv.sph.SphPlanner.Companion.applicationContext
911
import de.koenidv.sph.database.CoursesDb
1012
import de.koenidv.sph.objects.*
@@ -230,47 +232,56 @@ class RawParser {
230232
*/
231233
fun parseCoursesFromStudygroups(rawResponse: String): List<Course> {
232234
val courses = mutableListOf<Course>()
233-
// Remove stuff we don't need
234-
// There are multiple tables in this page, we'll just take the first one
235-
val rawContent = rawResponse.substring(rawResponse.indexOf("<tbody>") + 7, rawResponse.indexOf("</tbody>"))
236235

237-
/*
236+
// Return empty list if content is invalid
237+
try {
238+
239+
// Remove stuff we don't need
240+
// There are multiple tables in this page, we'll just take the first one
241+
val rawContent = rawResponse.substring(rawResponse.indexOf("<tbody>") + 7, rawResponse.indexOf("</tbody>"))
242+
243+
/*
238244
* It seems as if the sph id index is in the same order as the timetable
239245
* i.e. Q3Gvac03 - GYM is the 3rd History GK in the timetable
240246
* This means that the internal id's should be in the same order as they're taken from it
241247
* Let's just hope the best
242248
*/
243249

244-
val rawContents = rawContent.split("<tr").toMutableList()
245-
rawContents.removeFirst()
250+
val rawContents = rawContent.split("<tr").toMutableList()
251+
rawContents.removeFirst()
252+
253+
var namedId: String
254+
var uniformNamedId: String
255+
var sphId: String
256+
var teacherId: String
257+
var internalId: String
258+
val nameColorMap = Utility().parseStringArray(R.array.course_colors)
259+
260+
// Get data from each table row and save the courses
261+
for (entry in rawContents) {
262+
namedId = entry.substring(Utility().ordinalIndexOf(entry, "<td>", 1) + 4, entry.indexOf("<small>") - 1).trim()
263+
uniformNamedId = CourseParser().parseNamedId(namedId)
264+
sphId = entry.substring(entry.indexOf("<small>") + 8, entry.indexOf("</small>") - 1)
265+
teacherId = entry.substring(Utility().ordinalIndexOf(entry, "<td>", 2))
266+
teacherId = teacherId.substring(teacherId.indexOf("(") + 1, teacherId.indexOf(")")).toLowerCase(Locale.ROOT)
267+
internalId = IdParser().getCourseIdWithSph(sphId, teacherId, entry.contains("LK"))
246268

247-
var namedId: String
248-
var uniformNamedId: String
249-
var sphId: String
250-
var teacherId: String
251-
var internalId: String
252-
val nameColorMap = Utility().parseStringArray(R.array.course_colors)
269+
courses.add(Course(
270+
courseId = internalId,
271+
sph_id = sphId,
272+
named_id = uniformNamedId,
273+
id_teacher = teacherId,
274+
fullname = CourseParser().getFullnameFromInternald(internalId),
275+
isFavorite = true,
276+
isLK = entry.contains("LK"),
277+
color = (nameColorMap[uniformNamedId.substring(0, uniformNamedId.indexOf(" "))]
278+
?: nameColorMap["default"])!!.toColorInt()
279+
))
280+
}
253281

254-
// Get data from each table row and save the courses
255-
for (entry in rawContents) {
256-
namedId = entry.substring(Utility().ordinalIndexOf(entry, "<td>", 1) + 4, entry.indexOf("<small>") - 1).trim()
257-
uniformNamedId = CourseParser().parseNamedId(namedId)
258-
sphId = entry.substring(entry.indexOf("<small>") + 8, entry.indexOf("</small>") - 1)
259-
teacherId = entry.substring(Utility().ordinalIndexOf(entry, "<td>", 2))
260-
teacherId = teacherId.substring(teacherId.indexOf("(") + 1, teacherId.indexOf(")")).toLowerCase(Locale.ROOT)
261-
internalId = IdParser().getCourseIdWithSph(sphId, teacherId, entry.contains("LK"))
262-
263-
courses.add(Course(
264-
courseId = internalId,
265-
sph_id = sphId,
266-
named_id = uniformNamedId,
267-
id_teacher = teacherId,
268-
fullname = CourseParser().getFullnameFromInternald(internalId),
269-
isFavorite = true,
270-
isLK = entry.contains("LK"),
271-
color = (nameColorMap[namedId.substring(0, namedId.indexOf(" "))]
272-
?: nameColorMap["default"])!!.toColorInt()
273-
))
282+
} catch (e: StringIndexOutOfBoundsException) {
283+
Log.d(TAG, "Studygroups parsing failed!")
284+
Log.d(TAG, e.stackTraceToString())
274285
}
275286

276287
return courses
@@ -283,56 +294,93 @@ class RawParser {
283294
*/
284295
fun parseCoursesFromPostsoverview(rawResponse: String): List<Course> {
285296
val courses = mutableListOf<Course>()
286-
// Remove stuff we don't need, get second menu <ul>
287-
@Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER") var rawContent = rawResponse.substring(rawResponse.indexOf("<ul role=\"menu\"") + 15)
288-
rawContent = rawContent.substring(rawContent.indexOf("<ul role=\"menu\""))
289-
rawContent = rawContent.substring(0, rawContent.indexOf("</ul>"))
290-
291-
val rawContents = rawContent.split("<li >").toMutableList()
292-
rawContents.removeFirst() // Trash
293-
rawContents.removeFirst() // Overview link
297+
// Get the courses table
298+
val table = Jsoup.parse(rawResponse).select("#aktuell tbody")
294299

295300
var courseName: String
296-
var uniformCourseName: String
301+
var uniformNamedId: String
297302
var numberId: String
303+
var teacherId: String
304+
var isLK: Boolean
298305
var similiarCourses: List<Course>
299-
// Get values from list
300-
for (entry in rawContents) {
301-
if (entry.contains("a=sus_view&id=")) {
302-
numberId = entry.substring(entry.indexOf("a=sus_view&id=") + 14)
303-
numberId = numberId.substring(0, numberId.indexOf("\""))
304-
courseName = entry.substring(entry.indexOf("</span>") + 7, entry.indexOf("</a>")).trim()
305-
306-
// todo create new courses with teacher_id instead of using old ones: might not be available
307-
308-
// Make named id from post overview uniform
309-
uniformCourseName = CourseParser().parseNamedId(courseName)
310-
311-
// Get courses that might be the same as this one
312-
similiarCourses = CoursesDb.getInstance().getByNamedId(uniformCourseName).toMutableList()
313-
if (courseName.contains("""\(.*\)""".toRegex())) { // if contains text in brackets
314-
val courseToAdd = CoursesDb.getInstance().getBySphId(courseName.substring(courseName.indexOf("(") + 1, courseName.lastIndexOf(")")).replace("-GYM", " - GYM"))
315-
if (courseToAdd != null) similiarCourses.add(courseToAdd)
316-
}
306+
val nameColorMap = Utility().parseStringArray(R.array.course_colors)
307+
// Get values from table
308+
for (row in table.select("tr")) {
309+
numberId = row.attr("data-book")
310+
courseName = row.select("span.name").text()
311+
teacherId = row.select("span.teacher button")
312+
.first().ownText().toLowerCase(Locale.ROOT)
313+
isLK = courseName.contains("LK")
314+
315+
// Make named id from post overview uniform
316+
uniformNamedId = CourseParser().parseNamedId(courseName)
317+
318+
// Get courses that might be the same as this one
319+
similiarCourses = CoursesDb.getInstance().getByNamedId(uniformNamedId).toMutableList()
320+
321+
// If no similiar course was found, try getting all courses with the same subject and teacher
322+
var courseIdPrefix: String? = null
323+
if (similiarCourses.isEmpty()) {
324+
courseIdPrefix = IdParser().getCourseIdPrefixWithNamedId(uniformNamedId, teacherId)
325+
if (courseIdPrefix != null)
326+
similiarCourses.addAll(CoursesDb.getInstance().getByInternalPrefix(courseIdPrefix)
327+
.filter { it.isLK == null || it.isLK == isLK })
328+
}
317329

318-
// Make sure no course is in the list twice
319-
// This might happen because we add both by NamedId and SphId
320-
similiarCourses = similiarCourses.distinct()
321330

322-
when {
323-
similiarCourses.size == 1 -> {
324-
// Only one similiar course, this should be it.
325-
similiarCourses[0].number_id = numberId
326-
courses.add(similiarCourses[0])
327-
}
328-
similiarCourses.isEmpty() -> {
329-
// todo create new course with this namedid & numberid
330-
Toast.makeText(applicationContext(), "No valid course", Toast.LENGTH_SHORT).show()
331+
if (courseName.contains("""\(.*\)""".toRegex())) { // if contains text in brackets
332+
val courseToAdd = CoursesDb.getInstance().getBySphId(courseName.substring(courseName.indexOf("(") + 1, courseName.lastIndexOf(")")).replace("-GYM", " - GYM"))
333+
if (courseToAdd != null) similiarCourses.add(courseToAdd)
334+
}
335+
336+
// Make sure no course is in the list twice
337+
// This might happen because we add both by NamedId and SphId
338+
similiarCourses = similiarCourses.distinct()
339+
340+
when {
341+
similiarCourses.size == 1 -> {
342+
// Only one similiar course, this should be it.
343+
similiarCourses[0].apply {
344+
number_id = numberId
345+
named_id = uniformNamedId
346+
isFavorite = true
347+
fullname = CourseParser().getFullnameFromInternald(similiarCourses[0].courseId)
348+
color = (nameColorMap[
349+
uniformNamedId
350+
.substring(0, uniformNamedId.indexOf(" "))]
351+
?: nameColorMap["default"])!!.toColorInt()
331352
}
332-
similiarCourses.size > 1 -> {
333-
// todo handle multiple similiar courses
334-
Toast.makeText(applicationContext(), "Too many courses", Toast.LENGTH_SHORT).show()
353+
courses.add(similiarCourses[0])
354+
}
355+
similiarCourses.isEmpty() -> {
356+
// Create new course with this namedid & numberid
357+
// If no course could be found, there is no other course with the same internal prefix
358+
// (We tried getting every course by that)
359+
// This means we can just use the prefix - if available with an index 1
360+
if (courseIdPrefix != null) {
361+
courses.add(Course(
362+
courseId = courseIdPrefix + "_1",
363+
named_id = uniformNamedId,
364+
fullname = CourseParser().getFullnameFromInternald(courseIdPrefix + "_1"),
365+
number_id = numberId,
366+
id_teacher = teacherId,
367+
isFavorite = true,
368+
isLK = isLK,
369+
color = (nameColorMap[
370+
uniformNamedId
371+
.substring(0, uniformNamedId.indexOf(" "))]
372+
?: nameColorMap["default"])!!.toColorInt()
373+
))
335374
}
375+
Log.d(TAG, "No valid course for $uniformNamedId")
376+
}
377+
similiarCourses.size > 1 -> {
378+
// todo handle multiple similiar courses
379+
Toast.makeText(applicationContext(), "Too many courses", Toast.LENGTH_LONG).show()
380+
Log.d(TAG, "Too many courses for $uniformNamedId")
381+
// currently adds first course
382+
similiarCourses[0].number_id = numberId
383+
courses.add(similiarCourses[0])
336384
}
337385
}
338386
}

app/src/main/java/de/koenidv/sph/ui/OnboardingSigninFragment.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import de.koenidv.sph.networking.NetworkManager
2727
import de.koenidv.sph.networking.TokenManager
2828
import de.koenidv.sph.parsing.RawParser
2929
import okhttp3.OkHttpClient
30+
import java.util.*
3031

3132

3233
class OnboardingSigninFragment : Fragment() {
@@ -57,7 +58,6 @@ class OnboardingSigninFragment : Fragment() {
5758
val signinButton = view.findViewById<ExtendedFloatingActionButton>(R.id.signinButton)
5859

5960
signinButton.shrink()
60-
// todo default selection for GMB
6161

6262
// Load school names and ids to display them in a spinner
6363
// Hide loading icon and show contents once done
@@ -155,13 +155,17 @@ class OnboardingSigninFragment : Fragment() {
155155
textlayout1.visibility = View.GONE
156156
textlayout2.visibility = View.GONE
157157
signinButton.visibility = View.GONE
158-
(requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0)
158+
// Hide keyboard
159+
(requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
160+
.hideSoftInputFromWindow(view.windowToken, 0)
159161
// Hide the password
160162
passText.transformationMethod = PasswordTransformationMethod()
161163

162164

163165
prefs.edit()
164-
.putString("user", userText.text.toString().replace(" ", "."))
166+
.putString("user", userText.text.toString()
167+
.replace(" ", ".")
168+
.toLowerCase(Locale.ROOT))
165169
.putString("password", passText.text.toString())
166170
// We'll assume schools have been loaded, as ui won't let the user get here otherwise
167171
// We could mitigate this by checking for schoolIds.isNotEmpty()..
@@ -212,6 +216,11 @@ class OnboardingSigninFragment : Fragment() {
212216
}
213217
}
214218
}
219+
220+
// Restore credentials if sign in failed before
221+
userText.setText(prefs.getString("user", ""))
222+
passText.setText(prefs.getString("password", ""))
223+
215224
return view
216225
}
217226
}

0 commit comments

Comments
 (0)