From d619e25e71689b7c0e2201ea83a936d9088ca0db Mon Sep 17 00:00:00 2001 From: Sweetbread Date: Tue, 22 Apr 2025 18:07:35 +0300 Subject: [PATCH] fix: apply API changes --- app/src/main/java/ru/sweetbread/unn/API.kt | 117 +++++------ app/src/main/java/ru/sweetbread/unn/UNNApp.kt | 20 +- app/src/main/java/ru/sweetbread/unn/ui/API.kt | 181 ++++++++++++++++++ .../ru/sweetbread/unn/ui/composes/Blogpost.kt | 1 - gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 6 files changed, 256 insertions(+), 67 deletions(-) create mode 100644 app/src/main/java/ru/sweetbread/unn/ui/API.kt diff --git a/app/src/main/java/ru/sweetbread/unn/API.kt b/app/src/main/java/ru/sweetbread/unn/API.kt index 7144d8b..856cbcd 100644 --- a/app/src/main/java/ru/sweetbread/unn/API.kt +++ b/app/src/main/java/ru/sweetbread/unn/API.kt @@ -19,11 +19,18 @@ import ru.sweetbread.unn.db.loadSchedule import ru.sweetbread.unn.db.loadUserByBitrixId import ru.sweetbread.unn.ui.layout.LoginData import ru.sweetbread.unn.ui.layout.client +import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime +import java.time.ZoneId import java.time.format.DateTimeFormatter +/* + vuzAPI: + /profile/current -> Gets profile of current user + */ + private lateinit var PHPSESSID: String private lateinit var CSRF: String lateinit var ME: User @@ -31,6 +38,7 @@ lateinit var ME: User const val portalURL = "https://portal.unn.ru" const val ruzapiURL = "$portalURL/ruzapi" const val vuzapiURL = "$portalURL/bitrix/vuz/api" +const val prtl2URL = "$portalURL/portal2/api" const val restURL = "$portalURL/rest" enum class Type(val s: String) { @@ -109,7 +117,6 @@ class User( class Post( val id: Int, - val blogId: Int, val authorId: Int, val enableComments: Boolean, val numComments: Int, @@ -147,12 +154,11 @@ suspend fun auth( } ) if (r.status.value == 302) { + Log.d("tmp", "158") PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1] - GlobalScope.launch(Dispatchers.IO) { getMyself(login) getCSRF() - } return true } return false @@ -162,42 +168,45 @@ suspend fun auth( * Save info about current [User] in memory */ private suspend fun getMyself(login: String) { - GlobalScope.launch(Dispatchers.IO) { - val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo") { - parameter("uns", login.substring(1)) - }.bodyAsText()) + Log.d("tmp", "getMyself") + // WARNING: trailing / is important, 'cuz API devs are eating shit + val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo/") { + header("Cookie", "PHPSESSID=$PHPSESSID") + parameter("uns", login.drop(1)) + }.bodyAsText()) - val user = JSONObject( - client.get("$vuzapiURL/user") { - header("Cookie", "PHPSESSID=$PHPSESSID") - }.bodyAsText() - ) + val user = JSONObject( + client.get("$vuzapiURL/user") { + header("Cookie", "PHPSESSID=$PHPSESSID") + }.bodyAsText() + ) - ME = User( - unnId = studentinfo.getString("id").toInt(), - bitrixId = user.getInt("bitrix_id"), - userId = user.getInt("id"), - type = when (studentinfo.getString("type")) { - "lecturer" -> Type.Lecturer // ig,,, - else -> Type.Student - }, - email = user.getString("email"), - nameRu = user.getString("fullname"), - nameEn = user.getString("fullname_en"), - isMale = user.getString("sex") == "M", - birthday = LocalDate.parse( - user.getString("birthdate"), - DateTimeFormatter.ofPattern("yyyy-MM-dd") - ), - avatar = user.getJSONObject("photo").let { - AvatarSet( - it.getString("orig"), - it.getString("thumbnail"), - it.getString("small"), - ) - } - ) - } + Log.d("studentInfo", studentinfo.toString(2)) + + ME = User( + unnId = studentinfo.getString("id").toInt(), + bitrixId = user.getInt("bitrix_id"), + userId = user.getInt("id"), + type = when (studentinfo.getString("type")) { + "lecturer" -> Type.Lecturer // ig,,, + else -> Type.Student + }, + email = user.getString("email"), + nameRu = user.getString("fullname"), + nameEn = user.getString("fullname_en"), + isMale = user.getString("sex") == "M", + birthday = Instant + .parse(user.getString("birthdate")) + .atZone(ZoneId.of("Europe/Moscow")) + .toLocalDate(), + avatar = user.getJSONObject("photo").let { + AvatarSet( + it.getString("orig"), + it.getString("thumbnail"), + it.getString("small"), + ) + } + ) } suspend fun getScheduleDay( @@ -223,7 +232,7 @@ suspend fun getSchedule( start: LocalDate, finish: LocalDate ): ArrayList { - val unnDatePattern = DateTimeFormatter.ofPattern("yyyy.MM.dd") + val unnDatePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd") val r = client.get("$ruzapiURL/schedule/${type.s}/$id") { parameter("start", start.format(unnDatePattern)) @@ -302,36 +311,36 @@ suspend fun getSchedule( } suspend fun getCSRF() { + Log.d("tmp", "getCSRF") val r = client.get("$restURL/log.blogpost.get") { header("Cookie", "PHPSESSID=$PHPSESSID") parameter("sessid", "") } CSRF = JSONObject(r.bodyAsText()).getString("sessid") + Log.d("tmp", "end getCSRF") } suspend fun getBlogposts(): ArrayList { - val r = client.get("$restURL/log.blogpost.get") { + val r = client.get("$prtl2URL/news.php") { header("Cookie", "PHPSESSID=$PHPSESSID") - parameter("sessid", CSRF) + header("x-bitrix-sessid-token", CSRF) } - val json = JSONObject(r.bodyAsText()) - val result = json.getJSONArray("result") + val result = JSONArray(r.bodyAsText()) val out = arrayListOf() for (i in 0 until result.length()) { val el = result.getJSONObject(i) out.add( Post( - id = el.getString("ID").toInt(), - blogId = el.getString("BLOG_ID").toInt(), - authorId = el.getString("AUTHOR_ID").toInt(), - enableComments = el.getString("ENABLE_COMMENTS") == "Y", - numComments = el.getString("NUM_COMMENTS").toInt(), + id = el.getString("id").toInt(), + authorId = el.getJSONObject("author").getInt("id").toInt(), + enableComments = true, // FIXME: Delete the field or get correct value + numComments = el.getString("commentsnum").toInt(), date = LocalDateTime.parse( - el.getString("DATE_PUBLISH"), - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'+03:00'") + el.getString("time"), + DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss") ), - content = el.getString("DETAIL_TEXT") + content = el.getString("fulltext") ) ) } @@ -375,10 +384,10 @@ suspend fun getUser(id: Int): User { nameRu = json.getString("fullname"), nameEn = json.getString("fullname_en"), isMale = json.getString("sex") == "M", - birthday = LocalDate.parse( - json.getString("birthdate"), - DateTimeFormatter.ofPattern("yyyy-MM-dd") - ), + birthday = Instant + .parse(json.getString("birthdate")) + .atZone(ZoneId.of("Europe/Moscow")) + .toLocalDate(), avatar = json.getJSONObject("photo").let { AvatarSet( it.getString("orig"), diff --git a/app/src/main/java/ru/sweetbread/unn/UNNApp.kt b/app/src/main/java/ru/sweetbread/unn/UNNApp.kt index b5b20e6..4ce7f50 100644 --- a/app/src/main/java/ru/sweetbread/unn/UNNApp.kt +++ b/app/src/main/java/ru/sweetbread/unn/UNNApp.kt @@ -11,15 +11,15 @@ class UNNApp : Application() { override fun attachBaseContext(base: Context) { super.attachBaseContext(base) - initAcra { - buildConfigClass = BuildConfig::class.java - reportFormat = StringFormat.JSON - httpSender { - uri = BuildConfig.ACRA_URL - basicAuthLogin = BuildConfig.ACRA_LOGIN - basicAuthPassword = BuildConfig.ACRA_PASS - httpMethod = HttpSender.Method.POST - } - } +// initAcra { +// buildConfigClass = BuildConfig::class.java +// reportFormat = StringFormat.JSON +// httpSender { +// uri = BuildConfig.ACRA_URL +// basicAuthLogin = BuildConfig.ACRA_LOGIN +// basicAuthPassword = BuildConfig.ACRA_PASS +// httpMethod = HttpSender.Method.POST +// } +// } } } \ No newline at end of file diff --git a/app/src/main/java/ru/sweetbread/unn/ui/API.kt b/app/src/main/java/ru/sweetbread/unn/ui/API.kt new file mode 100644 index 0000000..c9df8cb --- /dev/null +++ b/app/src/main/java/ru/sweetbread/unn/ui/API.kt @@ -0,0 +1,181 @@ +package ru.sweetbread.unn.ui + +import android.util.Log +import io.ktor.client.request.forms.submitForm +import io.ktor.client.request.get +import io.ktor.client.request.parameter +import io.ktor.client.statement.bodyAsText +import io.ktor.http.parameters +import org.json.JSONArray +import org.json.JSONObject +import ru.sweetbread.unn.ui.layout.LoginData +import ru.sweetbread.unn.ui.layout.client +import java.time.LocalDate +import java.time.LocalTime +import java.time.format.DateTimeFormatter + +private lateinit var PHPSESSID: String +lateinit var ME: User + +const val portalURL = "https://portal.unn.ru" +const val ruzapiURL = "$portalURL/ruzapi" + +enum class Type(val s: String) { + Student("student"), + Group("group"), + Lecturer("lecturer"), + Auditorium("auditorium") +} + +enum class LecturerRank(val s: String) { + Lecturer("Lecturer"), + SLecturer("Senior Lecturer") +} + +class ScheduleUnit( + val auditorium: Auditorium, + val date: LocalDate, + val discipline: Discipline, + val kindOfWork: KindOfWork, + val lecturers: ArrayList, + val stream: String, + val begin: LocalTime, + val end: LocalTime) + +class Auditorium (val name: String, + val oid: Int, + val floor: Int, + val building: Building) +class Building(val name: String, + val gid: Int, + val oid: Int) + +class Discipline (val name: String, + val oid: Int, + val type: Int) + +class KindOfWork (val name: String, + val oid: Int, + val uid: String, + val complexity: Int) + +class Lecturer (val name: String, + val rank: LecturerRank, + val email: String, + val oid: Int, + val uid: String) + +class User (val id: String, + val uns: String, + val type: Type, + val email: String, + val name: String, + val info: String) + +/** + * Authorize user by [login] and [password] + * + * Also defines local vars [PHPSESSID] and [ME.id] + */ +suspend fun auth(login: String = LoginData.login, password: String = LoginData.password, forced: Boolean = false): Boolean { + if (!forced) { + if (::PHPSESSID.isInitialized and ::ME.isInitialized) + return true + } + val r = client.submitForm("$portalURL/auth/?login=yes", + formParameters = parameters { + append("AUTH_FORM", "Y") + append("TYPE", "AUTH") + append("backurl", "/") + append("USER_LOGIN", login) + append("USER_PASSWORD", password) + } + ) + if (r.status.value == 302) { + PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1] + getMyself(login) + return true + } + return false +} + +private suspend fun getMyself(login: String) { + val r = client.get("$ruzapiURL/studentinfo") { + parameter("uns", login.substring(1)) + } + val json = JSONObject(r.bodyAsText()) + ME = User( + id = json.getString("id"), + uns = json.getString("uns"), + type = when(json.getString("type")) { + "lecturer" -> Type.Lecturer // ig... + else -> Type.Student + }, + email = json.getString("email"), + name = json.getString("fio"), + info = json.getString("info") + ) +} + +suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: LocalDate, finish: LocalDate): ArrayList { + val r = client.get("$ruzapiURL/schedule/${type.s}/$id") { + parameter("start", start.format(DateTimeFormatter.ofPattern("yyyy.MM.dd"))) + parameter("finish", finish.format(DateTimeFormatter.ofPattern("yyyy.MM.dd"))) + parameter("lng", "1") + } + val json = JSONArray(r.bodyAsText()) + val out = arrayListOf() + for (i in 0 until json.length()) { + val unit = json.getJSONObject(i) + val lecturesJson = unit.getJSONArray("listOfLecturers") + val lecturers = arrayListOf() + + for (j in 0 until lecturesJson.length()) { + val lecturer = lecturesJson.getJSONObject(j) + lecturers.add( + Lecturer( + name = lecturer.getString("lecturer"), + email = lecturer.getString("lecturerEmail"), + oid = lecturer.getInt("lecturerOid"), + uid = lecturer.getString("lecturerUID"), + rank = when (lecturer.getString("lecturer_rank")) { + "СТПРЕП" -> LecturerRank.SLecturer + else -> LecturerRank.Lecturer + } + ) + ) + } + + out.add( + ScheduleUnit( + auditorium = Auditorium( + name = unit.getString("auditorium"), + oid = unit.getInt("auditoriumOid"), + floor = unit.getInt("auditoriumfloor"), + building = Building( + name = unit.getString("building"), + gid = unit.getInt("buildingGid"), + oid = unit.getInt("buildingOid") + ) + ), + date = LocalDate.parse(unit.getString("date"), DateTimeFormatter.ofPattern("yyyy.MM.dd")), + discipline = Discipline( + name = unit.getString("discipline"), + oid = unit.getInt("disciplineOid"), + type = unit.getInt("disciplinetypeload") + ), + kindOfWork = KindOfWork( + name = unit.getString("kindOfWork"), + complexity = unit.getInt("kindOfWorkComplexity"), + oid = unit.getInt("kindOfWorkOid"), + uid = unit.getString("kindOfWorkUid") + ), + lecturers = lecturers, + stream = unit.getString("stream"), + begin = LocalTime.parse(unit.getString("beginLesson"), DateTimeFormatter.ofPattern("HH:mm")), + end = LocalTime.parse(unit.getString("endLesson"), DateTimeFormatter.ofPattern("HH:mm")) + ) + ) + } + return out +} diff --git a/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt b/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt index e24fc19..4260c20 100644 --- a/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt +++ b/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt @@ -278,7 +278,6 @@ fun UserItemPreview() { fun PostItemPreview() { val post = Post( id = 154923, - blogId = 121212, authorId = 165945, enableComments = true, numComments = 0, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2a43bda..2757bed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] acraHttp = "5.11.3" -agp = "8.5.2" +agp = "8.7.0" calendar = "2.5.4" coilCompose = "2.7.0" compose = "1.6.4" # Updating this will cause an error! diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c66b25b..c75acdb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sat Mar 16 18:30:45 MSK 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists