[APIv2/Librus] Create Login methods, update API structure

This commit is contained in:
Kuba Szczodrzyński 2019-09-21 23:01:31 +02:00
parent 1bdee7857c
commit 76d39ac623
29 changed files with 1258 additions and 505 deletions

View File

@ -47,11 +47,10 @@ public class AppError {
public static final int CODE_LIBRUS_DISCONNECTED = 31;
public static final int CODE_PROFILE_ARCHIVED = 30;
public static final int CODE_INTERNAL_MISSING_DATA = 100;
// internal errors - not for user's information.
// these error codes are processed in API main classes
public static final int CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120;
public static final int CODE_INTERNAL_LIBRUS_ACCOUNT_410_ = 120;
public String TAG;
public int line;

View File

@ -51,7 +51,6 @@ import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback;
import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.api.v2.models.DataStore;
import pl.szczodrzynski.edziennik.datamodels.Announcement;
import pl.szczodrzynski.edziennik.datamodels.Attendance;
import pl.szczodrzynski.edziennik.datamodels.Event;
@ -213,8 +212,6 @@ public class Librus implements EdziennikInterface {
this.fullSync = profile == null || profile.getEmpty() || profile.shouldFullSync(activityContext);
this.today = Date.getToday();
DataStore ds = new DataStore(app.db, profileId);
this.librusEmail = loginStore.getLoginData("email", "");
this.librusPassword = loginStore.getLoginData("password", "");
if (profile == null) {

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2
import com.crashlytics.android.Crashlytics
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.v2.models.Data
open class Api(open val data: Data) {
fun finishWithError(error: AppError) {
try {
data.saveData()
} catch (e: Exception) {
Crashlytics.logException(e)
}
data.callback.onError(null, error)
}
}

View File

@ -16,33 +16,8 @@ const val FEATURE_MESSAGES_INBOX = 7
const val FEATURE_MESSAGES_OUTBOX = 8
const val FEATURE_ANNOUNCEMENTS = 9
const val LOGIN_TYPE_MOBIDZIENNIK = 1
const val LOGIN_TYPE_LIBRUS = 2
const val LOGIN_TYPE_IUCZNIOWIE = 3
const val LOGIN_TYPE_VULCAN = 4
const val LOGIN_TYPE_DEMO = 20
// LOGIN MODES
const val LOGIN_MODE_LIBRUS_EMAIL = 0
const val LOGIN_MODE_LIBRUS_SYNERGIA = 1
const val LOGIN_MODE_LIBRUS_JST = 2
const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0
const val LOGIN_MODE_IDZIENNIK_WEB = 0
const val LOGIN_MODE_VULCAN_WEB = 0
// LOGIN METHODS
const val LOGIN_METHOD_NOT_NEEDED = -1
const val LOGIN_METHOD_LIBRUS_PORTAL = 0
const val LOGIN_METHOD_LIBRUS_API = 1
const val LOGIN_METHOD_LIBRUS_SYNERGIA = 2
const val LOGIN_METHOD_LIBRUS_MESSAGES = 3
const val LOGIN_METHOD_MOBIDZIENNIK_API = 0
const val LOGIN_METHOD_IDZIENNIK_WEB = 0
const val LOGIN_METHOD_IDZIENNIK_API = 1
const val LOGIN_METHOD_VULCAN_WEB = 0
const val LOGIN_METHOD_VULCAN_API = 1
const val LIBRUS_USER_AGENT = "Dalvik/2.1.0 Android LibrusMobileApp"
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"
const val LIBRUS_REDIRECT_URL = "http://localhost/bar"
const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code"
@ -51,3 +26,19 @@ 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_API_URL = "https://api.librus.pl/2.0/"
const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token"
const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST"
const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE="
const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398"
const val LIBRUS_API_CLIENT_ID_JST = "49"
//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42"
const val LIBRUS_JST_DEMO_CODE = "68656A21"
const val LIBRUS_JST_DEMO_PIN = "1290"
const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/token/\$token/przenies/"
const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module/"
const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.api.v2
import android.util.Log
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiGrades
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiMe
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergiaGrades
import pl.szczodrzynski.edziennik.api.v2.models.Endpoint
@ -13,7 +14,8 @@ const val ENDPOINT_LIBRUS_API_ME = 0
const val ENDPOINT_LIBRUS_API_GRADES = 0
const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 0
val librusEndpoints = listOf(
val endpoints = listOf(
Endpoint(LOGIN_TYPE_LIBRUS, ENDPOINT_LIBRUS_API_ME, null, LibrusApiMe::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_API},
Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusSynergiaGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_SYNERGIA },
Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusApiGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_API }
)

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2
/*const val CODE_OTHER = 0
const val CODE_OK = 1
const val CODE_NO_INTERNET = 10
const val CODE_SSL_ERROR = 13
const val CODE_ARCHIVED = 5
const val CODE_MAINTENANCE = 6
const val CODE_LOGIN_ERROR = 7
const val CODE_ACCOUNT_MISMATCH = 8
const val CODE_APP_SERVER_ERROR = 9
const val CODE_MULTIACCOUNT_SETUP = 12
const val CODE_TIMEOUT = 11
const val CODE_PROFILE_NOT_FOUND = 14
const val CODE_ATTACHMENT_NOT_AVAILABLE = 28
const val CODE_INVALID_LOGIN = 2
const val CODE_INVALID_SERVER_ADDRESS = 21
const val CODE_INVALID_SCHOOL_NAME = 22
const val CODE_INVALID_DEVICE = 23
const val CODE_OLD_PASSWORD = 4
const val CODE_INVALID_TOKEN = 24
const val CODE_EXPIRED_TOKEN = 27
const val CODE_INVALID_SYMBOL = 25
const val CODE_INVALID_PIN = 26
const val CODE_LIBRUS_NOT_ACTIVATED = 29
const val CODE_SYNERGIA_NOT_ACTIVATED = 32
const val CODE_LIBRUS_DISCONNECTED = 31
const val CODE_PROFILE_ARCHIVED = 30*/
const val CODE_INVALID_LOGIN_MODE = 130
const val CODE_INTERNAL_MISSING_DATA = 100
const val CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120
const val CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED = 121
const val CODE_LOGIN_METHOD_NOT_SATISFIED = 122
const val CODE_LIBRUS_PROFILE_NULL = 123
const val ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED = 124
const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901

View File

@ -4,3 +4,47 @@
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusPortal
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusSynergia
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
const val LOGIN_TYPE_MOBIDZIENNIK = 1
const val LOGIN_TYPE_LIBRUS = 2
const val LOGIN_TYPE_IUCZNIOWIE = 3
const val LOGIN_TYPE_VULCAN = 4
const val LOGIN_TYPE_DEMO = 20
// LOGIN MODES
const val LOGIN_MODE_LIBRUS_EMAIL = 0
const val LOGIN_MODE_LIBRUS_SYNERGIA = 1
const val LOGIN_MODE_LIBRUS_JST = 2
const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0
const val LOGIN_MODE_IDZIENNIK_WEB = 0
const val LOGIN_MODE_VULCAN_WEB = 0
// LOGIN METHODS
const val LOGIN_METHOD_NOT_NEEDED = -1
const val LOGIN_METHOD_LIBRUS_PORTAL = 100// 0
const val LOGIN_METHOD_LIBRUS_API = 200// 1
const val LOGIN_METHOD_LIBRUS_SYNERGIA = 300 // 2
const val LOGIN_METHOD_LIBRUS_MESSAGES = 400
const val LOGIN_METHOD_MOBIDZIENNIK_API = 100
const val LOGIN_METHOD_IDZIENNIK_WEB = 100
const val LOGIN_METHOD_IDZIENNIK_API = 200
const val LOGIN_METHOD_VULCAN_WEB = 100
const val LOGIN_METHOD_VULCAN_API = 200
val librusLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, null, LoginLibrusPortal::class.java) { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_API, null, LoginLibrusApi::class.java) { _, loginStore ->
if (loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) LOGIN_METHOD_LIBRUS_PORTAL else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, listOf(FEATURE_GRADES, FEATURE_HOMEWORKS, FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_OUTBOX), LoginLibrusSynergia::class.java) { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, listOf(FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_OUTBOX), LoginLibrusMessages::class.java) { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
}
)

View File

@ -1,111 +1,31 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import android.content.Context
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.interfaces.*
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_JST
import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_SYNERGIA
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginJst
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor
import pl.szczodrzynski.edziennik.api.v2.models.DataStore
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.MessageFull
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.datamodels.ProfileFull
import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo
import pl.szczodrzynski.edziennik.models.Endpoint
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.lang.Exception
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore) : EdziennikInterface {
private val TAG = "librus.Librus"
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: SyncCallback) {
lateinit var syncCallback: SyncCallback
lateinit var featureList: ArrayList<Int>
lateinit var dataStore: DataStore
var onLogin: (() -> Unit)? = null
val internalErrorList = ArrayList<Int>()
val internalErrorList = mutableListOf<Int>()
lateinit var data: DataLibrus
init {
data = DataLibrus(app, profile, loginStore).apply {
callback = wrapCallback(this@Librus.callback)
}
fun isError(error: AppError?): Boolean {
if (error == null)
return false
syncCallback.onError(null, error)
return true
}
/* _ _ _
| | (_) |
| | _| |__ _ __ _ _ ___
| | | | '_ \| '__| | | / __|
| |____| | |_) | | | |_| \__ \
|______|_|_.__/|_| \__,_|__*/
private fun loginLibrus() {
LoginLibrus(app, loginStore, syncCallback) {
if (profile == null) {
firstLoginLibrus()
return@LoginLibrus
}
synergiaTokenExtractor()
}
}
private fun firstLoginLibrus() {
FirstLoginLibrus(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
private fun synergiaTokenExtractor() {
if (profile == null) {
throw Exception("Profile may not be null")
}
SynergiaTokenExtractor(app, profile, loginStore, syncCallback) {
d(TAG, "Profile $profile")
d(TAG, "LoginStore $loginStore")
onLogin?.invoke()
}
}
/* _____ _
/ ____| (_)
| (___ _ _ _ __ ___ _ __ __ _ _ __ _
\___ \| | | | '_ \ / _ \ '__/ _` | |/ _` |
____) | |_| | | | | __/ | | (_| | | (_| |
|_____/ \__, |_| |_|\___|_| \__, |_|\__,_|
__/ | __/ |
|___/ |__*/
private fun loginSynergia() {
LoginSynergia(app, loginStore, syncCallback) {
if (profile == null) {
firstLoginSynergia()
return@LoginSynergia
}
onLogin?.invoke()
}
}
private fun firstLoginSynergia() {
FirstLoginSynergia(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
/* _ _____ _______
| |/ ____|__ __|
| | (___ | |
_ | |\___ \ | |
| |__| |____) | | |
\____/|_____/ |*/
private fun loginJst() {
LoginJst(app, null, loginStore, syncCallback) {
if (profile == null) {
firstLoginSynergia()
return@LoginJst
}
onLogin?.invoke()
}
}
private fun wrapCallback(callback: SyncCallback): SyncCallback {
@ -135,67 +55,11 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore) :
CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> {
internalErrorList.add(error.errorCode)
loginStore.removeLoginData("refreshToken") // force a clean login
loginLibrus()
//loginLibrus()
}
else -> callback.onError(activityContext, error)
}
}
}
}
fun login(callback: SyncCallback) {
this.internalErrorList.clear()
this.syncCallback = wrapCallback(callback)
when (loginStore.mode) {
LOGIN_MODE_LIBRUS_EMAIL -> {
loginLibrus()
}
LOGIN_MODE_LIBRUS_SYNERGIA -> {
}
LOGIN_MODE_LIBRUS_JST -> {
}
}
}
fun getData() {
}
override fun sync(activityContext: Context, callback: SyncCallback, profileId: Int, profile: Profile?, loginStore: LoginStore) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncMessages(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncFeature(activityContext: Context, callback: SyncCallback, profile: ProfileFull, vararg featureList: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getMessage(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, messageCallback: MessageGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getAttachment(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, attachmentId: Long, attachmentCallback: AttachmentGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getRecipientList(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, recipientListGetCallback: RecipientListGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getComposeInfo(profile: ProfileFull): MessagesComposeInfo {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getConfigurableEndpoints(profile: Profile?): MutableMap<String, Endpoint> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isEndpointEnabled(profile: Profile?, defaultActive: Boolean, name: String?): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -0,0 +1,6 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus

View File

@ -0,0 +1,172 @@
package pl.szczodrzynski.edziennik.api.v2.librus
import android.content.Context
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.interfaces.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.MessageFull
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.datamodels.ProfileFull
import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo
import pl.szczodrzynski.edziennik.models.Endpoint
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.lang.Exception
class LibrusOld(val app: App, val profile: Profile?, val loginStore: LoginStore) : EdziennikInterface {
private val TAG = "librus.Librus"
lateinit var syncCallback: SyncCallback
lateinit var featureList: ArrayList<Int>
lateinit var data: Data
var onLogin: (() -> Unit)? = null
val internalErrorList = ArrayList<Int>()
fun isError(error: AppError?): Boolean {
if (error == null)
return false
syncCallback.onError(null, error)
return true
}
/* _ _ _
| | (_) |
| | _| |__ _ __ _ _ ___
| | | | '_ \| '__| | | / __|
| |____| | |_) | | | |_| \__ \
|______|_|_.__/|_| \__,_|__*/
private fun firstLoginLibrus() {
FirstLoginLibrus(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
private fun synergiaTokenExtractor() {
if (profile == null) {
throw Exception("Profile may not be null")
}
}
/* _____ _
/ ____| (_)
| (___ _ _ _ __ ___ _ __ __ _ _ __ _
\___ \| | | | '_ \ / _ \ '__/ _` | |/ _` |
____) | |_| | | | | __/ | | (_| | | (_| |
|_____/ \__, |_| |_|\___|_| \__, |_|\__,_|
__/ | __/ |
|___/ |__*/
private fun loginSynergia() {
}
private fun firstLoginSynergia() {
FirstLoginSynergia(app, loginStore, syncCallback) { profileList ->
syncCallback.onLoginFirst(profileList, loginStore)
}
}
/* _ _____ _______
| |/ ____|__ __|
| | (___ | |
_ | |\___ \ | |
| |__| |____) | | |
\____/|_____/ |*/
private fun loginJst() {
}
private fun wrapCallback(callback: SyncCallback): SyncCallback {
return object : SyncCallback {
override fun onSuccess(activityContext: Context?, profileFull: ProfileFull?) {
callback.onSuccess(activityContext, profileFull)
}
override fun onProgress(progressStep: Int) {
callback.onProgress(progressStep)
}
override fun onActionStarted(stringResId: Int) {
callback.onActionStarted(stringResId)
}
override fun onLoginFirst(profileList: MutableList<Profile>?, loginStore: LoginStore?) {
callback.onLoginFirst(profileList, loginStore)
}
override fun onError(activityContext: Context?, error: AppError) {
when (error.errorCode) {
in internalErrorList -> {
// finish immediately if the same error occurs twice during the same sync
callback.onError(activityContext, error)
}
/* CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> {
internalErrorList.add(error.errorCode)
loginStore.removeLoginData("refreshToken") // force a clean login
//loginLibrus()
}*/
else -> callback.onError(activityContext, error)
}
}
}
}
fun login(callback: SyncCallback) {
this.internalErrorList.clear()
this.syncCallback = wrapCallback(callback)
when (loginStore.mode) {
LOGIN_MODE_LIBRUS_EMAIL -> {
//loginLibrus()
}
LOGIN_MODE_LIBRUS_SYNERGIA -> {
}
LOGIN_MODE_LIBRUS_JST -> {
}
}
}
fun getData() {
}
override fun sync(activityContext: Context, callback: SyncCallback, profileId: Int, profile: Profile?, loginStore: LoginStore) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncMessages(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun syncFeature(activityContext: Context, callback: SyncCallback, profile: ProfileFull, vararg featureList: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getMessage(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, messageCallback: MessageGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getAttachment(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, attachmentId: Long, attachmentCallback: AttachmentGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getRecipientList(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, recipientListGetCallback: RecipientListGetCallback) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getComposeInfo(profile: ProfileFull): MessagesComposeInfo {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getConfigurableEndpoints(profile: Profile?): MutableMap<String, Endpoint> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isEndpointEnabled(profile: Profile?, defaultActive: Boolean, name: String?): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus
import android.content.Context
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusPortal
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class LibrusTest(val app: App) {
companion object {
private const val TAG = "LibrusTest"
}
val profile = Profile(1, "Profil", "xd", 1).apply {
//putStudentData("accountLogin", "1234567")
//putStudentData("accountPassword", "zaq1@WSX")
putStudentData("accountCode", LIBRUS_JST_DEMO_CODE)
putStudentData("accountPin", LIBRUS_JST_DEMO_PIN)
}
val loginStore = LoginStore(1, LOGIN_TYPE_LIBRUS, JsonObject().apply {
addProperty("email", "test@example.com")
addProperty("password", "zaq1@WSX")
}).also {
it.mode = LOGIN_MODE_LIBRUS_JST
}
fun go() {
val data = DataLibrus(app, profile, loginStore).apply {
callback = object : ProgressCallback {
override fun onProgress(progressStep: Int) {
}
override fun onActionStarted(stringResId: Int) {
d(TAG, app.getString(stringResId))
}
override fun onError(activityContext: Context?, error: AppError) {
error.changeIfCodeOther()
d(TAG, "Error "+error.getDetails(app))
}
}
}
LoginLibrus(data, LOGIN_METHOD_LIBRUS_API) {
d(TAG, "Login succeeded.")
d(TAG, "Profile data: ${data.profile?.studentData?.toString()}")
d(TAG, "LoginStore data: ${data.loginStore.data}")
}
}
}

View File

@ -1,10 +1,122 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
class DataLibrus(val app: App, val profile: Profile, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) {
class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
}
/* _____ _ _
| __ \ | | | |
| |__) |__ _ __| |_ __ _| |
| ___/ _ \| '__| __/ _` | |
| | | (_) | | | || (_| | |
|_| \___/|_| \__\__,_|*/
private var mPortalEmail: String? = null
var portalEmail: String?
get() { mPortalEmail = mPortalEmail ?: loginStore.getLoginData("email", null); return mPortalEmail }
set(value) { loginStore.putLoginData("email", value); mPortalEmail = value }
private var mPortalPassword: String? = null
var portalPassword: String?
get() { mPortalPassword = mPortalPassword ?: loginStore.getLoginData("password", null); return mPortalPassword }
set(value) { loginStore.putLoginData("password", value); mPortalPassword = value }
private var mPortalAccessToken: String? = null
var portalAccessToken: String?
get() { mPortalAccessToken = mPortalAccessToken ?: loginStore.getLoginData("accessToken", null); return mPortalAccessToken }
set(value) { loginStore.putLoginData("accessToken", value); mPortalAccessToken = value }
private var mPortalRefreshToken: String? = null
var portalRefreshToken: String?
get() { mPortalRefreshToken = mPortalRefreshToken ?: loginStore.getLoginData("refreshToken", null); return mPortalRefreshToken }
set(value) { loginStore.putLoginData("refreshToken", value); mPortalRefreshToken = value }
private var mPortalTokenExpiryTime: Long? = null
var portalTokenExpiryTime: Long
get() { mPortalTokenExpiryTime = mPortalTokenExpiryTime ?: loginStore.getLoginData("tokenExpiryTime", 0L); return mPortalTokenExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("tokenExpiryTime", value); mPortalTokenExpiryTime = value }
/* _____ _____
/\ | __ \_ _|
/ \ | |__) || |
/ /\ \ | ___/ | |
/ ____ \| | _| |_
/_/ \_\_| |____*/
/**
* A Synergia login, like 1234567u.
* Used: for login (API Login Method) in Synergia mode.
* And also in various places in [pl.szczodrzynski.edziennik.api.v2.models.Endpoint]s
*/
private var mApiLogin: String? = null
var apiLogin: String?
get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin }
set(value) { profile?.putStudentData("accountLogin", value) ?: return; mApiLogin = value }
/**
* A Synergia password.
* Used: for login (API Login Method) in Synergia mode.
*/
private var mApiPassword: String? = null
var apiPassword: String?
get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword }
set(value) { profile?.putStudentData("accountPassword", value) ?: return; mApiPassword = value }
/**
* A JST login Code.
* Used only during first login in JST mode.
*/
private var mApiCode: String? = null
var apiCode: String?
get() { mApiCode = mApiCode ?: profile?.getStudentData("accountCode", null); return mApiCode }
set(value) { profile?.putStudentData("accountCode", value) ?: return; mApiCode = value }
/**
* A JST login PIN.
* Used only during first login in JST mode.
*/
private var mApiPin: String? = null
var apiPin: String?
get() { mApiPin = mApiPin ?: profile?.getStudentData("accountPin", null); return mApiPin }
set(value) { profile?.putStudentData("accountPin", value) ?: return; mApiPin = value }
/**
* A Synergia API access token.
* Used in all Api Endpoints.
* Created in Login Method Api.
* Applicable for all login modes.
*/
private var mApiAccessToken: String? = null
var apiAccessToken: String?
get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken }
set(value) { profile?.putStudentData("accountToken", value) ?: return; mApiAccessToken = value }
/**
* A Synergia API refresh token.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
*/
private var mApiRefreshToken: String? = null
var apiRefreshToken: String?
get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken }
set(value) { profile?.putStudentData("accountRefreshToken", value) ?: return; mApiRefreshToken = value }
/**
* The expiry time for [apiAccessToken], as a UNIX timestamp.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
* Used when refreshing the [apiAccessToken] in Portal mode ([pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor])
*/
private var mApiTokenExpiryTime: Long? = null
var apiTokenExpiryTime: Long
get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L }
set(value) { profile?.putStudentData("accountTokenTime", value) ?: return; mApiTokenExpiryTime = value }
/* ____ _ _
/ __ \| | | |
| | | | |_| |__ ___ _ __
| | | | __| '_ \ / _ \ '__|
| |__| | |_| | | | __/ |
\____/ \__|_| |_|\___|*/
var isPremium
get() = profile?.getStudentData("isPremium", false) ?: false
set(value) { profile?.putStudentData("isPremium", value) }
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import com.google.gson.JsonNull
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.AppError
import pl.szczodrzynski.edziennik.api.AppError.CODE_MAINTENANCE
import pl.szczodrzynski.edziennik.api.AppError.CODE_OTHER
import pl.szczodrzynski.edziennik.api.v2.Api
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_API_URL
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_USER_AGENT
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
open class LibrusApi(override val data: DataLibrus) : Api(data) {
companion object {
const val TAG = "LibrusApi"
}
fun apiRequest(endpoint: String, callback: (json: JsonObject?) -> Unit) {
d(TAG, "Requesting $LIBRUS_API_URL$endpoint")
Request.builder()
.url(if (data.fakeLogin) "http://szkolny.eu/librus/api/$endpoint" else LIBRUS_API_URL + endpoint)
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "Bearer ${data.apiAccessToken}")
.get()
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_BAD_REQUEST)
.callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
if (response.parserErrorBody != null && response.parserErrorBody == "Nieprawidłowy węzeł.") {
callback(null)
return
}
finishWithError(AppError(TAG, 453, CODE_MAINTENANCE, response))
return
}
if (json.get("Status") != null) {
val message = json.get("Message")
val code = json.get("Code")
d(TAG, "apiRequest Error " + json.get("Status").asString + " " + (if (message == null) "" else message.asString) + " " + (if (code == null) "" else code.asString) + "\n\n" + response.request().url().toString())
if (message != null && message !is JsonNull && message.asString == "Student timetable is not public") {
try {
callback(null)
} catch (e: NullPointerException) {
e.printStackTrace()
d(TAG, "apiRequest exception " + e.message)
finishWithError(AppError(TAG, 503, CODE_OTHER, response, e, json))
}
return
}
if (code != null
&& code !is JsonNull
&& (code.asString == "LuckyNumberIsNotActive"
|| code.asString == "NotesIsNotActive"
|| code.asString == "AccessDeny")) {
try {
callback(null)
} catch (e: NullPointerException) {
e.printStackTrace()
d(TAG, "apiRequest exception " + e.message)
finishWithError(AppError(TAG, 504, CODE_OTHER, response, e, json))
}
return
}
val errorText = json.get("Status").asString + " " + (if (message == null) "" else message.asString) + " " + if (code == null) "" else code.asString
if (code != null && code !is JsonNull && code.asString == "TokenIsExpired") {
finishWithError(AppError(TAG, 74, CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED, errorText, response, json))
return
}
finishWithError(AppError(TAG, 497, CODE_OTHER, errorText, response, json))
return
}
try {
callback(json)
} catch (e: NullPointerException) {
e.printStackTrace()
d(TAG, "apiRequest exception " + e.message)
finishWithError(AppError(TAG, 505, CODE_OTHER, response, e, json))
}
}
override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 405) {
// method not allowed
finishWithError(AppError(TAG, 511, CODE_OTHER, response, throwable))
return
}
if (response.code() == 500) {
// TODO: 2019-09-10 dirty hotfix
if ("Classrooms" == endpoint) {
callback(null)
return
}
finishWithError(AppError(TAG, 516, CODE_MAINTENANCE, response, throwable))
return
}
finishWithError(AppError(TAG, 520, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
}

View File

@ -2,19 +2,16 @@ package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.DataStore
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
class LibrusApiGrades(val app: App,
val profile: Profile,
val loginStore: LoginStore,
val dataStore: DataStore,
val data: Data,
val callback: ProgressCallback,
val onSuccess: () -> Unit) : EndpointInterface {
override fun sync() {
}
val onSuccess: () -> Unit) {
init {

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
class LibrusApiMe(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
init {
apiRequest("Me") { json ->
val me = json?.getJsonObject("Me")
val account = me?.getJsonObject("Account")
val user = me?.getJsonObject("User")
data.isPremium = account?.getBoolean("isPremium") == true || account?.getBoolean("isPremiumDemo") == true
val isParent = account?.getInt("GroupId") == 5
data.profile?.accountNameLong =
if (isParent)
buildFullName(account?.getString("FirstName"), account?.getString("LastName"))
else null
data.profile?.studentNameLong =
buildFullName(user?.getString("FirstName"), user?.getString("LastName"))
onSuccess()
}
}
}

View File

@ -2,16 +2,16 @@ package pl.szczodrzynski.edziennik.api.v2.librus.data
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.models.DataStore
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
class LibrusSynergiaGrades(val app: App,
val profile: Profile,
val loginStore: LoginStore,
val dataStore: DataStore,
val callback: ProgressCallback,
val onSuccess: () -> Unit) {
val profile: Profile,
val loginStore: LoginStore,
val data: Data,
val callback: ProgressCallback,
val onSuccess: () -> Unit) {
init {

View File

@ -1,20 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.librus.login
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.ILoginMethod
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
class LoginJst(
app: App,
profile: Profile?,
loginStore: LoginStore,
callback: ProgressCallback,
onSuccess: () -> Unit
): ILoginMethod(app, profile, loginStore, callback, onSuccess) {
companion object {
private const val TAG = "librus.LoginJst"
}
}

View File

@ -1,183 +1,79 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-21.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.login
import android.util.Pair
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.c
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
import java.util.ArrayList
import java.util.regex.Pattern
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
import pl.szczodrzynski.edziennik.utils.Utils.d
import kotlin.math.log
class LoginLibrus(val app: App, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) {
class LoginLibrus(val data: DataLibrus, vararg loginMethodIds: Int, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "librus.LoginLibrus"
private const val TAG = "LoginLibrus"
}
private var loginMethodList = mutableListOf<Int>()
init {
// ustawiamy tokeny, generujemy itp
// nic nie robimy z dostępem do api.librus.pl
// to będzie później
val accessToken = loginStore.getLoginData("accessToken", null)
val refreshToken = loginStore.getLoginData("refreshToken", null)
val tokenExpiryTime = loginStore.getLoginData("tokenExpiryTime", 0L)
for (loginMethodId in loginMethodIds) {
var requiredLoginMethod = loginMethodId
while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) {
librusLoginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod ->
loginMethodList.add(requiredLoginMethod)
requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore)
}
}
}
loginMethodList = loginMethodList.toHashSet().toMutableList()
loginMethodList.sort()
// succeed having a non-expired access token and a refresh token
if (tokenExpiryTime-30 > System.currentTimeMillis() / 1000 && refreshToken != null && accessToken != null) {
nextLoginMethod()
}
private fun nextLoginMethod() {
if (loginMethodList.isEmpty()) {
onSuccess()
return
}
else if (refreshToken != null) {
app.cookieJar.clearForDomain("portal.librus.pl")
accessToken(null, refreshToken)
}
else {
app.cookieJar.clearForDomain("portal.librus.pl")
authorize(LIBRUS_AUTHORIZE_URL)
useLoginMethod(loginMethodList.removeAt(0)) {
nextLoginMethod()
}
}
private fun authorize(url: String?) {
callback.onActionStarted(R.string.sync_action_authorizing)
Request.builder()
.url(url)
.userAgent(LIBRUS_USER_AGENT)
.withClient(app.httpLazy)
.callback(object : TextCallbackHandler() {
override fun onSuccess(data: String, response: Response) {
//d("headers "+response.headers().toString());
val location = response.headers().get("Location")
if (location != null) {
val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
if (authMatcher.find()) {
accessToken(authMatcher.group(1), null)
} else {
//callback.onError(activityContext, Edziennik.CODE_OTHER, "Auth code not found: "+location);
authorize(location)
}
} else {
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(data)
if (csrfMatcher.find()) {
login(csrfMatcher.group(1))
} else {
callback.onError(null, AppError(TAG, 463, CODE_OTHER, "CSRF token not found.", response, data))
}
}
}
override fun onFailure(response: Response, throwable: Throwable) {
callback.onError(null, AppError(TAG, 207, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
private fun login(csrfToken: String) {
callback.onActionStarted(R.string.sync_action_logging_in)
val email = loginStore.getLoginData("email", "")
val password = loginStore.getLoginData("password", "")
Request.builder()
.url(LIBRUS_LOGIN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("email", email)
.addParameter("password", password)
.addHeader("X-CSRF-TOKEN", csrfToken)
.contentType(MediaTypeUtils.APPLICATION_JSON)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(data: JsonObject?, response: Response) {
if (data == null) {
if (response.parserErrorBody != null && response.parserErrorBody.contains("wciąż nieaktywne")) {
callback.onError(null, AppError(TAG, 487, CODE_LIBRUS_NOT_ACTIVATED, response))
}
callback.onError(null, AppError(TAG, 489, CODE_MAINTENANCE, response))
return
}
if (data.get("errors") != null) {
callback.onError(null, AppError(TAG, 490, CODE_OTHER, data.get("errors").asJsonArray.get(0).asString, response, data))
return
}
authorize(data.getString("redirect") ?: LIBRUS_AUTHORIZE_URL)
}
override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 403 || response.code() == 401) {
callback.onError(null, AppError(TAG, 248, CODE_INVALID_LOGIN, response, throwable))
return
}
callback.onError(null, AppError(TAG, 251, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
private var refreshTokenFailed = false
private fun accessToken(code: String?, refreshToken: String?) {
callback.onActionStarted(R.string.sync_action_getting_token)
val params = ArrayList<Pair<String, Any>>()
params.add(Pair("client_id", LIBRUS_CLIENT_ID))
if (code != null) {
params.add(Pair("grant_type", "authorization_code"))
params.add(Pair("code", code))
params.add(Pair("redirect_uri", LIBRUS_REDIRECT_URL))
} else if (refreshToken != null) {
params.add(Pair("grant_type", "refresh_token"))
params.add(Pair("refresh_token", refreshToken))
private fun useLoginMethod(loginMethodId: Int, onSuccess: () -> Unit) {
if (data.loginMethods.contains(loginMethodId)) {
onSuccess()
return
}
d(TAG, "Using login method $loginMethodId")
when (loginMethodId) {
LOGIN_METHOD_LIBRUS_PORTAL -> {
LoginLibrusPortal(data) {
data.loginMethods.add(loginMethodId)
onSuccess()
}
}
LOGIN_METHOD_LIBRUS_API -> {
LoginLibrusApi(data) {
data.loginMethods.add(loginMethodId)
onSuccess()
}
}
LOGIN_METHOD_LIBRUS_SYNERGIA -> {
LoginLibrusApi(data) {
data.loginMethods.add(loginMethodId)
onSuccess()
}
}
LOGIN_METHOD_LIBRUS_MESSAGES -> {
LoginLibrusApi(data) {
data.loginMethods.add(loginMethodId)
onSuccess()
}
}
}
Request.builder()
.url(LIBRUS_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParams(params)
.allowErrorCode(HTTP_UNAUTHORIZED)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(data: JsonObject?, response: Response) {
if (data == null) {
callback.onError(null, AppError(TAG, 539, CODE_MAINTENANCE, response))
return
}
if (data.get("error") != null) {
val hint = data.getString("hint")
if (!refreshTokenFailed && refreshToken != null && (hint == "Token has been revoked" || hint == "Token has expired")) {
c(TAG, "refreshing the token failed. Trying to log in again.")
refreshTokenFailed = true
authorize(LIBRUS_AUTHORIZE_URL)
return
}
val errorText = data.getString("error") + " " + (data.getString("message") ?: "") + " " + (hint ?: "")
callback.onError(null, AppError(TAG, 552, CODE_OTHER, errorText, response, data))
return
}
try {
loginStore.putLoginData("tokenType", data.getString("token_type"))
loginStore.putLoginData("accessToken", data.getString("access_token"))
loginStore.putLoginData("refreshToken", data.getString("refresh_token"))
loginStore.putLoginData("tokenExpiryTime", System.currentTimeMillis() / 1000 + (data.getInt("expires_in") ?: 86400))
onSuccess()
} catch (e: NullPointerException) {
callback.onError(null, AppError(TAG, 311, CODE_OTHER, response, e, data))
}
}
override fun onFailure(response: Response, throwable: Throwable) {
callback.onError(null, AppError(TAG, 317, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
}

View File

@ -0,0 +1,246 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
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.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import java.net.HttpURLConnection
import java.net.HttpURLConnection.*
class LoginLibrusApi {
companion object {
private const val TAG = "LoginLibrusApi"
}
private lateinit var data: DataLibrus
private lateinit var onSuccess: () -> Unit
constructor(data: DataLibrus, onSuccess: () -> Unit) {
this.data = data
this.onSuccess = onSuccess
if (data.profile == null) {
data.callback.onError(null, AppError(TAG, 19, CODE_LIBRUS_PROFILE_NULL))
return
}
if (data.apiTokenExpiryTime-30 > currentTimeUnix() && data.apiAccessToken.isNotNullNorEmpty()) {
onSuccess()
}
else {
when (data.loginStore.mode) {
LOGIN_MODE_LIBRUS_EMAIL -> loginWithPortal()
LOGIN_MODE_LIBRUS_SYNERGIA -> loginWithSynergia()
LOGIN_MODE_LIBRUS_JST -> loginWithJst()
else -> {
data.callback.onError(null, AppError(TAG, 25, CODE_INVALID_LOGIN_MODE))
}
}
}
}
private fun loginWithPortal() {
if (!data.loginMethods.contains(LOGIN_METHOD_LIBRUS_PORTAL)) {
data.callback.onError(null, AppError(TAG, 26, CODE_LOGIN_METHOD_NOT_SATISFIED))
return
}
SynergiaTokenExtractor(data) {
onSuccess()
}
}
private fun copyFromLoginStore() {
data.loginStore.data?.apply {
if (has("accountLogin")) {
data.apiLogin = getString("accountLogin")
remove("accountLogin")
}
if (has("accountPassword")) {
data.apiPassword = getString("accountPassword")
remove("accountPassword")
}
if (has("accountCode")) {
data.apiCode = getString("accountCode")
remove("accountCode")
}
if (has("accountPin")) {
data.apiPin = getString("accountPin")
remove("accountPin")
}
}
}
private fun loginWithSynergia() {
copyFromLoginStore()
if (data.apiRefreshToken != null) {
// refresh a Synergia token
synergiaRefreshToken()
}
else if (data.apiLogin != null && data.apiPassword != null) {
synergiaGetToken()
}
else {
// cannot log in: token expired, no login data present
data.callback.onError(null, AppError(TAG, 91, CODE_INVALID_LOGIN))
}
}
private fun loginWithJst() {
copyFromLoginStore()
if (data.apiRefreshToken != null) {
// refresh a JST token
jstRefreshToken()
}
else if (data.apiCode != null && data.apiPin != null) {
// get a JST token from Code and PIN
jstGetToken()
}
else {
// cannot log in: token expired, no login data present
data.callback.onError(null, AppError(TAG, 110, CODE_INVALID_LOGIN))
}
}
private val tokenCallback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.callback.onError(null, AppError(TAG, 117, CODE_MAINTENANCE, response))
return
}
json.getString("error")?.let { error ->
when (error) {
"librus_captcha_needed" -> {
}
"connection_problems" -> {
}
"invalid_client" -> {
}
"librus_reg_accept_needed" -> {
}
"librus_change_password_error" -> {
}
"librus_password_change_required" -> {
}
"invalid_grant" -> {
}
else -> {
}
}
return
}
try {
data.apiAccessToken = json.getString("access_token")
data.apiRefreshToken = json.getString("refresh_token")
data.apiTokenExpiryTime = currentTimeUnix() + json.getInt("expires_in", 86400)
onSuccess()
} catch (e: NullPointerException) {
data.callback.onError(null, AppError(TAG, 154, EXCEPTION_LOGIN_LIBRUS_API_TOKEN, response, e, json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.callback.onError(null, AppError(TAG, 159, CODE_OTHER, response, throwable))
}
}
private fun synergiaGetToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "password")
.addParameter("username", data.apiLogin)
.addParameter("password", data.apiPassword)
.addParameter("librus_long_term_token", "1")
.addParameter("librus_rules_accepted", "1")
.addHeader("Authorization", "Basic $LIBRUS_API_AUTHORIZATION")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
private fun synergiaRefreshToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "refresh_token")
.addParameter("refresh_token", data.apiRefreshToken)
.addParameter("librus_long_term_token", "1")
.addParameter("librus_rules_accepted", "1")
.addHeader("Authorization", "Basic $LIBRUS_API_AUTHORIZATION")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
private fun jstGetToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_JST_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "implicit_grant")
.addParameter("client_id", LIBRUS_API_CLIENT_ID_JST)
.addParameter("secret", LIBRUS_API_SECRET_JST)
.addParameter("code", data.apiCode)
.addParameter("pin", data.apiPin)
.addParameter("librus_rules_accepted", "1")
.addParameter("librus_mobile_rules_accepted", "1")
.addParameter("librus_long_term_token", "1")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
private fun jstRefreshToken() {
Request.builder()
.url(LIBRUS_API_TOKEN_JST_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("grant_type", "refresh_token")
.addParameter("client_id", LIBRUS_API_CLIENT_ID_JST)
.addParameter("refresh_token", data.apiRefreshToken)
.addParameter("librus_long_term_token", "1")
.addParameter("mobile_app_accept_rules", "1")
.addParameter("synergy_accept_rules", "1")
.contentType(MediaTypeUtils.APPLICATION_FORM)
.post()
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED)
.callback(tokenCallback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,8 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.login
class LoginLibrusMessages {
}

View File

@ -0,0 +1,179 @@
package pl.szczodrzynski.edziennik.api.v2.librus.login
import android.util.Pair
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.utils.Utils.c
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
import java.util.ArrayList
import java.util.regex.Pattern
class LoginLibrusPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "LoginLibrusPortal"
}
init { run {
if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) {
data.callback.onError(null, AppError(TAG, 27, CODE_INVALID_LOGIN_MODE))
return@run
}
if (data.portalEmail == null || data.portalPassword == null) {
data.callback.onError(null, AppError(TAG, 31, CODE_INVALID_LOGIN))
return@run
}
// succeed having a non-expired access token and a refresh token
if (data.portalTokenExpiryTime-30 > currentTimeUnix() && data.portalRefreshToken.isNotNullNorEmpty() && data.portalAccessToken.isNotNullNorEmpty()) {
onSuccess()
}
else if (data.portalRefreshToken != null) {
data.app.cookieJar.clearForDomain("portal.librus.pl")
accessToken(null, data.portalRefreshToken)
}
else {
data.app.cookieJar.clearForDomain("portal.librus.pl")
authorize(LIBRUS_AUTHORIZE_URL)
}
}}
private fun authorize(url: String?) {
data.callback.onActionStarted(R.string.sync_action_authorizing)
Request.builder()
.url(url)
.userAgent(LIBRUS_USER_AGENT)
.withClient(data.app.httpLazy)
.callback(object : TextCallbackHandler() {
override fun onSuccess(json: String, response: Response) {
val location = response.headers().get("Location")
if (location != null) {
val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location)
if (authMatcher.find()) {
accessToken(authMatcher.group(1), null)
} else {
authorize(location)
}
} else {
val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(json)
if (csrfMatcher.find()) {
login(csrfMatcher.group(1))
} else {
data.callback.onError(null, AppError(TAG, 463, CODE_OTHER, "CSRF token not found.", response, json))
}
}
}
override fun onFailure(response: Response, throwable: Throwable) {
data.callback.onError(null, AppError(TAG, 207, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
private fun login(csrfToken: String) {
data.callback.onActionStarted(R.string.sync_action_logging_in)
Request.builder()
.url(LIBRUS_LOGIN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParameter("email", data.portalEmail)
.addParameter("password", data.portalPassword)
.addHeader("X-CSRF-TOKEN", csrfToken)
.contentType(MediaTypeUtils.APPLICATION_JSON)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
data.callback.onError(null, AppError(TAG, 487, CODE_LIBRUS_NOT_ACTIVATED, response))
return
}
data.callback.onError(null, AppError(TAG, 489, CODE_MAINTENANCE, response))
return
}
if (json.get("errors") != null) {
data.callback.onError(null, AppError(TAG, 490, CODE_OTHER, json.getJsonArray("errors")?.get(0)?.asString, response, json))
return
}
authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL))
}
override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 403 || response.code() == 401) {
data.callback.onError(null, AppError(TAG, 248, CODE_INVALID_LOGIN, response, throwable))
return
}
data.callback.onError(null, AppError(TAG, 251, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
private var refreshTokenFailed = false
private fun accessToken(code: String?, refreshToken: String?) {
data.callback.onActionStarted(R.string.sync_action_getting_token)
val params = ArrayList<Pair<String, Any>>()
params.add(Pair("client_id", LIBRUS_CLIENT_ID))
if (code != null) {
params.add(Pair("grant_type", "authorization_code"))
params.add(Pair("code", code))
params.add(Pair("redirect_uri", LIBRUS_REDIRECT_URL))
} else if (refreshToken != null) {
params.add(Pair("grant_type", "refresh_token"))
params.add(Pair("refresh_token", refreshToken))
}
Request.builder()
.url(LIBRUS_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT)
.addParams(params)
.allowErrorCode(HTTP_UNAUTHORIZED)
.post()
.callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
data.callback.onError(null, AppError(TAG, 539, CODE_MAINTENANCE, response))
return
}
json.getString("error")?.let { error ->
val hint = json.getString("hint", "")
val message = json.getString("message", "")
if (!refreshTokenFailed && refreshToken != null && (hint == "Token has been revoked" || hint == "Token has expired")) {
c(TAG, "refreshing the token failed. Trying to log in again.")
refreshTokenFailed = true
authorize(LIBRUS_AUTHORIZE_URL)
return
}
val errorText = "$error $message $hint"
data.callback.onError(null, AppError(TAG, 552, CODE_OTHER, errorText, response, json))
return
}
try {
data.portalAccessToken = json.getString("access_token")
data.portalRefreshToken = json.getString("refresh_token")
data.portalTokenExpiryTime = currentTimeUnix() + json.getInt("expires_in", 86400)
onSuccess()
} catch (e: NullPointerException) {
data.callback.onError(null, AppError(TAG, 311, CODE_OTHER, response, e, json))
}
}
override fun onFailure(response: Response, throwable: Throwable) {
data.callback.onError(null, AppError(TAG, 317, CODE_OTHER, response, throwable))
}
})
.build()
.enqueue()
}
}

View File

@ -0,0 +1,8 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.api.v2.librus.login
class LoginLibrusSynergia {
}

View File

@ -1,12 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.librus.login
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.datamodels.LoginStore
class LoginSynergia(val app: App, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "librus.LoginSynergia"
}
}

View File

@ -8,41 +8,43 @@ import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_USER_AGENT
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNT_URL
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.*
class SynergiaTokenExtractor(val app: App, val profile: Profile, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) {
class SynergiaTokenExtractor(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "librus.SynergiaToken"
}
init {
val accountToken = profile.getStudentData("accountToken", null)
val accountTokenTime = profile.getStudentData("accountTokenTime", 0L)
if (accountToken.isNotNullNorEmpty() && currentTimeUnix() - accountTokenTime < 3 * 60 * 60) {
init { run {
if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) {
data.callback.onError(null, AppError(TAG, 23, CODE_INVALID_LOGIN_MODE))
return@run
}
if (data.profile == null) {
data.callback.onError(null, AppError(TAG, 28, CODE_LIBRUS_PROFILE_NULL))
return@run
}
if (data.apiTokenExpiryTime-30 > currentTimeUnix() && data.apiAccessToken.isNotNullNorEmpty()) {
onSuccess()
}
else {
if (!synergiaAccount())
callback.onError(null, AppError(TAG, 33, CODE_INTERNAL_MISSING_DATA))
synergiaAccount()
}
}
}}
private fun synergiaAccount(): Boolean {
val accountLogin = profile.getStudentData("accountLogin", null) ?: return false
val tokenType = loginStore.getLoginData("tokenType", null) ?: return false
val accessToken = loginStore.getLoginData("accessToken", null) ?: return false
callback.onActionStarted(R.string.sync_action_getting_account)
val accountLogin = data.apiLogin ?: return false
val accessToken = data.portalAccessToken ?: return false
data.callback.onActionStarted(R.string.sync_action_getting_account)
d(TAG, "Requesting " + (LIBRUS_ACCOUNT_URL + accountLogin))
Request.builder()
.url(LIBRUS_ACCOUNT_URL + accountLogin)
.userAgent(LIBRUS_USER_AGENT)
.addHeader("Authorization", "$tokenType $accessToken")
.addHeader("Authorization", "Bearer $accessToken")
.get()
.allowErrorCode(HTTP_NOT_FOUND)
.allowErrorCode(HTTP_FORBIDDEN)
@ -50,57 +52,56 @@ class SynergiaTokenExtractor(val app: App, val profile: Profile, val loginStore:
.allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_GONE)
.callback(object : JsonCallbackHandler() {
override fun onSuccess(data: JsonObject?, response: Response) {
if (data == null) {
callback.onError(null, AppError(TAG, 641, CODE_MAINTENANCE, response))
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
data.callback.onError(null, AppError(TAG, 641, CODE_MAINTENANCE, response))
return
}
if (response.code() == 410) {
val reason = data.get("reason")
val reason = json.get("reason")
if (reason != null && reason !is JsonNull && reason.asString == "requires_an_action") {
callback.onError(null, AppError(TAG, 1078, CODE_LIBRUS_DISCONNECTED, response, data))
data.callback.onError(null, AppError(TAG, 1078, CODE_LIBRUS_DISCONNECTED, response, json))
return
}
callback.onError(null, AppError(TAG, 70, CODE_INTERNAL_LIBRUS_ACCOUNT_410))
data.callback.onError(null, AppError(TAG, 70, CODE_INTERNAL_LIBRUS_ACCOUNT_410))
return
}
if (data.get("message") != null) {
val message = data.get("message").asString
if (json.get("message") != null) {
val message = json.get("message").asString
if (message == "Account not found") {
callback.onError(null, AppError(TAG, 651, CODE_OTHER, app.getString(R.string.sync_error_register_student_not_associated_format, profile.studentNameLong, accountLogin), response, data))
data.callback.onError(null, AppError(TAG, 651, CODE_OTHER, data.app.getString(R.string.sync_error_register_student_not_associated_format, data.profile?.studentNameLong ?: "", accountLogin), response, json))
return
}
callback.onError(null, AppError(TAG, 654, CODE_OTHER, message + "\n\n" + accountLogin, response, data))
data.callback.onError(null, AppError(TAG, 654, CODE_OTHER, message + "\n\n" + accountLogin, response, json))
return
}
if (response.code() == HTTP_OK) {
try {
// synergiaAccount is executed when a synergia token needs a refresh
val accountId = data.getInt("id")
val accountToken = data.getString("accessToken")
val accountId = json.getInt("id")
val accountToken = json.getString("accessToken")
if (accountId == null || accountToken == null) {
callback.onError(null, AppError(TAG, 1284, CODE_OTHER, data))
data.callback.onError(null, AppError(TAG, 1284, CODE_OTHER, json))
return
}
profile.putStudentData("accountId", accountId)
profile.putStudentData("accountToken", accountToken)
profile.putStudentData("accountTokenTime", System.currentTimeMillis() / 1000)
profile.studentNameLong = data.getString("studentName")
val nameParts = data.getString("studentName")?.split(" ")?.toTypedArray()
profile.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0)
data.apiAccessToken = accountToken
data.apiTokenExpiryTime = currentTimeUnix() + 6*60*60
data.profile?.studentNameLong = json.getString("studentName")
val nameParts = json.getString("studentName")?.split(" ")?.toTypedArray()
data.profile?.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0)
onSuccess()
} catch (e: NullPointerException) {
e.printStackTrace()
callback.onError(null, AppError(TAG, 662, CODE_OTHER, response, e, data))
data.callback.onError(null, AppError(TAG, 662, CODE_OTHER, response, e, json))
}
} else {
callback.onError(null, AppError(TAG, 425, CODE_OTHER, response, data))
data.callback.onError(null, AppError(TAG, 425, CODE_OTHER, response, json))
}
}
override fun onFailure(response: Response, throwable: Throwable) {
callback.onError(null, AppError(TAG, 432, CODE_OTHER, response, throwable))
data.callback.onError(null, AppError(TAG, 432, CODE_OTHER, response, throwable))
}
})
.build()

View File

@ -3,15 +3,21 @@ package pl.szczodrzynski.edziennik.api.v2.models
import android.util.LongSparseArray
import androidx.core.util.forEach
import androidx.core.util.isNotEmpty
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.datamodels.*
import pl.szczodrzynski.edziennik.models.Date
data class DataStore(private val appDb: AppDb, val profileId: Int) {
open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) {
var fakeLogin = false
lateinit var callback: ProgressCallback
val loginMethods = mutableListOf<Int>()
val teacherList: LongSparseArray<Teacher> = LongSparseArray()
val subjectList: LongSparseArray<Subject> = LongSparseArray()
val teacherList = LongSparseArray<Teacher>()
val subjectList = LongSparseArray<Subject>()
val teamList = mutableListOf<Team>()
val lessonList = mutableListOf<Lesson>()
val lessonChangeList = mutableListOf<LessonChange>()
@ -28,15 +34,19 @@ data class DataStore(private val appDb: AppDb, val profileId: Int) {
val metadataList = mutableListOf<Metadata>()
val messageMetadataList = mutableListOf<Metadata>()
private val db by lazy { app.db }
init {
clear()
appDb.teacherDao().getAllNow(profileId).forEach { teacher ->
teacherList.put(teacher.id, teacher)
}
appDb.subjectDao().getAllNow(profileId).forEach { subject ->
subjectList.put(subject.id, subject)
if (profile != null) {
db.teacherDao().getAllNow(profile.id).forEach { teacher ->
teacherList.put(teacher.id, teacher)
}
db.subjectDao().getAllNow(profile.id).forEach { subject ->
subjectList.put(subject.id, subject)
}
}
/*val teacher = teachers.byNameFirstLast("Jan Kowalski") ?: Teacher(1, 1, "", "").let {
@ -67,57 +77,60 @@ data class DataStore(private val appDb: AppDb, val profileId: Int) {
}
fun saveData() {
if (profile == null)
return
if (teacherList.isNotEmpty()) {
val tempList: ArrayList<Teacher> = ArrayList()
teacherList.forEach { _, teacher ->
tempList.add(teacher)
}
appDb.teacherDao().addAll(tempList)
db.teacherDao().addAll(tempList)
}
if (subjectList.isNotEmpty()) {
val tempList: ArrayList<Subject> = ArrayList()
subjectList.forEach { _, subject ->
tempList.add(subject)
}
appDb.subjectDao().addAll(tempList)
db.subjectDao().addAll(tempList)
}
if (teamList.isNotEmpty())
appDb.teamDao().addAll(teamList)
db.teamDao().addAll(teamList)
if (lessonList.isNotEmpty()) {
appDb.lessonDao().clear(profileId)
appDb.lessonDao().addAll(lessonList)
db.lessonDao().clear(profile.id)
db.lessonDao().addAll(lessonList)
}
if (lessonChangeList.isNotEmpty())
appDb.lessonChangeDao().addAll(lessonChangeList)
db.lessonChangeDao().addAll(lessonChangeList)
if (gradeCategoryList.isNotEmpty())
appDb.gradeCategoryDao().addAll(gradeCategoryList)
db.gradeCategoryDao().addAll(gradeCategoryList)
if (gradeList.isNotEmpty()) {
appDb.gradeDao().clear(profileId)
appDb.gradeDao().addAll(gradeList)
db.gradeDao().clear(profile.id)
db.gradeDao().addAll(gradeList)
}
if (eventList.isNotEmpty()) {
appDb.eventDao().removeFuture(profileId, Date.getToday())
appDb.eventDao().addAll(eventList)
db.eventDao().removeFuture(profile.id, Date.getToday())
db.eventDao().addAll(eventList)
}
if (eventTypeList.isNotEmpty())
appDb.eventTypeDao().addAll(eventTypeList)
db.eventTypeDao().addAll(eventTypeList)
if (noticeList.isNotEmpty()) {
appDb.noticeDao().clear(profileId)
appDb.noticeDao().addAll(noticeList)
db.noticeDao().clear(profile.id)
db.noticeDao().addAll(noticeList)
}
if (attendanceList.isNotEmpty())
appDb.attendanceDao().addAll(attendanceList)
db.attendanceDao().addAll(attendanceList)
if (announcementList.isNotEmpty())
appDb.announcementDao().addAll(announcementList)
db.announcementDao().addAll(announcementList)
if (messageList.isNotEmpty())
appDb.messageDao().addAllIgnore(messageList)
db.messageDao().addAllIgnore(messageList)
if (messageRecipientList.isNotEmpty())
appDb.messageRecipientDao().addAll(messageRecipientList)
db.messageRecipientDao().addAll(messageRecipientList)
if (messageRecipientIgnoreList.isNotEmpty())
appDb.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList)
db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList)
if (metadataList.isNotEmpty())
appDb.metadataDao().addAllIgnore(metadataList)
db.metadataDao().addAllIgnore(metadataList)
if (messageMetadataList.isNotEmpty())
appDb.metadataDao().setSeen(messageMetadataList)
db.metadataDao().setSeen(messageMetadataList)
}
}

View File

@ -14,13 +14,14 @@ import pl.szczodrzynski.edziennik.datamodels.Profile
* @param loginType type of the e-register this endpoint handles
* @param endpointId a unique ID of this endpoint
* @param featureIds a [List] of [Feature]s (their IDs) this endpoint can download
* May be null if no strict feature set is associated with this method.
* @param endpointClass a [Class] which constructor will be invoked when a data download is needed
* @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore].
*/
class Endpoint(
val loginType: Int,
val endpointId: Int,
val featureIds: List<Int>,
val featureIds: List<Int>?,
val endpointClass: Class<*>,
val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int
)

View File

@ -1,6 +1,5 @@
package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.api.v2.endpoint
data class Feature(val featureId: Int, val loginOptions: Map<Int, List<Int>>) {

View File

@ -17,13 +17,14 @@ import pl.szczodrzynski.edziennik.datamodels.Profile
* @param loginType type of the e-register this login method handles
* @param loginMethodId a unique ID of this login method
* @param featureIds a [List] of [Feature]s (their IDs) this login method can provide access to
* May be null if no strict feature set is associated with this method.
* @param loginMethodClass a [Class] which constructor will be invoked when a log in is needed
* @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore].
*/
class LoginMethod(
val loginType: Int,
val loginMethodId: Int,
val featureIds: List<Int>,
val featureIds: List<Int>?,
val loginMethodClass: Class<*>,
val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int
)

View File

@ -44,7 +44,8 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.api.AppError;
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.api.v2.librus.Librus;
import pl.szczodrzynski.edziennik.api.v2.librus.LibrusOld;
import pl.szczodrzynski.edziennik.api.v2.librus.LibrusTest;
import pl.szczodrzynski.edziennik.databinding.CardLuckyNumberBinding;
import pl.szczodrzynski.edziennik.databinding.CardUpdateBinding;
import pl.szczodrzynski.edziennik.databinding.FragmentHomeBinding;
@ -115,45 +116,18 @@ public class HomeFragment extends Fragment {
return true;
});*/
b.testButton.setOnClickListener((v -> {
LoginStore loginStore = new LoginStore(1, LOGIN_TYPE_LIBRUS, new JsonObject());
loginStore.putLoginData("email", "example@example.com");
loginStore.putLoginData("password", "zaq1@WSX");
Profile profile = new Profile(1, "test", "testsubname", 1);
profile.putStudentData("accountLogin", "1234567");
new Librus(app, profile, loginStore).login(new SyncCallback() {
@Override
public void onLoginFirst(List<Profile> profileList, LoginStore loginStore) {
}
@Override
public void onSuccess(Context activityContext, ProfileFull profileFull) {
}
@Override
public void onProgress(int progressStep) {
}
@Override
public void onActionStarted(int stringResId) {
}
@Override
public void onError(Context activityContext, @NonNull AppError error) {
}
});
}));
b.composeButton.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
b.composeButton.setOnClickListener((v -> {
startActivity(new Intent(activity, MessagesComposeActivity.class));
}));
LibrusTest test = new LibrusTest(app);
b.testButton.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
b.testButton.setOnClickListener((v -> {
test.go();
}));
//((TextView)v.findViewById(R.id.nextSync)).setText(getString(R.string.next_sync_format,Time.fromMillis(app.appJobs.syncJobTime).getStringHMS()));