forked from github/szkolny
[API/Vulcan] Implement Vulcan lucky numbers.
This commit is contained in:
parent
e8dad29e5d
commit
f685a4dceb
@ -168,6 +168,7 @@ const val ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED = 349
|
||||
const val ERROR_VULCAN_WEB_LOGGED_OUT = 350
|
||||
const val ERROR_VULCAN_WEB_CERTIFICATE_POST_FAILED = 351
|
||||
const val ERROR_VULCAN_WEB_GRADUATE_ACCOUNT = 352
|
||||
const val ERROR_VULCAN_WEB_NO_SCHOOLS = 353
|
||||
|
||||
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
|
||||
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402
|
||||
|
@ -140,7 +140,10 @@ object Regexes {
|
||||
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
|
||||
}
|
||||
val VULCAN_WEB_PERMISSIONS by lazy {
|
||||
"""permissions: '([A-z0-9\/=+\-_]+?)'""".toRegex()
|
||||
"""permissions: '([A-z0-9/=+\-_]+?)'""".toRegex()
|
||||
}
|
||||
val VULCAN_WEB_SYMBOL_VALIDATE by lazy {
|
||||
"""[A-z0-9]+""".toRegex(IGNORE_CASE)
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,9 +17,10 @@ import pl.szczodrzynski.edziennik.values
|
||||
|
||||
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||
|
||||
fun isWebMainLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
|
||||
&& apiFingerprint[symbol].isNotNullNorEmpty()
|
||||
&& apiPrivateKey[symbol].isNotNullNorEmpty()
|
||||
fun isWebMainLoginValid() = webExpiryTime-30 > currentTimeUnix()
|
||||
&& webAuthCookie.isNotNullNorEmpty()
|
||||
&& webHost.isNotNullNorEmpty()
|
||||
&& webType.isNotNullNorEmpty()
|
||||
&& symbol.isNotNullNorEmpty()
|
||||
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
|
||||
&& apiFingerprint[symbol].isNotNullNorEmpty()
|
||||
|
@ -19,6 +19,7 @@ const val ENDPOINT_VULCAN_API_NOTICES = 1070
|
||||
const val ENDPOINT_VULCAN_API_ATTENDANCE = 1080
|
||||
const val ENDPOINT_VULCAN_API_MESSAGES_INBOX = 1090
|
||||
const val ENDPOINT_VULCAN_API_MESSAGES_SENT = 1100
|
||||
const val ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS = 2010
|
||||
|
||||
val VulcanFeatures = listOf(
|
||||
// timetable
|
||||
@ -61,6 +62,13 @@ val VulcanFeatures = listOf(
|
||||
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
|
||||
},
|
||||
|
||||
/**
|
||||
* Lucky number - using WEB Main.
|
||||
*/
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
|
||||
ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN
|
||||
), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN)).withShouldSync { data -> data.shouldSyncLuckyNumber() },
|
||||
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
|
||||
ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API,
|
||||
ENDPOINT_VULCAN_API_DICTIONARIES to LOGIN_METHOD_VULCAN_API
|
||||
|
@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web.VulcanWebLuckyNumber
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
@ -86,6 +87,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
|
||||
VulcanApiMessagesSent(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
||||
VulcanWebLuckyNumber(data, lastSync, onSuccess)
|
||||
}
|
||||
else -> onSuccess(endpointId)
|
||||
}
|
||||
}
|
||||
|
@ -44,9 +44,9 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
Jspoon.create().adapter(CufsCertificate::class.java)
|
||||
}
|
||||
|
||||
fun saveCertificate(certificate: String) {
|
||||
fun saveCertificate(xml: String) {
|
||||
val file = File(data.app.filesDir, "cert_"+(data.webUsername ?: data.webEmail)+".xml")
|
||||
file.writeText(certificate)
|
||||
file.writeText(xml)
|
||||
}
|
||||
|
||||
fun readCertificate(): String? {
|
||||
@ -56,20 +56,20 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
return null
|
||||
}
|
||||
|
||||
fun parseCertificate(certificate: String): CufsCertificate {
|
||||
val xml = certificate
|
||||
fun parseCertificate(xml: String): CufsCertificate {
|
||||
val xmlParsed = xml
|
||||
.replace("<[a-z]+?:".toRegex(), "<")
|
||||
.replace("</[a-z]+?:".toRegex(), "</")
|
||||
.replace("\\sxmlns.*?=\".+?\"".toRegex(), "")
|
||||
|
||||
return certificateAdapter.fromHtml(xml)
|
||||
return certificateAdapter.fromHtml(xmlParsed).also {
|
||||
it.xml = xml
|
||||
}
|
||||
}
|
||||
|
||||
fun postCertificate(certificate: String, symbol: String, onResult: (symbol: String, state: Int) -> Unit): Boolean {
|
||||
val cufsCertificate = parseCertificate(certificate)
|
||||
|
||||
fun postCertificate(certificate: CufsCertificate, symbol: String, onResult: (symbol: String, state: Int) -> Unit): Boolean {
|
||||
// check if the certificate is valid
|
||||
if (Date.fromIso(cufsCertificate.expiryDate) < System.currentTimeMillis())
|
||||
if (Date.fromIso(certificate.expiryDate) < System.currentTimeMillis())
|
||||
return false
|
||||
|
||||
val callback = object : TextCallbackHandler() {
|
||||
@ -86,6 +86,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
if (!validateCallback(text, response, jsonResponse = false)) {
|
||||
return
|
||||
}
|
||||
data.webExpiryTime = Date.fromIso(certificate.expiryDate) / 1000L
|
||||
onResult(symbol, STATE_SUCCESS)
|
||||
}
|
||||
|
||||
@ -102,8 +103,8 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
.userAgent(SYSTEM_USER_AGENT)
|
||||
.post()
|
||||
.addParameter("wa", "wsignin1.0")
|
||||
.addParameter("wctx", cufsCertificate.targetUrl)
|
||||
.addParameter("wresult", certificate)
|
||||
.addParameter("wctx", certificate.targetUrl)
|
||||
.addParameter("wresult", certificate.xml)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
|
||||
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
|
||||
@ -138,7 +139,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
|
||||
|
||||
val schoolSymbols = mutableListOf<String>()
|
||||
val clientUrl = "https://uonetplus-uczen.${data.webHost}/$symbol/"
|
||||
val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/"
|
||||
var clientIndex = text.indexOf(clientUrl)
|
||||
var count = 0
|
||||
while (clientIndex != -1 && count < 100) {
|
||||
@ -149,6 +150,17 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
clientIndex = text.indexOf(clientUrl, startIndex = endIndex)
|
||||
count++
|
||||
}
|
||||
schoolSymbols.removeAll {
|
||||
it.toLowerCase() == "default"
|
||||
|| !it.matches(Regexes.VULCAN_WEB_SYMBOL_VALIDATE)
|
||||
}
|
||||
|
||||
if (postErrors && schoolSymbols.isEmpty()) {
|
||||
data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_SCHOOLS)
|
||||
.withResponse(response)
|
||||
.withApiResponse(text))
|
||||
return
|
||||
}
|
||||
|
||||
onSuccess(text, schoolSymbols)
|
||||
}
|
||||
@ -209,7 +221,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
webType: Int,
|
||||
endpoint: String,
|
||||
method: Int = POST,
|
||||
parameters: Map<String, Any> = emptyMap(),
|
||||
parameters: Map<String, Any?> = emptyMap(),
|
||||
onSuccess: (json: JsonObject, response: Response?) -> Unit
|
||||
) {
|
||||
val url = "https://" + when (webType) {
|
||||
|
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class HomepageTile(
|
||||
@SerializedName("Nazwa")
|
||||
val name: String?,
|
||||
@SerializedName("Url")
|
||||
val url: String?,
|
||||
@SerializedName("Zawartosc")
|
||||
val children: List<HomepageTile>
|
||||
)
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2020-4-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web
|
||||
|
||||
import pl.szczodrzynski.edziennik.DAY
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_WEB_ENDPOINT_LUCKY_NUMBER
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.getJsonArray
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class VulcanWebLuckyNumber(override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanWebMain(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanWebLuckyNumber"
|
||||
}
|
||||
|
||||
init {
|
||||
webGetJson(TAG, WEB_MAIN, VULCAN_WEB_ENDPOINT_LUCKY_NUMBER, parameters = mapOf(
|
||||
"permissions" to data.webPermissions
|
||||
)) { json, _ ->
|
||||
val tiles = json
|
||||
.getJsonArray("data")
|
||||
?.mapNotNull { data.app.gson.fromJson(it.toString(), HomepageTile::class.java) }
|
||||
?.flatMap { it.children }
|
||||
|
||||
if (tiles == null) {
|
||||
data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS)
|
||||
return@webGetJson
|
||||
}
|
||||
|
||||
var nextSync = System.currentTimeMillis() + 1* DAY *1000
|
||||
|
||||
tiles.firstOrNull { it.name == data.schoolShort }?.children?.firstOrNull()?.let { tile ->
|
||||
// "Szczęśliwy numer w dzienniku: 16"
|
||||
return@let tile.name?.substringAfterLast(' ')?.toIntOrNull()?.let { number ->
|
||||
// lucky number present
|
||||
val luckyNumberObject = LuckyNumber(
|
||||
profileId,
|
||||
Date.getToday(),
|
||||
number
|
||||
)
|
||||
|
||||
data.luckyNumberList.add(luckyNumberObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_LUCKY_NUMBER,
|
||||
luckyNumberObject.date.value.toLong(),
|
||||
true,
|
||||
profile?.empty ?: false,
|
||||
System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
} ?: {
|
||||
// no lucky number
|
||||
if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour >= 22) {
|
||||
// working days, after 10PM
|
||||
// consider the lucky number is disabled; sync in 4 days
|
||||
nextSync = System.currentTimeMillis() + 4*DAY*1000
|
||||
}
|
||||
else if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour < 22) {
|
||||
// working days, before 10PM
|
||||
|
||||
}
|
||||
else {
|
||||
// weekends
|
||||
nextSync = Week.getNearestWeekDayDate(Week.MONDAY).combineWith(Time(5, 0, 0))
|
||||
}
|
||||
}()
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.CufsCertificate
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain
|
||||
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
|
||||
@ -32,17 +33,18 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
init {
|
||||
if (data.loginStore.mode == LOGIN_MODE_VULCAN_WEB) {
|
||||
VulcanLoginWebMain(data) {
|
||||
val certificate = web.readCertificate() ?: run {
|
||||
val xml = web.readCertificate() ?: run {
|
||||
data.error(ApiError(TAG, ERROR_VULCAN_WEB_NO_CERTIFICATE))
|
||||
return@VulcanLoginWebMain
|
||||
}
|
||||
val certificate = web.parseCertificate(xml)
|
||||
|
||||
if (data.symbol != null && data.symbol != "default") {
|
||||
tryingSymbols += data.symbol ?: "default"
|
||||
}
|
||||
else {
|
||||
val cufsCertificate = web.parseCertificate(certificate)
|
||||
tryingSymbols += cufsCertificate.userInstances
|
||||
|
||||
tryingSymbols += certificate.userInstances
|
||||
}
|
||||
|
||||
checkSymbol(certificate)
|
||||
@ -56,7 +58,7 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSymbol(certificate: String) {
|
||||
private fun checkSymbol(certificate: CufsCertificate) {
|
||||
if (tryingSymbols.isEmpty()) {
|
||||
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
|
||||
onSuccess()
|
||||
@ -80,12 +82,16 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
// postCertificate returns false if the cert is not valid anymore
|
||||
if (!result) {
|
||||
data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED)
|
||||
.withApiResponse(certificate))
|
||||
.withApiResponse(certificate.xml))
|
||||
}
|
||||
}
|
||||
|
||||
private fun webRegisterDevice(symbol: String, onSuccess: () -> Unit) {
|
||||
web.getStartPage(symbol, postErrors = false) { _, schoolSymbols ->
|
||||
if (schoolSymbols.isEmpty()) {
|
||||
onSuccess()
|
||||
return@getStartPage
|
||||
}
|
||||
data.symbol = symbol
|
||||
val schoolSymbol = data.schoolSymbol ?: schoolSymbols.firstOrNull()
|
||||
web.webGetJson(TAG, VulcanWebMain.WEB_NEW, "$schoolSymbol/$VULCAN_WEB_ENDPOINT_REGISTER_DEVICE") { result, _ ->
|
||||
|
@ -18,4 +18,6 @@ class CufsCertificate {
|
||||
|
||||
@Selector(value = "Attribute[AttributeName=UserInstance] AttributeValue")
|
||||
var userInstances: List<String> = listOf()
|
||||
|
||||
var xml = ""
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.fslogin.FSLogin
|
||||
import pl.szczodrzynski.fslogin.realm.CufsRealm
|
||||
|
||||
@ -82,17 +83,34 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
else -> return false
|
||||
}
|
||||
|
||||
val certificate = web.readCertificate()?.let { web.parseCertificate(it) }
|
||||
if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) {
|
||||
useCertificate(certificate)
|
||||
return true
|
||||
}
|
||||
|
||||
val fsLogin = FSLogin(data.app.http, debug = App.debugMode)
|
||||
fsLogin.performLogin(
|
||||
realm = realm,
|
||||
username = data.webUsername ?: data.webEmail ?: return false,
|
||||
password = data.webPassword ?: return false,
|
||||
onSuccess = { certificate ->
|
||||
web.saveCertificate(certificate.wresult)
|
||||
onSuccess = { fsCertificate ->
|
||||
web.saveCertificate(fsCertificate.wresult)
|
||||
useCertificate(web.parseCertificate(fsCertificate.wresult))
|
||||
},
|
||||
onFailure = { errorText ->
|
||||
// TODO
|
||||
data.error(ApiError(TAG, 0).withThrowable(RuntimeException(errorText)))
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun useCertificate(certificate: CufsCertificate) {
|
||||
// auto-post certificate when not first login
|
||||
if (data.profile != null && data.symbol != null && data.symbol != "default") {
|
||||
val result = web.postCertificate(certificate.wresult, data.symbol ?: "default") { _, state ->
|
||||
val result = web.postCertificate(certificate, data.symbol ?: "default") { _, state ->
|
||||
when (state) {
|
||||
VulcanWebMain.STATE_SUCCESS -> {
|
||||
web.getStartPage { _, _ -> onSuccess() }
|
||||
@ -104,20 +122,12 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
// postCertificate returns false if the cert is not valid anymore
|
||||
if (!result) {
|
||||
data.error(ApiError(TAG, ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED)
|
||||
.withApiResponse(certificate.wresult))
|
||||
.withApiResponse(certificate.xml))
|
||||
}
|
||||
}
|
||||
else {
|
||||
// first login - succeed immediately
|
||||
onSuccess()
|
||||
}
|
||||
},
|
||||
onFailure = { errorText ->
|
||||
// TODO
|
||||
data.error(ApiError(TAG, 0).withThrowable(RuntimeException(errorText)))
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user