[API/Gdynia] Add first login and login page.

This commit is contained in:
Kacper Ziubryniewicz 2020-05-17 23:04:44 +02:00
parent ea27492436
commit e761a0ec8a
19 changed files with 289 additions and 4 deletions

View File

@ -1247,3 +1247,13 @@ operator fun <K, V> Iterable<Pair<K, V>>.get(key: K): V? {
}
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
fun String?.nullIfEmpty() = when {
this.isNullOrEmpty() -> null
else -> this
}
fun String?.nullIfBlank() = when {
this.isNullOrBlank() -> null
else -> this
}

View File

@ -126,3 +126,5 @@ const val PODLASIE_API_USER_ENDPOINT = "/pobierzDaneUcznia"
const val GDYNIA_WEB_URL = "https://nasze.miasto.gdynia.pl/ed_miej"
const val GDYNIA_WEB_LOGIN = "login.pl"
const val GDYNIA_WEB_LOGIN_CHECK = "login_check.pl"
const val GDYNIA_WEB_START = "zest_start.pl"
const val GDYNIA_WEB_DISPLAY = "display.pl"

View File

@ -233,5 +233,6 @@ const val ERROR_ONEDRIVE_DOWNLOAD = 930
const val EXCEPTION_VULCAN_WEB_LOGIN = 931
const val EXCEPTION_VULCAN_WEB_REQUEST = 932
const val EXCEPTION_PODLASIE_API_REQUEST = 940
const val EXCEPTION_GDYNIA_WEB_REQUEST = 950
const val LOGIN_NO_ARGUMENTS = 1201

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.login.GdyniaLoginWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi
@ -154,6 +155,11 @@ val podlasieLoginMethods = listOf(
const val LOGIN_TYPE_GDYNIA = 7
const val LOGIN_MODE_GDYNIA_WEB = 0
const val LOGIN_METHOD_GDYNIA_WEB = 100
val gdyniaLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_GDYNIA, LOGIN_METHOD_GDYNIA_WEB, GdyniaLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
)
val templateLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java)

View File

@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.Edudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.Gdynia
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.Idziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.Librus
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.Mobidziennik
@ -82,6 +83,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_EDUDZIENNIK -> Edudziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_PODLASIE -> Podlasie(app, profile, loginStore, taskCallback)
LOGIN_TYPE_GDYNIA -> Gdynia(app, profile, loginStore, taskCallback)
LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback)
else -> null
}

View File

@ -34,6 +34,21 @@ class DataGdynia(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
get() { mLoginPassword = mLoginPassword ?: loginStore.getLoginData("password", null); return mLoginPassword }
set(value) { loginStore.putLoginData("password", value); mLoginPassword = value }
private var mStudentLogin: String? = null
var studentLogin: String?
get() { mStudentLogin = mStudentLogin ?: profile?.getStudentData("studentLogin", null); return mStudentLogin }
set(value) { profile?.putStudentData("studentLogin", value) ?: return; mStudentLogin = value }
private var mStudentAlias: String? = null
var studentAlias: String?
get() { mStudentAlias = mStudentAlias ?: profile?.getStudentData("studentAlias", null); return mStudentAlias }
set(value) { profile?.putStudentData("studentAlias", value) ?: return; mStudentAlias = value }
private var mStudentEmail: String? = null
var studentEmail: String?
get() { mStudentEmail = mStudentEmail ?: profile?.getStudentData("studentEmail", null); return mStudentEmail }
set(value) { profile?.putStudentData("studentEmail", value) ?: return; mStudentEmail = value }
/* __ __ _
\ \ / / | |
\ \ /\ / /__| |__

View File

@ -6,9 +6,14 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.gdynia
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.data.GdyniaData
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.firstlogin.GdyniaFirstLogin
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.login.GdyniaLogin
import pl.szczodrzynski.edziennik.data.api.gdyniaLoginMethods
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.prepare
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
@ -19,7 +24,7 @@ import pl.szczodrzynski.edziennik.utils.Utils
class Gdynia(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
companion object {
private const val TAG = "Gdynia"
private const val TAG = "Gdynia"
}
val internalErrorList = mutableListOf<Int>()
@ -32,8 +37,29 @@ private const val TAG = "Gdynia"
}
}
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
private fun completed() {
data.saveData()
callback.onCompleted()
}
/* _______ _ _ _ _ _
|__ __| | /\ | | (_) | | |
| | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___
| | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \
| | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | |
|_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_|
__/ |
|__*/
override fun sync(featureIds: List<Int>, viewId: Int?, onlyEndpoints: List<Int>?, arguments: JsonObject?) {
data.arguments = arguments
data.prepare(gdyniaLoginMethods, GdyniaFeatures, featureIds, viewId, onlyEndpoints)
Utils.d(TAG, "LoginMethod IDs: ${data.targetLoginMethodIds}")
Utils.d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
GdyniaLogin(data) {
GdyniaData(data) {
completed()
}
}
}
override fun getMessage(message: MessageFull) {
@ -65,7 +91,9 @@ private const val TAG = "Gdynia"
}
override fun firstLogin() {
TODO("Not yet implemented")
GdyniaFirstLogin(data) {
completed()
}
}
override fun cancel() {

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-5-17
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.gdynia
import pl.szczodrzynski.edziennik.data.api.models.Feature
val GdyniaFeatures = listOf<Feature>()

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-5-17
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.data
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.DataGdynia
import pl.szczodrzynski.edziennik.utils.Utils
class GdyniaData(val data: DataGdynia, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "GdyniaData"
}
init {
nextEndpoint(onSuccess)
}
private fun nextEndpoint(onSuccess: () -> Unit) {
if (data.targetEndpointIds.isEmpty()) {
onSuccess()
return
}
if (data.cancelled) {
onSuccess()
return
}
val id = data.targetEndpointIds.firstKey()
val lastSync = data.targetEndpointIds.remove(id)
useEndpoint(id, lastSync) {
data.progress(data.progressStep)
nextEndpoint(onSuccess)
}
}
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
when (endpointId) {
else -> onSuccess(endpointId)
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-5-17
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.data
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.DataGdynia
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
open class GdyniaWeb(open val data: DataGdynia, open val lastSync: Long?) {
companion object {
private const val TAG = "GdyniaWeb"
}
fun webGet(tag: String, endpoint: String, parameters: Map<String, Any?>? = null, onSuccess: (text: String) -> Unit) {
val url = "$GDYNIA_WEB_URL/$endpoint"
d(tag, "Request: Gdynia/Web - $url")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text == null || response == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response)
.withApiResponse(text))
return
}
try {
onSuccess(text)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_GDYNIA_WEB_REQUEST)
.withApiResponse(text)
.withResponse(response)
.withThrowable(e))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url(url)
.userAgent(SYSTEM_USER_AGENT)
.apply {
parameters?.forEach { (key, value) ->
addParameter(key, value)
}
}
.get()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2020-5-17
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.firstlogin
import org.greenrobot.eventbus.EventBus
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.GDYNIA_WEB_DISPLAY
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_GDYNIA
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.DataGdynia
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.data.GdyniaWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.gdynia.login.GdyniaLoginWeb
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.data.db.entity.Profile
class GdyniaFirstLogin(val data: DataGdynia, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "GdyniaFirstLogin"
}
private val web = GdyniaWeb(data, null)
private val profileList = mutableListOf<Profile>()
init {
val loginStoreId = data.loginStore.id
val loginStoreType = LOGIN_TYPE_GDYNIA
GdyniaLoginWeb(data) {
web.webGet(TAG, GDYNIA_WEB_DISPLAY, parameters = mapOf(
"form" to "zmiana_danych"
)) { html ->
run {
val doc = Jsoup.parse(html)
val firstName = doc.selectFirst("#f_imie")?.`val`() ?: return@run
val lastName = doc.selectFirst("#f_nazwisko")?.`val`() ?: return@run
val studentNameLong = "$firstName $lastName".fixName()
val studentNameShort = studentNameLong.getShortName()
val login = doc.selectFirst("#f_login").`val`().nullIfBlank()
val alias = doc.selectFirst("#f_kod_logowania").`val`().nullIfBlank()
val email = doc.selectFirst("#f_email")?.`val`().nullIfBlank()
val subname = alias ?: email ?: data.loginUsername
val profile = Profile(
loginStoreId,
loginStoreId,
loginStoreType,
studentNameLong,
subname,
studentNameLong,
studentNameShort,
null
).apply {
studentData["studentLogin"] = login
studentData["studentAlias"] = alias
studentData["studentEmail"] = email
}
profileList.add(profile)
}
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
}
}

View File

@ -34,7 +34,7 @@ class TemplateData(val data: DataTemplate, val onSuccess: () -> Unit) {
}
val id = data.targetEndpointIds.firstKey()
val lastSync = data.targetEndpointIds.remove(id)
useEndpoint(id, lastSync) { endpointId ->
useEndpoint(id, lastSync) {
data.progress(data.progressStep)
nextEndpoint(onSuccess)
}

View File

@ -361,6 +361,37 @@ object LoginInfo {
errorCodes = mapOf()
)
)
),
Register(
loginType = LOGIN_TYPE_GDYNIA,
internalName = "gdynia",
registerName = R.string.login_type_gdynia,
registerLogo = R.drawable.login_logo_gdynia,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_GDYNIA_WEB,
name = R.string.login_mode_gdynia_web,
icon = R.drawable.login_mode_gdynia_web,
guideText = R.string.login_mode_gdynia_web_guide,
credentials = listOf(
Credential(
keyName = "username",
name = R.string.login_hint_username,
icon = CommunityMaterial.Icon.cmd_account_outline,
emptyText = R.string.login_error_no_username,
invalidText = R.string.login_error_incorrect_username,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "^[a-zA-Z0-9_\\-.]+$",
caseMode = Credential.CaseMode.UNCHANGED
),
getPasswordCredential("password")
),
errorCodes = mapOf(
ERROR_LOGIN_GDYNIA_WEB_INVALID_CREDENTIALS to R.string.login_error_incorrect_login_or_password
)
)
)
)
) }

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -193,6 +193,7 @@
<string name="error_931" translatable="false">EXCEPTION_VULCAN_WEB_LOGIN</string>
<string name="error_932" translatable="false">EXCEPTION_VULCAN_WEB_REQUEST</string>
<string name="error_940" translatable="false">EXCEPTION_PODLASIE_API_REQUEST</string>
<string name="error_950" translatable="false">EXCEPTION_GDYNIA_WEB_REQUEST</string>
<string name="error_1201" translatable="false">LOGIN_NO_ARGUMENTS</string>
@ -385,6 +386,7 @@
<string name="error_931_reason">EXCEPTION_VULCAN_WEB_LOGIN</string>
<string name="error_932_reason">EXCEPTION_VULCAN_WEB_REQUEST</string>
<string name="error_940_reason">Zgłoś błąd: wyjątek w API PPE</string>
<string name="error_950_reason">Zgłoś błąd: wyjątek w dzienniku Platformy Miejskiej Gdyni</string>
<string name="error_1201_reason">Nie podano parametrów</string>
</resources>

View File

@ -1357,4 +1357,7 @@
<string name="login_mode_podlasie_api_guide">Podaj token aplikacji mobilnej.</string>
<string name="edziennik_progress_login_podlasie_api">Logowanie do PPE…</string>
<string name="edziennik_progress_login_gdynia_web">Logowanie do dziennika gdyńskiego…</string>
<string name="login_type_gdynia">Platforma Miejska Gdyni</string>
<string name="login_mode_gdynia_web">Zaloguj używając nazwy użytkownika i hasła</string>
<string name="login_mode_gdynia_web_guide">Podaj nazwę użytkownika oraz hasło, za pomocą których logujesz się na stronie internetowej dziennika.</string>
</resources>