Compare commits

...

22 Commits

Author SHA1 Message Date
92d7a46314 [4.6] Update build.gradle, signing and changelog. 2021-02-26 23:41:04 +01:00
7aaefa977b [Mobidziennik/Web] Add attendance migration. 2021-02-26 23:30:14 +01:00
530034d7da [Mobidziennik/Web] Fix custom attendance type symbols. 2021-02-26 23:30:14 +01:00
44647946e0 Merge pull request #2 from szkolny-eu/feature/idziennik-rip
Remove iDziennik implementation [*]
2021-02-26 23:29:11 +01:00
efd63797e4 [Mobidziennik/Web] Force trimming searched subject name. 2021-02-26 23:08:06 +01:00
74820fe67d [Mobidziennik/Web] Fix counting e-attendance. 2021-02-26 23:07:31 +01:00
e825af0ff0 [Mobidziennik/Web] Fix getting a sent message. 2021-02-26 22:46:52 +01:00
256f6c8732 [Idziennik] Remove iDziennik implementation [*] 2021-02-26 22:30:01 +01:00
22abad35cc Merge pull request #1 from szkolny-eu/feature/vulcan-hebe
Vulcan Hebe implementation
2021-02-26 20:41:51 +01:00
6a33088352 [Vulcan/Web] Fix extracting school symbol. 2021-02-26 20:37:21 +01:00
01c41645ab [Vulcan/Web] Update error descriptions. 2021-02-26 20:33:56 +01:00
54f74d2a3c [Vulcan/Hebe] Match error codes to login form fields. 2021-02-26 20:28:18 +01:00
e0688fe79b [Vulcan/Hebe] Update error descriptions. 2021-02-26 20:25:58 +01:00
36c7bb1127 [Vulcan/Hebe] Add full sync migration. 2021-02-26 20:10:52 +01:00
045205754e [Vulcan/Api] Remove Vulcan API implementation [*] 2021-02-26 19:39:56 +01:00
e34e4d6906 [Lab] Add JSON editor. 2021-02-26 17:27:17 +01:00
481af64137 [Login] Reformat LoginInfo. 2021-02-26 13:22:13 +01:00
2b3a7f6b41 [Vulcan/Web] Release web login mode as testing. 2021-02-26 13:10:50 +01:00
e3fe03c5fc [Vulcan] Update logo to the new design. 2021-02-26 13:06:48 +01:00
9740e0b7bf [Login] Fix crashing on register availability error. 2021-02-26 11:53:04 +01:00
58a26cc0c6 [Vulcan/Hebe] Fix error handling. 2021-02-26 11:52:45 +01:00
d201c0c448 [Vulcan/Hebe] Add server error code handling. 2021-02-25 22:09:00 +01:00
86 changed files with 769 additions and 6005 deletions

View File

@ -183,7 +183,6 @@ dependencies {
//implementation "org.redundent:kotlin-xml-builder:1.5.3"
implementation "io.github.wulkanowy:signer-android:0.1.1"
implementation 'com.github.wulkanowy.uonet-request-signer:hebe-jvm:a99ca50a31'
implementation "androidx.work:work-runtime-ktx:${versions.work}"

View File

@ -1,8 +1,12 @@
<h3>Wersja 4.6-beta.1, 2021-02-25</h3>
<h3>Wersja 4.6, 2021-02-26</h3>
<ul>
<li>Vulcan: dodano możliwość logowania adresem e-mail lub nazwą użytkownika.</li>
<li>Vulcan: dodano obsługę załączników w wiadomościach i zadaniach domowych.</li>
<li>Zalecane jest zalogowanie się ponownie.</li>
<li>Vulcan: aplikacja Szkolny.eu zaktualizowana w związku z wygaszeniem aplikacji Dzienniczek+.</li>
<li>Vulcan: dodano wyświetlanie <b>szczęśliwego numerka.</b></li>
<li>Vulcan: dodano możliwość logowania adresem e-mail lub nazwą użytkownika - wersja testowa.</li>
<li>Vulcan: usunięto wyświetlanie zadań domowych i sprawdzianów z poprzednich lat.</li>
<li>Mobidziennik: naprawiono otwieranie wysłanej wiadomości.</li>
<li>Mobidziennik: poprawiono wyświetlanie oraz liczenie e-obecności.</li>
<li>iDziennik: usunięto dziennik w związku z wygaszeniem systemu. [*]</li>
</ul>
<br>
<br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0xe5, 0x07, 0x0f, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
0x9f, 0xff, 0x0f, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -90,11 +90,6 @@ const val IDZIENNIK_API_MESSAGES_SENT = "Wiadomosci/Wyslane"
val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
const val VULCAN_API_USER_AGENT = "MobileUserAgent"
const val VULCAN_API_APP_NAME = "VULCAN-Android-ModulUcznia"
const val VULCAN_API_APP_VERSION = "20.5.1.470"
const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06"
const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB"
const val VULCAN_HEBE_USER_AGENT = "Dart/2.10 (dart:io)"
const val VULCAN_HEBE_APP_NAME = "DzienniczekPlus 2.0"
const val VULCAN_HEBE_APP_VERSION = "21.02.09 (G)"
@ -106,23 +101,6 @@ val VULCAN_API_DEVICE_NAME by lazy {
base.take(baseMaxLength) + VULCAN_API_DEVICE_NAME_SUFFIX
}
const val VULCAN_API_ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat"
const val VULCAN_API_ENDPOINT_STUDENT_LIST = "mobile-api/Uczen.v3.UczenStart/ListaUczniow"
const val VULCAN_API_ENDPOINT_DICTIONARIES = "mobile-api/Uczen.v3.Uczen/Slowniki"
const val VULCAN_API_ENDPOINT_TIMETABLE = "mobile-api/Uczen.v3.Uczen/PlanLekcjiZeZmianami"
const val VULCAN_API_ENDPOINT_GRADES = "mobile-api/Uczen.v3.Uczen/Oceny"
const val VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS = "mobile-api/Uczen.v3.Uczen/OcenyPodsumowanie"
const val VULCAN_API_ENDPOINT_EVENTS = "mobile-api/Uczen.v3.Uczen/Sprawdziany"
const val VULCAN_API_ENDPOINT_HOMEWORK = "mobile-api/Uczen.v3.Uczen/ZadaniaDomowe"
const val VULCAN_API_ENDPOINT_NOTICES = "mobile-api/Uczen.v3.Uczen/UwagiUcznia"
const val VULCAN_API_ENDPOINT_ATTENDANCE = "mobile-api/Uczen.v3.Uczen/Frekwencje"
const val VULCAN_API_ENDPOINT_MESSAGES_RECEIVED = "mobile-api/Uczen.v3.Uczen/WiadomosciOdebrane"
const val VULCAN_API_ENDPOINT_MESSAGES_SENT = "mobile-api/Uczen.v3.Uczen/WiadomosciWyslane"
const val VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS = "mobile-api/Uczen.v3.Uczen/ZmienStatusWiadomosci"
const val VULCAN_API_ENDPOINT_MESSAGES_ADD = "mobile-api/Uczen.v3.Uczen/DodajWiadomosc"
const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"
const val VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/WiadomosciZalacznik"
const val VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS = "mobile-api/Uczen.v3.Uczen/ZadaniaDomoweZalacznik"
const val VULCAN_WEB_ENDPOINT_LUCKY_NUMBER = "Start.mvc/GetKidsLuckyNumbers"
const val VULCAN_WEB_ENDPOINT_REGISTER_DEVICE = "RejestracjaUrzadzeniaToken.mvc/Get"
const val VULCAN_HEBE_ENDPOINT_REGISTER_NEW = "api/mobile/register/new"

View File

@ -148,17 +148,11 @@ const val ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM = 218
const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301
const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302
const val ERROR_LOGIN_VULCAN_INVALID_PIN = 309
const val ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING = 310
const val ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING = 311
const val ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING = 312
const val ERROR_LOGIN_VULCAN_EXPIRED_TOKEN = 321
const val ERROR_LOGIN_VULCAN_OTHER = 322
const val ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN = 330
const val ERROR_LOGIN_VULCAN_NO_PUPILS = 331
const val ERROR_VULCAN_API_MAINTENANCE = 340
const val ERROR_VULCAN_API_BAD_REQUEST = 341
const val ERROR_VULCAN_API_OTHER = 342
const val ERROR_VULCAN_ATTACHMENT_DOWNLOAD = 343
const val ERROR_VULCAN_WEB_DATA_MISSING = 344
const val ERROR_VULCAN_WEB_429 = 345
@ -171,30 +165,14 @@ 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_VULCAN_HEBE_OTHER = 354
const val ERROR_VULCAN_HEBE_SIGNATURE_ERROR = 360
const val ERROR_VULCAN_HEBE_INVALID_PAYLOAD = 361
const val ERROR_VULCAN_HEBE_FIREBASE_ERROR = 362
const val ERROR_VULCAN_HEBE_CERTIFICATE_GONE = 363
const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364
const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365
const val ERROR_VULCAN_API_DEPRECATED = 390
const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401
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_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
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH = 421
const val ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER = 422
const val ERROR_IDZIENNIK_WEB_ACCESS_DENIED = 430
const val ERROR_IDZIENNIK_WEB_OTHER = 431
const val ERROR_IDZIENNIK_WEB_MAINTENANCE = 432
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_IDZIENNIK_API_NO_REGISTER = 452
const val ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION = 453
const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501
const val ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER = 510
const val ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID = 511
@ -217,14 +195,10 @@ const val EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903
const val EXCEPTION_LIBRUS_API_REQUEST = 904
const val EXCEPTION_LIBRUS_SYNERGIA_REQUEST = 905
const val EXCEPTION_MOBIDZIENNIK_WEB_REQUEST = 906
const val EXCEPTION_VULCAN_API_REQUEST = 907
const val EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST = 908
const val EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST = 909
const val EXCEPTION_NOTIFY = 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
const val EXCEPTION_EDUDZIENNIK_WEB_REQUEST = 920
const val EXCEPTION_EDUDZIENNIK_FILE_REQUEST = 921
const val ERROR_ONEDRIVE_DOWNLOAD = 930

View File

@ -5,8 +5,6 @@
package pl.szczodrzynski.edziennik.data.api
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb
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
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginMessages
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPortal
@ -16,14 +14,13 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.Mobidzie
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.login.PodlasieLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginHebe
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain
import pl.szczodrzynski.edziennik.data.api.models.LoginMethod
// librus
// mobidziennik
// idziennik
// idziennik [*]
// vulcan
// mobireg
@ -35,14 +32,10 @@ const val LOGIN_TYPE_IDZIENNIK = 3
const val LOGIN_TYPE_TEMPLATE = 21
// LOGIN MODES
const val LOGIN_MODE_IDZIENNIK_WEB = 0
const val LOGIN_MODE_TEMPLATE_WEB = 0
// LOGIN METHODS
const val LOGIN_METHOD_NOT_NEEDED = -1
const val LOGIN_METHOD_IDZIENNIK_WEB = 100
const val LOGIN_METHOD_IDZIENNIK_API = 200
const val LOGIN_METHOD_TEMPLATE_WEB = 100
const val LOGIN_METHOD_TEMPLATE_API = 200
@ -104,7 +97,6 @@ const val LOGIN_METHOD_VULCAN_WEB_MAIN = 100
const val LOGIN_METHOD_VULCAN_WEB_NEW = 200
const val LOGIN_METHOD_VULCAN_WEB_OLD = 300
const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400
const val LOGIN_METHOD_VULCAN_API = 500
const val LOGIN_METHOD_VULCAN_HEBE = 600
val vulcanLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java)
@ -119,12 +111,6 @@ val vulcanLoginMethods = listOf(
.withIsPossible { _, _ -> false }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },*/
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
.withIsPossible { _, loginStore ->
loginStore.mode == LOGIN_MODE_VULCAN_API
}
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_HEBE, VulcanLoginHebe::class.java)
.withIsPossible { _, loginStore ->
loginStore.mode != LOGIN_MODE_VULCAN_API
@ -132,16 +118,6 @@ val vulcanLoginMethods = listOf(
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
)
val idziennikLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_WEB, IdziennikLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_API, IdziennikLoginApi::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_IDZIENNIK_WEB }
)
const val LOGIN_TYPE_EDUDZIENNIK = 5
const val LOGIN_MODE_EDUDZIENNIK_WEB = 0
const val LOGIN_METHOD_EDUDZIENNIK_WEB = 100

View File

@ -9,7 +9,6 @@ import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.Edudziennik
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
import pl.szczodrzynski.edziennik.data.api.edziennik.podlasie.Podlasie
@ -123,7 +122,6 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)
LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback)
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_TEMPLATE -> Template(app, profile, loginStore, taskCallback)

View File

@ -1,175 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB
import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isWebLoginValid() = loginExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() && webAuth.isNotNullNorEmpty()
fun isApiLoginValid() = apiExpiryTime-30 > currentTimeUnix() && apiBearer.isNotNullNorEmpty()
override fun satisfyLoginMethods() {
loginMethods.clear()
if (isWebLoginValid()) {
loginMethods += LOGIN_METHOD_IDZIENNIK_WEB
app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", webSessionId)
app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", webAuth)
}
if (isApiLoginValid())
loginMethods += LOGIN_METHOD_IDZIENNIK_API
}
override fun generateUserCode() = "$webSchoolName:$webUsername:$registerId"
private var mLoginExpiryTime: Long? = null
var loginExpiryTime: Long
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 }
/* __ __ _
\ \ / / | |
\ \ /\ / /__| |__
\ \/ \/ / _ \ '_ \
\ /\ / __/ |_) |
\/ \/ \___|_._*/
private var mWebSchoolName: String? = null
var webSchoolName: String?
get() { mWebSchoolName = mWebSchoolName ?: loginStore.getLoginData("schoolName", null); return mWebSchoolName }
set(value) { loginStore.putLoginData("schoolName", value); mWebSchoolName = value }
private var mWebUsername: String? = null
var webUsername: String?
get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("username", null); return mWebUsername }
set(value) { loginStore.putLoginData("username", value); mWebUsername = value }
private var mWebPassword: String? = null
var webPassword: String?
get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("password", null); return mWebPassword }
set(value) { loginStore.putLoginData("password", value); mWebPassword = value }
private var mWebSessionId: String? = null
var webSessionId: String?
get() { mWebSessionId = mWebSessionId ?: loginStore.getLoginData("webSessionId", null); return mWebSessionId }
set(value) { loginStore.putLoginData("webSessionId", value); mWebSessionId = value }
private var mWebAuth: String? = null
var webAuth: String?
get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth }
set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value }
private var mWebSelectedRegister: Int? = null
var webSelectedRegister: Int
get() { mWebSelectedRegister = mWebSelectedRegister ?: loginStore.getLoginData("webSelectedRegister", 0); return mWebSelectedRegister ?: 0 }
set(value) { loginStore.putLoginData("webSelectedRegister", value); mWebSelectedRegister = value }
/* _
/\ (_)
/ \ _ __ _
/ /\ \ | '_ \| |
/ ____ \| |_) | |
/_/ \_\ .__/|_|
| |
|*/
private var mApiBearer: String? = null
var apiBearer: String?
get() { mApiBearer = mApiBearer ?: loginStore.getLoginData("apiBearer", null); return mApiBearer }
set(value) { loginStore.putLoginData("apiBearer", value); mApiBearer = value }
/* ____ _ _
/ __ \| | | |
| | | | |_| |__ ___ _ __
| | | | __| '_ \ / _ \ '__|
| |__| | |_| | | | __/ |
\____/ \__|_| |_|\___|*/
private var mStudentId: String? = null
var studentId: String?
get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId }
set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value }
private var mRegisterId: Int? = null
var registerId: Int
get() { mRegisterId = mRegisterId ?: profile?.getStudentData("registerId", 0); return mRegisterId ?: 0 }
set(value) { profile?.putStudentData("registerId", value) ?: return; mRegisterId = value }
private var mSchoolYearId: Int? = null
var schoolYearId: Int
get() { mSchoolYearId = mSchoolYearId ?: profile?.getStudentData("schoolYearId", 0); return mSchoolYearId ?: 0 }
set(value) { profile?.putStudentData("schoolYearId", value) ?: return; mSchoolYearId = value }
/* _ _ _ _ _
| | | | | (_) |
| | | | |_ _| |___
| | | | __| | / __|
| |__| | |_| | \__ \
\____/ \__|_|_|__*/
fun getSubject(name: String, id: Long?, shortName: String): Subject {
var subject = if (id == null)
subjectList.singleOrNull { it.longName == name }
else
subjectList.singleOrNull { it.id == id }
if (subject == null) {
subject = Subject(profileId, id
?: name.crc16().toLong(), name, shortName)
subjectList[subject.id] = subject
}
return subject
}
fun getTeacher(firstName: String, lastName: String): Teacher {
val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" }
return validateTeacher(teacher, firstName, lastName)
}
fun getTeacher(firstNameChar: Char, lastName: String): Teacher {
val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" }
return validateTeacher(teacher, firstNameChar.toString(), lastName)
}
fun getTeacherByLastFirst(nameLastFirst: String): Teacher {
val nameParts = nameLastFirst.split(" ")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[1], nameParts[0])
}
fun getTeacherByFirstLast(nameFirstLast: String): Teacher {
val nameParts = nameFirstLast.split(" ")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0], nameParts[1])
}
fun getTeacherByFDotLast(nameFDotLast: String): Teacher {
val nameParts = nameFDotLast.split(".")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1])
}
fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String): Teacher {
val nameParts = nameFDotSpaceLast.split(".")
return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1])
}
private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String): Teacher {
(teacher ?: Teacher(profileId, -1, firstName, lastName).apply {
id = shortName.crc16().toLong()
teacherList[id] = this
}).apply {
if (firstName.length > 1)
name = firstName
surname = lastName
return this
}
}
}

View File

@ -1,169 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikData
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.firstlogin.IdziennikFirstLogin
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLogin
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.db.entity.*
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
companion object {
private const val TAG = "Idziennik"
}
val internalErrorList = mutableListOf<Int>()
val data: DataIdziennik
private var afterLogin: (() -> Unit)? = null
init {
data = DataIdziennik(app, profile, loginStore).apply {
callback = wrapCallback(this@Idziennik.callback)
satisfyLoginMethods()
}
}
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(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId, onlyEndpoints)
login()
}
private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) {
d(TAG, "Trying to login with ${data.targetLoginMethodIds}")
if (internalErrorList.isNotEmpty()) {
d(TAG, " - Internal errors:")
internalErrorList.forEach { d(TAG, " - code $it") }
}
loginMethodId?.let { data.prepareFor(idziennikLoginMethods, it) }
afterLogin?.let { this.afterLogin = it }
IdziennikLogin(data) {
data()
}
}
private fun data() {
d(TAG, "Endpoint IDs: ${data.targetEndpointIds}")
if (internalErrorList.isNotEmpty()) {
d(TAG, " - Internal errors:")
internalErrorList.forEach { d(TAG, " - code $it") }
}
afterLogin?.invoke() ?: IdziennikData(data) {
completed()
}
}
override fun getMessage(message: MessageFull) {
login(LOGIN_METHOD_IDZIENNIK_WEB) {
IdziennikWebGetMessage(data, message) {
completed()
}
}
}
override fun sendMessage(recipients: List<Teacher>, subject: String, text: String) {
login(LOGIN_METHOD_IDZIENNIK_API) {
IdziennikWebSendMessage(data, recipients, subject, text) {
completed()
}
}
}
override fun markAllAnnouncementsAsRead() {}
override fun getAnnouncement(announcement: AnnouncementFull) {}
override fun getAttachment(owner: Any, attachmentId: Long, attachmentName: String) {
login(LOGIN_METHOD_IDZIENNIK_WEB) {
if (owner is Message) {
IdziennikWebGetAttachment(data, owner, attachmentId, attachmentName) {
completed()
}
}
else if (owner is Event) {
IdziennikWebGetHomeworkAttachment(data, owner, attachmentId, attachmentName) {
completed()
}
}
}
}
override fun getRecipientList() {
login(LOGIN_METHOD_IDZIENNIK_WEB) {
IdziennikWebGetRecipientList(data) {
completed()
}
}
}
override fun getEvent(eventFull: EventFull) {
login(LOGIN_METHOD_IDZIENNIK_WEB) {
IdziennikWebGetHomework(data, eventFull) {
completed()
}
}
}
override fun firstLogin() { IdziennikFirstLogin(data) { completed() } }
override fun cancel() {
d(TAG, "Cancelled")
data.cancel()
}
private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback {
return object : EdziennikCallback {
override fun onCompleted() { callback.onCompleted() }
override fun onProgress(step: Float) { callback.onProgress(step) }
override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) }
override fun onError(apiError: ApiError) {
if (apiError.errorCode in internalErrorList) {
// finish immediately if the same error occurs twice during the same sync
callback.onError(apiError)
return
}
internalErrorList.add(apiError.errorCode)
when (apiError.errorCode) {
ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION,
ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH,
ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER,
ERROR_IDZIENNIK_WEB_ACCESS_DENIED,
ERROR_IDZIENNIK_API_ACCESS_DENIED -> {
data.loginMethods.remove(LOGIN_METHOD_IDZIENNIK_WEB)
data.prepareFor(idziennikLoginMethods, LOGIN_METHOD_IDZIENNIK_WEB)
data.loginExpiryTime = 0
login()
}
ERROR_IDZIENNIK_API_NO_REGISTER -> {
data()
}
else -> callback.onError(apiError)
}
}
}
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.models.Feature
const val ENDPOINT_IDZIENNIK_WEB_TIMETABLE = 1030
const val ENDPOINT_IDZIENNIK_WEB_GRADES = 1040
const val ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES = 1050
const val ENDPOINT_IDZIENNIK_WEB_EXAMS = 1060
const val ENDPOINT_IDZIENNIK_WEB_HOMEWORK = 1061
const val ENDPOINT_IDZIENNIK_WEB_NOTICES = 1070
const val ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS = 1080
const val ENDPOINT_IDZIENNIK_WEB_ATTENDANCE = 1090
const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX = 1110
const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT = 1120
const val ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER = 2010
const val ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX = 2110
const val ENDPOINT_IDZIENNIK_API_MESSAGES_SENT = 2120
val IdziennikFeatures = listOf(
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_TIMETABLE, listOf(
ENDPOINT_IDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_GRADES, listOf(
ENDPOINT_IDZIENNIK_WEB_GRADES to LOGIN_METHOD_IDZIENNIK_WEB,
ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_AGENDA, listOf(
ENDPOINT_IDZIENNIK_WEB_EXAMS to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_HOMEWORK, listOf(
ENDPOINT_IDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_IDZIENNIK_WEB_NOTICES to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ATTENDANCE, listOf(
ENDPOINT_IDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ANNOUNCEMENTS, listOf(
ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
/*Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2),*/
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_IDZIENNIK_API_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER to LOGIN_METHOD_IDZIENNIK_API
), listOf(LOGIN_METHOD_IDZIENNIK_API))
)

View File

@ -1,118 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-29.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.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.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils
import java.net.HttpURLConnection
open class IdziennikApi(open val data: DataIdziennik, open val lastSync: Long?) {
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<String, Any> = 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) {
"Uczeń nie posiada aktywnej pozycji w dzienniku" -> ERROR_IDZIENNIK_API_NO_REGISTER
"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_BAD_REQUEST)
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.allowErrorCode(HttpURLConnection.HTTP_INTERNAL_ERROR)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -1,91 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiCurrentRegister
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesInbox
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesSent
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.*
import pl.szczodrzynski.edziennik.utils.Utils
class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikData"
}
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) { endpointId ->
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) {
ENDPOINT_IDZIENNIK_WEB_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
IdziennikWebTimetable(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades)
IdziennikWebGrades(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades)
IdziennikWebProposedGrades(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_EXAMS -> {
data.startProgress(R.string.edziennik_progress_endpoint_exams)
IdziennikWebExams(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_HOMEWORK -> {
data.startProgress(R.string.edziennik_progress_endpoint_homework)
IdziennikWebHomework(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
IdziennikWebNotices(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_announcements)
IdziennikWebAnnouncements(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_ATTENDANCE -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
IdziennikWebAttendance(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER -> {
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
IdziennikApiCurrentRegister(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
IdziennikApiMessagesInbox(data, lastSync, onSuccess)
}
ENDPOINT_IDZIENNIK_API_MESSAGES_SENT -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
IdziennikApiMessagesSent(data, lastSync, onSuccess)
}
else -> onSuccess(endpointId)
}
}
}

View File

@ -1,238 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.FileCallbackHandler
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebSwitchRegister
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.File
import java.net.HttpURLConnection.HTTP_INTERNAL_ERROR
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?) {
companion object {
const val TAG = "IdziennikWeb"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun webApiGet(tag: String, endpoint: String, parameters: Map<String, Any> = emptyMap(), onSuccess: (json: JsonObject) -> Unit) {
d(tag, "Request: Idziennik/Web/API - $IDZIENNIK_WEB_URL/$endpoint")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GET_RECIPIENT_LIST) {
data.error(ApiError(tag, ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION)
.withResponse(response)
.withApiResponse(json))
return
}
if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GRADES) {
// special override for accounts where displaying grades
// for another student requires switching it manually
if (data.registerId != data.webSelectedRegister) {
IdziennikWebSwitchRegister(data, data.registerId) {
webApiGet(tag, endpoint, parameters, onSuccess)
}
return
}
}
when {
response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED
response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
response?.parserErrorBody != null -> when {
response.parserErrorBody.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
response.parserErrorBody.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
response.parserErrorBody.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_IDZIENNIK_WEB_OTHER
}
else -> null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(json?.toString() ?: response?.parserErrorBody)
.withResponse(response))
return
}
if (json == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
try {
onSuccess(json)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_API_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.postJson()
.apply {
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)
is Boolean -> json.addProperty(name, value)
}
}
setJsonBody(json)
}
.allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_INTERNAL_ERROR)
.callback(callback)
.build()
.enqueue()
}
fun webGet(tag: String, endpoint: String, parameters: Map<String, Any> = emptyMap(), onSuccess: (text: String) -> Unit) {
d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_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
}
if (!text.contains("czyWyswietlicDostepMobilny")) {
when {
text.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR
text.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
text.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_IDZIENNIK_WEB_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
try {
onSuccess(text)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_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_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.apply {
if (parameters.isEmpty()) get()
else post()
parameters.map { (name, value) ->
addParameter(name, value)
}
}
.callback(callback)
.build()
.enqueue()
}
fun webGetFile(tag: String, endpoint: String, targetFile: File, parameters: Map<String, Any>,
onSuccess: (file: File) -> Unit, onProgress: (written: Long, total: Long) -> Unit) {
d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint")
val callback = object : FileCallbackHandler(targetFile) {
override fun onSuccess(file: File?, response: Response?) {
if (file == null) {
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
.withResponse(response))
return
}
try {
onSuccess(file)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_FILE_REQUEST)
.withResponse(response)
.withThrowable(e))
}
}
override fun onProgress(bytesWritten: Long, bytesTotal: Long) {
try {
onProgress(bytesWritten, bytesTotal)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_FILE_REQUEST)
.withThrowable(e))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$endpoint")
.userAgent(IDZIENNIK_USER_AGENT)
.apply {
parameters.forEach { (k, v) -> addParameter(k, v) }
}
.contentType("application/x-www-form-urlencoded")
.post()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-29.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_CURRENT_REGISTER
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.entity.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,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikApi(data, lastSync) {
companion object {
private const val TAG = "IdziennikApiCurrentRegister"
}
init {
apiGet(TAG, IDZIENNIK_API_CURRENT_REGISTER) { json ->
if (json !is JsonObject) {
onSuccess(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER)
return@apiGet
}
var nextSync = System.currentTimeMillis() + 14*DAY*1000
val settings = json.getJsonObject("ustawienia")?.apply {
getString("poczatekSemestru1")?.let { profile?.dateSemester1Start = Date.fromY_m_d(it) }
getString("koniecSemestru1")?.let { profile?.dateSemester2Start = Date.fromY_m_d(it).stepForward(0, 0, 1) }
getString("koniecSemestru2")?.let { profile?.dateYearEnd = 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(
profileId = data.profileId,
date = luckyNumberDate,
number = luckyNumber
)
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
true,
data.profile?.empty ?: false
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER, syncAt = nextSync)
onSuccess(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER)
}
}
}

View File

@ -1,99 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-30.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api
import com.google.gson.JsonArray
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_DELETED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.getBoolean
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.crc32
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikApiMessagesInbox(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikApi(data, lastSync) {
companion object {
private const val TAG = "IdziennikApiMessagesInbox"
}
init {
apiGet(TAG, IDZIENNIK_API_MESSAGES_INBOX) { json ->
if (json !is JsonArray) {
onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX)
return@apiGet
}
json.asJsonObjectList().forEach { jMessage ->
val subject = jMessage.getString("tytul") ?: ""
if (subject.contains("(") && subject.startsWith("iDziennik - "))
return@forEach
if (subject.startsWith("Uwaga dla ucznia (klasa:"))
return@forEach
val messageIdStr = jMessage.getString("id")
val messageId = crc32((messageIdStr + "0").toByteArray())
var body = "[META:$messageIdStr;-1]"
body += jMessage.getString("tresc")?.replace("\n".toRegex(), "<br>")
val readDate = if (jMessage.getBoolean("odczytana") == true) Date.fromIso(jMessage.getString("wersjaRekordu")) else 0
val sentDate = Date.fromIso(jMessage.getString("dataWyslania"))
val sender = jMessage.getAsJsonObject("nadawca")
var firstName = sender.getString("imie")
var lastName = sender.getString("nazwisko")
if (firstName.isNullOrEmpty() || lastName.isNullOrEmpty()) {
firstName = "usunięty"
lastName = "użytkownik"
}
val rTeacher = data.getTeacher(
firstName,
lastName
)
rTeacher.loginId = /*sender.getString("id") + ":" + */sender.getString("usr")
rTeacher.setTeacherType(Teacher.TYPE_OTHER)
val message = Message(
profileId = profileId,
id = messageId,
type = if (jMessage.getBoolean("rekordUsuniety") == true) TYPE_DELETED else TYPE_RECEIVED,
subject = subject,
body = body,
senderId = rTeacher.id,
addedDate = sentDate
)
val messageRecipient = MessageRecipient(
profileId,
-1 /* me */,
-1,
readDate,
/*messageId*/ messageId
)
data.messageList.add(message)
data.messageRecipientList.add(messageRecipient)
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
message.id,
readDate > 0,
readDate > 0 || profile?.empty ?: false
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX)
}
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-30.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api
import com.google.gson.JsonArray
import pl.szczodrzynski.edziennik.DAY
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_MESSAGES_SENT
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_SENT
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.utils.Utils.crc32
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikApiMessagesSent(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikApi(data, lastSync) {
companion object {
private const val TAG = "IdziennikApiMessagesSent"
}
init {
apiGet(TAG, IDZIENNIK_API_MESSAGES_SENT) { json ->
if (json !is JsonArray) {
onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT)
return@apiGet
}
json.asJsonObjectList()?.forEach { jMessage ->
val messageIdStr = jMessage.get("id").asString
val messageId = crc32((messageIdStr + "1").toByteArray())
val subject = jMessage.get("tytul").asString
var body = "[META:$messageIdStr;-1]"
body += jMessage.get("tresc").asString.replace("\n".toRegex(), "<br>")
val sentDate = Date.fromIso(jMessage.get("dataWyslania").asString)
val message = Message(
profileId = profileId,
id = messageId,
type = TYPE_SENT,
subject = subject,
body = body,
senderId = null,
addedDate = sentDate
)
for (recipientEl in jMessage.getAsJsonArray("odbiorcy")) {
val recipient = recipientEl.asJsonObject
var firstName = recipient.get("imie").asString
var lastName = recipient.get("nazwisko").asString
if (firstName.isEmpty() || lastName.isEmpty()) {
firstName = "usunięty"
lastName = "użytkownik"
}
val rTeacher = data.getTeacher(firstName, lastName)
rTeacher.loginId = /*recipient.get("id").asString + ":" + */recipient.get("usr").asString
val messageRecipient = MessageRecipient(
profileId,
rTeacher.id,
-1,
-1,
/*messageId*/ messageId
)
data.messageRecipientIgnoreList.add(messageRecipient)
}
data.messageList.add(message)
data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES)
onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT)
}
}
}

View File

@ -1,78 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Announcement
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebAnnouncements(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebAnnouncements"
}
init {
val param = JsonObject()
param.add("parametryFiltrow", JsonArray())
webApiGet(TAG, IDZIENNIK_WEB_ANNOUNCEMENTS, mapOf(
"uczenId" to (data.studentId ?: ""),
"param" to param
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
for (jAnnouncementEl in json.getAsJsonArray("ListK")) {
val jAnnouncement = jAnnouncementEl.asJsonObject
// jAnnouncement
val announcementId = jAnnouncement.getLong("Id") ?: -1
val rTeacher = data.getTeacherByFirstLast(jAnnouncement.getString("Autor") ?: "")
val addedDate = jAnnouncement.getString("DataDodania")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull() ?: System.currentTimeMillis()
val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) }
val announcementObject = Announcement(
profileId = profileId,
id = announcementId,
subject = jAnnouncement.get("Temat").asString,
text = jAnnouncement.get("Tresc").asString,
startDate = startDate,
endDate = null,
teacherId = rTeacher.id,
addedDate = addedDate
)
data.announcementList.add(announcementObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ANNOUNCEMENT,
announcementObject.id,
profile?.empty ?: false,
profile?.empty ?: false
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS)
}
}
}

View File

@ -1,180 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_ATTENDANCE
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_ATTENDANCE
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_ABSENT_EXCUSED
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_BELATED
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT_CUSTOM
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_RELEASED
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_UNKNOWN
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
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 IdziennikWebAttendance(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebAttendance"
}
private var attendanceYear = Date.getToday().year
private var attendanceMonth = Date.getToday().month
private var attendancePrevMonthChecked = false
init {
getAttendance()
}
private fun getAttendance() {
webApiGet(TAG, IDZIENNIK_WEB_ATTENDANCE, mapOf(
"idPozDziennika" to data.registerId,
"mc" to attendanceMonth,
"rok" to attendanceYear,
"dataTygodnia" to ""
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
for (jAttendanceEl in json.getAsJsonArray("Obecnosci")) {
val jAttendance = jAttendanceEl.asJsonObject
// jAttendance
val type = jAttendance.get("TypObecnosci").asInt
// skip "zajęcia nie odbyły się" and "Ferie"
if (type == 5 || type == 7)
continue
val date = Date.fromY_m_d(jAttendance.get("Data").asString)
val time = Time.fromH_m(jAttendance.get("OdDoGodziny").asString)
if (date.combineWith(time) > System.currentTimeMillis())
continue
val id = jAttendance.get("IdLesson").asString.crc16().toLong()
val rSubject = data.getSubject(jAttendance.get("Przedmiot").asString, jAttendance.get("IdPrzedmiot").asLong, "")
val rTeacher = data.getTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").asString)
var baseType = TYPE_UNKNOWN
var typeName = "nieznany rodzaj"
var typeSymbol: String? = null
var typeColor: Long? = null
/* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosciUcznia_lmt637231494660000000.js */
/* https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/obecnosci/obecnosci_lmt637231494660000000.css */
when (type) {
1 -> {
baseType = TYPE_ABSENT_EXCUSED
typeName = "nieobecność usprawiedliwiona"
typeColor = 0xffffe099
}
2 -> {
baseType = TYPE_BELATED
typeName = "spóźnienie"
typeColor = 0xffffffaa
}
3 -> {
baseType = TYPE_ABSENT
typeName = "nieobecność nieusprawiedliwiona"
typeColor = 0xffffad99
}
4, 9 -> {
baseType = TYPE_RELEASED
if (type == 4) {
typeName = "zwolnienie"
typeColor = 0xffa8beff
}
if (type == 9) {
typeName = "zwolniony / obecny"
typeSymbol = "zb"
typeColor = 0xffff69b4
}
}
8 -> {
baseType = TYPE_PRESENT_CUSTOM
typeName = "wycieczka"
typeSymbol = "w"
typeColor = null
}
0 -> {
baseType = TYPE_PRESENT
typeName = "obecny"
typeColor = 0xffccffcc
}
}
val semester = profile?.dateToSemester(date) ?: 1
val attendanceObject = Attendance(
profileId = profileId,
id = id,
baseType = baseType,
typeName = typeName,
typeShort = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType),
typeSymbol = typeSymbol ?: data.app.attendanceManager.getTypeShort(baseType),
typeColor = typeColor?.toInt(),
date = date,
startTime = time,
semester = semester,
teacherId = rTeacher.id,
subjectId = rSubject.id
).also {
it.lessonTopic = jAttendance.getString("PrzedmiotTemat")
it.lessonNumber = jAttendance.getInt("Godzina")
}
data.attendanceList.add(attendanceObject)
if (attendanceObject.baseType != TYPE_PRESENT) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ATTENDANCE,
attendanceObject.id,
profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN,
profile?.empty ?: false || baseType == TYPE_PRESENT_CUSTOM || baseType == TYPE_UNKNOWN
))
}
}
val attendanceDateValue = attendanceYear * 10000 + attendanceMonth * 100
if (profile?.empty == true && attendanceDateValue > profile?.getSemesterStart(1)?.value ?: 99999999) {
attendancePrevMonthChecked = true // do not need to check prev month later
attendanceMonth--
if (attendanceMonth < 1) {
attendanceMonth = 12
attendanceYear--
}
getAttendance()
} else if (!attendancePrevMonthChecked /* get also the previous month */) {
attendanceMonth--
if (attendanceMonth < 1) {
attendanceMonth = 12
attendanceYear--
}
attendancePrevMonthChecked = true
getAttendance()
} else {
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE)
}
}
}
}

View File

@ -1,127 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class IdziennikWebExams(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebExams"
}
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().apply {
addProperty("strona", 1)
addProperty("iloscNaStrone", "99")
addProperty("iloscRekordow", -1)
addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu")
addProperty("kierunekSort", 0)
addProperty("maxIloscZaznaczonych", 0)
addProperty("panelFiltrow", 0)
}
webApiGet(TAG, IDZIENNIK_WEB_EXAMS, mapOf(
"idP" to data.registerId,
"rok" to examsYear,
"miesiac" to examsMonth,
"param" to param
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { exam ->
val id = exam.getLong("_recordId") ?: return@forEach
val examDate = Date.fromY_m_d(exam.getString("data") ?: return@forEach)
val subjectName = exam.getString("przedmiot") ?: return@forEach
val subjectId = data.getSubject(subjectName, null, subjectName).id
val teacherName = exam.getString("wpisal") ?: return@forEach
val teacherId = data.getTeacherByLastFirst(teacherName).id
val topic = exam.getString("zakres")?.trim() ?: ""
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, examDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
val eventType = when (exam.getString("rodzaj")?.toLowerCase(Locale.getDefault())) {
"sprawdzian/praca klasowa",
"sprawdzian",
"praca klasowa" -> Event.TYPE_EXAM
"kartkówka" -> Event.TYPE_SHORT_QUIZ
else -> Event.TYPE_EXAM
}
val eventObject = Event(
profileId = profileId,
id = id,
date = examDate,
time = startTime,
topic = topic,
color = null,
type = eventType,
teacherId = teacherId,
subjectId = subjectId,
teamId = data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_EVENT,
eventObject.id,
profile?.empty ?: false,
profile?.empty ?: false
))
}
if (profile?.empty == true && examsMonthsChecked < 3 /* how many months backwards to check? */) {
examsMonthsChecked++
examsMonth--
if (examsMonth < 1) {
examsMonth = 12
examsYear--
}
getExams()
} else if (!examsNextMonthChecked /* get also one month forward */) {
val showDate = Date.getToday().stepForward(0, 1, 0)
examsYear = showDate.year
examsMonth = showDate.month
examsNextMonthChecked = true
getExams()
} else {
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_EXAMS, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_EXAMS)
}
}
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-28
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_ATTACHMENT
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.Utils
import java.io.File
class IdziennikWebGetAttachment(override val data: DataIdziennik,
val owner: Any,
val attachmentId: Long,
val attachmentName: String,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
const val TAG = "IdziennikWebGetAttachment"
}
init {
val message = owner as Message
val messageId = "\\[META:([A-z0-9]+);([0-9-]+)]".toRegex().find(message.body ?: "")?.get(2) ?: -1
val targetFile = File(Utils.getStorageDir(), attachmentName)
webGetFile(TAG, IDZIENNIK_WEB_GET_ATTACHMENT, targetFile, mapOf(
"id" to messageId,
"fileName" to attachmentName
), { file ->
val event = AttachmentGetEvent(
profileId,
owner,
attachmentId,
AttachmentGetEvent.TYPE_FINISHED,
file.absolutePath
)
val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.ownerId}_${event.attachmentId}")
Utils.writeStringToFile(attachmentDataFile, event.fileName)
EventBus.getDefault().postSticky(event)
onSuccess()
}) { written, _ ->
val event = AttachmentGetEvent(
profileId,
owner,
attachmentId,
AttachmentGetEvent.TYPE_PROGRESS,
bytesWritten = written
)
EventBus.getDefault().postSticky(event)
}
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-4-1.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.events.EventGetEvent
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.getBoolean
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
class IdziennikWebGetHomework(override val data: DataIdziennik,
val event: EventFull,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
private const val TAG = "IdziennikWebGetHomework"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_GET_HOMEWORK, mapOf(
"idP" to data.registerId,
"idPD" to event.id
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
val homework = json.getJsonObject("praca") ?: return@webApiGet
if (homework.getBoolean("zalacznik", false)) {
event.attachmentIds = mutableListOf(event.id)
event.attachmentNames = mutableListOf("Załącznik do zadania")
}
else {
event.attachmentIds = mutableListOf()
event.attachmentNames = mutableListOf()
}
event.homeworkBody = homework.getString("tresc")
data.eventList.add(event)
data.eventListReplace = true
EventBus.getDefault().postSticky(EventGetEvent(event))
onSuccess()
}
}
}

View File

@ -1,100 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-4-1.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import com.google.gson.JsonObject
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_HOMEWORK_ATTACHMENT
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.set
import pl.szczodrzynski.edziennik.utils.Utils
import java.io.File
class IdziennikWebGetHomeworkAttachment(override val data: DataIdziennik,
val owner: Any,
val attachmentId: Long,
val attachmentName: String,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
const val TAG = "IdziennikWebGetHomeworkAttachment"
}
init {
val homework = owner as Event
/*val request = Request.Builder()
.url("")
.build()
data.app.http.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withThrowable(e))
}
override fun onResponse(call: Call, response: Response) {
val filename = response.header("content-disposition")?.substringAfter("\"")?.substringBeforeLast("\"")
val file: File = File(Utils.getStorageDir(), filename)
val sink = file.sink().buffer()
response.body()?.source()?.let {
sink.writeAll(it)
}
sink.close()
}
})*/
webGet(TAG, IDZIENNIK_WEB_GET_HOMEWORK_ATTACHMENT) { text ->
val hiddenFields = JsonObject()
Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text).forEach {
hiddenFields[it[1]] = it[2]
}
webGetFile(TAG, IDZIENNIK_WEB_GET_HOMEWORK_ATTACHMENT, Utils.getStorageDir(), mapOf(
"__VIEWSTATE" to hiddenFields.getString("__VIEWSTATE", ""),
"__VIEWSTATEGENERATOR" to hiddenFields.getString("__VIEWSTATEGENERATOR", ""),
"__EVENTVALIDATION" to hiddenFields.getString("__EVENTVALIDATION", ""),
"__EVENTTARGET" to "ctl00\$cphContent\$bt_pobraniePliku",
"ctl00\$dxComboUczniowie" to data.registerId,
"ctl00\$cphContent\$idPracyDomowej" to attachmentId
), { file ->
val event = AttachmentGetEvent(
profileId,
owner,
attachmentId,
AttachmentGetEvent.TYPE_FINISHED,
file.absolutePath
)
val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.ownerId}_${event.attachmentId}")
Utils.writeStringToFile(attachmentDataFile, event.fileName)
homework.attachmentNames = mutableListOf(file.name)
data.eventList.add(homework)
data.eventListReplace = true
EventBus.getDefault().postSticky(event)
onSuccess()
}) { written, _ ->
val event = AttachmentGetEvent(
profileId,
owner,
attachmentId,
AttachmentGetEvent.TYPE_PROGRESS,
bytesWritten = written
)
EventBus.getDefault().postSticky(event)
}
}
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-28
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_MESSAGE
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebGetMessage(override val data: DataIdziennik,
private val message: MessageFull,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
const val TAG = "IdziennikWebGetMessage"
}
init { data.profile?.also { profile ->
val metaPattern = "\\[META:([A-z0-9]+);([0-9-]+)]".toRegex()
val meta = metaPattern.find(message.body!!)
val messageIdString = meta?.get(1) ?: ""
webApiGet(TAG, IDZIENNIK_WEB_GET_MESSAGE, parameters = mapOf(
"idWiadomosci" to messageIdString,
"typWiadomosci" to if (message.type == TYPE_SENT) 1 else 0
)) { json ->
json.getJsonObject("d")?.getJsonObject("Wiadomosc")?.also {
val id = it.getLong("_recordId")
message.body = message.body?.replace(metaPattern, "[META:$messageIdString;$id]")
message.clearAttachments()
it.getJsonArray("ListaZal")?.asJsonObjectList()?.forEach { attachment ->
message.addAttachment(
attachment.getLong("Id") ?: return@forEach,
attachment.getString("Nazwa") ?: return@forEach,
-1
)
}
message.recipients?.clear()
when (message.type) {
TYPE_RECEIVED -> {
val recipientObject = MessageRecipientFull(
profileId = profileId,
id = -1,
messageId = message.id
)
val readDateString = it.getString("DataOdczytania")
recipientObject.readDate = if (readDateString.isNullOrBlank()) System.currentTimeMillis()
else Date.fromIso(readDateString)
recipientObject.fullName = profile.accountName ?: profile.studentNameLong
data.messageRecipientList.add(recipientObject)
message.addRecipient(recipientObject)
}
TYPE_SENT -> {
it.getJsonArray("ListaOdbiorcow")?.asJsonObjectList()?.forEach { recipient ->
val recipientName = recipient.getString("NazwaOdbiorcy") ?: return@forEach
val teacher = data.getTeacherByLastFirst(recipientName)
val recipientObject = MessageRecipientFull(
profileId = profileId,
id = teacher.id,
messageId = message.id
)
recipientObject.readDate = recipient.getLong("Status") ?: return@forEach
recipientObject.fullName = teacher.fullName
data.messageRecipientList.add(recipientObject)
message.addRecipient(recipientObject)
}
}
}
if (!message.seen) {
message.seen = true
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
message.id,
message.seen,
message.notified
))
}
data.messageList.add(message)
data.messageListReplace = true
EventBus.getDefault().postSticky(MessageGetEvent(message))
onSuccess()
}
}
} ?: onSuccess() }
}

View File

@ -1,78 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-30.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import androidx.room.OnConflictStrategy
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_RECIPIENT_LIST
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
class IdziennikWebGetRecipientList(override val data: DataIdziennik,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
private const val TAG = "IdziennikWebGetRecipientList"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_GET_RECIPIENT_LIST, mapOf(
"idP" to data.registerId
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("ListK_Pracownicy")?.asJsonObjectList()?.forEach { recipient ->
val name = recipient.getString("ImieNazwisko") ?: ": "
val (fullName, subject) = name.split(": ").let {
Pair(it.getOrNull(0), it.getOrNull(1))
}
val guid = recipient.getString("Id") ?: ""
// get teacher by ID or create it
val teacher = data.getTeacherByFirstLast(fullName ?: " ")
teacher.loginId = guid
teacher.setTeacherType(Teacher.TYPE_TEACHER)
// unset OTHER that is automatically set in IdziennikApiMessages*
teacher.unsetTeacherType(Teacher.TYPE_OTHER)
teacher.typeDescription = subject
}
json.getJsonArray("ListK_Opiekunowie")?.asJsonObjectList()?.forEach { recipient ->
val name = recipient.getString("ImieNazwisko") ?: ": "
val (fullName, parentOf) = Regexes.IDZIENNIK_MESSAGES_RECIPIENT_PARENT.find(name)?.let {
Pair(it.groupValues.getOrNull(1), it.groupValues.getOrNull(2))
} ?: Pair(null, null)
val guid = recipient.getString("Id") ?: ""
// get teacher by ID or create it
val teacher = data.getTeacherByFirstLast(fullName ?: " ")
teacher.loginId = guid
teacher.setTeacherType(Teacher.TYPE_PARENT)
// unset OTHER that is automatically set in IdziennikApiMessages*
teacher.unsetTeacherType(Teacher.TYPE_OTHER)
teacher.typeDescription = parentOf
}
val event = RecipientListGetEvent(
data.profileId,
data.teacherList.filter { it.loginId != null }
)
profile?.lastReceiversSync = System.currentTimeMillis()
data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE
EventBus.getDefault().postSticky(event)
onSuccess()
}
}
}

View File

@ -1,178 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import android.graphics.Color
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebGrades(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebGrades"
}
init { data.profile?.also { profile ->
webApiGet(TAG, IDZIENNIK_WEB_GRADES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.onEach { subjectJson ->
val subject = data.getSubject(
subjectJson.getString("Przedmiot") ?: return@onEach,
subjectJson.getLong("IdPrzedmiotu") ?: return@onEach,
subjectJson.getString("Przedmiot") ?: return@onEach
)
subjectJson.getJsonArray("Oceny")?.asJsonObjectList()?.forEach { grade ->
val id = grade.getLong("idK") ?: return@forEach
val category = grade.getString("Kategoria") ?: ""
val name = grade.getString("Ocena") ?: "?"
val semester = grade.getInt("Semestr") ?: 1
val teacher = data.getTeacherByLastFirst(grade.getString("Wystawil") ?: return@forEach)
val countToAverage = grade.getBoolean("DoSredniej") ?: true
var value = grade.getFloat("WartoscDoSred") ?: 0.0f
val weight = if (countToAverage)
grade.getFloat("Waga") ?: 0.0f
else
0.0f
val gradeColor = grade.getString("Kolor") ?: ""
var colorInt = 0xff2196f3.toInt()
if (gradeColor.isNotEmpty()) {
colorInt = Color.parseColor("#$gradeColor")
}
val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis()
val gradeObject = Grade(
profileId = profileId,
id = id,
name = name,
type = TYPE_NORMAL,
value = value,
weight = weight,
color = colorInt,
category = category,
description = null,
comment = null,
semester = semester,
teacherId = teacher.id,
subjectId = subject.id,
addedDate = addedDate
)
when (grade.getInt("Typ")) {
0 -> {
val history = grade.getJsonArray("Historia")?.asJsonObjectList()
if (history?.isNotEmpty() == true) {
var sum = gradeObject.value * gradeObject.weight
var count = gradeObject.weight
for (historyItem in history) {
val countToTheAverage = historyItem.getBoolean("DoSredniej") ?: false
value = historyItem.get("WartoscDoSred").asFloat
val weight = historyItem.get("Waga").asFloat
if (value > 0 && countToTheAverage) {
sum += value * weight
count += weight
}
val historyColor = historyItem.getString("Kolor") ?: ""
colorInt = 0xff2196f3.toInt()
if (historyColor.isNotEmpty()) {
colorInt = Color.parseColor("#$historyColor")
}
val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis()
val historyObject = Grade(
profileId = profileId,
id = gradeObject.id * -1,
name = historyItem.getString("Ocena") ?: "",
type = TYPE_NORMAL,
value = value,
weight = if (value > 0f && countToTheAverage) weight * -1f else 0f,
color = colorInt,
category = historyItem.getString("Kategoria"),
description = historyItem.getString("Uzasadnienie"),
comment = null,
semester = historyItem.getInt("Semestr") ?: 1,
teacherId = teacher.id,
subjectId = subject.id,
addedDate = addedDate
)
historyObject.parentId = gradeObject.id
data.gradeList.add(historyObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
historyObject.id,
true,
true
))
}
// update the current grade's value with an average of all historical grades and itself
if (sum > 0 && count > 0) {
gradeObject.value = sum / count
}
gradeObject.isImprovement = true // gradeObject is the improved grade. Originals are historyObjects
}
}
1 -> {
gradeObject.type = Grade.TYPE_SEMESTER1_FINAL
gradeObject.name = value.toInt().toString()
gradeObject.weight = 0f
}
2 -> {
gradeObject.type = Grade.TYPE_YEAR_FINAL
gradeObject.name = value.toInt().toString()
gradeObject.weight = 0f
}
}
data.gradeList.add(gradeObject)
data.metadataList.add(
Metadata(
profileId,
Metadata.TYPE_GRADE,
id,
data.profile.empty,
data.profile.empty
))
}
}
data.toRemove.addAll(listOf(
Grade.TYPE_NORMAL,
Grade.TYPE_SEMESTER1_FINAL,
Grade.TYPE_YEAR_FINAL
).map {
DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it)
})
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_GRADES, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_GRADES)
}
} ?: onSuccess(ENDPOINT_IDZIENNIK_WEB_GRADES) }
}

View File

@ -1,100 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-25
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebHomework(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebHomework"
}
init {
val param = JsonObject().apply {
addProperty("strona", 1)
addProperty("iloscNaStrone", 997)
addProperty("iloscRekordow", -1)
addProperty("kolumnaSort", "DataZadania")
addProperty("kierunekSort", 0)
addProperty("maxIloscZaznaczonych", 0)
addProperty("panelFiltrow", 0)
}
webApiGet(TAG, IDZIENNIK_WEB_HOMEWORK, mapOf(
"idP" to data.registerId,
"data" to Date.getToday().stringY_m_d,
"wszystkie" to true,
"param" to param
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { homework ->
val id = homework.getLong("_recordId") ?: return@forEach
val eventDate = Date.fromY_m_d(homework.getString("dataO") ?: return@forEach)
val addedDate = Date.fromY_m_d(homework.getString("dataZ") ?: return@forEach)
val subjectName = homework.getString("przed") ?: return@forEach
val subjectId = data.getSubject(subjectName, null, subjectName).id
val teacherName = homework.getString("usr") ?: return@forEach
val teacherId = data.getTeacherByLastFirst(teacherName).id
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.displayStartTime
val topic = homework.getString("tytul")?.trim() ?: ""
val seen = when (profile?.empty) {
true -> true
else -> eventDate < Date.getToday()
}
val eventObject = Event(
profileId = profileId,
id = id,
date = eventDate,
time = startTime,
topic = topic,
color = null,
type = Event.TYPE_HOMEWORK,
teacherId = teacherId,
subjectId = subjectId,
teamId = data.teamClass?.id ?: -1,
addedDate = addedDate.inMillis
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_HOMEWORK,
eventObject.id,
seen,
seen
))
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_HOMEWORK, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_HOMEWORK)
}
}
}

View File

@ -1,84 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_NOTICES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_NOTICES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEGATIVE
import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_NEUTRAL
import pl.szczodrzynski.edziennik.data.db.entity.Notice.Companion.TYPE_POSITIVE
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebNotices(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebNotices"
}
init {
webApiGet(TAG, IDZIENNIK_WEB_NOTICES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
for (jNoticeEl in json.getAsJsonArray("SUwaga")) {
val jNotice = jNoticeEl.asJsonObject
// jNotice
val noticeId = jNotice.get("id").asString.crc16().toLong()
val rTeacher = data.getTeacherByLastFirst(jNotice.get("Nauczyciel").asString)
val addedDate = Date.fromY_m_d(jNotice.get("Data").asString)
var nType = TYPE_NEUTRAL
val jType = jNotice.get("Typ").asString
if (jType == "n") {
nType = TYPE_NEGATIVE
} else if (jType == "p") {
nType = TYPE_POSITIVE
}
val noticeObject = Notice(
profileId = profileId,
id = noticeId,
type = nType,
semester = jNotice.get("Semestr").asInt,
text = jNotice.getString("Tresc") ?: "",
category = null,
points = null,
teacherId = rTeacher.id,
addedDate = addedDate.inMillis
)
data.noticeList.add(noticeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_NOTICE,
noticeObject.id,
profile?.empty ?: false,
profile?.empty ?: false
))
}
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_NOTICES, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_NOTICES)
}
}
}

View File

@ -1,152 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-28.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_MISSING_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED
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.getJsonObject
import pl.szczodrzynski.edziennik.getString
class IdziennikWebProposedGrades(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebProposedGrades"
}
init { data.profile?.also { profile ->
webApiGet(TAG, IDZIENNIK_WEB_MISSING_GRADES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
val manager = data.app.gradesManager
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { subject ->
val subjectName = subject.getString("Przedmiot") ?: return@forEach
val subjectObject = data.getSubject(subjectName, null, subjectName)
val semester1Proposed = subject.getString("OcenaSem1") ?: ""
val semester1Value = manager.getGradeValue(semester1Proposed)
val semester1Id = subjectObject.id * (-100) - 1
val semester1Type =
if (semester1Value == 0f) TYPE_DESCRIPTIVE
else TYPE_SEMESTER1_PROPOSED
val semester1Name = when {
semester1Value == 0f -> " "
semester1Value % 1.0f == 0f -> semester1Value.toInt().toString()
else -> semester1Value.toString()
}
val semester1Color =
if (semester1Value == 0f) 0xff536dfe.toInt()
else -1
val semester2Proposed = subject.getString("OcenaSem2") ?: ""
val semester2Value = manager.getGradeValue(semester2Proposed)
val semester2Id = subjectObject.id * (-100) - 2
val semester2Type =
if (semester2Value == 0f) TYPE_DESCRIPTIVE
else TYPE_YEAR_PROPOSED
val semester2Name = when {
semester2Value == 0f -> " "
semester2Value % 1.0f == 0f -> semester2Value.toInt().toString()
else -> semester2Value.toString()
}
val semester2Color =
if (semester2Value == 0f) 0xffff4081.toInt()
else -1
if (semester1Proposed != "") {
val addedDate = if (data.profile.empty)
data.profile.dateSemester1Start.inMillis
else
System.currentTimeMillis()
val gradeObject = Grade(
profileId = profileId,
id = semester1Id,
name = semester1Name,
type = semester1Type,
value = semester1Value,
weight = 0f,
color = semester1Color,
category = if (semester1Value == 0f) "Ocena opisowa semestralna" else null,
description = if (semester1Value == 0f) semester1Proposed else null,
comment = null,
semester = 1,
teacherId = -1,
subjectId = subjectObject.id,
addedDate = addedDate
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
profile.empty,
profile.empty
))
}
if (semester2Proposed != "") {
val addedDate = if (data.profile.empty)
data.profile.dateSemester2Start.inMillis
else
System.currentTimeMillis()
val gradeObject = Grade(
profileId = profileId,
id = semester2Id,
name = semester2Name,
type = semester2Type,
value = semester2Value,
weight = 0f,
color = semester2Color,
category = if (semester2Value == 0f) "Ocena opisowa końcoworoczna" else null,
description = if (semester2Value == 0f) semester2Proposed else null,
comment = null,
semester = 2,
teacherId = -1,
subjectId = subjectObject.id,
addedDate = addedDate
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
profile.empty,
profile.empty
))
}
}
data.toRemove.addAll(listOf(TYPE_SEMESTER1_PROPOSED, TYPE_YEAR_PROPOSED).map {
DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it)
})
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES)
}
} ?: onSuccess(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES) }
}

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-30.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_SEND_MESSAGE
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesSent
import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import java.util.*
class IdziennikWebSendMessage(override val data: DataIdziennik,
val recipients: List<Teacher>,
val subject: String,
val text: String,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
private const val TAG = "IdziennikWebSendMessage"
}
init {
val recipientsArray = JsonArray()
for (teacher in recipients) {
teacher.loginId?.let {
recipientsArray += it
}
}
webApiGet(TAG, IDZIENNIK_WEB_SEND_MESSAGE, mapOf(
"Wiadomosc" to JsonObject(
"Tytul" to subject,
"Tresc" to text,
"Confirmation" to false,
"GuidMessage" to UUID.randomUUID().toString().toUpperCase(Locale.ROOT),
"Odbiorcy" to recipientsArray
)
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
if (json.getBoolean("CzyJestBlad") != false) {
// TODO error
return@webApiGet
}
IdziennikApiMessagesSent(data, null) {
val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject }
val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id }
val event = MessageSentEvent(data.profileId, message, message?.addedDate)
EventBus.getDefault().postSticky(event)
onSuccess()
}
}
}
}

View File

@ -1,36 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOME
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getString
class IdziennikWebSwitchRegister(override val data: DataIdziennik,
val registerId: Int,
val onSuccess: () -> Unit
) : IdziennikWeb(data, null) {
companion object {
private const val TAG = "IdziennikWebSwitchRegister"
}
init {
val hiddenFields = data.loginStore.getLoginData("hiddenFields", JsonObject())
// TODO error checking
webGet(TAG, IDZIENNIK_WEB_HOME, mapOf(
"__VIEWSTATE" to hiddenFields.getString("__VIEWSTATE", ""),
"__VIEWSTATEGENERATOR" to hiddenFields.getString("__VIEWSTATEGENERATOR", ""),
"__EVENTVALIDATION" to hiddenFields.getString("__EVENTVALIDATION", ""),
"ctl00\$dxComboUczniowie" to registerId
)) { text ->
Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let {
val registerId = it[1].toIntOrNull() ?: return@let
data.webSelectedRegister = registerId
}
onSuccess()
}
}
}

View File

@ -1,194 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-22
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.LessonRange
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
class IdziennikWebTimetable(override val data: DataIdziennik,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : IdziennikWeb(data, lastSync) {
companion object {
private const val TAG = "IdziennikWebTimetable"
}
init { data.profile?.also { profile ->
val currentWeekStart = Week.getWeekStart()
if (Date.getToday().weekDay > 4) {
currentWeekStart.stepForward(0, 0, 7)
}
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf(
"idPozDziennika" to data.registerId,
"pidRokSzkolny" to data.schoolYearId,
"data" to "${weekStart.stringY_m_d}T10:00:00.000Z"
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("GodzinyLekcyjne")?.asJsonObjectList()?.forEachIndexed { index, range ->
val lessonRange = LessonRange(
profileId,
index + 1,
range.getString("Poczatek")?.let { Time.fromH_m(it) }
?: return@forEachIndexed,
range.getString("Koniec")?.let { Time.fromH_m(it) } ?: return@forEachIndexed
)
data.lessonRanges[lessonRange.lessonNumber] = lessonRange
}
val dates = mutableSetOf<Int>()
val lessons = mutableListOf<Lesson>()
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson ->
val subject = data.getSubject(
lesson.getString("Nazwa") ?: return@forEach,
lesson.getLong("Id"),
lesson.getString("Skrot") ?: ""
)
val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel")
?: return@forEach)
val newSubjectName = lesson.getString("PrzedmiotZastepujacy")
val newSubject = when (newSubjectName.isNullOrBlank()) {
true -> null
else -> data.getSubject(newSubjectName, null, newSubjectName)
}
val newTeacherName = lesson.getString("NauZastepujacy")
val newTeacher = when (newTeacherName.isNullOrBlank()) {
true -> null
else -> data.getTeacherByFDotLast(newTeacherName)
}
val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach
val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1)
?: return@forEach]
val lessonDate = weekStart.clone().stepForward(0, 0, weekDay)
val classroom = lesson.getString("NazwaSali")
val type = lesson.getInt("TypZastepstwa") ?: -1
val lessonObject = Lesson(profileId, -1)
when (type) {
1, 2, 3, 4, 5 -> {
lessonObject.apply {
this.type = Lesson.TYPE_CHANGE
this.date = lessonDate
this.lessonNumber = lessonRange.lessonNumber
this.startTime = lessonRange.startTime
this.endTime = lessonRange.endTime
this.subjectId = newSubject?.id
this.teacherId = newTeacher?.id
this.teamId = data.teamClass?.id
this.classroom = classroom
this.oldDate = lessonDate
this.oldLessonNumber = lessonRange.lessonNumber
this.oldStartTime = lessonRange.startTime
this.oldEndTime = lessonRange.endTime
this.oldSubjectId = subject.id
this.oldTeacherId = teacher.id
this.oldTeamId = data.teamClass?.id
this.oldClassroom = classroom
}
}
0 -> {
lessonObject.apply {
this.type = Lesson.TYPE_CANCELLED
this.oldDate = lessonDate
this.oldLessonNumber = lessonRange.lessonNumber
this.oldStartTime = lessonRange.startTime
this.oldEndTime = lessonRange.endTime
this.oldSubjectId = subject.id
this.oldTeacherId = teacher.id
this.oldTeamId = data.teamClass?.id
this.oldClassroom = classroom
}
}
else -> {
lessonObject.apply {
this.type = Lesson.TYPE_NORMAL
this.date = lessonDate
this.lessonNumber = lessonRange.lessonNumber
this.startTime = lessonRange.startTime
this.endTime = lessonRange.endTime
this.subjectId = subject.id
this.teacherId = teacher.id
this.teamId = data.teamClass?.id
this.classroom = classroom
}
}
}
lessonObject.id = lessonObject.buildId()
dates.add(lessonDate.value)
lessons.add(lessonObject)
val seen = profile.empty || lessonDate < Date.getToday()
if (lessonObject.type != Lesson.TYPE_NORMAL && lessonDate >= Date.getToday()) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonObject.id,
seen,
seen
))
}
}
val date: Date = weekStart.clone()
while (date <= weekEnd) {
if (!dates.contains(date.value)) {
lessons.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1)
}
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.lessonList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS)
onSuccess(ENDPOINT_IDZIENNIK_WEB_TIMETABLE)
}
}}
}

View File

@ -1,97 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-27.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.firstlogin
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR
import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_SETTINGS
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_IDZIENNIK
import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginWeb
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.set
import pl.szczodrzynski.edziennik.swapFirstLastName
class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikFirstLogin"
}
private val web = IdziennikWeb(data, null)
private val profileList = mutableListOf<Profile>()
init {
val loginStoreId = data.loginStore.id
val loginStoreType = LOGIN_TYPE_IDZIENNIK
var firstProfileId = loginStoreId
IdziennikLoginWeb(data) {
web.webGet(TAG, IDZIENNIK_WEB_SETTINGS) { text ->
//val accounts = json.getJsonArray("accounts")
val isParent = Regexes.IDZIENNIK_LOGIN_FIRST_IS_PARENT.find(text)?.get(1) != "0"
val accountNameLong = if (isParent)
Regexes.IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME.find(text)?.get(1)?.swapFirstLastName()?.fixName()
else null
var schoolYearStart: Int? = null
var schoolYearEnd: Int? = null
var schoolYearName: String? = null
val schoolYearId = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let {
schoolYearName = it[2]+"/"+it[3]
schoolYearStart = it[2].toIntOrNull()
schoolYearEnd = it[3].toIntOrNull()
it[1].toIntOrNull()
} ?: run {
data.error(ApiError(TAG, ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR)
.withApiResponse(text))
return@webGet
}
Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text)
.toMutableList()
.reversed()
.forEach { match ->
val registerId = match[1].toIntOrNull() ?: return@forEach
val studentId = match[2]
val firstName = match[3]
val lastName = match[4]
val className = match[5] + " " + match[6]
val studentNameLong = "$firstName $lastName".fixName()
val studentNameShort = "$firstName ${lastName[0]}.".fixName()
val accountName = if (accountNameLong == studentNameLong) null else accountNameLong
val profile = Profile(
firstProfileId++,
loginStoreId,
loginStoreType,
studentNameLong,
data.webUsername,
studentNameLong,
studentNameShort,
accountName
).apply {
schoolYearStart?.let { studentSchoolYearStart = it }
studentClassName = className
studentData["studentId"] = studentId
studentData["registerId"] = registerId
studentData["schoolYearId"] = schoolYearId
}
profileList.add(profile)
}
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-25.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.utils.Utils
class IdziennikLogin(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLogin"
}
private var cancelled = false
init {
nextLoginMethod(onSuccess)
}
private fun nextLoginMethod(onSuccess: () -> Unit) {
if (data.targetLoginMethodIds.isEmpty()) {
onSuccess()
return
}
if (cancelled) {
onSuccess()
return
}
useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId ->
data.progress(data.progressStep)
if (usedMethodId != -1)
data.loginMethods.add(usedMethodId)
nextLoginMethod(onSuccess)
}
}
private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) {
// this should never be true
if (data.loginMethods.contains(loginMethodId)) {
onSuccess(-1)
return
}
Utils.d(TAG, "Using login method $loginMethodId")
when (loginMethodId) {
LOGIN_METHOD_IDZIENNIK_WEB -> {
data.startProgress(R.string.edziennik_progress_login_idziennik_web)
IdziennikLoginWeb(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_IDZIENNIK_API -> {
data.startProgress(R.string.edziennik_progress_login_idziennik_api)
IdziennikLoginApi(data) { onSuccess(loginMethodId) }
}
}
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-27.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
class IdziennikLoginApi(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLoginApi"
}
init { run {
if (data.isApiLoginValid()) {
onSuccess()
}
else {
onSuccess()
}
}}
}

View File

@ -1,178 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-26.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.TextCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "IdziennikLoginWeb"
}
init { run {
if (data.isWebLoginValid()) {
data.app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", data.webSessionId)
data.app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", data.webAuth)
onSuccess()
}
else {
data.app.cookieJar.clear("iuczniowie.progman.pl")
if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) {
loginWithCredentials()
}
else {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
}
}
}}
private fun loginWithCredentials() {
Utils.d(TAG, "Request: Idziennik/Login/Web - $IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
val loginCallback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
if (text.isNullOrEmpty()) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
// login succeeded: there is a start page
if (text.contains("czyWyswietlicDostepMobilny")) {
val cookies = data.app.cookieJar.getAll("iuczniowie.progman.pl")
run {
data.webSessionId = cookies["ASP.NET_SessionId_iDziennik"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION
data.webAuth = cookies[".ASPXAUTH"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH
data.apiBearer = cookies["Bearer"]?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER
data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */
data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */
val hiddenFields = JsonObject()
Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text).forEach {
hiddenFields[it[1]] = it[2]
}
data.loginStore.putLoginData("hiddenFields", hiddenFields)
Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let {
val registerId = it[1].toIntOrNull() ?: return@let
data.webSelectedRegister = registerId
}
// for profiles created after archiving
data.schoolYearId = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let {
it[1].toIntOrNull()
} ?: data.schoolYearId
data.profile?.studentClassName = Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text)
.firstOrNull { it[1].toIntOrNull() == data.registerId }
?.let { "${it[5]} ${it[6]}" } ?: data.profile?.studentClassName
data.profile?.let { profile ->
Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also {
val number = it[1].toIntOrNull() ?: return@also
val luckyNumberObject = LuckyNumber(
profileId = data.profileId,
date = Date.getToday(),
number = number
)
data.luckyNumberList.add(luckyNumberObject)
data.metadataList.add(
Metadata(
profile.id,
Metadata.TYPE_LUCKY_NUMBER,
luckyNumberObject.date.value.toLong(),
true,
profile.empty
))
}
}
return@run null
}?.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
onSuccess()
return
}
val errorText = Regexes.IDZIENNIK_LOGIN_ERROR.find(text)?.get(1)
when {
errorText?.contains("nieprawidłową nazwę szkoły") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME
errorText?.contains("nieprawidłowy login lub hasło") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN
text.contains("Identyfikator zgłoszenia") -> ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR
text.contains("Hasło dostępu do systemu wygasło") -> ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED
text.contains("Trwają prace konserwacyjne") -> ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE
else -> ERROR_LOGIN_IDZIENNIK_WEB_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
val getCallback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
Request.builder()
.url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.userAgent(IDZIENNIK_USER_AGENT)
.addHeader("Origin", "https://iuczniowie.progman.pl")
.addHeader("Referer", "$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.apply {
Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text ?: return@apply).forEach {
addParameter(it[1], it[2])
}
}
.addParameter("ctl00\$ContentPlaceHolder\$nazwaPrzegladarki", IDZIENNIK_USER_AGENT)
.addParameter("ctl00\$ContentPlaceHolder\$NazwaSzkoly", data.webSchoolName)
.addParameter("ctl00\$ContentPlaceHolder\$UserName", data.webUsername)
.addParameter("ctl00\$ContentPlaceHolder\$Password", data.webPassword)
.addParameter("ctl00\$ContentPlaceHolder\$captcha", "")
.addParameter("ctl00\$ContentPlaceHolder\$Logowanie", "Zaloguj")
.post()
.allowErrorCode(502)
.callback(loginCallback)
.build()
.enqueue()
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN")
.userAgent(IDZIENNIK_USER_AGENT)
.get()
.allowErrorCode(502)
.callback(getCallback)
.build()
.enqueue()
}
}

View File

@ -174,7 +174,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
val topic = lesson[1].substringAfter(" - ", missingDelimiterValue = "").takeIf { it.isNotBlank() }
if (topic?.startsWith("Lekcja odwołana: ") == true || entry.isEmpty())
return
val subjectName = lesson[1].substringBefore(" - ")
val subjectName = lesson[1].substringBefore(" - ").trim()
//val team = lesson[3]
val teacherName = lesson[3].fixName()
@ -188,7 +188,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
}
entry = entry.removePrefix(typeSymbol)
var isCounted = true
var isCustom = false
val baseType = when (typeSymbol) {
"." -> TYPE_PRESENT
"|" -> TYPE_ABSENT
@ -196,11 +196,12 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
"s" -> TYPE_BELATED
"z" -> TYPE_RELEASED
else -> {
isCounted = false
isCustom = true
when (typeSymbol) {
"e" -> TYPE_PRESENT_CUSTOM
"en" -> TYPE_ABSENT
"ep" -> TYPE_PRESENT_CUSTOM
"+ₑ" -> TYPE_ABSENT_EXCUSED
else -> TYPE_UNKNOWN
}
}
@ -210,10 +211,11 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
"e" -> 0xff673ab7
"en" -> 0xffec407a
"ep" -> 0xff4caf50
"+ₑ" -> 0xff795548
else -> null
}?.toInt()
val typeShort = if (isCounted)
val typeShort = if (!isCustom)
data.app.attendanceManager.getTypeShort(baseType)
else
typeSymbol
@ -237,7 +239,6 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik,
subjectId = subjectId
).also {
it.lessonTopic = topic
it.isCounted = isCounted
}
data.attendanceList.add(attendanceObject)

View File

@ -74,16 +74,16 @@ class MobidziennikWebGetMessage(override val data: DataMobidziennik,
message.senderId = null
content.select("table.spis tr:has(td)")?.forEach { recipientEl ->
val senderEl = recipientEl.select("td:eq(0)").first()
val senderEl = recipientEl.select("td:eq(1)")?.first() ?: return@forEach
val senderName = senderEl.text().fixName()
val teacher = data.teacherList.singleOrNull { it.fullNameLastFirst == senderName }
val receiverId = teacher?.id ?: -1
var readDate = 0L
val isReadEl = recipientEl.select("td:eq(2)").first()
if (isReadEl.ownText() != "NIE") {
val readDateEl = recipientEl.select("td:eq(3) small").first()
val isReadEl = recipientEl.select("td:eq(4)")?.first() ?: return@forEach
if (isReadEl.html().contains("tak")) {
val readDateEl = recipientEl.select("td:eq(5) small")?.first() ?: return@forEach
Regexes.MOBIDZIENNIK_MESSAGE_SENT_READ_DATE.find(readDateEl.ownText())?.let {
val date = Date(
it[3].toIntOrNull() ?: 2019,

View File

@ -46,7 +46,7 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
when (e.tagName()) {
"div" -> {
Regexes.MOBIDZIENNIK_GRADES_SUBJECT_NAME.find(e.outerHtml())?.let {
subjectName = it[1]
subjectName = it[1].trim()
}
}
"span" -> {

View File

@ -5,16 +5,15 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.crc16
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_HEBE
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN
import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_VULCAN_API
import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.fslogin.realm.RealmData
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
@ -22,10 +21,6 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
&& (webExpiryTime[symbol]?.toLongOrNull() ?: 0) - 30 > currentTimeUnix()
&& webAuthCookie[symbol].isNotNullNorEmpty()
&& webRealmData != null
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
&& apiFingerprint[symbol].isNotNullNorEmpty()
&& apiPrivateKey[symbol].isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty()
fun isHebeLoginValid() = hebePublicKey.isNotNullNorEmpty()
&& hebePrivateKey.isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty()
@ -35,34 +30,11 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
if (isWebMainLoginValid()) {
loginMethods += LOGIN_METHOD_VULCAN_WEB_MAIN
}
if (isApiLoginValid()) {
loginMethods += LOGIN_METHOD_VULCAN_API
}
if (isHebeLoginValid()) {
loginMethods += LOGIN_METHOD_VULCAN_HEBE
}
}
init {
// during the first sync `profile.studentClassName` is already set
if (loginStore.mode == LOGIN_MODE_VULCAN_API
&& teamList.values().none { it.type == Team.TYPE_CLASS }) {
profile?.studentClassName?.also { name ->
val id = Utils.crc16(name.toByteArray()).toLong()
val teamObject = Team(
profileId,
id,
name,
Team.TYPE_CLASS,
"$schoolCode:$name",
-1
)
teamList.put(id, teamObject)
}
}
}
override fun generateUserCode() = "$schoolCode:$studentId"
fun buildDeviceId(): String {
@ -224,16 +196,6 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
get() { mApiPin = mApiPin ?: loginStore.getLoginData("apiPin", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPin ?: mapOf() }
set(value) { loginStore.putLoginData("apiPin", app.gson.toJson(value)); mApiPin = value }
private var mApiFingerprint: Map<String?, String?>? = null
var apiFingerprint: Map<String?, String?> = mapOf()
get() { mApiFingerprint = mApiFingerprint ?: loginStore.getLoginData("apiFingerprint", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiFingerprint ?: mapOf() }
set(value) { loginStore.putLoginData("apiFingerprint", app.gson.toJson(value)); mApiFingerprint = value }
private var mApiPrivateKey: Map<String?, String?>? = null
var apiPrivateKey: Map<String?, String?> = mapOf()
get() { mApiPrivateKey = mApiPrivateKey ?: loginStore.getLoginData("apiPrivateKey", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mApiPrivateKey ?: mapOf() }
set(value) { loginStore.putLoginData("apiPrivateKey", app.gson.toJson(value)); mApiPrivateKey = value }
/* _ _ _ _____ _____
| | | | | | /\ | __ \_ _|
| |__| | ___| |__ ___ / \ | |__) || |

View File

@ -10,9 +10,6 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.helper.OneDriveDownloadAttachment
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanData
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiAttachments
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiMessagesChangeStatus
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiSendMessage
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeMessagesChangeStatus
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeSendMessage
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.firstlogin.VulcanFirstLogin
@ -69,6 +66,11 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
}
private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) {
if (data.loginStore.mode == LOGIN_MODE_VULCAN_API) {
data.error(TAG, ERROR_VULCAN_API_DEPRECATED)
return
}
d(TAG, "Trying to login with ${data.targetLoginMethodIds}")
if (internalErrorList.isNotEmpty()) {
d(TAG, " - Internal errors:")
@ -93,60 +95,21 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
}
override fun getMessage(message: MessageFull) {
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
login(LOGIN_METHOD_VULCAN_HEBE) {
if (message.seen) {
EventBus.getDefault().postSticky(MessageGetEvent(message))
completed()
return@login
}
VulcanHebeMessagesChangeStatus(data, message) {
completed()
}
}
return
}
login(LOGIN_METHOD_VULCAN_API) {
if (message.attachmentIds != null) {
VulcanApiMessagesChangeStatus(data, message) {
completed()
}
login(LOGIN_METHOD_VULCAN_HEBE) {
if (message.seen) {
EventBus.getDefault().postSticky(MessageGetEvent(message))
completed()
return@login
}
val list = data.app.db.messageDao().getAllNow(data.profileId)
VulcanApiAttachments(data, list, message, MessageFull::class) { _ ->
list.forEach {
if (it.attachmentIds == null)
it.attachmentIds = mutableListOf()
data.messageList.add(it)
}
data.messageListReplace = true
if (message.seen) {
EventBus.getDefault().postSticky(MessageGetEvent(message))
completed()
return@VulcanApiAttachments
}
VulcanApiMessagesChangeStatus(data, message) {
completed()
}
VulcanHebeMessagesChangeStatus(data, message) {
completed()
}
}
}
override fun sendMessage(recipients: List<Teacher>, subject: String, text: String) {
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
login(LOGIN_METHOD_VULCAN_HEBE) {
VulcanHebeSendMessage(data, recipients, subject, text) {
completed()
}
}
return
}
login(LOGIN_METHOD_VULCAN_API) {
VulcanApiSendMessage(data, recipients, subject, text) {
login(LOGIN_METHOD_VULCAN_HEBE) {
VulcanHebeSendMessage(data, recipients, subject, text) {
completed()
}
}
@ -200,27 +163,10 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
}
override fun getEvent(eventFull: EventFull) {
if (loginStore.mode != LOGIN_MODE_VULCAN_API) {
eventFull.homeworkBody = ""
eventFull.homeworkBody = ""
EventBus.getDefault().postSticky(EventGetEvent(eventFull))
completed()
return
}
login(LOGIN_METHOD_VULCAN_API) {
val list = data.app.db.eventDao().getAllNow(data.profileId).filter { !it.addedManually }
VulcanApiAttachments(data, list, eventFull, EventFull::class) { _ ->
list.forEach {
it.homeworkBody = ""
data.eventList.add(it)
}
data.eventListReplace = true
EventBus.getDefault().postSticky(EventGetEvent(eventFull))
completed()
}
}
EventBus.getDefault().postSticky(EventGetEvent(eventFull))
completed()
}
override fun firstLogin() { VulcanFirstLogin(data) { completed() } }

View File

@ -7,19 +7,6 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.models.Feature
const val ENDPOINT_VULCAN_API_UPDATE_SEMESTER = 1000
const val ENDPOINT_VULCAN_API_PUSH_CONFIG = 1005
const val ENDPOINT_VULCAN_API_DICTIONARIES = 1010
const val ENDPOINT_VULCAN_API_TIMETABLE = 1020
const val ENDPOINT_VULCAN_API_EVENTS = 1030
const val ENDPOINT_VULCAN_API_GRADES = 1040
const val ENDPOINT_VULCAN_API_GRADES_SUMMARY = 1050
const val ENDPOINT_VULCAN_API_HOMEWORK = 1060
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
const val ENDPOINT_VULCAN_HEBE_MAIN = 3000
@ -38,56 +25,31 @@ const val ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER = 3200
val VulcanFeatures = listOf(
// timetable
Feature(LOGIN_TYPE_VULCAN, FEATURE_TIMETABLE, listOf(
ENDPOINT_VULCAN_API_TIMETABLE to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_TIMETABLE, listOf(
ENDPOINT_VULCAN_HEBE_TIMETABLE to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// agenda
Feature(LOGIN_TYPE_VULCAN, FEATURE_AGENDA, listOf(
ENDPOINT_VULCAN_API_EVENTS to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_AGENDA, listOf(
ENDPOINT_VULCAN_HEBE_EXAMS to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// grades
Feature(LOGIN_TYPE_VULCAN, FEATURE_GRADES, listOf(
ENDPOINT_VULCAN_API_GRADES to LOGIN_METHOD_VULCAN_API,
ENDPOINT_VULCAN_API_GRADES_SUMMARY to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_GRADES, listOf(
ENDPOINT_VULCAN_HEBE_GRADES to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// homework
Feature(LOGIN_TYPE_VULCAN, FEATURE_HOMEWORK, listOf(
ENDPOINT_VULCAN_API_HOMEWORK to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_HOMEWORK, listOf(
ENDPOINT_VULCAN_HEBE_HOMEWORK to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// behaviour
Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_VULCAN_API_NOTICES to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_VULCAN_HEBE_NOTICES to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// attendance
Feature(LOGIN_TYPE_VULCAN, FEATURE_ATTENDANCE, listOf(
ENDPOINT_VULCAN_API_ATTENDANCE to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_ATTENDANCE, listOf(
ENDPOINT_VULCAN_HEBE_ATTENDANCE to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// messages
Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_VULCAN_API_MESSAGES_INBOX to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_SENT, listOf(
ENDPOINT_VULCAN_API_MESSAGES_SENT to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_MESSAGES_INBOX, listOf(
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
@ -96,11 +58,6 @@ val VulcanFeatures = listOf(
), listOf(LOGIN_METHOD_VULCAN_HEBE)),
// push config
Feature(LOGIN_TYPE_VULCAN, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_VULCAN_API_PUSH_CONFIG to LOGIN_METHOD_VULCAN_API
), listOf(LOGIN_METHOD_VULCAN_API)).withShouldSync { data ->
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
},
Feature(LOGIN_TYPE_VULCAN, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE)).withShouldSync { data ->
@ -124,37 +81,8 @@ val VulcanFeatures = listOf(
.withShouldSync { data -> data.shouldSyncLuckyNumber() }
.withPriority(1),
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
), listOf(LOGIN_METHOD_VULCAN_API)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
ENDPOINT_VULCAN_HEBE_MAIN to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK to LOGIN_METHOD_VULCAN_HEBE
), listOf(LOGIN_METHOD_VULCAN_HEBE))
/*Feature(LOGIN_TYPE_VULCAN, FEATURE_STUDENT_INFO, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_STUDENT_NUMBER, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_SCHOOL_INFO, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_CLASS_INFO, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_TEAM_INFO, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_TEACHERS, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_SUBJECTS, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),
Feature(LOGIN_TYPE_VULCAN, FEATURE_CLASSROOMS, listOf(
ENDPOINT_VULCAN_API to LOGIN_METHOD_VULCAN_WEB
), listOf(LOGIN_METHOD_VULCAN_WEB)),*/
)

View File

@ -1,132 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-19
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import io.github.wulkanowy.signer.android.signContent
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection
import java.util.*
open class VulcanApi(open val data: DataVulcan, open val lastSync: Long?) {
companion object {
const val TAG = "VulcanApi"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
fun apiGet(
tag: String,
endpoint: String,
method: Int = POST,
parameters: Map<String, Any> = emptyMap(),
baseUrl: Boolean = false,
onSuccess: (json: JsonObject, response: Response?) -> Unit
) {
val url = "${if (baseUrl) data.apiUrl else data.fullApiUrl}$endpoint"
d(tag, "Request: Vulcan/Api - $url")
val finalPayload = JsonObject()
parameters.map { (name, value) ->
when (value) {
is JsonObject -> finalPayload.add(name, value)
is JsonArray -> finalPayload.add(name, value)
is String -> finalPayload.addProperty(name, value)
is Int -> finalPayload.addProperty(name, value)
is Long -> finalPayload.addProperty(name, value)
is Float -> finalPayload.addProperty(name, value)
is Char -> finalPayload.addProperty(name, value)
is Boolean -> finalPayload.addProperty(name, value)
}
}
finalPayload.addProperty("RemoteMobileTimeKey", System.currentTimeMillis() / 1000)
finalPayload.addProperty("TimeKey", System.currentTimeMillis() / 1000 - 1)
finalPayload.addProperty("RequestId", UUID.randomUUID().toString())
finalPayload.addProperty("RemoteMobileAppVersion", VULCAN_API_APP_VERSION)
finalPayload.addProperty("RemoteMobileAppName", VULCAN_API_APP_NAME)
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
if (response?.code() ?: 200 != 200) {
when (response?.code()) {
503 -> ERROR_VULCAN_API_MAINTENANCE
400 -> ERROR_VULCAN_API_BAD_REQUEST
else -> ERROR_VULCAN_API_OTHER
}.let { errorCode ->
data.error(ApiError(tag, errorCode)
.withResponse(response)
.withApiResponse(json?.toString() ?: response?.parserErrorBody))
return
}
}
if (json == null) {
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
try {
onSuccess(json, response)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_VULCAN_API_REQUEST)
.withResponse(response)
.withThrowable(e)
.withApiResponse(json))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url(url)
.userAgent(VULCAN_API_USER_AGENT)
.addHeader("RequestCertificateKey", data.apiFingerprint[data.symbol])
.addHeader("RequestSignatureValue",
try {
signContent(
data.apiPrivateKey[data.symbol] ?: "",
finalPayload.toString()
)
} catch (e: Exception) {e.printStackTrace();""})
.apply {
when (method) {
GET -> get()
POST -> post()
}
}
.setJsonBody(finalPayload)
.allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST)
.allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN)
.allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED)
.allowErrorCode(HttpURLConnection.HTTP_UNAVAILABLE)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -5,10 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_API_DEPRECATED
import pl.szczodrzynski.edziennik.data.api.LOGIN_MODE_VULCAN_API
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.hebe.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.web.VulcanWebLuckyNumber
import pl.szczodrzynski.edziennik.data.db.entity.Message
@ -33,11 +30,7 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER
)
init { run {
if (data.loginStore.mode == LOGIN_MODE_VULCAN_API) {
data.error(TAG, ERROR_VULCAN_API_DEPRECATED)
return@run
}
init {
if (data.studentSemesterNumber == 2 && data.profile?.empty != false) {
firstSemesterSync = true
// set to sync 1st semester first
@ -52,7 +45,7 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
}
onSuccess()
}
}}
}
private fun nextEndpoint(onSuccess: () -> Unit) {
if (data.targetEndpointIds.isEmpty()) {
@ -88,54 +81,6 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) {
Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync")
when (endpointId) {
ENDPOINT_VULCAN_API_UPDATE_SEMESTER -> {
data.startProgress(R.string.edziennik_progress_endpoint_student_info)
VulcanApiUpdateSemester(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_PUSH_CONFIG -> {
data.startProgress(R.string.edziennik_progress_endpoint_push_config)
VulcanApiPushConfig(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_DICTIONARIES -> {
data.startProgress(R.string.edziennik_progress_endpoint_dictionaries)
VulcanApiDictionaries(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_GRADES -> {
data.startProgress(R.string.edziennik_progress_endpoint_grades)
VulcanApiGrades(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_GRADES_SUMMARY -> {
data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades)
VulcanApiProposedGrades(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_EVENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_events)
VulcanApiEvents(data, isHomework = false, lastSync = lastSync, onSuccess = onSuccess)
}
ENDPOINT_VULCAN_API_HOMEWORK -> {
data.startProgress(R.string.edziennik_progress_endpoint_homework)
VulcanApiEvents(data, isHomework = true, lastSync = lastSync, onSuccess = onSuccess)
}
ENDPOINT_VULCAN_API_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
VulcanApiNotices(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_ATTENDANCE -> {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
VulcanApiAttendance(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
VulcanApiTimetable(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
VulcanApiMessagesInbox(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_API_MESSAGES_SENT -> {
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)

View File

@ -221,12 +221,35 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
}
val status = json.getJsonObject("Status")
if (status?.getInt("Code") != 0) {
val statusCode = status?.getInt("Code") ?: 0
if (statusCode != 0) {
val statusMessage = status?.getString("Message")
val errorCode = when (statusCode) {
-1 -> ERROR_VULCAN_HEBE_SERVER_ERROR
100 -> ERROR_VULCAN_HEBE_SIGNATURE_ERROR
101 -> ERROR_VULCAN_HEBE_INVALID_PAYLOAD
106 -> ERROR_VULCAN_HEBE_FIREBASE_ERROR
108 -> ERROR_VULCAN_HEBE_CERTIFICATE_GONE
200 -> when (true) {
statusMessage?.contains("Class") -> ERROR_LOGIN_VULCAN_NO_PUPILS
statusMessage?.contains("Token") -> ERROR_LOGIN_VULCAN_INVALID_TOKEN
else -> ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND
}
201 -> ERROR_LOGIN_VULCAN_EXPIRED_TOKEN
203 -> when (json.getJsonObject("Envelope").getInt("AvailableRetries")) {
2 -> ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING
1 -> ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING
else -> ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING
}
204 -> ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING
else -> ERROR_VULCAN_HEBE_OTHER
}
data.error(
ApiError(tag, ERROR_VULCAN_HEBE_OTHER)
ApiError(tag, errorCode)
.withResponse(response)
.withApiResponse(json.toString())
)
return
}
val envelope = if (json.get("Envelope").isJsonNull && null is T)

View File

@ -168,7 +168,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
var count = 0
while (clientIndex != -1 && count < 100) {
val startIndex = clientIndex + clientUrl.length
val endIndex = text.indexOf('"', startIndex = startIndex)
val endIndex = text.indexOfAny(charArrayOf('"', '/'), startIndex = startIndex)
val schoolSymbol = text.substring(startIndex, endIndex)
schoolSymbols += schoolSymbol
clientIndex = text.indexOf(clientUrl, startIndex = endIndex)

View File

@ -1,124 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-4-6.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.reflect.KClass
class VulcanApiAttachments(override val data: DataVulcan,
val list: List<*>,
val owner: Any?,
val ownerClass: KClass<*>,
val onSuccess: (list: List<*>) -> Unit
) : VulcanApi(data, null) {
companion object {
const val TAG = "VulcanApiAttachments"
}
init { run {
val endpoint = when (ownerClass) {
MessageFull::class -> VULCAN_API_ENDPOINT_MESSAGES_ATTACHMENTS
EventFull::class -> VULCAN_API_ENDPOINT_HOMEWORK_ATTACHMENTS
else -> null
} ?: return@run
val idName = when (ownerClass) {
MessageFull::class -> "IdWiadomosc"
EventFull::class -> "IdZadanieDomowe"
else -> null
} ?: return@run
val startDate = profile?.getSemesterStart(profile?.currentSemester ?: 1)?.inUnix ?: 0
val endDate = Date.getToday().stepForward(0, 1, 0).inUnix
apiGet(TAG, endpoint, parameters = mapOf(
"DataPoczatkowa" to startDate,
"DataKoncowa" to endDate,
"LoginId" to data.studentLoginId,
"IdUczen" to data.studentId
)) { json, _ ->
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { attachment ->
val id = attachment.getLong("Id") ?: return@forEach
val itemId = attachment.getLong(idName) ?: return@forEach
val url = attachment.getString("Url") ?: return@forEach
val fileName = "${attachment.getString("NazwaPliku")}:$url"
list.forEach {
if (it is MessageFull
&& it.profileId == profileId
&& it.id == itemId
&& it.attachmentIds?.contains(id) != true) {
if (it.attachmentIds == null)
it.attachmentIds = mutableListOf()
if (it.attachmentNames == null)
it.attachmentNames = mutableListOf()
it.attachmentIds?.add(id)
it.attachmentNames?.add(fileName)
}
if (it is EventFull
&& it.profileId == profileId
&& it.id == itemId
&& it.attachmentIds?.contains(id) != true) {
if (it.attachmentIds == null)
it.attachmentIds = mutableListOf()
if (it.attachmentNames == null)
it.attachmentNames = mutableListOf()
it.attachmentIds?.add(id)
it.attachmentNames?.add(fileName)
}
if (owner is MessageFull
&& it is MessageFull
&& owner.profileId == it.profileId
&& owner.id == it.id) {
owner.attachmentIds = it.attachmentIds
owner.attachmentNames = it.attachmentNames
}
if (owner is EventFull
&& it is EventFull
&& owner.profileId == it.profileId
&& owner.id == it.id) {
owner.attachmentIds = it.attachmentIds
owner.attachmentNames = it.attachmentNames
}
}
}
/*if (owner is MessageFull) {
list.forEach {
(it as? MessageFull)?.let { message ->
data.messageList.add(message)
}
}
data.messageListReplace = true
}
if (owner is EventFull) {
list.forEach {
(it as? EventFull)?.let { it1 ->
it1.homeworkBody = ""
data.eventList.add(it1)
}
}
data.eventListReplace = true
}*/
onSuccess(list)
}
}}
}

View File

@ -1,110 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import androidx.core.util.isEmpty
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_ATTENDANCE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_ATTENDANCE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Attendance.Companion.TYPE_PRESENT
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiAttendance(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiAttendance"
}
init { data.profile?.also { profile ->
if (data.attendanceTypes.isEmpty()) {
data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id }
}
val semesterId = data.studentSemesterId
val semesterNumber = data.studentSemesterNumber
if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) {
getAttendance(profile, semesterId - 1, semesterNumber - 1) {
getAttendance(profile, semesterId, semesterNumber) {
finish()
}
}
}
else {
getAttendance(profile, semesterId, semesterNumber) {
finish()
}
}
} ?: onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE) }
private fun finish() {
data.setSyncNext(ENDPOINT_VULCAN_API_ATTENDANCE, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_API_ATTENDANCE)
}
private fun getAttendance(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) {
val startDate = profile.getSemesterStart(semesterNumber).stringY_m_d
val endDate = profile.getSemesterEnd(semesterNumber).stringY_m_d
apiGet(TAG, VULCAN_API_ENDPOINT_ATTENDANCE, parameters = mapOf(
"DataPoczatkowa" to startDate,
"DataKoncowa" to endDate,
"IdOddzial" to data.studentClassId,
"IdUczen" to data.studentId,
"IdOkresKlasyfikacyjny" to semesterId
)) { json, _ ->
json.getJsonObject("Data")?.getJsonArray("Frekwencje")?.forEach { attendanceEl ->
val attendance = attendanceEl.asJsonObject
val type = data.attendanceTypes.get(attendance.getLong("IdKategoria") ?: return@forEach)
?: return@forEach
val id = (attendance.getInt("Dzien") ?: 0) + (attendance.getInt("Numer") ?: 0)
val lessonDateMillis = Date.fromY_m_d(attendance.getString("DzienTekst")).inMillis
val lessonDate = Date.fromMillis(lessonDateMillis)
val startTime = data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime
val lessonSemester = semesterNumber
val attendanceObject = Attendance(
profileId = profileId,
id = id.toLong(),
baseType = type.baseType,
typeName = type.typeName,
typeShort = type.typeShort,
typeSymbol = type.typeSymbol,
typeColor = type.typeColor,
date = lessonDate,
startTime = startTime,
semester = lessonSemester,
teacherId = -1,
subjectId = attendance.getLong("IdPrzedmiot") ?: -1,
addedDate = lessonDate.combineWith(startTime)
).also {
it.lessonNumber = attendance.getInt("Numer")
it.isCounted = it.baseType != Attendance.TYPE_RELEASED
}
data.attendanceList.add(attendanceObject)
if (type.baseType != TYPE_PRESENT) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ATTENDANCE,
attendanceObject.id,
profile.empty || type.baseType == Attendance.TYPE_PRESENT_CUSTOM || type.baseType == Attendance.TYPE_UNKNOWN,
profile.empty || type.baseType == Attendance.TYPE_PRESENT_CUSTOM || type.baseType == Attendance.TYPE_UNKNOWN
))
}
}
onSuccess()
}
}
}

View File

@ -1,173 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-20
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_DICTIONARIES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_DICTIONARIES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.utils.models.Time
class VulcanApiDictionaries(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiDictionaries"
}
init {
apiGet(TAG, VULCAN_API_ENDPOINT_DICTIONARIES) { json, _ ->
val elements = json.getJsonObject("Data")
elements?.getJsonArray("Pracownicy")?.forEach { saveTeacher(it.asJsonObject) }
elements?.getJsonArray("Przedmioty")?.forEach { saveSubject(it.asJsonObject) }
elements?.getJsonArray("PoryLekcji")?.forEach { saveLessonRange(it.asJsonObject) }
elements?.getJsonArray("KategorieOcen")?.forEach { saveGradeCategory(it.asJsonObject) }
elements?.getJsonArray("KategorieUwag")?.forEach { saveNoticeType(it.asJsonObject) }
elements?.getJsonArray("KategorieFrekwencji")?.forEach { saveAttendanceType(it.asJsonObject) }
data.setSyncNext(ENDPOINT_VULCAN_API_DICTIONARIES, 4 * DAY)
onSuccess(ENDPOINT_VULCAN_API_DICTIONARIES)
}
}
private fun saveTeacher(teacher: JsonObject) {
val id = teacher.getLong("Id") ?: return
val name = teacher.getString("Imie") ?: ""
val surname = teacher.getString("Nazwisko") ?: ""
val loginId = teacher.getString("LoginId") ?: "-1"
val teacherObject = Teacher(
profileId,
id,
name,
surname,
loginId
)
data.teacherList.put(id, teacherObject)
}
private fun saveSubject(subject: JsonObject) {
val id = subject.getLong("Id") ?: return
val longName = subject.getString("Nazwa") ?: ""
val shortName = subject.getString("Kod") ?: ""
val subjectObject = Subject(
profileId,
id,
longName,
shortName
)
data.subjectList.put(id, subjectObject)
}
private fun saveLessonRange(lessonRange: JsonObject) {
val lessonNumber = lessonRange.getInt("Numer") ?: return
val startTime = lessonRange.getString("PoczatekTekst")?.let { Time.fromH_m(it) } ?: return
val endTime = lessonRange.getString("KoniecTekst")?.let { Time.fromH_m(it) } ?: return
val lessonRangeObject = LessonRange(
profileId,
lessonNumber,
startTime,
endTime
)
data.lessonRanges.put(lessonNumber, lessonRangeObject)
}
private fun saveGradeCategory(gradeCategory: JsonObject) {
val id = gradeCategory.getLong("Id") ?: return
val name = gradeCategory.getString("Nazwa") ?: ""
val gradeCategoryObject = GradeCategory(
profileId,
id,
0.0f,
-1,
name
)
data.gradeCategories.put(id, gradeCategoryObject)
}
private fun saveNoticeType(noticeType: JsonObject) {
val id = noticeType.getLong("Id") ?: return
val name = noticeType.getString("Nazwa") ?: ""
val noticeTypeObject = NoticeType(
profileId,
id,
name
)
data.noticeTypes.put(id, noticeTypeObject)
}
private fun saveAttendanceType(attendanceType: JsonObject) {
val id = attendanceType.getLong("Id") ?: return
val typeName = attendanceType.getString("Nazwa") ?: ""
val absent = attendanceType.getBoolean("Nieobecnosc") ?: false
val excused = attendanceType.getBoolean("Usprawiedliwione") ?: false
val baseType = if (absent) {
if (excused)
Attendance.TYPE_ABSENT_EXCUSED
else
Attendance.TYPE_ABSENT
} else {
val belated = attendanceType.getBoolean("Spoznienie") ?: false
val released = attendanceType.getBoolean("Zwolnienie") ?: false
val present = attendanceType.getBoolean("Obecnosc") ?: true
if (belated)
if (excused)
Attendance.TYPE_BELATED_EXCUSED
else
Attendance.TYPE_BELATED
else if (released)
Attendance.TYPE_RELEASED
else if (present)
Attendance.TYPE_PRESENT
else
Attendance.TYPE_UNKNOWN
}
val (typeColor, typeSymbol) = when (id.toInt()) {
1 -> 0xffffffff to "" // obecność
2 -> 0xffffa687 to "" // nieobecność
3 -> 0xfffcc150 to "u" // nieobecność usprawiedliwiona
4 -> 0xffede049 to "s" // spóźnienie
5 -> 0xffbbdd5f to "su" // spóźnienie usprawiedliwione
6 -> 0xffa9c9fd to "ns" // nieobecny z przyczyn szkolnych
7 -> 0xffddbbe5 to "z" // zwolniony
8 -> 0xffffffff to "" // usunięty wpis
else -> null to "?"
}
val typeShort = when (id.toInt()) {
6 -> "ns" // nieobecny z przyczyn szkolnych
8 -> "" // usunięty wpis
else -> data.app.attendanceManager.getTypeShort(baseType)
}
val attendanceTypeObject = AttendanceType(
profileId,
id,
baseType,
typeName,
typeShort,
typeSymbol,
typeColor?.toInt()
)
data.attendanceTypes.put(id, attendanceTypeObject)
}
}

View File

@ -1,133 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-20
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_EVENTS
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_EVENTS
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_HOMEWORK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getBoolean
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiEvents(override val data: DataVulcan,
override val lastSync: Long?,
private val isHomework: Boolean,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiEvents"
}
init { data.profile?.also { profile ->
val semesterId = data.studentSemesterId
val semesterNumber = data.studentSemesterNumber
if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) {
getEvents(profile, semesterId - 1, semesterNumber - 1) {
getEvents(profile, semesterId, semesterNumber) {
finish()
}
}
}
else {
getEvents(profile, semesterId, semesterNumber) {
finish()
}
}
} ?: onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS) }
private fun finish() {
when (isHomework) {
true -> {
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS)
}
false -> {
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS)
}
}
onSuccess(if (isHomework) ENDPOINT_VULCAN_API_HOMEWORK else ENDPOINT_VULCAN_API_EVENTS)
}
private fun getEvents(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) {
val startDate = when (profile.empty) {
true -> profile.getSemesterStart(semesterNumber).stringY_m_d
else -> Date.getToday().stepForward(0, -1, 0).stringY_m_d
}
val endDate = profile.getSemesterEnd(semesterNumber).stringY_m_d
val endpoint = when (isHomework) {
true -> VULCAN_API_ENDPOINT_HOMEWORK
else -> VULCAN_API_ENDPOINT_EVENTS
}
apiGet(TAG, endpoint, parameters = mapOf(
"DataPoczatkowa" to startDate,
"DataKoncowa" to endDate,
"IdOddzial" to data.studentClassId,
"IdUczen" to data.studentId,
"IdOkresKlasyfikacyjny" to semesterId
)) { json, _ ->
val events = json.getJsonArray("Data")
events?.forEach { eventEl ->
val event = eventEl.asJsonObject
val id = event?.getLong("Id") ?: return@forEach
val eventDate = Date.fromY_m_d(event.getString("DataTekst") ?: return@forEach)
val subjectId = event.getLong("IdPrzedmiot") ?: -1
val teacherId = event.getLong("IdPracownik") ?: -1
val topic = event.getString("Opis")?.trim() ?: ""
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
val type = when (isHomework) {
true -> Event.TYPE_HOMEWORK
else -> when (event.getBoolean("Rodzaj")) {
false -> Event.TYPE_SHORT_QUIZ
else -> Event.TYPE_EXAM
}
}
val teamId = event.getLong("IdOddzial") ?: data.teamClass?.id ?: -1
val eventObject = Event(
profileId = profileId,
id = id,
date = eventDate,
time = startTime,
topic = topic,
color = null,
type = type,
teacherId = teacherId,
subjectId = subjectId,
teamId = teamId
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
if (isHomework) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT,
id,
profile.empty,
profile.empty
))
}
onSuccess()
}
}
}

View File

@ -1,146 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-19
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import java.text.DecimalFormat
import kotlin.math.roundToInt
class VulcanApiGrades(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiGrades"
}
init { data.profile?.also { profile ->
val semesterId = data.studentSemesterId
val semesterNumber = data.studentSemesterNumber
if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) {
getGrades(profile, semesterId - 1, semesterNumber - 1) {
getGrades(profile, semesterId, semesterNumber) {
finish()
}
}
}
else {
getGrades(profile, semesterId, semesterNumber) {
finish()
}
}
} ?: onSuccess(ENDPOINT_VULCAN_API_GRADES) }
private fun finish() {
data.toRemove.add(DataRemoveModel.Grades.semesterWithType(data.studentSemesterNumber, TYPE_NORMAL))
data.setSyncNext(ENDPOINT_VULCAN_API_GRADES, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_API_GRADES)
}
private fun getGrades(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) {
apiGet(TAG, VULCAN_API_ENDPOINT_GRADES, parameters = mapOf(
"IdUczen" to data.studentId,
"IdOkresKlasyfikacyjny" to semesterId
)) { json, _ ->
val grades = json.getJsonArray("Data")
grades?.forEach { gradeEl ->
val grade = gradeEl.asJsonObject
val id = grade.getLong("Id") ?: return@forEach
val categoryId = grade.getLong("IdKategoria") ?: -1
val category = data.gradeCategories.singleOrNull{ it.categoryId == categoryId }?.text
?: ""
val teacherId = grade.getLong("IdPracownikD") ?: -1
val subjectId = grade.getLong("IdPrzedmiot") ?: -1
val description = grade.getString("Opis")
val comment = grade.getString("Komentarz")
var value = grade.getFloat("Wartosc")
var weight = grade.getFloat("WagaOceny") ?: 0.0f
val modificatorValue = grade.getFloat("WagaModyfikatora")
val numerator = grade.getFloat("Licznik")
val denominator = grade.getFloat("Mianownik")
val addedDate = (grade.getLong("DataModyfikacji") ?: return@forEach) * 1000
var finalDescription = ""
var name = when (numerator != null && denominator != null) {
true -> {
value = numerator / denominator
finalDescription += DecimalFormat("#.##").format(numerator) +
"/" + DecimalFormat("#.##").format(denominator)
weight = 0.0f
(value * 100).roundToInt().toString() + "%"
}
else -> {
if (value != null) modificatorValue?.also { value += it }
else weight = 0.0f
grade.getString("Wpis") ?: ""
}
}
comment?.also {
if (name == "") name = it
else finalDescription = (if (finalDescription == "") "" else " ") + it
}
description?.also {
finalDescription = (if (finalDescription == "") "" else " - ") + it
}
val color = when (name) {
"1-", "1", "1+" -> 0xffd65757
"2-", "2", "2+" -> 0xff9071b3
"3-", "3", "3+" -> 0xffd2ab24
"4-", "4", "4+" -> 0xff50b6d6
"5-", "5", "5+" -> 0xff2cbd92
"6-", "6", "6+" -> 0xff91b43c
else -> 0xff3D5F9C
}.toInt()
val gradeObject = Grade(
profileId = profileId,
id = id,
name = name,
type = TYPE_NORMAL,
value = value ?: 0.0f,
weight = weight,
color = color,
category = category,
description = finalDescription,
comment = null,
semester = semesterNumber,
teacherId = teacherId,
subjectId = subjectId,
addedDate = addedDate
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
id,
profile.empty,
profile.empty
))
}
onSuccess()
}
}
}

View File

@ -1,63 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-12
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS
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.events.MessageGetEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
class VulcanApiMessagesChangeStatus(override val data: DataVulcan,
private val messageObject: MessageFull,
val onSuccess: () -> Unit
) : VulcanApi(data, null) {
companion object {
const val TAG = "VulcanApiMessagesChangeStatus"
}
init {
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS, parameters = mapOf(
"WiadomoscId" to messageObject.id,
"FolderWiadomosci" to "Odebrane",
"Status" to "Widoczna",
"LoginId" to data.studentLoginId,
"IdUczen" to data.studentId
)) { _, _ ->
if (!messageObject.seen) {
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
messageObject.id,
true,
true
))
messageObject.seen = true
}
if (messageObject.type != TYPE_SENT) {
val messageRecipientObject = MessageRecipient(
profileId,
-1,
-1,
System.currentTimeMillis(),
messageObject.id
)
data.messageRecipientList.add(messageRecipientObject)
}
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
}
}
}

View File

@ -1,103 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-01
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_MESSAGES_RECEIVED
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.text.replace
class VulcanApiMessagesInbox(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiMessagesInbox"
}
init {
data.profile?.also { profile ->
val startDate = when (profile.empty) {
true -> profile.getSemesterStart(profile.currentSemester).inUnix
else -> Date.getToday().stepForward(0, -2, 0).inUnix
}
val endDate = Date.getToday().stepForward(0, 1, 0).inUnix
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_RECEIVED, parameters = mapOf(
"DataPoczatkowa" to startDate,
"DataKoncowa" to endDate,
"LoginId" to data.studentLoginId,
"IdUczen" to data.studentId
)) { json, _ ->
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { message ->
val id = message.getLong("WiadomoscId") ?: return@forEach
val subject = message.getString("Tytul") ?: ""
val body = message.getString("Tresc") ?: ""
val senderLoginId = message.getString("NadawcaId") ?: return@forEach
val senderId = data.teacherList.singleOrNull { it.loginId == senderLoginId }?.id ?: {
val senderName = message.getString("Nadawca") ?: ""
senderName.splitName()?.let { (senderLastName, senderFirstName) ->
val teacherObject = Teacher(
profileId,
-1 * Utils.crc16(senderName.toByteArray()).toLong(),
senderFirstName,
senderLastName,
senderLoginId
)
data.teacherList.put(teacherObject.id, teacherObject)
teacherObject.id
}
}.invoke()
val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 }
?: -1
val readDate = message.getLong("DataPrzeczytaniaUnixEpoch")?.let { it * 1000 }
?: -1
val messageObject = Message(
profileId = profileId,
id = id,
type = TYPE_RECEIVED,
subject = subject,
body = body.replace("\n", "<br>"),
senderId = senderId,
addedDate = sentDate
)
val messageRecipientObject = MessageRecipient(
profileId,
-1,
-1,
readDate,
id
)
data.messageList.add(messageObject)
data.messageRecipientList.add(messageRecipientObject)
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
id,
readDate > 0,
readDate > 0
))
}
data.setSyncNext(ENDPOINT_VULCAN_API_MESSAGES_INBOX, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_API_MESSAGES_INBOX)
}
} ?: onSuccess(ENDPOINT_VULCAN_API_MESSAGES_INBOX)
}
}

View File

@ -1,119 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-5
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_MESSAGES_SENT
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_MESSAGES_SENT
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Message.Companion.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.text.replace
class VulcanApiMessagesSent(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiMessagesSent"
}
init {
data.profile?.also { profile ->
val startDate = when (profile.empty) {
true -> profile.getSemesterStart(profile.currentSemester).inUnix
else -> Date.getToday().stepForward(0, -2, 0).inUnix
}
val endDate = Date.getToday().stepForward(0, 1, 0).inUnix
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_SENT, parameters = mapOf(
"DataPoczatkowa" to startDate,
"DataKoncowa" to endDate,
"LoginId" to data.studentLoginId,
"IdUczen" to data.studentId
)) { json, _ ->
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { message ->
val id = message.getLong("WiadomoscId") ?: return@forEach
val subject = message.getString("Tytul") ?: ""
val body = message.getString("Tresc") ?: ""
val readBy = message.getInt("Przeczytane") ?: 0
val unreadBy = message.getInt("Nieprzeczytane") ?: 0
val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 } ?: -1
message.getJsonArray("Adresaci")?.asJsonObjectList()
?.onEach { receiver ->
val receiverLoginId = receiver.getString("LoginId")
?: return@onEach
val receiverId = data.teacherList.singleOrNull { it.loginId == receiverLoginId }?.id
?: {
val receiverName = receiver.getString("Nazwa") ?: ""
receiverName.splitName()?.let { (receiverLastName, receiverFirstName) ->
val teacherObject = Teacher(
profileId,
-1 * Utils.crc16(receiverName.toByteArray()).toLong(),
receiverFirstName,
receiverLastName,
receiverLoginId
)
data.teacherList.put(teacherObject.id, teacherObject)
teacherObject.id
}
}.invoke() ?: -1
val readDate: Long = when (readBy) {
0 -> 0
else -> when (unreadBy) {
0 -> 1
else -> -1
}
}
val messageRecipientObject = MessageRecipient(
profileId,
receiverId,
-1,
readDate,
id
)
data.messageRecipientList.add(messageRecipientObject)
}
val messageObject = Message(
profileId = profileId,
id = id,
type = TYPE_SENT,
subject = subject,
body = body.replace("\n", "<br>"),
senderId = null,
addedDate = sentDate
)
data.messageList.add(messageObject)
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
id,
true,
true
))
}
data.setSyncNext(ENDPOINT_VULCAN_API_MESSAGES_SENT, 1 * DAY, DRAWER_ITEM_MESSAGES)
onSuccess(ENDPOINT_VULCAN_API_MESSAGES_SENT)
}
}
}
}

View File

@ -1,97 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-23
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import androidx.core.util.isEmpty
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_NOTICES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_NOTICES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.toSparseArray
class VulcanApiNotices(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiNotices"
}
init { data.profile?.also { profile ->
if (data.noticeTypes.isEmpty()) {
data.db.noticeTypeDao().getAllNow(profileId).toSparseArray(data.noticeTypes) { it.id }
}
val semesterId = data.studentSemesterId
val semesterNumber = data.studentSemesterNumber
if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) {
getNotices(profile, semesterId - 1, semesterNumber - 1) {
getNotices(profile, semesterId, semesterNumber) {
finish()
}
}
}
else {
getNotices(profile, semesterId, semesterNumber) {
finish()
}
}
} ?: onSuccess(ENDPOINT_VULCAN_API_NOTICES) }
private fun finish() {
data.setSyncNext(ENDPOINT_VULCAN_API_NOTICES, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_API_NOTICES)
}
private fun getNotices(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) {
apiGet(TAG, VULCAN_API_ENDPOINT_NOTICES, parameters = mapOf(
"IdUczen" to data.studentId,
"IdOkresKlasyfikacyjny" to data.studentSemesterId
)) { json, _ ->
json.getJsonArray("Data")?.forEach { noticeEl ->
val notice = noticeEl.asJsonObject
val id = notice.getLong("Id") ?: return@forEach
val text = notice.getString("TrescUwagi") ?: return@forEach
val teacherId = notice.getLong("IdPracownik") ?: -1
val addedDate = notice.getLong("DataModyfikacji")?.times(1000) ?: System.currentTimeMillis()
val categoryId = notice.getLong("IdKategoriaUwag") ?: -1
val categoryText = data.noticeTypes[categoryId]?.name ?: ""
val noticeObject = Notice(
profileId = profileId,
id = id,
type = Notice.TYPE_NEUTRAL,
semester = profile.currentSemester,
text = text,
category = categoryText,
points = null,
teacherId = teacherId,
addedDate = addedDate
)
data.noticeList.add(noticeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_NOTICE,
id,
profile.empty,
profile.empty
))
}
onSuccess()
}
}
}

View File

@ -1,112 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import com.google.gson.JsonArray
import pl.szczodrzynski.edziennik.HOUR
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_GRADES_SUMMARY
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL
import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.Utils
class VulcanApiProposedGrades(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiProposedGrades"
}
init { data.profile?.also { profile ->
val semesterId = data.studentSemesterId
val semesterNumber = data.studentSemesterNumber
if (semesterNumber == 2 && lastSync ?: 0 < profile.dateSemester1Start.inMillis) {
getProposedGrades(profile, semesterId - 1, semesterNumber - 1) {
getProposedGrades(profile, semesterId, semesterNumber) {
finish()
}
}
}
else {
getProposedGrades(profile, semesterId, semesterNumber) {
finish()
}
}
} ?: onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY) }
private fun finish() {
data.setSyncNext(ENDPOINT_VULCAN_API_GRADES_SUMMARY, 6*HOUR)
onSuccess(ENDPOINT_VULCAN_API_GRADES_SUMMARY)
}
private fun getProposedGrades(profile: Profile, semesterId: Int, semesterNumber: Int, onSuccess: () -> Unit) {
apiGet(TAG, VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS, parameters = mapOf(
"IdUczen" to data.studentId,
"IdOkresKlasyfikacyjny" to semesterId
)) { json, _ ->
val grades = json.getJsonObject("Data")
grades.getJsonArray("OcenyPrzewidywane")?.let {
processGradeList(it, semesterNumber, isFinal = false)
}
grades.getJsonArray("OcenyKlasyfikacyjne")?.let {
processGradeList(it, semesterNumber, isFinal = true)
}
onSuccess()
}
}
private fun processGradeList(grades: JsonArray, semesterNumber: Int, isFinal: Boolean) {
grades.asJsonObjectList().forEach { grade ->
val name = grade.get("Wpis").asString
val value = Utils.getGradeValue(name)
val subjectId = grade.get("IdPrzedmiot").asLong
val id = subjectId * -100 - semesterNumber
val color = Utils.getVulcanGradeColor(name)
val gradeObject = Grade(
profileId = profileId,
id = id,
name = name,
type = if (semesterNumber == 1) {
if (isFinal) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER1_PROPOSED
} else {
if (isFinal) TYPE_SEMESTER2_FINAL else TYPE_SEMESTER2_PROPOSED
},
value = value,
weight = 0f,
color = color,
category = "",
description = null,
comment = null,
semester = semesterNumber,
teacherId = -1,
subjectId = subjectId
)
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
data.profile?.empty ?: false,
data.profile?.empty ?: false
))
}
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-20.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_PUSH
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_PUSH_CONFIG
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
class VulcanApiPushConfig(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiPushConfig"
}
init { data.app.config.sync.tokenVulcan?.also { tokenVulcan ->
apiGet(TAG, VULCAN_API_ENDPOINT_PUSH, parameters = mapOf(
"Token" to tokenVulcan,
"IdUczen" to data.studentId,
"PushOcena" to true,
"PushFrekwencja" to true,
"PushUwaga" to true,
"PushWiadomosc" to true
)) { _, _ ->
// sync always: this endpoint has .shouldSync set
data.setSyncNext(ENDPOINT_VULCAN_API_PUSH_CONFIG, SYNC_ALWAYS)
data.app.config.sync.tokenVulcanList =
data.app.config.sync.tokenVulcanList + profileId
onSuccess(ENDPOINT_VULCAN_API_PUSH_CONFIG)
}
} ?: onSuccess(ENDPOINT_VULCAN_API_PUSH_CONFIG) }
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-29.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_MESSAGES_ADD
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.events.MessageSentEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
class VulcanApiSendMessage(override val data: DataVulcan,
val recipients: List<Teacher>,
val subject: String,
val text: String,
val onSuccess: () -> Unit
) : VulcanApi(data, null) {
companion object {
private const val TAG = "VulcanApiSendMessage"
}
init {
val recipientsArray = JsonArray()
for (teacher in recipients) {
teacher.loginId?.let {
recipientsArray += JsonObject(
"LoginId" to it,
"Nazwa" to "${teacher.fullNameLastFirst} - pracownik"
)
}
}
val params = mapOf(
"NadawcaWiadomosci" to (profile?.accountName ?: profile?.studentNameLong ?: ""),
"Tytul" to subject,
"Tresc" to text,
"Adresaci" to recipientsArray,
"LoginId" to data.studentLoginId,
"IdUczen" to data.studentId
)
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_ADD, parameters = params) { json, _ ->
val messageId = json.getJsonObject("Data").getLong("WiadomoscId")
if (messageId == null) {
// TODO error
return@apiGet
}
VulcanApiMessagesSent(data, null) {
val message = data.messageList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject }
val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == messageId }
val event = MessageSentEvent(data.profileId, message, message?.addedDate)
EventBus.getDefault().postSticky(event)
onSuccess()
}
}
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-10-20
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
class VulcanApiTemplate(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApi"
}
init {
/* data.profile?.also { profile ->
apiGet(TAG, VULCAN_API_ENDPOINT_) { json, _ ->
data.setSyncNext(ENDPOINT_VULCAN_API_, SYNC_ALWAYS)
onSuccess()
}
} */
}
}

View File

@ -1,216 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-13
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.Regexes.VULCAN_SHIFT_ANNOTATION
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_TIMETABLE
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.utils.Utils.crc16
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week
class VulcanApiTimetable(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiTimetable"
}
init { data.profile?.also { profile ->
val currentWeekStart = Week.getWeekStart()
if (Date.getToday().weekDay > 4) {
currentWeekStart.stepForward(0, 0, 7)
}
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf(
"DataPoczatkowa" to weekStart.stringY_m_d,
"DataKoncowa" to weekEnd.stringY_m_d,
"IdUczen" to data.studentId,
"IdOddzial" to data.studentClassId,
"IdOkresKlasyfikacyjny" to data.studentSemesterId
)) { json, _ ->
val dates = mutableSetOf<Int>()
val lessons = mutableListOf<Lesson>()
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson ->
if (lesson.getBoolean("PlanUcznia") != true)
return@forEach
val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst"))
val lessonNumber = lesson.getInt("NumerLekcji")
val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }
val startTime = lessonRange?.startTime
val endTime = lessonRange?.endTime
val teacherId = lesson.getLong("IdPracownik")
val classroom = lesson.getString("Sala")
val oldTeacherId = lesson.getLong("IdPracownikOld")
val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: ""
val type = when {
changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET
changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE
changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE
lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED
else -> Lesson.TYPE_NORMAL
}
val teamId = lesson.getString("PodzialSkrot")?.let { teamName ->
val name = "${data.teamClass?.name} $teamName"
val id = name.crc16().toLong()
var team = data.teamList.singleOrNull { it.name == name }
if (team == null) {
team = Team(
profileId,
id,
name,
Team.TYPE_VIRTUAL,
"${data.schoolCode}:$name",
teacherId ?: oldTeacherId ?: -1
)
data.teamList[id] = team
}
team.id
} ?: data.teamClass?.id ?: -1
val subjectId = lesson.getLong("IdPrzedmiot").let { id ->
// get the specified subject name
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
val condition = when (id) {
// "special" subject - e.g. one time classes, a trip, etc.
0L -> { subject: Subject -> subject.longName == subjectName }
// normal subject, check if it exists
else -> { subject: Subject -> subject.id == id }
}
data.subjectList.singleOrNull(condition)?.id ?: {
/**
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
*/
val subjectObject = Subject(
profileId,
if (id == null || id == 0L)
-1 * crc16(subjectName.toByteArray()).toLong()
else
id,
subjectName,
subjectName
)
data.subjectList.put(subjectObject.id, subjectObject)
subjectObject.id
}()
}
val lessonObject = Lesson(profileId, -1).apply {
this.type = type
when (type) {
Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> {
this.date = lessonDate
this.lessonNumber = lessonNumber
this.startTime = startTime
this.endTime = endTime
this.subjectId = subjectId
this.teacherId = teacherId
this.teamId = teamId
this.classroom = classroom
this.oldTeacherId = oldTeacherId
}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
this.oldDate = lessonDate
this.oldLessonNumber = lessonNumber
this.oldStartTime = startTime
this.oldEndTime = endTime
this.oldSubjectId = subjectId
this.oldTeacherId = teacherId
this.oldTeamId = teamId
this.oldClassroom = classroom
}
}
if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) {
val shift = VULCAN_SHIFT_ANNOTATION.find(changeAnnotation)
val oldLessonNumber = shift?.get(2)?.toInt()
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }
val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber }
val oldStartTime = oldLessonRange?.startTime
val oldEndTime = oldLessonRange?.endTime
when (type) {
Lesson.TYPE_SHIFTED_SOURCE -> {
this.lessonNumber = oldLessonNumber
this.date = oldLessonDate
this.startTime = oldStartTime
this.endTime = oldEndTime
}
Lesson.TYPE_SHIFTED_TARGET -> {
this.oldLessonNumber = oldLessonNumber
this.oldDate = oldLessonDate
this.oldStartTime = oldStartTime
this.oldEndTime = oldEndTime
}
}
}
this.id = buildId()
}
val seen = profile.empty || lessonDate < Date.getToday()
if (type != Lesson.TYPE_NORMAL) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
lessonObject.id,
seen,
seen
))
}
dates.add(lessonDate.value)
lessons.add(lessonObject)
}
val date: Date = weekStart.clone()
while (date <= weekEnd) {
if (!dates.contains(date.value)) {
lessons.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1)
}
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.lessonList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS)
onSuccess(ENDPOINT_VULCAN_API_TIMETABLE)
}
}}
}

View File

@ -1,78 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-2-1.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_NO_STUDENTS_IN_ACCOUNT
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_STUDENT_LIST
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_UPDATE_SEMESTER
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiUpdateSemester(override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanApi(data, lastSync) {
companion object {
const val TAG = "VulcanApiUpdateSemester"
}
init { data.profile?.also { profile ->
apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, response ->
val students = json.getJsonArray("Data")
if (students == null || students.isEmpty()) {
data.error(ApiError(TAG, ERROR_NO_STUDENTS_IN_ACCOUNT)
.withResponse(response)
.withApiResponse(json))
return@apiGet
}
students.asJsonObjectList().firstOrNull {
it.getInt("Id") == data.studentId
}?.let { student ->
val studentClassId = student.getInt("IdOddzial") ?: return@let
val studentClassName = student.getString("OkresPoziom").toString() + (student.getString("OddzialSymbol") ?: return@let)
val studentSemesterId = student.getInt("IdOkresKlasyfikacyjny") ?: return@let
val currentSemesterStartDate = student.getLong("OkresDataOd") ?: return@let
val currentSemesterEndDate = (student.getLong("OkresDataDo") ?: return@let) + 86400
val studentSemesterNumber = student.getInt("OkresNumer") ?: return@let
var dateSemester1Start: Date? = null
var dateSemester2Start: Date? = null
var dateYearEnd: Date? = null
when (studentSemesterNumber) {
1 -> {
dateSemester1Start = Date.fromMillis(currentSemesterStartDate * 1000)
dateSemester2Start = Date.fromMillis(currentSemesterEndDate * 1000)
}
2 -> {
dateSemester2Start = Date.fromMillis(currentSemesterStartDate * 1000)
dateYearEnd = Date.fromMillis(currentSemesterEndDate * 1000)
}
}
data.studentClassId = studentClassId
data.studentSemesterId = studentSemesterId
data.studentSemesterNumber = studentSemesterNumber
data.profile.studentData["semester${studentSemesterNumber}Id"] = studentSemesterId
data.currentSemesterEndDate = currentSemesterEndDate
profile.studentClassName = studentClassName
dateSemester1Start?.let {
profile.dateSemester1Start = it
profile.studentSchoolYearStart = it.year
}
dateSemester2Start?.let { profile.dateSemester2Start = it }
dateYearEnd?.let { profile.dateYearEnd = it }
}
data.setSyncNext(ENDPOINT_VULCAN_API_UPDATE_SEMESTER, if (data.studentSemesterNumber == 2) 7*DAY else 2*DAY)
onSuccess(ENDPOINT_VULCAN_API_UPDATE_SEMESTER)
}
} ?: onSuccess(ENDPOINT_VULCAN_API_UPDATE_SEMESTER) }
}

View File

@ -8,25 +8,21 @@ import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
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.VulcanHebe
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanWebMain
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe.VulcanHebeMain
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.VulcanLoginHebe
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginWebMain
import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
companion object {
const val TAG = "VulcanFirstLogin"
}
private val api = VulcanApi(data, null)
private val web = VulcanWebMain(data, null)
private val hebe = VulcanHebe(data, null)
private val profileList = mutableListOf<Profile>()
@ -54,12 +50,6 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
checkSymbol(certificate)
}
}
else if (data.loginStore.mode == LOGIN_MODE_VULCAN_API) {
registerDevice {
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
else {
registerDeviceHebe {
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(profileList, data.loginStore))
@ -118,96 +108,6 @@ class VulcanFirstLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
}
}
private fun registerDevice(onSuccess: () -> Unit) {
VulcanLoginApi(data) {
api.apiGet(TAG, VULCAN_API_ENDPOINT_STUDENT_LIST, baseUrl = true) { json, _ ->
val students = json.getJsonArray("Data")
if (students == null || students.isEmpty()) {
EventBus.getDefault().postSticky(FirstLoginFinishedEvent(listOf(), data.loginStore))
onSuccess()
return@apiGet
}
students.forEach { studentEl ->
val student = studentEl.asJsonObject
val schoolSymbol = student.getString("JednostkaSprawozdawczaSymbol") ?: return@forEach
val schoolShort = student.getString("JednostkaSprawozdawczaSkrot") ?: return@forEach
val schoolCode = "${data.symbol}_$schoolSymbol"
val studentId = student.getInt("Id") ?: return@forEach
val studentLoginId = student.getInt("UzytkownikLoginId") ?: return@forEach
val studentClassId = student.getInt("IdOddzial") ?: return@forEach
val studentClassName = student.getString("OkresPoziom").toString() + (student.getString("OddzialSymbol") ?: return@forEach)
val studentSemesterId = student.getInt("IdOkresKlasyfikacyjny") ?: return@forEach
val studentFirstName = student.getString("Imie") ?: ""
val studentLastName = student.getString("Nazwisko") ?: ""
val studentNameLong = "$studentFirstName $studentLastName".fixName()
val studentNameShort = "$studentFirstName ${studentLastName[0]}.".fixName()
val userLogin = student.getString("UzytkownikLogin") ?: ""
val currentSemesterStartDate = student.getLong("OkresDataOd") ?: return@forEach
val currentSemesterEndDate = (student.getLong("OkresDataDo") ?: return@forEach) + 86400
val studentSemesterNumber = student.getInt("OkresNumer") ?: return@forEach
val isParent = student.getString("UzytkownikRola") == "opiekun"
val accountName = if (isParent)
student.getString("UzytkownikNazwa")?.swapFirstLastName()?.fixName()
else null
var dateSemester1Start: Date? = null
var dateSemester2Start: Date? = null
var dateYearEnd: Date? = null
when (studentSemesterNumber) {
1 -> {
dateSemester1Start = Date.fromMillis(currentSemesterStartDate * 1000)
dateSemester2Start = Date.fromMillis(currentSemesterEndDate * 1000)
}
2 -> {
dateSemester2Start = Date.fromMillis(currentSemesterStartDate * 1000)
dateYearEnd = Date.fromMillis(currentSemesterEndDate * 1000)
}
}
val profile = Profile(
firstProfileId++,
loginStoreId,
LOGIN_TYPE_VULCAN,
studentNameLong,
userLogin,
studentNameLong,
studentNameShort,
accountName
).apply {
this.studentClassName = studentClassName
studentData["symbol"] = data.symbol
studentData["studentId"] = studentId
studentData["studentLoginId"] = studentLoginId
studentData["studentClassId"] = studentClassId
studentData["studentSemesterId"] = studentSemesterId
studentData["studentSemesterNumber"] = studentSemesterNumber
studentData["semester${studentSemesterNumber}Id"] = studentSemesterId
studentData["schoolSymbol"] = schoolSymbol
studentData["schoolShort"] = schoolShort
studentData["schoolName"] = schoolCode
studentData["currentSemesterEndDate"] = currentSemesterEndDate
}
dateSemester1Start?.let {
profile.dateSemester1Start = it
profile.studentSchoolYearStart = it.year
}
dateSemester2Start?.let { profile.dateSemester2Start = it }
dateYearEnd?.let { profile.dateYearEnd = it }
profileList.add(profile)
}
onSuccess()
}
}
}
private fun registerDeviceHebe(onSuccess: () -> Unit) {
VulcanLoginHebe(data) {
VulcanHebeMain(data).getStudents(

View File

@ -5,7 +5,6 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_API
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_HEBE
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_VULCAN_WEB_MAIN
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
@ -51,10 +50,6 @@ class VulcanLogin(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_login_vulcan_web_main)
VulcanLoginWebMain(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_VULCAN_API -> {
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
VulcanLoginApi(data) { onSuccess(loginMethodId) }
}
LOGIN_METHOD_VULCAN_HEBE -> {
data.startProgress(R.string.edziennik_progress_login_vulcan_api)
VulcanLoginHebe(data) { onSuccess(loginMethodId) }

View File

@ -1,226 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-10-6.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
import android.os.Build
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import io.github.wulkanowy.signer.android.getPrivateKeyFromCert
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiUpdateSemester
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.HTTP_BAD_REQUEST
import java.util.*
import java.util.regex.Pattern
class VulcanLoginApi(val data: DataVulcan, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "VulcanLoginApi"
}
init { run {
if (data.studentSemesterNumber == 1 && data.semester1Id == 0)
data.semester1Id = data.studentSemesterNumber
if (data.studentSemesterNumber == 2 && data.semester2Id == 0)
data.semester2Id = data.studentSemesterNumber
copyFromLoginStore()
if (data.profile != null && data.isApiLoginValid()) {
onSuccess()
}
else {
if (data.apiFingerprint[data.symbol].isNotNullNorEmpty()
&& data.apiPrivateKey[data.symbol].isNotNullNorEmpty()
&& data.symbol.isNotNullNorEmpty()) {
// (see data.isApiLoginValid())
// the semester end date is over
VulcanApiUpdateSemester(data, null) { onSuccess() }
return@run
}
if (data.symbol.isNotNullNorEmpty() && data.apiToken[data.symbol].isNotNullNorEmpty() && data.apiPin[data.symbol].isNotNullNorEmpty()) {
loginWithToken()
}
else {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
}
}
}}
private fun copyFromLoginStore() {
data.loginStore.data.apply {
// < v4.0 - PFX to Private Key migration
if (has("certificatePfx")) {
try {
val privateKey = getPrivateKeyFromCert(
if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
getString("certificatePfx") ?: ""
)
data.apiPrivateKey = mapOf(
data.symbol to privateKey
)
remove("certificatePfx")
} catch (e: Throwable) {
e.printStackTrace()
}
}
// 4.0 - new login form - copy user input to profile
if (has("symbol")) {
data.symbol = getString("symbol")
remove("symbol")
}
// 4.0 - before Vulcan Web impl - migrate from strings to Map of Symbol to String
if (has("deviceSymbol")) {
data.symbol = getString("deviceSymbol")
remove("deviceSymbol")
}
if (has("certificateKey")) {
data.apiFingerprint = data.apiFingerprint.toMutableMap().also {
it[data.symbol] = getString("certificateKey")
}
remove("certificateKey")
}
if (has("certificatePrivate")) {
data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also {
it[data.symbol] = getString("certificatePrivate")
}
remove("certificatePrivate")
}
// map form inputs to the symbol
if (has("deviceToken")) {
data.apiToken = data.apiToken.toMutableMap().also {
it[data.symbol] = getString("deviceToken")
}
remove("deviceToken")
}
if (has("devicePin")) {
data.apiPin = data.apiPin.toMutableMap().also {
it[data.symbol] = getString("devicePin")
}
remove("devicePin")
}
}
}
private fun loginWithToken() {
d(TAG, "Request: Vulcan/Login/Api - ${data.apiUrl}/$VULCAN_API_ENDPOINT_CERTIFICATE")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
if (response?.code() == HTTP_BAD_REQUEST) {
data.error(TAG, ERROR_LOGIN_VULCAN_INVALID_SYMBOL, response)
return
}
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
var tokenStatus = json.getString("TokenStatus")
if (tokenStatus == "Null" || tokenStatus == "CertGenerated")
tokenStatus = null
val error = tokenStatus ?: json.getString("Message")
error?.let { code ->
when (code) {
"TokenNotFound" -> ERROR_LOGIN_VULCAN_INVALID_TOKEN
"TokenDead" -> ERROR_LOGIN_VULCAN_EXPIRED_TOKEN
"WrongPIN" -> {
Pattern.compile("Liczba pozostałych prób: ([0-9])", Pattern.DOTALL).matcher(tokenStatus).let { matcher ->
if (matcher.matches())
ERROR_LOGIN_VULCAN_INVALID_PIN + 1 + matcher.group(1).toInt()
else
ERROR_LOGIN_VULCAN_INVALID_PIN
}
}
"Broken" -> ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING
"OnlyKindergarten" -> ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN
"NoPupils" -> ERROR_LOGIN_VULCAN_NO_PUPILS
else -> ERROR_LOGIN_VULCAN_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(json)
.withResponse(response))
return
}
}
val cert = json.getJsonObject("TokenCert")
if (cert == null) {
data.error(ApiError(TAG, ERROR_LOGIN_VULCAN_OTHER)
.withApiResponse(json)
.withResponse(response))
return
}
val privateKey = getPrivateKeyFromCert(
if (data.apiToken[data.symbol]?.get(0) == 'F') VULCAN_API_PASSWORD_FAKELOG else VULCAN_API_PASSWORD,
cert.getString("CertyfikatPfx") ?: ""
)
data.apiFingerprint = data.apiFingerprint.toMutableMap().also {
it[data.symbol] = cert.getString("CertyfikatKlucz")
}
data.apiToken = data.apiToken.toMutableMap().also {
it[data.symbol] = it[data.symbol]?.substring(0, 3)
}
data.apiPrivateKey = data.apiPrivateKey.toMutableMap().also {
it[data.symbol] = privateKey
}
data.loginStore.removeLoginData("certificatePfx")
data.loginStore.removeLoginData("apiPin")
onSuccess()
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
val szkolnyApi = SzkolnyApi(data.app)
val firebaseToken = szkolnyApi.runCatching({
getFirebaseToken("vulcan")
}, onError = {
// screw errors
}) ?: data.app.config.sync.tokenVulcan
Request.builder()
.url("${data.apiUrl}$VULCAN_API_ENDPOINT_CERTIFICATE")
.userAgent(VULCAN_API_USER_AGENT)
.addHeader("RequestMobileType", "RegisterDevice")
.addParameter("PIN", data.apiPin[data.symbol])
.addParameter("TokenKey", data.apiToken[data.symbol])
.addParameter("DeviceId", data.buildDeviceId())
.addParameter("DeviceName", VULCAN_API_DEVICE_NAME)
.addParameter("DeviceNameUser", "")
.addParameter("DeviceDescription", "")
.addParameter("DeviceSystemType", "Android")
.addParameter("DeviceSystemVersion", Build.VERSION.RELEASE)
.addParameter("RemoteMobileTimeKey", currentTimeUnix())
.addParameter("TimeKey", currentTimeUnix() - 1)
.addParameter("RequestId", UUID.randomUUID().toString())
.addParameter("AppVersion", VULCAN_API_APP_VERSION)
.addParameter("RemoteMobileAppVersion", VULCAN_API_APP_VERSION)
.addParameter("RemoteMobileAppName", VULCAN_API_APP_NAME)
.addParameter("FirebaseTokenKey", firebaseToken ?: "")
.postJson()
.allowErrorCode(HTTP_BAD_REQUEST)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -32,7 +32,7 @@ class VulcanLoginHebe(val data: DataVulcan, val onSuccess: () -> Unit) {
copyFromLoginStore()
if (data.profile != null && data.isApiLoginValid()) {
if (data.profile != null && data.isHebeLoginValid()) {
onSuccess()
}
else {

View File

@ -88,11 +88,13 @@ class SzkolnyApi(val app: App) : CoroutineScope {
withContext(Dispatchers.Default) { block.invoke(this@SzkolnyApi) }
}
catch (e: Exception) {
ErrorDetailsDialog(
withContext(coroutineContext) {
ErrorDetailsDialog(
activity,
listOf(e.toApiError(TAG)),
R.string.error_occured
)
)
}
null
}
}

View File

@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MDAwogntL3===.$param2".sha256()
return "$param1.MTIzNDU2Nzg5MDgzuDTD2K===.$param2".sha256()
}
}

View File

@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
LibrusLesson::class,
TimetableManual::class,
Metadata::class
], version = 89)
], version = 91)
@TypeConverters(
ConverterTime::class,
ConverterDate::class,
@ -174,7 +174,9 @@ abstract class AppDb : RoomDatabase() {
Migration86(),
Migration87(),
Migration88(),
Migration89()
Migration89(),
Migration90(),
Migration91()
).allowMainThreadQueries().build()
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-26.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration90 : Migration(89, 90) {
override fun migrate(database: SupportSQLiteDatabase) {
// get all profiles using Vulcan/Hebe
database.execSQL("CREATE TABLE _90_ids (id INTEGER NOT NULL);")
database.execSQL("INSERT INTO _90_ids SELECT profileId FROM profiles JOIN loginStores USING(loginStoreId) WHERE loginStores.loginStoreType = 4 AND loginStores.loginStoreMode != 0;")
// force full sync when updating from <4.6
database.execSQL("DELETE FROM endpointTimers WHERE profileId IN (SELECT id FROM _90_ids);")
database.execSQL("UPDATE profiles SET empty = 1 WHERE profileId IN (SELECT id FROM _90_ids);")
// remove messages and events to re-download attachments and remove older than current school year
database.execSQL("DELETE FROM messages WHERE profileId IN (SELECT id FROM _90_ids);")
database.execSQL("DELETE FROM events WHERE profileId IN (SELECT id FROM _90_ids) AND eventAddedManually = 0;")
// remove older data
database.execSQL("DELETE FROM notices WHERE profileId IN (SELECT id FROM _90_ids);")
// fix for v4.5 users who logged in using Vulcan/Web
database.execSQL("UPDATE loginStores SET loginStoreMode = 2 WHERE loginStoreType = 4 AND loginStoreMode = 1;")
database.execSQL("DROP TABLE _90_ids;")
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-26.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration91 : Migration(90, 91) {
override fun migrate(database: SupportSQLiteDatabase) {
// get all profiles using Vulcan/Hebe
database.execSQL("CREATE TABLE _91_ids (id INTEGER NOT NULL);")
database.execSQL("INSERT INTO _91_ids SELECT profileId FROM profiles JOIN loginStores USING(loginStoreId) WHERE loginStores.loginStoreType = 1;")
// force attendance sync - mobidziennik
// after enabling counting the e-attendance to statistics
database.execSQL("DELETE FROM endpointTimers WHERE profileId IN (SELECT id FROM _91_ids) AND endpointId = 2050;")
database.execSQL("UPDATE attendances SET attendanceIsCounted = 1 WHERE profileId IN (SELECT id FROM _91_ids);")
database.execSQL("UPDATE attendances SET attendanceBaseType = 2 WHERE profileId IN (SELECT id FROM _91_ids) AND attendanceTypeSymbol = ?;",
arrayOf("+ₑ"))
database.execSQL("DROP TABLE _91_ids;")
}
}

View File

@ -26,6 +26,7 @@ import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject
import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonArrayViewHolder
import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonElementViewHolder
import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonObjectViewHolder
import pl.szczodrzynski.edziennik.ui.modules.debug.viewholder.JsonSubObjectViewHolder
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
import kotlin.coroutines.CoroutineContext
@ -35,14 +36,20 @@ class LabJsonAdapter(
var onJsonElementClick: ((item: LabJsonElement) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope {
companion object {
private const val TAG = "AttendanceAdapter"
private const val TAG = "LabJsonAdapter"
private const val ITEM_TYPE_OBJECT = 0
private const val ITEM_TYPE_ARRAY = 1
private const val ITEM_TYPE_ELEMENT = 2
private const val ITEM_TYPE_SUB_OBJECT = 1
private const val ITEM_TYPE_ARRAY = 2
private const val ITEM_TYPE_ELEMENT = 3
const val STATE_CLOSED = 0
const val STATE_OPENED = 1
fun expand(item: Any, level: Int): MutableList<Any> {
val path = when (item) {
is LabJsonObject -> item.key + ":"
is LabJsonArray -> item.key + ":"
else -> ""
}
val json = when (item) {
is LabJsonObject -> item.jsonObject
is LabJsonArray -> item.jsonArray
@ -53,17 +60,16 @@ class LabJsonAdapter(
}
return when (json) {
is JsonObject -> json.entrySet().mapNotNull { wrap(it.key, it.value, level) }
is JsonArray -> json.mapIndexedNotNull { index, jsonElement -> wrap(index.toString(), jsonElement, level) }
else -> listOf(LabJsonElement("?", json, level))
is JsonObject -> json.entrySet().mapNotNull { wrap(path + it.key, it.value, level) }
is JsonArray -> json.mapIndexedNotNull { index, jsonElement -> wrap(path + index.toString(), jsonElement, level) }
else -> listOf(LabJsonElement("$path?", json, level))
}.toMutableList()
}
fun wrap(key: String, item: JsonElement, level: Int = 0): Any? {
fun wrap(key: String, item: JsonElement, level: Int = 0): Any {
return when (item) {
is JsonObject -> LabJsonObject(key, item, level + 1)
is JsonArray -> LabJsonArray(key, item, level + 1)
is JsonElement -> LabJsonElement(key, item, level + 1)
else -> null
else -> LabJsonElement(key, item, level + 1)
}
}
}
@ -80,6 +86,7 @@ class LabJsonAdapter(
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ITEM_TYPE_OBJECT -> JsonObjectViewHolder(inflater, parent)
ITEM_TYPE_SUB_OBJECT -> JsonSubObjectViewHolder(inflater, parent)
ITEM_TYPE_ARRAY -> JsonArrayViewHolder(inflater, parent)
ITEM_TYPE_ELEMENT -> JsonElementViewHolder(inflater, parent)
else -> throw IllegalArgumentException("Incorrect viewType")
@ -87,8 +94,10 @@ class LabJsonAdapter(
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is LabJsonObject -> ITEM_TYPE_OBJECT
return when (val item = items[position]) {
is LabJsonObject ->
if (item.level == 1) ITEM_TYPE_OBJECT
else ITEM_TYPE_SUB_OBJECT
is LabJsonArray -> ITEM_TYPE_ARRAY
is LabJsonElement -> ITEM_TYPE_ELEMENT
else -> throw IllegalArgumentException("Incorrect viewType")
@ -118,7 +127,7 @@ class LabJsonAdapter(
View.ROTATION,
if (model.state == STATE_CLOSED) 0f else 180f,
if (model.state == STATE_CLOSED) 180f else 0f
).setDuration(200).start();
).setDuration(200).start()
}
// hide the preview, show summary
@ -143,7 +152,9 @@ class LabJsonAdapter(
var end: Int = items.size
for (i in start until items.size) {
val model1 = items[i]
val level = (model1 as? ExpandableItemModel<*>)?.level ?: 3
val level = (model1 as? ExpandableItemModel<*>)?.level
?: (model1 as? LabJsonElement)?.level
?: model.level
if (level <= model.level) {
end = i
break
@ -170,6 +181,7 @@ class LabJsonAdapter(
val viewType = when (holder) {
is JsonObjectViewHolder -> ITEM_TYPE_OBJECT
is JsonSubObjectViewHolder -> ITEM_TYPE_SUB_OBJECT
is JsonArrayViewHolder -> ITEM_TYPE_ARRAY
is JsonElementViewHolder -> ITEM_TYPE_ELEMENT
else -> throw IllegalArgumentException("Incorrect viewType")
@ -180,6 +192,7 @@ class LabJsonAdapter(
when {
holder is JsonObjectViewHolder && item is LabJsonObject -> holder.onBind(activity, app, item, position, this)
holder is JsonSubObjectViewHolder && item is LabJsonObject -> holder.onBind(activity, app, item, position, this)
holder is JsonArrayViewHolder && item is LabJsonArray -> holder.onBind(activity, app, item, position, this)
holder is JsonElementViewHolder && item is LabJsonElement -> holder.onBind(activity, app, item, position, this)
}

View File

@ -10,16 +10,14 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.afollestad.materialdialogs.MaterialDialog
import com.google.gson.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.databinding.TemplateListPageFragmentBinding
import pl.szczodrzynski.edziennik.startCoroutineTimer
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
@ -38,6 +36,10 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
get() = job + Dispatchers.Main
// local/private variables go here
private lateinit var adapter: LabJsonAdapter
private val loginStore by lazy {
app.db.loginStoreDao().getByIdNow(app.profile.loginStoreId)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null
@ -48,22 +50,96 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
}
override fun onPageCreated(): Boolean { startCoroutineTimer(100L) {
val adapter = LabJsonAdapter(activity)
val json = JsonObject().also { json ->
json.add("app.profile", app.profile.studentData)
json.add("app.config", JsonParser().parse(app.gson.toJson(app.config.values)))
EdziennikTask.profile?.let {
json.add("API.profile", it.studentData)
} ?: {
json.addProperty("API.profile", "null")
}()
EdziennikTask.loginStore?.let {
json.add("API.loginStore", it.data)
} ?: {
json.addProperty("API.loginStore", "null")
}()
}
adapter.items = LabJsonAdapter.expand(json, 0)
adapter = LabJsonAdapter(activity, onJsonElementClick = { item ->
try {
var parent: Any = Unit
var obj: Any = Unit
var objName: String = ""
item.key.split(":").forEach { el ->
parent = obj
obj = when (el) {
"App.profile" -> app.profile
"App.profile.studentData" -> app.profile.studentData
"App.profile.loginStore" -> loginStore?.data ?: JsonObject()
"App.config" -> app.config.values
else -> when (obj) {
is JsonObject -> (obj as JsonObject).get(el)
is JsonArray -> (obj as JsonArray).get(el.toInt())
is HashMap<*, *> -> (obj as HashMap<String, String?>)[el].toString()
else -> {
val field = obj::class.java.getDeclaredField(el)
field.isAccessible = true
field.get(obj) ?: return@forEach
}
}
}
objName = el
}
val objVal = obj
val value = when (objVal) {
is JsonPrimitive -> when {
objVal.isString -> objVal.asString
objVal.isNumber -> objVal.asNumber.toString()
objVal.isBoolean -> objVal.asBoolean.toString()
else -> objVal.asString
}
else -> objVal.toString()
}
MaterialDialog.Builder(activity)
.input("value", value, false) { _, input ->
val input = input.toString()
when (parent) {
is JsonObject -> {
val v = objVal as JsonPrimitive
when {
v.isString -> (parent as JsonObject)[objName] = input
v.isNumber -> (parent as JsonObject)[objName] = input.toLong()
v.isBoolean -> (parent as JsonObject)[objName] = input.toBoolean()
}
}
is JsonArray -> {
}
is HashMap<*, *> -> app.config.set(objName, input)
else -> {
val field = parent::class.java.getDeclaredField(objName)
field.isAccessible = true
val newVal = when (objVal) {
is Int -> input.toInt()
is Boolean -> input.toBoolean()
is Float -> input.toFloat()
is Char -> input.toCharArray()[0]
is String -> input
is Long -> input.toLong()
is Double -> input.toDouble()
else -> input
}
field.set(parent, newVal)
}
}
when (item.key.substringBefore(":")) {
"App.profile" -> app.profileSave()
"App.profile.studentData" -> app.profileSave()
"App.profile.loginStore" -> app.db.loginStoreDao().add(loginStore)
}
showJson()
}
.title(item.key)
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.show()
}
catch (e: Exception) {
activity.error(ApiError.fromThrowable(TAG, e))
}
})
showJson()
b.list.adapter = adapter
b.list.apply {
@ -79,4 +155,15 @@ class LabProfileFragment : LazyFragment(), CoroutineScope {
b.noData.isVisible = false
}; return true }
private fun showJson() {
val json = JsonObject().also { json ->
json.add("App.profile", app.gson.toJsonTree(app.profile))
json.add("App.profile.studentData", app.profile.studentData)
json.add("App.profile.loginStore", loginStore?.data ?: JsonObject())
json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values)))
}
adapter.items = LabJsonAdapter.expand(json, 0)
adapter.notifyDataSetChanged()
}
}

View File

@ -13,6 +13,7 @@ import androidx.core.view.isInvisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.databinding.LabItemObjectBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter
import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonArray
@ -32,6 +33,10 @@ class JsonArrayViewHolder(
override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonArray, position: Int, adapter: LabJsonAdapter) {
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp)
b.type.text = "Array"
b.dropdownIcon.rotation = when (item.state) {
AttendanceAdapter.STATE_CLOSED -> 0f
else -> 180f
@ -39,7 +44,7 @@ class JsonArrayViewHolder(
b.previewContainer.isInvisible = item.state != AttendanceAdapter.STATE_CLOSED
b.summaryContainer.isInvisible = item.state == AttendanceAdapter.STATE_CLOSED
b.key.text = item.key
b.key.text = item.key.substringAfterLast(":")
b.previewContainer.text = item.jsonArray.toString().take(200)
b.summaryContainer.text = item.jsonArray.size().toString() + " elements"
}

View File

@ -32,6 +32,8 @@ class JsonElementViewHolder(
override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonElement, position: Int, adapter: LabJsonAdapter) {
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp)
b.type.text = when (item.jsonElement) {
is JsonPrimitive -> when {
item.jsonElement.isNumber -> "Number"
@ -45,7 +47,9 @@ class JsonElementViewHolder(
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
b.key.text = listOf(
item.key.asColoredSpannable(colorSecondary),
item.key
.substringAfterLast(":")
.asColoredSpannable(colorSecondary),
": ",
item.jsonElement.toString().asItalicSpannable()
).concat("")

View File

@ -13,6 +13,7 @@ import androidx.core.view.isInvisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.databinding.LabItemObjectBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter
import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject
@ -32,6 +33,10 @@ class JsonObjectViewHolder(
override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonObject, position: Int, adapter: LabJsonAdapter) {
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp)
b.type.text = "Object"
b.dropdownIcon.rotation = when (item.state) {
AttendanceAdapter.STATE_CLOSED -> 0f
else -> 180f
@ -39,7 +44,7 @@ class JsonObjectViewHolder(
b.previewContainer.isInvisible = item.state != AttendanceAdapter.STATE_CLOSED
b.summaryContainer.isInvisible = item.state == AttendanceAdapter.STATE_CLOSED
b.key.text = item.key
b.key.text = item.key.substringAfterLast(":")
b.previewContainer.text = item.jsonObject.toString().take(200)
b.summaryContainer.text = item.jsonObject.size().toString() + " elements"
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-26.
*/
package pl.szczodrzynski.edziennik.ui.modules.debug.viewholder
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.databinding.LabItemSubObjectBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.modules.debug.LabJsonAdapter
import pl.szczodrzynski.edziennik.ui.modules.debug.models.LabJsonObject
import pl.szczodrzynski.edziennik.ui.modules.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.utils.Themes
class JsonSubObjectViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: LabItemSubObjectBinding = LabItemSubObjectBinding.inflate(inflater, parent, false)
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<LabJsonObject, LabJsonAdapter> {
companion object {
private const val TAG = "JsonSubObjectViewHolder"
}
@SuppressLint("SetTextI18n")
override fun onBind(activity: AppCompatActivity, app: App, item: LabJsonObject, position: Int, adapter: LabJsonAdapter) {
val contextWrapper = ContextThemeWrapper(activity, Themes.appTheme)
b.root.setPadding(item.level * 8.dp + 8.dp, 8.dp, 8.dp, 8.dp)
b.type.text = "Object"
b.dropdownIcon.rotation = when (item.state) {
AttendanceAdapter.STATE_CLOSED -> 0f
else -> 180f
}
b.key.text = item.key.substringAfterLast(":")
}
}

View File

@ -16,443 +16,402 @@ import pl.szczodrzynski.fslogin.realm.RealmData
object LoginInfo {
private fun getEmailCredential(keyName: String) = FormField(
keyName = keyName,
name = R.string.login_hint_email,
icon = CommunityMaterial.Icon.cmd_at,
emptyText = R.string.login_error_no_email,
invalidText = R.string.login_error_incorrect_email,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+",
caseMode = FormField.CaseMode.LOWER_CASE
)
private fun getPasswordCredential(keyName: String) = FormField(
keyName = keyName,
name = R.string.login_hint_password,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
emptyText = R.string.login_error_no_password,
invalidText = R.string.login_error_incorrect_login_or_password,
errorCodes = mapOf(),
isRequired = true,
validationRegex = ".*",
hideText = true
keyName = keyName,
name = R.string.login_hint_email,
icon = CommunityMaterial.Icon.cmd_at,
emptyText = R.string.login_error_no_email,
invalidText = R.string.login_error_incorrect_email,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "([\\w.\\-_+]+)?\\w+@[\\w-_]+(\\.\\w+)+",
caseMode = FormField.CaseMode.LOWER_CASE
)
val list by lazy { listOf(
private fun getPasswordCredential(keyName: String) = FormField(
keyName = keyName,
name = R.string.login_hint_password,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
emptyText = R.string.login_error_no_password,
invalidText = R.string.login_error_incorrect_login_or_password,
errorCodes = mapOf(),
isRequired = true,
validationRegex = ".*",
hideText = true
)
val list by lazy {
listOf(
Register(
loginType = LOGIN_TYPE_LIBRUS,
internalName = "librus",
registerName = R.string.login_register_librus,
registerLogo = R.drawable.login_logo_librus,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_LIBRUS_EMAIL,
name = R.string.login_mode_librus_email,
icon = R.drawable.login_mode_librus_email,
hintText = R.string.login_mode_librus_email_hint,
guideText = R.string.login_mode_librus_email_guide,
isRecommended = true,
credentials = listOf(
getEmailCredential("email"),
getPasswordCredential("password")
loginType = LOGIN_TYPE_LIBRUS,
internalName = "librus",
registerName = R.string.login_register_librus,
registerLogo = R.drawable.login_logo_librus,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_LIBRUS_EMAIL,
name = R.string.login_mode_librus_email,
icon = R.drawable.login_mode_librus_email,
hintText = R.string.login_mode_librus_email_hint,
guideText = R.string.login_mode_librus_email_guide,
isRecommended = true,
credentials = listOf(
getEmailCredential("email"),
getPasswordCredential("password")
),
errorCodes = mapOf(
ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED to R.string.login_error_account_not_activated,
ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
ERROR_CAPTCHA_LIBRUS_PORTAL to R.string.error_3001_reason
)
),
/*Mode(
loginMode = LOGIN_MODE_LIBRUS_SYNERGIA,
name = R.string.login_mode_librus_synergia,
icon = R.drawable.login_mode_librus_synergia,
hintText = R.string.login_mode_librus_synergia_hint,
guideText = R.string.login_mode_librus_synergia_guide,
credentials = listOf(
Credential(
keyName = "accountLogin",
name = R.string.login_hint_login,
icon = CommunityMaterial.Icon.cmd_account_outline,
emptyText = R.string.login_error_no_login,
invalidText = R.string.login_error_incorrect_login,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[A-z0-9._\\-+]+",
caseMode = Credential.CaseMode.LOWER_CASE
),
errorCodes = mapOf(
ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED to R.string.login_error_account_not_activated,
ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
ERROR_CAPTCHA_LIBRUS_PORTAL to R.string.error_3001_reason
)
getPasswordCredential("accountPassword")
),
/*Mode(
loginMode = LOGIN_MODE_LIBRUS_SYNERGIA,
name = R.string.login_mode_librus_synergia,
icon = R.drawable.login_mode_librus_synergia,
hintText = R.string.login_mode_librus_synergia_hint,
guideText = R.string.login_mode_librus_synergia_guide,
credentials = listOf(
Credential(
keyName = "accountLogin",
name = R.string.login_hint_login,
icon = CommunityMaterial.Icon.cmd_account_outline,
emptyText = R.string.login_error_no_login,
invalidText = R.string.login_error_incorrect_login,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[A-z0-9._\\-+]+",
caseMode = Credential.CaseMode.LOWER_CASE
),
getPasswordCredential("accountPassword")
),
errorCodes = mapOf(
ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_login_or_password
)
),*/
Mode(
loginMode = LOGIN_MODE_LIBRUS_JST,
name = R.string.login_mode_librus_jst,
icon = R.drawable.login_mode_librus_jst,
hintText = R.string.login_mode_librus_jst_hint,
guideText = R.string.login_mode_librus_jst_guide,
credentials = listOf(
FormField(
keyName = "accountCode",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon.cmd_code_braces,
emptyText = R.string.login_error_no_token,
invalidText = R.string.login_error_incorrect_token,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[A-Z0-9_]+",
caseMode = FormField.CaseMode.UPPER_CASE
),
FormField(
keyName = "accountPin",
name = R.string.login_hint_pin,
icon = CommunityMaterial.Icon2.cmd_lock,
emptyText = R.string.login_error_no_pin,
invalidText = R.string.login_error_incorrect_pin,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[a-z0-9_]+",
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_code_or_pin,
ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_code_or_pin
)
errorCodes = mapOf(
ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_login_or_password
)
)
),
Register(
loginType = LOGIN_TYPE_VULCAN,
internalName = "vulcan",
registerName = R.string.login_type_vulcan,
registerLogo = R.drawable.login_logo_vulcan,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_VULCAN_HEBE,
name = R.string.login_mode_vulcan_api,
icon = R.drawable.login_mode_vulcan_hebe,
hintText = R.string.login_mode_vulcan_api_hint,
guideText = R.string.login_mode_vulcan_api_guide,
isRecommended = true,
credentials = listOf(
FormField(
keyName = "deviceToken",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon.cmd_code_braces,
emptyText = R.string.login_error_no_token,
invalidText = R.string.login_error_incorrect_token,
errorCodes = mapOf(
ERROR_LOGIN_VULCAN_INVALID_TOKEN to R.string.login_error_incorrect_token
),
isRequired = true,
validationRegex = "[A-Z0-9]{5,12}",
caseMode = FormField.CaseMode.UPPER_CASE
),
FormField(
keyName = "symbol",
name = R.string.login_hint_symbol,
icon = CommunityMaterial.Icon2.cmd_school,
emptyText = R.string.login_error_no_symbol,
invalidText = R.string.login_error_incorrect_symbol,
errorCodes = mapOf(
ERROR_LOGIN_VULCAN_INVALID_SYMBOL to R.string.login_error_incorrect_symbol
),
isRequired = true,
validationRegex = "[a-z0-9_-]+",
caseMode = FormField.CaseMode.LOWER_CASE
),
FormField(
keyName = "devicePin",
name = R.string.login_hint_pin,
icon = CommunityMaterial.Icon2.cmd_lock,
emptyText = R.string.login_error_no_pin,
invalidText = R.string.login_error_incorrect_pin,
errorCodes = mapOf(
ERROR_LOGIN_VULCAN_INVALID_PIN to R.string.login_error_incorrect_pin
),
isRequired = true,
validationRegex = "[0-9]+",
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
ERROR_LOGIN_VULCAN_EXPIRED_TOKEN to R.string.login_error_expired_token
)
),*/
Mode(
loginMode = LOGIN_MODE_LIBRUS_JST,
name = R.string.login_mode_librus_jst,
icon = R.drawable.login_mode_librus_jst,
hintText = R.string.login_mode_librus_jst_hint,
guideText = R.string.login_mode_librus_jst_guide,
credentials = listOf(
FormField(
keyName = "accountCode",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon.cmd_code_braces,
emptyText = R.string.login_error_no_token,
invalidText = R.string.login_error_incorrect_token,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[A-Z0-9_]+",
caseMode = FormField.CaseMode.UPPER_CASE
),
Mode(
loginMode = LOGIN_MODE_VULCAN_WEB,
name = R.string.login_mode_vulcan_web,
icon = R.drawable.login_mode_vulcan_web,
hintText = R.string.login_mode_vulcan_web_hint,
guideText = R.string.login_mode_vulcan_web_guide,
isDevOnly = true,
isPlatformSelection = true,
credentials = listOf(
getEmailCredential("email"),
FormField(
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-Z]{7}[0-9]+",
caseMode = FormField.CaseMode.UPPER_CASE
),
getPasswordCredential("password")
),
errorCodes = mapOf()
FormField(
keyName = "accountPin",
name = R.string.login_hint_pin,
icon = CommunityMaterial.Icon2.cmd_lock,
emptyText = R.string.login_error_no_pin,
invalidText = R.string.login_error_incorrect_pin,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[a-z0-9_]+",
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN to R.string.login_error_incorrect_code_or_pin,
ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST to R.string.login_error_incorrect_code_or_pin
)
)
)
),
Register(
loginType = LOGIN_TYPE_MOBIDZIENNIK,
internalName = "mobidziennik",
registerName = R.string.login_type_mobidziennik,
registerLogo = R.drawable.login_logo_mobidziennik,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_MOBIDZIENNIK_WEB,
name = R.string.login_mode_mobidziennik_web,
icon = R.drawable.login_mode_mobidziennik_web,
hintText = R.string.login_mode_mobidziennik_web_hint,
guideText = R.string.login_mode_mobidziennik_web_guide,
credentials = listOf(
FormField(
keyName = "username",
name = R.string.login_hint_login_email,
icon = CommunityMaterial.Icon.cmd_account_outline,
emptyText = R.string.login_error_no_login,
invalidText = R.string.login_error_incorrect_login,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "^[a-z0-9_\\-@+.]+$",
caseMode = FormField.CaseMode.LOWER_CASE
),
FormField(
keyName = "password",
name = R.string.login_hint_password,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
emptyText = R.string.login_error_no_password,
invalidText = R.string.login_error_incorrect_login_or_password,
errorCodes = mapOf(
ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD to R.string.login_error_old_password
),
isRequired = true,
validationRegex = ".*",
hideText = true
),
FormField(
keyName = "serverName",
name = R.string.login_hint_address,
icon = CommunityMaterial.Icon2.cmd_web,
emptyText = R.string.login_error_no_address,
invalidText = R.string.login_error_incorrect_address,
errorCodes = mapOf(
ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS to R.string.login_error_incorrect_address
),
isRequired = true,
validationRegex = "^[a-z0-9_\\-]+\$",
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED to R.string.sync_error_archived
)
loginType = LOGIN_TYPE_VULCAN,
internalName = "vulcan",
registerName = R.string.login_type_vulcan,
registerLogo = R.drawable.login_logo_vulcan,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_VULCAN_HEBE,
name = R.string.login_mode_vulcan_api,
icon = R.drawable.login_mode_vulcan_hebe,
hintText = R.string.login_mode_vulcan_api_hint,
guideText = R.string.login_mode_vulcan_api_guide,
isRecommended = true,
credentials = listOf(
FormField(
keyName = "deviceToken",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon.cmd_code_braces,
emptyText = R.string.login_error_no_token,
invalidText = R.string.login_error_incorrect_token,
errorCodes = mapOf(
ERROR_LOGIN_VULCAN_INVALID_TOKEN to R.string.login_error_incorrect_token,
ERROR_LOGIN_VULCAN_EXPIRED_TOKEN to R.string.login_error_expired_token
),
isRequired = true,
validationRegex = "[A-Z0-9]{5,12}",
caseMode = FormField.CaseMode.UPPER_CASE
),
FormField(
keyName = "symbol",
name = R.string.login_hint_symbol,
icon = CommunityMaterial.Icon2.cmd_school,
emptyText = R.string.login_error_no_symbol,
invalidText = R.string.login_error_incorrect_symbol,
errorCodes = mapOf(
ERROR_LOGIN_VULCAN_INVALID_SYMBOL to R.string.login_error_incorrect_symbol
),
isRequired = true,
validationRegex = "[a-z0-9_-]+",
caseMode = FormField.CaseMode.LOWER_CASE
),
FormField(
keyName = "devicePin",
name = R.string.login_hint_pin,
icon = CommunityMaterial.Icon2.cmd_lock,
emptyText = R.string.login_error_no_pin,
invalidText = R.string.login_error_incorrect_pin,
errorCodes = mapOf(
ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING to R.string.error_310_reason,
ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING to R.string.error_311_reason,
ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING to R.string.error_312_reason
),
isRequired = true,
validationRegex = "[0-9]+",
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf()
),
Mode(
loginMode = LOGIN_MODE_VULCAN_WEB,
name = R.string.login_mode_vulcan_web,
icon = R.drawable.login_mode_vulcan_web,
hintText = R.string.login_mode_vulcan_web_hint,
guideText = R.string.login_mode_vulcan_web_guide,
isTesting = true,
isPlatformSelection = true,
credentials = listOf(
getEmailCredential("email"),
FormField(
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-Z]{7}[0-9]+",
caseMode = FormField.CaseMode.UPPER_CASE
),
getPasswordCredential("password")
),
errorCodes = mapOf()
)
)
),
Register(
loginType = LOGIN_TYPE_IDZIENNIK,
internalName = "idziennik",
registerName = R.string.login_type_idziennik,
registerLogo = R.drawable.login_logo_iuczniowie,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_IDZIENNIK_WEB,
name = R.string.login_mode_idziennik_web,
icon = R.drawable.login_mode_idziennik_web,
hintText = R.string.login_mode_idziennik_web_hint,
guideText = R.string.login_mode_idziennik_web_guide,
credentials = listOf(
FormField(
keyName = "schoolName",
name = R.string.login_hint_school_name,
icon = CommunityMaterial.Icon2.cmd_school,
emptyText = R.string.login_error_no_school_name,
invalidText = R.string.login_error_incorrect_school_name,
errorCodes = mapOf(
ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME to R.string.login_error_incorrect_school_name
),
isRequired = true,
validationRegex = "^[a-z0-9_\\-.]+$",
caseMode = FormField.CaseMode.LOWER_CASE
),
FormField(
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-z0-9_\\-.]+$",
caseMode = FormField.CaseMode.LOWER_CASE
),
getPasswordCredential("password")
),
errorCodes = mapOf(
ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password
)
loginType = LOGIN_TYPE_MOBIDZIENNIK,
internalName = "mobidziennik",
registerName = R.string.login_type_mobidziennik,
registerLogo = R.drawable.login_logo_mobidziennik,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_MOBIDZIENNIK_WEB,
name = R.string.login_mode_mobidziennik_web,
icon = R.drawable.login_mode_mobidziennik_web,
hintText = R.string.login_mode_mobidziennik_web_hint,
guideText = R.string.login_mode_mobidziennik_web_guide,
credentials = listOf(
FormField(
keyName = "username",
name = R.string.login_hint_login_email,
icon = CommunityMaterial.Icon.cmd_account_outline,
emptyText = R.string.login_error_no_login,
invalidText = R.string.login_error_incorrect_login,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "^[a-z0-9_\\-@+.]+$",
caseMode = FormField.CaseMode.LOWER_CASE
),
FormField(
keyName = "password",
name = R.string.login_hint_password,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
emptyText = R.string.login_error_no_password,
invalidText = R.string.login_error_incorrect_login_or_password,
errorCodes = mapOf(
ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD to R.string.login_error_old_password
),
isRequired = true,
validationRegex = ".*",
hideText = true
),
FormField(
keyName = "serverName",
name = R.string.login_hint_address,
icon = CommunityMaterial.Icon2.cmd_web,
emptyText = R.string.login_error_no_address,
invalidText = R.string.login_error_incorrect_address,
errorCodes = mapOf(
ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS to R.string.login_error_incorrect_address
),
isRequired = true,
validationRegex = "^[a-z0-9_\\-]+\$",
caseMode = FormField.CaseMode.LOWER_CASE
)
),
errorCodes = mapOf(
ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password,
ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED to R.string.sync_error_archived
)
)
)
),
Register(
loginType = LOGIN_TYPE_EDUDZIENNIK,
internalName = "edudziennik",
registerName = R.string.login_type_edudziennik,
registerLogo = R.drawable.login_logo_edudziennik,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_EDUDZIENNIK_WEB,
name = R.string.login_mode_edudziennik_web,
icon = R.drawable.login_mode_edudziennik_web,
hintText = R.string.login_mode_edudziennik_web_hint,
guideText = R.string.login_mode_edudziennik_web_guide,
credentials = listOf(
getEmailCredential("email"),
getPasswordCredential("password")
),
errorCodes = mapOf(
ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password
)
)
loginType = LOGIN_TYPE_EDUDZIENNIK,
internalName = "edudziennik",
registerName = R.string.login_type_edudziennik,
registerLogo = R.drawable.login_logo_edudziennik,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_EDUDZIENNIK_WEB,
name = R.string.login_mode_edudziennik_web,
icon = R.drawable.login_mode_edudziennik_web,
hintText = R.string.login_mode_edudziennik_web_hint,
guideText = R.string.login_mode_edudziennik_web_guide,
credentials = listOf(
getEmailCredential("email"),
getPasswordCredential("password")
),
errorCodes = mapOf(
ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN to R.string.login_error_incorrect_login_or_password
)
)
)
),
Register(
loginType = LOGIN_TYPE_PODLASIE,
internalName = "podlasie",
registerName = R.string.login_type_podlasie,
registerLogo = R.drawable.login_logo_podlasie,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_PODLASIE_API,
name = R.string.login_mode_podlasie_api,
icon = R.drawable.login_mode_podlasie_api,
guideText = R.string.login_mode_podlasie_api_guide,
credentials = listOf(
FormField(
keyName = "apiToken",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
emptyText = R.string.login_error_no_token,
invalidText = R.string.login_error_incorrect_token,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[a-zA-Z0-9]{10}",
caseMode = FormField.CaseMode.UNCHANGED
),
FormCheckbox(
keyName = "logoutDevices",
name = R.string.login_podlasie_logout_devices,
checked = false,
errorCodes = mapOf(
ERROR_LOGIN_PODLASIE_API_DEVICE_LIMIT to R.string.error_602_reason
)
)
),
errorCodes = mapOf()
loginType = LOGIN_TYPE_PODLASIE,
internalName = "podlasie",
registerName = R.string.login_type_podlasie,
registerLogo = R.drawable.login_logo_podlasie,
loginModes = listOf(
Mode(
loginMode = LOGIN_MODE_PODLASIE_API,
name = R.string.login_mode_podlasie_api,
icon = R.drawable.login_mode_podlasie_api,
guideText = R.string.login_mode_podlasie_api_guide,
credentials = listOf(
FormField(
keyName = "apiToken",
name = R.string.login_hint_token,
icon = CommunityMaterial.Icon2.cmd_lock_outline,
emptyText = R.string.login_error_no_token,
invalidText = R.string.login_error_incorrect_token,
errorCodes = mapOf(),
isRequired = true,
validationRegex = "[a-zA-Z0-9]{10}",
caseMode = FormField.CaseMode.UNCHANGED
),
FormCheckbox(
keyName = "logoutDevices",
name = R.string.login_podlasie_logout_devices,
checked = false,
errorCodes = mapOf(
ERROR_LOGIN_PODLASIE_API_DEVICE_LIMIT to R.string.error_602_reason
)
)
),
errorCodes = mapOf()
)
)
)
) }
)
}
data class Register(
val loginType: Int,
val internalName: String,
val registerName: Int,
@DrawableRes
val registerLogo: Int,
val loginType: Int,
val internalName: String,
val registerName: Int,
@DrawableRes
val registerLogo: Int,
val loginModes: List<Mode>
val loginModes: List<Mode>
) : ExpandableItemModel<Mode>(loginModes.toMutableList()) {
override var level = 1
}
data class Mode(
val loginMode: Int,
val loginMode: Int,
@StringRes
val name: Int,
@DrawableRes
val icon: Int,
@StringRes
val hintText: Int? = null,
@StringRes
val guideText: Int,
@StringRes
val name: Int,
@DrawableRes
val icon: Int,
@StringRes
val hintText: Int? = null,
@StringRes
val guideText: Int,
val isRecommended: Boolean = false,
val isTesting: Boolean = false,
val isDevOnly: Boolean = false,
val isPlatformSelection: Boolean = false,
val isRecommended: Boolean = false,
val isTesting: Boolean = false,
val isDevOnly: Boolean = false,
val isPlatformSelection: Boolean = false,
val credentials: List<BaseCredential>,
val errorCodes: Map<Int, Int>
val credentials: List<BaseCredential>,
val errorCodes: Map<Int, Int>
)
data class Platform(
val id: Int,
val name: String,
val description: String?,
val icon: String,
val screenshot: String?,
val formFields: List<String>,
val realmData: RealmData
val id: Int,
val name: String,
val description: String?,
val icon: String,
val screenshot: String?,
val formFields: List<String>,
val realmData: RealmData
)
open class BaseCredential(
open val keyName: String,
@StringRes
open val name: Int,
open val errorCodes: Map<Int, Int>
open val keyName: String,
@StringRes
open val name: Int,
open val errorCodes: Map<Int, Int>
)
data class FormField(
override val keyName: String,
override val keyName: String,
@StringRes
override val name: Int,
val icon: IIcon,
@StringRes
val placeholder: Int? = null,
@StringRes
val emptyText: Int,
@StringRes
val invalidText: Int,
override val errorCodes: Map<Int, Int>,
@StringRes
val hintText: Int? = null,
@StringRes
override val name: Int,
val icon: IIcon,
@StringRes
val placeholder: Int? = null,
@StringRes
val emptyText: Int,
@StringRes
val invalidText: Int,
override val errorCodes: Map<Int, Int>,
@StringRes
val hintText: Int? = null,
val isRequired: Boolean = true,
val validationRegex: String,
val caseMode: CaseMode = CaseMode.UNCHANGED,
val hideText: Boolean = false,
val stripTextRegex: String? = null
val isRequired: Boolean = true,
val validationRegex: String,
val caseMode: CaseMode = CaseMode.UNCHANGED,
val hideText: Boolean = false,
val stripTextRegex: String? = null
) : BaseCredential(keyName, name, errorCodes) {
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
}
data class FormCheckbox(
override val keyName: String,
@StringRes
override val name: Int,
val checked: Boolean = false,
override val errorCodes: Map<Int, Int> = mapOf()
override val keyName: String,
@StringRes
override val name: Int,
val checked: Boolean = false,
override val errorCodes: Map<Int, Int> = mapOf()
) : BaseCredential(keyName, name, errorCodes)
var chooserList: MutableList<Any>? = null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -30,11 +30,12 @@
tools:text="lessonRanges" />
<TextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:fontFamily="monospace"
android:text="JsonObject"
android:text="Object"
android:textAppearance="@style/NavView.TextView.Helper" />
<com.mikepenz.iconics.view.IconicsImageView

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-2-26.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<TextView
android:id="@+id/key"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:fontFamily="monospace"
android:maxLines="2"
tools:text="lessonRanges" />
<TextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:fontFamily="monospace"
android:text="Object"
android:textAppearance="@style/NavView.TextView.Helper" />
<com.mikepenz.iconics.view.IconicsImageView
android:id="@+id/dropdownIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="centerInside"
app:iiv_color="?android:textColorSecondary"
app:iiv_icon="cmd-chevron-down"
app:iiv_size="18dp"
tools:src="@android:drawable/ic_menu_more" />
</LinearLayout>
</layout>

View File

@ -118,42 +118,31 @@
<string name="error_301" translatable="false">ERROR_LOGIN_VULCAN_INVALID_SYMBOL</string>
<string name="error_302" translatable="false">ERROR_LOGIN_VULCAN_INVALID_TOKEN</string>
<string name="error_309" translatable="false">ERROR_LOGIN_VULCAN_INVALID_PIN</string>
<string name="error_310" translatable="false">ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING</string>
<string name="error_311" translatable="false">ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING</string>
<string name="error_312" translatable="false">ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING</string>
<string name="error_321" translatable="false">ERROR_LOGIN_VULCAN_EXPIRED_TOKEN</string>
<string name="error_322" translatable="false">ERROR_LOGIN_VULCAN_OTHER</string>
<string name="error_330" translatable="false">ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN</string>
<string name="error_331" translatable="false">ERROR_LOGIN_VULCAN_NO_PUPILS</string>
<string name="error_340" translatable="false">ERROR_VULCAN_API_MAINTENANCE</string>
<string name="error_341" translatable="false">ERROR_VULCAN_API_BAD_REQUEST</string>
<string name="error_342" translatable="false">ERROR_VULCAN_API_OTHER</string>
<string name="error_343" translatable="false">ERROR_VULCAN_ATTACHMENT_DOWNLOAD</string>
<string name="error_344" translatable="false">ERROR_VULCAN_WEB_DATA_MISSING</string>
<string name="error_345" translatable="false">ERROR_VULCAN_WEB_429</string>
<string name="error_346" translatable="false">ERROR_VULCAN_WEB_OTHER</string>
<string name="error_347" translatable="false">ERROR_VULCAN_WEB_NO_CERTIFICATE</string>
<string name="error_348" translatable="false">ERROR_VULCAN_WEB_NO_REGISTER</string>
<string name="error_349" translatable="false">ERROR_VULCAN_WEB_CERTIFICATE_EXPIRED</string>
<string name="error_350" translatable="false">ERROR_VULCAN_WEB_LOGGED_OUT</string>
<string name="error_351" translatable="false">ERROR_VULCAN_WEB_CERTIFICATE_POST_FAILED</string>
<string name="error_352" translatable="false">ERROR_VULCAN_WEB_GRADUATE_ACCOUNT</string>
<string name="error_353" translatable="false">ERROR_VULCAN_WEB_NO_SCHOOLS</string>
<string name="error_354" translatable="false">ERROR_VULCAN_HEBE_OTHER</string>
<string name="error_360" translatable="false">ERROR_VULCAN_HEBE_SIGNATURE_ERROR</string>
<string name="error_361" translatable="false">ERROR_VULCAN_HEBE_INVALID_PAYLOAD</string>
<string name="error_362" translatable="false">ERROR_VULCAN_HEBE_FIREBASE_ERROR</string>
<string name="error_363" translatable="false">ERROR_VULCAN_HEBE_CERTIFICATE_GONE</string>
<string name="error_364" translatable="false">ERROR_VULCAN_HEBE_SERVER_ERROR</string>
<string name="error_365" translatable="false">ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND</string>
<string name="error_390" translatable="false">ERROR_VULCAN_API_DEPRECATED</string>
<string name="error_401" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN</string>
<string name="error_402" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME</string>
<string name="error_403" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED</string>
<string name="error_404" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE</string>
<string name="error_405" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR</string>
<string name="error_410" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_OTHER</string>
<string name="error_411" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_API_NO_ACCESS</string>
<string name="error_420" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION</string>
<string name="error_421" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH</string>
<string name="error_422" translatable="false">ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER</string>
<string name="error_430" translatable="false">ERROR_IDZIENNIK_WEB_ACCESS_DENIED</string>
<string name="error_431" translatable="false">ERROR_IDZIENNIK_WEB_OTHER</string>
<string name="error_432" translatable="false">ERROR_IDZIENNIK_WEB_MAINTENANCE</string>
<string name="error_433" translatable="false">ERROR_IDZIENNIK_WEB_SERVER_ERROR</string>
<string name="error_434" translatable="false">ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED</string>
<string name="error_440" translatable="false">ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR</string>
<string name="error_441" translatable="false">ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA</string>
<string name="error_450" translatable="false">ERROR_IDZIENNIK_API_ACCESS_DENIED</string>
<string name="error_451" translatable="false">ERROR_IDZIENNIK_API_OTHER</string>
<string name="error_452" translatable="false">ERROR_IDZIENNIK_API_NO_REGISTER</string>
<string name="error_453" translatable="false">ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION</string>
<string name="error_501" translatable="false">ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN</string>
<string name="error_510" translatable="false">ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER</string>
<string name="error_511" translatable="false">ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID</string>
@ -176,14 +165,10 @@
<string name="error_904" translatable="false">EXCEPTION_LIBRUS_API_REQUEST</string>
<string name="error_905" translatable="false">EXCEPTION_LIBRUS_SYNERGIA_REQUEST</string>
<string name="error_906" translatable="false">EXCEPTION_MOBIDZIENNIK_WEB_REQUEST</string>
<string name="error_907" translatable="false">EXCEPTION_VULCAN_API_REQUEST</string>
<string name="error_908" translatable="false">EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST</string>
<string name="error_909" translatable="false">EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST</string>
<string name="error_910" translatable="false">EXCEPTION_NOTIFY</string>
<string name="error_911" translatable="false">EXCEPTION_LIBRUS_MESSAGES_REQUEST</string>
<string name="error_912" translatable="false">EXCEPTION_IDZIENNIK_WEB_REQUEST</string>
<string name="error_913" translatable="false">EXCEPTION_IDZIENNIK_WEB_API_REQUEST</string>
<string name="error_914" translatable="false">EXCEPTION_IDZIENNIK_API_REQUEST</string>
<string name="error_920" translatable="false">EXCEPTION_EDUDZIENNIK_WEB_REQUEST</string>
<string name="error_921" translatable="false">EXCEPTION_EDUDZIENNIK_FILE_REQUEST</string>
<string name="error_930" translatable="false">ERROR_ONEDRIVE_DOWNLOAD</string>
@ -307,42 +292,31 @@
<string name="error_301_reason">Nieprawidłowy symbol</string>
<string name="error_302_reason">Nieprawidłowy token</string>
<string name="error_309_reason">Nieprawidłowy PIN</string>
<string name="error_310_reason">Nieprawidłowy PIN: pozostało 0 prób</string>
<string name="error_310_reason">Nieprawidłowy PIN. Wygeneruj nowy token.</string>
<string name="error_311_reason">Nieprawidłowy PIN: pozostała 1 próba</string>
<string name="error_312_reason">Nieprawidłowy PIN: pozostały 2 próby</string>
<string name="error_321_reason">Token wygasły, wygeneruj ponownie</string>
<string name="error_322_reason">Inny błąd logowania do dziennika VULCAN®</string>
<string name="error_330_reason">Dziennik przedszkolny - logowanie niemożliwe</string>
<string name="error_331_reason">Brak uczniów przypisanych do konta, bądź ukończyli oni już szkołę</string>
<string name="error_340_reason">VULCAN®: przerwa techniczna</string>
<string name="error_341_reason">VULCAN®: błąd żądania, zgłoś błąd</string>
<string name="error_342_reason">VULCAN®: inny błąd, wyślij zgłoszenie</string>
<string name="error_343_reason">VULCAN®: nie znaleziono adresu załącznika</string>
<string name="error_344_reason">VULCAN®: brakujące dane logowania FS. Usuń profil i zaloguj się ponownie.</string>
<string name="error_345_reason">VULCAN®: błąd logowania (429). Dziennik może być przeciążony.</string>
<string name="error_346_reason">VULCAN®: inny błąd logowania</string>
<string name="error_347_reason">VULCAN®: nie znaleziono zapisanego certyfikatu</string>
<string name="error_348_reason">VULCAN®: konto nie jest przypisane do żadnej szkoły</string>
<string name="error_349_reason">VULCAN®: certyfikat logowania wygasł</string>
<string name="error_350_reason">VULCAN®: wylogowano z konta</string>
<string name="error_351_reason">VULCAN®: nie udało się wysłać certyfikatu</string>
<string name="error_352_reason">VULCAN®: konto nie posiada już uczniów uczęszczających do szkoły</string>
<string name="error_353_reason">VULCAN®: nie znaleziono żadnej szkoły. Zgłoś błąd.</string>
<string name="error_354_reason">VULCAN®: nieznany błąd pobierania danych</string>
<string name="error_360_reason">Nieprawidłowy podpis zapytania. Sprawdź ustawienia daty i godziny w systemie.</string>
<string name="error_361_reason">VULCAN®: zapytanie zawiera niepoprawne dane</string>
<string name="error_362_reason">Błąd powiadomień push. Sprawdź połączenie z internetem.</string>
<string name="error_363_reason">VULCAN®: urządzenie usunięte. Zaloguj się ponownie do dziennika.</string>
<string name="error_364_reason">VULCAN®: błąd serwera. Dziennik może być przeciążony.</string>
<string name="error_365_reason">VULCAN®: nie znaleziono bytu</string>
<string name="error_390_reason">W związku z wygaszeniem aplikacji Dzienniczek+ przez firmę Vulcan, należy zalogować się ponownie.\n\nAby móc dalej korzystać z aplikacji Szkolny.eu, otwórz Ustawienia i wybierz opcję Dodaj nowego ucznia.\nNastępnie zaloguj się do dziennika Vulcan zgodnie z instrukcją.\n\nPrzepraszamy za niedogodności.</string>
<string name="error_401_reason">Nieprawidłowe dane logowania</string>
<string name="error_402_reason">Nieprawidłowa nazwa szkoły</string>
<string name="error_403_reason">iDziennik: wymagana zmiana hasła. Zaloguj się na stronie iDziennika, by dokonać zmiany hasła.</string>
<string name="error_404_reason">Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem</string>
<string name="error_405_reason">Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem (serwer zwrócił błąd)</string>
<string name="error_410_reason">iDziennik: inny błąd logowania</string>
<string name="error_411_reason">iDziennik: brak dostępu do API</string>
<string name="error_420_reason">iDziennik: nie znaleziono sesji</string>
<string name="error_421_reason">iDziennik: nie znaleziono tokenu autoryzacji</string>
<string name="error_422_reason">iDziennik: nie znaleziono tokenu API</string>
<string name="error_430_reason">iDziennik: brak dostępu. Zaloguj się ponownie.</string>
<string name="error_431_reason">iDziennik: inny błąd synchronizacji</string>
<string name="error_432_reason">Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem</string>
<string name="error_433_reason">Chwilowa przerwa techniczna iDziennika, lub inny problem z serwerem (serwer zwrócił błąd)</string>
<string name="error_434_reason">iDziennik: wymagana zmiana hasła. Zaloguj się na stronie iDziennika, by dokonać zmiany hasła.</string>
<string name="error_440_reason">iDziennik: nie znaleziono numeru roku szkolnego. Uczeń może nie być aktywny w aktualnym roku szkolnym.</string>
<string name="error_441_reason">iDziennik: serwer nie zwrócił żadnych danych</string>
<string name="error_450_reason">Brak dostępu do API iDziennika</string>
<string name="error_451_reason">Inny błąd API iDziennika</string>
<string name="error_452_reason">iDziennik: nie znaleziono ID dziennika. Zgłoś błąd programiście.</string>
<string name="error_453_reason">Twoje konto nie ma uprawnień do wysyłania wiadomości (bądź wystąpił inny błąd serwera iDziennika)</string>
<string name="error_501_reason">Błędny email lub hasło</string>
<string name="error_510_reason">Inny błąd logowania</string>
<string name="error_511_reason">Brak ID sesji</string>
@ -365,14 +339,10 @@
<string name="error_904_reason">Zgłoś błąd: wyjątek w API LIBRUS®</string>
<string name="error_905_reason">EXCEPTION_LIBRUS_SYNERGIA_REQUEST</string>
<string name="error_906_reason">EXCEPTION_MOBIDZIENNIK_WEB_REQUEST</string>
<string name="error_907_reason">EXCEPTION_VULCAN_API_REQUEST</string>
<string name="error_908_reason">EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST</string>
<string name="error_909_reason">EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST</string>
<string name="error_910_reason">EXCEPTION_NOTIFY</string>
<string name="error_911_reason">Zgłoś błąd: pobieranie wiadomości LIBRUS®</string>
<string name="error_912_reason">EXCEPTION_IDZIENNIK_WEB_REQUEST</string>
<string name="error_913_reason">EXCEPTION_IDZIENNIK_WEB_API_REQUEST</string>
<string name="error_914_reason">EXCEPTION_IDZIENNIK_API_REQUEST</string>
<string name="error_920_reason">Wystąpił błąd</string>
<string name="error_921_reason">Wystąpił błąd podczas pobierania pliku</string>
<string name="error_930_reason">Nie udało się pobrać pliku z OneDrive</string>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.4.30'
release = [
versionName: "4.6-beta.1",
versionCode: 4060001
versionName: "4.6",
versionCode: 4060099
]
setup = [