diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt index fecfecbd..465ea051 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt @@ -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 */ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt index 676ccdb9..6b0df35a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt @@ -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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt index 22fcef35..d8961e74 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt @@ -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() { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusApi.kt index 497d4845..d913e303 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusApi.kt @@ -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 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusPortal.kt new file mode 100644 index 00000000..c5cc886c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/LibrusPortal.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/LibrusFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/LibrusFirstLogin.kt new file mode 100644 index 00000000..5a92178c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/LibrusFirstLogin.kt @@ -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() + + 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() + val accountLogins = mutableListOf() + val accountTokens = mutableListOf() + val accountNamesLong = mutableListOf() + val accountNamesShort = mutableListOf() + + 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 -> + + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginApi.kt index 22309ac6..fe8bd5cd 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LibrusLoginApi.kt @@ -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 } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt index be0fd126..27fdbe6a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/firstlogin/MobidziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/firstlogin/MobidziennikFirstLogin.kt index 1dfdfb84..aa98c478 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/firstlogin/MobidziennikFirstLogin.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/mobidziennik/firstlogin/MobidziennikFirstLogin.kt @@ -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]) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt index ac2f1344..c32703d2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt @@ -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()) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataRemoveModel.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataRemoveModel.kt index 19d24371..1e97d733 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataRemoveModel.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataRemoveModel.kt @@ -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 }