[APIv2] Implement Grades remove model. Implement Librus first login.

This commit is contained in:
Kuba Szczodrzyński 2019-10-15 06:49:19 +02:00
parent e91b4fcf8b
commit f79263e628
11 changed files with 236 additions and 82 deletions

View File

@ -19,11 +19,13 @@ const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?clie
const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"
const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"
const val LIBRUS_ACCOUNT_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts/fresh/" // + login
const val LIBRUS_ACCOUNTS_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts"
const val LIBRUS_ACCOUNT_URL = "/v2/SynergiaAccounts/fresh/" // + login
const val LIBRUS_ACCOUNTS_URL = "/v2/SynergiaAccounts"
/** https://api.librus.pl/2.0 */
const val LIBRUS_API_URL = "https://api.librus.pl/2.0"
/** https://portal.librus.pl/api */
const val LIBRUS_PORTAL_URL = "https://portal.librus.pl/api"
/** https://api.librus.pl/OAuth/Token */
const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token"
/** https://api.librus.pl/OAuth/TokenJST */

View File

@ -46,6 +46,8 @@ const val ERROR_PROFILE_MISSING = 105
const val ERROR_INVALID_LOGIN_MODE = 110
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115
const val CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120
const val CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED = 121
const val ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED = 124

View File

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusData
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLogin
import pl.szczodrzynski.edziennik.api.v2.librusLoginMethods
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
@ -62,7 +63,9 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
}
override fun firstLogin() {
// TODO
LibrusFirstLogin(data) {
completed()
}
}
override fun cancel() {

View File

@ -17,7 +17,7 @@ import java.net.HttpURLConnection.*
open class LibrusApi(open val data: DataLibrus) {
companion object {
const val TAG = "LibrusApi"
private const val TAG = "LibrusApi"
}
val profileId

View File

@ -0,0 +1,101 @@
package pl.szczodrzynski.edziennik.api.v2.librus.data
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection
open class LibrusPortal(open val data: DataLibrus) {
companion object {
private const val TAG = "LibrusPortal"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun portalGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject, response: Response?) -> Unit) {
d(tag, "Request: Librus/Portal - $LIBRUS_PORTAL_URL$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
val error = if (response?.code() == 200) null else
json.getString("reason") ?:
json.getString("message") ?:
json.getString("hint") ?:
json.getString("Code")
error?.let { code ->
when (code) {
"requires_an_action" -> ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED
"Access token is invalid" -> ERROR_LIBRUS_PORTAL_TOKEN_EXPIRED
"ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED
"Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND
else -> ERROR_LIBRUS_PORTAL_OTHER
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withApiResponse(json)
.withResponse(response))
return
}
}
if (response?.code() == HttpURLConnection.HTTP_OK) {
try {
onSuccess(json, response)
} catch (e: NullPointerException) {
e.printStackTrace()
data.error(ApiError(tag, EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
} else {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url(LIBRUS_PORTAL_URL + endpoint)
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.portalAccessToken}")
.apply {
when (method) {
GET -> get()
POST -> post()
}
if (payload != null)
setJsonBody(payload)
}
.allowErrorCode(HttpURLConnection.HTTP_NOT_FOUND)
.allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
.allowErrorCode(HttpURLConnection.HTTP_GONE)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,102 @@
package pl.szczodrzynski.edziennik.api.v2.librus.firstlogin
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.api.v2.ERROR_NO_STUDENTS_IN_ACCOUNT
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNTS_URL
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL
import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusPortal
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.api.AppError.CODE_LIBRUS_DISCONNECTED
import pl.szczodrzynski.edziennik.data.api.AppError.CODE_SYNERGIA_NOT_ACTIVATED
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "LibrusFirstLogin"
}
private val portal = LibrusPortal(data)
private val api = LibrusApi(data)
private val profileList = mutableListOf<Profile>()
init {
if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) {
// email login: use Portal for account list
LibrusLoginPortal(data) {
portal.portalGet(TAG, LIBRUS_ACCOUNTS_URL) { json, response ->
val accounts = json.getJsonArray("accounts")
if (accounts == null || accounts.size() < 1) {
data.error(ApiError(TAG, ERROR_NO_STUDENTS_IN_ACCOUNT)
.withResponse(response)
.withApiResponse(json))
return@portalGet
}
val accountDataTime = json.getLong("lastModification")
val accountIds = mutableListOf<Int>()
val accountLogins = mutableListOf<String>()
val accountTokens = mutableListOf<String>()
val accountNamesLong = mutableListOf<String>()
val accountNamesShort = mutableListOf<String>()
for (accountEl in accounts) {
val account = accountEl.asJsonObject
val state = account.getString("state")
when (state) {
"requiring_an_action" -> CODE_LIBRUS_DISCONNECTED
"need-activation" -> CODE_SYNERGIA_NOT_ACTIVATED
else -> null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(json)
.withResponse(response))
return@portalGet
}
accountIds.add(account.getInt("id") ?: continue)
accountLogins.add(account.getString("login") ?: continue)
accountTokens.add(account.getString("accessToken") ?: continue)
accountNamesLong.add(account.getString("studentName") ?: continue)
val nameParts = account.getString("studentName")?.split(" ") ?: continue
accountNamesShort.add(nameParts[0] + " " + nameParts[1][0] + ".")
}
for (index in accountIds.indices) {
val newProfile = Profile()
newProfile.studentNameLong = accountNamesLong[index]
newProfile.studentNameShort = accountNamesShort[index]
newProfile.name = newProfile.studentNameLong
newProfile.subname = data.portalEmail
newProfile.empty = true
newProfile.putStudentData("accountId", accountIds[index])
newProfile.putStudentData("accountLogin", accountLogins[index])
newProfile.putStudentData("accountToken", accountTokens[index])
newProfile.putStudentData("accountTokenTime", accountDataTime ?: 0)
profileList.add(newProfile)
}
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
}
else {
// synergia or JST login: use Api for account info
LibrusLoginApi(data) {
api.apiGet(TAG, "Me") { json ->
}
}
}
}
}

View File

@ -9,14 +9,15 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
import java.net.HttpURLConnection.HTTP_BAD_REQUEST
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
class LibrusLoginApi {
companion object {
@ -31,7 +32,7 @@ class LibrusLoginApi {
this.data = data
this.onSuccess = onSuccess
if (data.profile == null) {
if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL && data.profile == null) {
data.error(ApiError(TAG, ERROR_PROFILE_MISSING))
return
}

View File

@ -1,18 +1,15 @@
package pl.szczodrzynski.edziennik.api.v2.librus.login
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusPortal
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
class SynergiaTokenExtractor(val data: DataLibrus, val onSuccess: () -> Unit) {
class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusPortal(data) {
companion object {
private const val TAG = "SynergiaTokenExtractor"
}
@ -44,7 +41,7 @@ class SynergiaTokenExtractor(val data: DataLibrus, val onSuccess: () -> Unit) {
private fun synergiaAccount(): Boolean {
val accountLogin = data.apiLogin ?: return false
val accessToken = data.portalAccessToken ?: return false
data.portalAccessToken ?: return false
d(TAG, "Request: Librus/SynergiaTokenExtractor - $LIBRUS_ACCOUNT_URL$accountLogin")
@ -63,77 +60,14 @@ class SynergiaTokenExtractor(val data: DataLibrus, val onSuccess: () -> Unit) {
// TODO remove this
data.profile?.studentNameLong = json.getString("studentName")
val nameParts = json.getString("studentName")?.split(" ")?.toTypedArray()
val nameParts = json.getString("studentName")?.split(" ")
data.profile?.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0)
onSuccess()
}
}
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
val error = if (response?.code() == 200) null else
json.getString("reason") ?:
json.getString("message") ?:
json.getString("hint") ?:
json.getString("Code")
error?.let { code ->
when (code) {
"requires_an_action" -> ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED
"Access token is invalid" -> ERROR_LIBRUS_PORTAL_TOKEN_EXPIRED
"ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED
"Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND
else -> ERROR_LIBRUS_PORTAL_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(json)
.withResponse(response))
return
}
}
if (response?.code() == HTTP_OK) {
try {
onSuccess(json, response)
} catch (e: NullPointerException) {
e.printStackTrace()
data.error(ApiError(TAG, EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
} else {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url(LIBRUS_ACCOUNT_URL + accountLogin)
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer $accessToken")
.get()
.allowErrorCode(HTTP_NOT_FOUND)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_GONE)
.callback(callback)
.build()
.enqueue()
portalGet(TAG, LIBRUS_ACCOUNT_URL+accountLogin, onSuccess = onSuccess)
return true
}
}

View File

@ -40,7 +40,7 @@ class MobidziennikFirstLogin(val data: DataMobidziennik, val onSuccess: () -> Un
val profile = Profile()
profile.studentNameLong = studentNamesLong[index]
profile.studentNameShort = studentNamesShort[index]
profile.name = profile.studentNameLong + " (APIv2)"
profile.name = profile.studentNameLong
profile.subname = data.loginUsername
profile.empty = true
profile.putStudentData("studentId", studentIds[index])

View File

@ -190,6 +190,11 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
db.gradeCategoryDao().clear(profileId)
db.gradeCategoryDao().addAll(gradeCategories.values())
gradesToRemove?.let { it ->
it.removeAll?.let { _ -> db.gradeDao().clear(profileId) }
it.removeSemester?.let { semester -> db.gradeDao().clearForSemester(profileId, semester) }
}
if (lessonList.isNotEmpty()) {
db.lessonDao().clear(profile.id)
db.lessonDao().addAll(lessonList)
@ -197,7 +202,6 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
if (lessonChangeList.isNotEmpty())
db.lessonChangeDao().addAll(lessonChangeList)
if (gradeList.isNotEmpty()) {
db.gradeDao().clear(profile.id)
db.gradeDao().addAll(gradeList)
}
if (eventList.isNotEmpty()) {

View File

@ -7,10 +7,15 @@ package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.utils.models.Date
class DataRemoveModel {
var removeAll: Boolean? = null
var removeSemester: Int? = null
var removeDateFrom: Date? = null
var removeDateTo: Date? = null
constructor() {
this.removeAll = true
}
constructor(semester: Int) {
this.removeSemester = semester
}