diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt index 90aa98ec..49b24503 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt @@ -58,6 +58,10 @@ const val IDZIENNIK_WEB_ATTENDANCE = "mod_panelRodzica/obecnosci/WS_obecnosciUcz const val IDZIENNIK_WEB_ANNOUNCEMENTS = "mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia" const val IDZIENNIK_WEB_MESSAGES_LIST = "mod_komunikator/WS_wiadomosci.asmx/PobierzListeWiadomosci" +val IDZIENNIK_API_USER_AGENT = SYSTEM_USER_AGENT +const val IDZIENNIK_API_URL = "https://iuczniowie.progman.pl/idziennik/api" +const val IDZIENNIK_API_CURRENT_REGISTER = "Uczniowie/\$STUDENT_ID/AktualnyDziennik" + val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT const val VULCAN_API_USER_AGENT = "MobileUserAgent" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt index ae9db1e7..f1f3564c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Errors.kt @@ -133,7 +133,6 @@ const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402 const val ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 403 const val ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE = 404 const val ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR = 405 -const val ERROR_LOGIN_IDZIENNIK_WEB_API_ERROR = 406 /* {"Message":"There was an error processing the request.","StackTrace":"","ExceptionType":""} */ const val ERROR_LOGIN_IDZIENNIK_WEB_OTHER = 410 const val ERROR_LOGIN_IDZIENNIK_WEB_API_NO_ACCESS = 411 /* {"d":{"__type":"mds.Web.mod_komunikator.WS_mod_wiadomosci+detailWiadomosci","Wiadomosc":{"_recordId":0,"DataNadania":null,"DataOdczytania":null,"Nadawca":null,"ListaOdbiorcow":[],"Tytul":null,"Text":null,"ListaZal":[]},"Bledy":{"__type":"mds.Module.Globalne+sBledy","CzyJestBlad":true,"ListaBledow":["Nie masz dostępu do tych zasobów!"],"ListaKodowBledow":[]},"czyJestWiecej":false}} */ const val ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION = 420 @@ -146,6 +145,8 @@ const val ERROR_IDZIENNIK_WEB_SERVER_ERROR = 433 const val ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 434 const val ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR = 440 const val ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA = 441 +const val ERROR_IDZIENNIK_API_ACCESS_DENIED = 450 +const val ERROR_IDZIENNIK_API_OTHER = 451 const val ERROR_TEMPLATE_WEB_OTHER = 801 @@ -160,3 +161,4 @@ const val EXCEPTION_NOTIFY_AND_SYNC = 910 const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911 const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912 const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913 +const val EXCEPTION_IDZIENNIK_API_REQUEST = 914 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt index a35d95ba..532d021b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Regexes.kt @@ -40,24 +40,24 @@ object Regexes { val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { - "".toRegex(RegexOption.DOT_MATCHES_ALL) + """""".toRegex(RegexOption.DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_ERROR by lazy { - "id=\"spanErrorMessage\">(.*?)(.*?)(.+?)".toRegex(RegexOption.DOT_MATCHES_ALL) + """Imię i nazwisko:.+?">(.+?)""".toRegex(RegexOption.DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy { - "id=\"ctl00_CzyRodzic\" value=\"([01])\" />".toRegex() + """id="ctl00_CzyRodzic" value="([01])" />""".toRegex() } val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy { - "name=\"ctl00\\\$dxComboRokSzkolny\".+?selected=\"selected\".*?value=\"([0-9]+)\">([0-9/]+)<".toRegex(RegexOption.DOT_MATCHES_ALL) + """name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9/]+)<""".toRegex(RegexOption.DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy { - "".toRegex(RegexOption.DOT_MATCHES_ALL) + """""".toRegex(RegexOption.DOT_MATCHES_ALL) } val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { - "(.+?)\\s(.+?)\\s*\\((.+?),\\s*(.+?)\\)".toRegex(RegexOption.DOT_MATCHES_ALL) + """(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)""".toRegex(RegexOption.DOT_MATCHES_ALL) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt index 61629c3d..a774129d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/DataIdziennik.kt @@ -18,7 +18,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { fun isWebLoginValid() = loginExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() && webAuth.isNotNullNorEmpty() - fun isApiLoginValid() = loginExpiryTime-30 > currentTimeUnix() && apiBearer.isNotNullNorEmpty() + fun isApiLoginValid() = apiExpiryTime-30 > currentTimeUnix() && apiBearer.isNotNullNorEmpty() override fun satisfyLoginMethods() { loginMethods.clear() @@ -46,6 +46,11 @@ class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data( get() { mLoginExpiryTime = mLoginExpiryTime ?: loginStore.getLoginData("loginExpiryTime", 0L); return mLoginExpiryTime ?: 0L } set(value) { loginStore.putLoginData("loginExpiryTime", value); mLoginExpiryTime = value } + private var mApiExpiryTime: Long? = null + var apiExpiryTime: Long + get() { mApiExpiryTime = mApiExpiryTime ?: loginStore.getLoginData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("apiExpiryTime", value); mApiExpiryTime = value } + /* __ __ _ \ \ / / | | \ \ /\ / /__| |__ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikApi.kt new file mode 100644 index 00000000..66a87dfa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikApi.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-29. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.data + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.api.v2.* +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.api.v2.models.ApiError +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils +import java.net.HttpURLConnection + +open class IdziennikApi(open val data: DataIdziennik) { + companion object { + const val TAG = "IdziennikApi" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun apiGet(tag: String, endpointTemplate: String, method: Int = GET, parameters: Map = emptyMap(), onSuccess: (json: JsonElement) -> Unit) { + val endpoint = endpointTemplate.replace("\$STUDENT_ID", data.studentId ?: "") + Utils.d(tag, "Request: Idziennik/API - $IDZIENNIK_API_URL/$endpoint") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + val json = try { + JsonParser().parse(text) + } catch (_: Exception) { null } + + var error: String? = null + if (json == null) { + error = text + } + else if (json is JsonObject) { + error = if (response?.code() == 200) null else + json.getString("message") ?: json.toString() + } + error?.let { code -> + when (code) { + "Authorization has been denied for this request." -> ERROR_IDZIENNIK_API_ACCESS_DENIED + else -> ERROR_IDZIENNIK_API_OTHER + }.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + try { + onSuccess(json!!) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_IDZIENNIK_API_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_API_URL/$endpoint") + .userAgent(IDZIENNIK_API_USER_AGENT) + .addHeader("Authorization", "Bearer ${data.apiBearer}") + .apply { + when (method) { + GET -> get() + POST -> { + postJson() + val json = JsonObject() + parameters.map { (name, value) -> + when (value) { + is JsonObject -> json.add(name, value) + is JsonArray -> json.add(name, value) + is String -> json.addProperty(name, value) + is Int -> json.addProperty(name, value) + is Long -> json.addProperty(name, value) + is Float -> json.addProperty(name, value) + is Char -> json.addProperty(name, value) + } + } + setJsonBody(json) + } + } + } + .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .allowErrorCode(HttpURLConnection.HTTP_INTERNAL_ERROR) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt index 8a78e6ee..4f3b3e66 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/IdziennikData.kt @@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.api.v2.idziennik.data import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.api.v2.idziennik.* +import pl.szczodrzynski.edziennik.api.v2.idziennik.data.api.IdziennikApiCurrentRegister import pl.szczodrzynski.edziennik.api.v2.idziennik.data.web.* import pl.szczodrzynski.edziennik.utils.Utils @@ -65,6 +66,10 @@ class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_attendance) IdziennikWebAttendance(data) { onSuccess() } } + ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + IdziennikApiCurrentRegister(data) { onSuccess() } + } else -> onSuccess() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiCurrentRegister.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiCurrentRegister.kt new file mode 100644 index 00000000..2366e625 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/api/IdziennikApiCurrentRegister.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-29. + */ + +package pl.szczodrzynski.edziennik.api.v2.idziennik.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_API_CURRENT_REGISTER +import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER +import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikApi +import pl.szczodrzynski.edziennik.data.db.modules.luckynumber.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class IdziennikApiCurrentRegister(override val data: DataIdziennik, + val onSuccess: () -> Unit) : IdziennikApi(data) { + companion object { + private const val TAG = "IdziennikApiCurrentRegister" + } + + init { + data.profile?.luckyNumber = -1 + data.profile?.luckyNumberDate = null + + apiGet(TAG, IDZIENNIK_API_CURRENT_REGISTER) { json -> + if (json !is JsonObject) { + onSuccess() + return@apiGet + } + + var nextSync = System.currentTimeMillis() + 14*DAY*1000 + + val settings = json.getJsonObject("ustawienia")?.apply { + profile?.dateSemester1Start = getString("poczatekSemestru1")?.let { Date.fromY_m_d(it) } + profile?.dateSemester2Start = getString("koniecSemestru1")?.let { Date.fromY_m_d(it).stepForward(0, 0, 1) } + profile?.dateYearEnd = getString("koniecSemestru2")?.let { Date.fromY_m_d(it) } + } + + json.getInt("szczesliwyNumerek")?.let { luckyNumber -> + val luckyNumberDate = Date.getToday() + settings.getString("godzinaPublikacjiSzczesliwegoLosu") + ?.let { Time.fromH_m(it) } + ?.let { publishTime -> + val now = Time.getNow() + if (publishTime.value < 150000 && now.value < publishTime.value) { + nextSync = luckyNumberDate.combineWith(publishTime) + luckyNumberDate.stepForward(0, 0, -1) // the lucky number is still for yesterday + } + else if (publishTime.value >= 150000 && now.value > publishTime.value) { + luckyNumberDate.stepForward(0, 0, 1) // the lucky number is already for tomorrow + nextSync = luckyNumberDate.combineWith(publishTime) + } + else if (publishTime.value < 150000) { + nextSync = luckyNumberDate + .clone() + .stepForward(0, 0, 1) + .combineWith(publishTime) + } + else { + nextSync = luckyNumberDate.combineWith(publishTime) + } + } + + + val luckyNumberObject = LuckyNumber( + data.profileId, + Date.getToday(), + luckyNumber + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + data.profile?.empty ?: false, + data.profile?.empty ?: false, + System.currentTimeMillis() + )) + } + + + data.setSyncNext(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER, syncAt = nextSync) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAttendance.kt index 68823c17..1bcbddac 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebAttendance.kt @@ -25,13 +25,14 @@ class IdziennikWebAttendance(override val data: DataIdziennik, private const val TAG = "IdziennikWebAttendance" } + private var attendanceYear = Date.getToday().year + private var attendanceMonth = Date.getToday().month + private var attendancePrevMonthChecked = false + init { getAttendance() } - private var attendanceYear: Int = Date.getToday().year - private var attendanceMonth: Int = Date.getToday().month - private var attendancePrevMonthChecked = false private fun getAttendance() { webApiGet(TAG, IDZIENNIK_WEB_ATTENDANCE, mapOf( "idPozDziennika" to data.registerId, diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebExams.kt index eb0e2c0e..3a9a3982 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/data/web/IdziennikWebExams.kt @@ -24,14 +24,15 @@ class IdziennikWebExams(override val data: DataIdziennik, private const val TAG = "IdziennikWebExams" } - init { - getExams() - } - private var examsYear = Date.getToday().year private var examsMonth = Date.getToday().month private var examsMonthsChecked = 0 private var examsNextMonthChecked = false // TO DO temporary // no more // idk + + init { + getExams() + } + private fun getExams() { val param = JsonObject() param.addProperty("strona", 1) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt index 775200c6..85f9af55 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/idziennik/login/IdziennikLoginWeb.kt @@ -8,6 +8,8 @@ import im.wangchao.mhttp.Request import im.wangchao.mhttp.Response import im.wangchao.mhttp.callback.TextCallbackHandler import okhttp3.Cookie +import pl.szczodrzynski.edziennik.HOUR +import pl.szczodrzynski.edziennik.MINUTE import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik import pl.szczodrzynski.edziennik.api.v2.models.ApiError @@ -65,7 +67,8 @@ class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { data.webSessionId = cookies.singleOrNull { it.name() == "ASP.NET_SessionId_iDziennik" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION data.webAuth = cookies.singleOrNull { it.name() == ".ASPXAUTH" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH data.apiBearer = cookies.singleOrNull { it.name() == "Bearer" }?.value() ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER - data.loginExpiryTime = response.getUnixDate() + 45 * 60 /* 45 min */ + data.loginExpiryTime = response.getUnixDate() + 45 * MINUTE + data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */ return@run null }?.let { errorCode -> data.error(ApiError(TAG, errorCode) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt index ed4a50b7..bd38d628 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Data.kt @@ -324,7 +324,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) } } - fun setSyncNext(endpointId: Int, syncIn: Long? = null, viewId: Int? = null) { + fun setSyncNext(endpointId: Int, syncIn: Long? = null, viewId: Int? = null, syncAt: Long? = null) { EndpointTimer(profile?.id ?: -1, endpointId).apply { syncedNow() @@ -334,6 +334,9 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) else syncIn(syncIn) } + if (syncAt != null) { + nextSync = syncAt + } if (viewId != null) syncWhenView(viewId)