forked from github/szkolny
Compare commits
28 Commits
v4.5
...
v4.6-beta.
Author | SHA1 | Date | |
---|---|---|---|
359432d24d | |||
edf8ec20f0 | |||
3ad9e5da1f | |||
459bbf78b2 | |||
d0baf02750 | |||
a5bb7d9c6e | |||
a939d95ea3 | |||
4c081c970e | |||
b75ab76c2a | |||
9da6dbccb3 | |||
66444ae35b | |||
3e8b3de2b7 | |||
85ac5769a1 | |||
93e3d5994a | |||
0cf24c527b | |||
97c5acd6ba | |||
30aeb70647 | |||
b599d679c4 | |||
1eecd24d91 | |||
c698dfdb73 | |||
c7a44f5ced | |||
c8ee6ff1e7 | |||
552acd4043 | |||
ede101ea20 | |||
13c2640ed5 | |||
dd0739fd4b | |||
9023f13932 | |||
f8456fb087 |
@ -211,7 +211,7 @@ dependencies {
|
||||
implementation 'com.qifan.powerpermission:powerpermission:1.3.0'
|
||||
implementation 'com.qifan.powerpermission:powerpermission-coroutines:1.3.0'
|
||||
|
||||
implementation 'com.github.kuba2k2.FSLogin:lib:master-SNAPSHOT'
|
||||
implementation 'com.github.kuba2k2.FSLogin:lib:2.0.0'
|
||||
implementation 'pl.droidsonroids:jspoon:1.3.2'
|
||||
implementation "com.squareup.retrofit2:converter-scalars:2.8.1"
|
||||
implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2"
|
||||
|
@ -67,3 +67,6 @@
|
||||
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; }
|
||||
-keepclassmembers class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; }
|
||||
-keepclassmembernames class pl.szczodrzynski.edziennik.ui.modules.login.LoginInfo$Platform { *; }
|
||||
|
||||
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData { *; }
|
||||
-keepclassmembernames class pl.szczodrzynski.fslogin.realm.RealmData$Type { *; }
|
||||
|
@ -1,7 +1,8 @@
|
||||
<h3>Wersja 4.5, 2021-02-21</h3>
|
||||
<h3>Wersja 4.6-beta.1, 2021-02-25</h3>
|
||||
<ul>
|
||||
<li>Vulcan: aplikacja Szkolny.eu zaktualizowana w związku z wygaszeniem aplikacji Dzienniczek+.</li>
|
||||
<li><b>Mogą pojawić się brakujące funkcje, np. wysyłanie wiadomości - zostaną one wprowadzone w najbliższych dniach.</b></li>
|
||||
<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>
|
||||
</ul>
|
||||
<br>
|
||||
<br>
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
/*secret password - removed for source code publication*/
|
||||
static toys AES_IV[16] = {
|
||||
0x4f, 0x43, 0x04, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
0xe5, 0x07, 0x0f, 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);
|
||||
|
||||
|
@ -43,10 +43,9 @@ import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.gms.security.ProviderInstaller
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.gson.*
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import im.wangchao.mhttp.Response
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
@ -538,6 +537,12 @@ fun String.md5(): String {
|
||||
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
|
||||
}
|
||||
|
||||
fun String.sha1Hex(): String {
|
||||
val md = MessageDigest.getInstance("SHA-1")
|
||||
md.update(toByteArray())
|
||||
return md.digest().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
fun String.sha256(): ByteArray {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
md.update(toByteArray())
|
||||
@ -697,6 +702,21 @@ fun JsonObject(vararg properties: Pair<String, Any?>): JsonObject {
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonObject.toBundle(): Bundle {
|
||||
return Bundle().also {
|
||||
for ((key, value) in this.entrySet()) {
|
||||
when (value) {
|
||||
is JsonObject -> it.putBundle(key, value.toBundle())
|
||||
is JsonPrimitive -> when {
|
||||
value.isString -> it.putString(key, value.asString)
|
||||
value.isBoolean -> it.putBoolean(key, value.asBoolean)
|
||||
value.isNumber -> it.putInt(key, value.asInt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonArray(vararg properties: Any?): JsonArray {
|
||||
return JsonArray().apply {
|
||||
for (property in properties) {
|
||||
|
@ -751,6 +751,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) {
|
||||
EventBus.getDefault().removeStickyEvent(event)
|
||||
if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
|
||||
if (event.error.profileId != App.profileId)
|
||||
return
|
||||
ErrorDetailsDialog(this, listOf(event.error))
|
||||
}
|
||||
navView.toolbar.apply {
|
||||
subtitleFormat = R.string.toolbar_subtitle
|
||||
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
|
||||
@ -758,9 +763,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
|
||||
}
|
||||
mainSnackbar.dismiss()
|
||||
errorSnackbar.addError(event.error).show()
|
||||
if (event.error.errorCode == ERROR_VULCAN_API_DEPRECATED) {
|
||||
ErrorDetailsDialog(this, listOf(event.error))
|
||||
}
|
||||
}
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
|
||||
|
@ -105,6 +105,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
|
||||
get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() }
|
||||
set(value) { set("widgetConfigs", value); mWidgetConfigs = value }
|
||||
|
||||
private var mArchiverEnabled: Boolean? = null
|
||||
var archiverEnabled: Boolean
|
||||
get() { mArchiverEnabled = mArchiverEnabled ?: values.get("archiverEnabled", true); return mArchiverEnabled ?: true }
|
||||
set(value) { set("archiverEnabled", value); mArchiverEnabled = value }
|
||||
|
||||
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
|
||||
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
|
||||
init {
|
||||
|
@ -127,15 +127,20 @@ 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"
|
||||
const val VULCAN_HEBE_ENDPOINT_MAIN = "api/mobile/register/hebe"
|
||||
const val VULCAN_HEBE_ENDPOINT_PUSH_ALL = "api/mobile/push/all"
|
||||
const val VULCAN_HEBE_ENDPOINT_TIMETABLE = "api/mobile/schedule"
|
||||
const val VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGES = "api/mobile/schedule/changes"
|
||||
const val VULCAN_HEBE_ENDPOINT_ADDRESSBOOK = "api/mobile/addressbook"
|
||||
const val VULCAN_HEBE_ENDPOINT_EXAMS = "api/mobile/exam"
|
||||
const val VULCAN_HEBE_ENDPOINT_GRADES = "api/mobile/grade"
|
||||
const val VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY = "api/mobile/grade/summary"
|
||||
const val VULCAN_HEBE_ENDPOINT_HOMEWORK = "api/mobile/homework"
|
||||
const val VULCAN_HEBE_ENDPOINT_NOTICES = "api/mobile/note"
|
||||
const val VULCAN_HEBE_ENDPOINT_ATTENDANCE = "api/mobile/lesson"
|
||||
const val VULCAN_HEBE_ENDPOINT_MESSAGES = "api/mobile/message"
|
||||
const val VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS = "api/mobile/message/status"
|
||||
const val VULCAN_HEBE_ENDPOINT_MESSAGES_SEND = "api/mobile/message"
|
||||
const val VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER = "api/mobile/school/lucky"
|
||||
|
||||
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"
|
||||
|
||||
|
@ -121,19 +121,15 @@ val vulcanLoginMethods = listOf(
|
||||
|
||||
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java)
|
||||
.withIsPossible { _, loginStore ->
|
||||
loginStore.mode != LOGIN_MODE_VULCAN_HEBE
|
||||
loginStore.mode == LOGIN_MODE_VULCAN_API
|
||||
}
|
||||
.withRequiredLoginMethod { _, loginStore ->
|
||||
if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED
|
||||
},
|
||||
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
|
||||
|
||||
LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_HEBE, VulcanLoginHebe::class.java)
|
||||
.withIsPossible { _, loginStore ->
|
||||
loginStore.mode != LOGIN_MODE_VULCAN_API
|
||||
}
|
||||
.withRequiredLoginMethod { _, loginStore ->
|
||||
if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_MAIN else LOGIN_METHOD_NOT_NEEDED
|
||||
}
|
||||
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
|
||||
)
|
||||
|
||||
val idziennikLoginMethods = listOf(
|
||||
|
@ -156,7 +156,7 @@ object Regexes {
|
||||
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
|
||||
}
|
||||
val VULCAN_WEB_PERMISSIONS by lazy {
|
||||
"""permissions: '([A-z0-9/=+\-_]+?)'""".toRegex()
|
||||
"""permissions: '([A-z0-9/=+\-_|]+?)'""".toRegex()
|
||||
}
|
||||
val VULCAN_WEB_SYMBOL_VALIDATE by lazy {
|
||||
"""[A-z0-9]+""".toRegex(IGNORE_CASE)
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
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.data.api.LOGIN_METHOD_VULCAN_HEBE
|
||||
@ -14,14 +15,13 @@ 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.fslogin.realm.RealmData
|
||||
|
||||
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
|
||||
|
||||
fun isWebMainLoginValid() = webExpiryTime-30 > currentTimeUnix()
|
||||
&& webAuthCookie.isNotNullNorEmpty()
|
||||
&& webHost.isNotNullNorEmpty()
|
||||
&& webType.isNotNullNorEmpty()
|
||||
&& symbol.isNotNullNorEmpty()
|
||||
fun isWebMainLoginValid() = symbol.isNotNullNorEmpty()
|
||||
&& (webExpiryTime[symbol]?.toLongOrNull() ?: 0) - 30 > currentTimeUnix()
|
||||
&& webAuthCookie[symbol].isNotNullNorEmpty()
|
||||
&& webRealmData != null
|
||||
fun isApiLoginValid() = currentSemesterEndDate-30 > currentTimeUnix()
|
||||
&& apiFingerprint[symbol].isNotNullNorEmpty()
|
||||
&& apiPrivateKey[symbol].isNotNullNorEmpty()
|
||||
@ -297,49 +297,15 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
\/ \/ \___|_.__/ |_| |_____/ |______\___/ \__, |_|_| |_|
|
||||
__/ |
|
||||
|__*/
|
||||
/**
|
||||
* Federation Services login type.
|
||||
* This might be one of: cufs, adfs, adfslight.
|
||||
*/
|
||||
var webType: String?
|
||||
get() { mWebType = mWebType ?: loginStore.getLoginData("webType", null); return mWebType }
|
||||
set(value) { loginStore.putLoginData("webType", value); mWebType = value }
|
||||
private var mWebType: String? = null
|
||||
var webRealmData: RealmData?
|
||||
get() { mWebRealmData = mWebRealmData ?: loginStore.getLoginData("webRealmData", JsonObject()).let {
|
||||
app.gson.fromJson(it, RealmData::class.java)
|
||||
}; return mWebRealmData }
|
||||
set(value) { loginStore.putLoginData("webRealmData", app.gson.toJsonTree(value) as JsonObject); mWebRealmData = value }
|
||||
private var mWebRealmData: RealmData? = null
|
||||
|
||||
/**
|
||||
* Web server providing the federation services login.
|
||||
* If this is present, WEB_MAIN login is considered as available.
|
||||
*/
|
||||
var webHost: String?
|
||||
get() { mWebHost = mWebHost ?: loginStore.getLoginData("webHost", null); return mWebHost }
|
||||
set(value) { loginStore.putLoginData("webHost", value); mWebHost = value }
|
||||
private var mWebHost: String? = null
|
||||
|
||||
/**
|
||||
* An ID used in ADFS & ADFSLight login types.
|
||||
*/
|
||||
var webAdfsId: String?
|
||||
get() { mWebAdfsId = mWebAdfsId ?: loginStore.getLoginData("webAdfsId", null); return mWebAdfsId }
|
||||
set(value) { loginStore.putLoginData("webAdfsId", value); mWebAdfsId = value }
|
||||
private var mWebAdfsId: String? = null
|
||||
|
||||
/**
|
||||
* A domain override for ADFS Light.
|
||||
*/
|
||||
var webAdfsDomain: String?
|
||||
get() { mWebAdfsDomain = mWebAdfsDomain ?: loginStore.getLoginData("webAdfsDomain", null); return mWebAdfsDomain }
|
||||
set(value) { loginStore.putLoginData("webAdfsDomain", value); mWebAdfsDomain = value }
|
||||
private var mWebAdfsDomain: String? = null
|
||||
|
||||
var webIsHttpCufs: Boolean
|
||||
get() { mWebIsHttpCufs = mWebIsHttpCufs ?: loginStore.getLoginData("webIsHttpCufs", false); return mWebIsHttpCufs ?: false }
|
||||
set(value) { loginStore.putLoginData("webIsHttpCufs", value); mWebIsHttpCufs = value }
|
||||
private var mWebIsHttpCufs: Boolean? = null
|
||||
|
||||
var webIsScopedAdfs: Boolean
|
||||
get() { mWebIsScopedAdfs = mWebIsScopedAdfs ?: loginStore.getLoginData("webIsScopedAdfs", false); return mWebIsScopedAdfs ?: false }
|
||||
set(value) { loginStore.putLoginData("webIsScopedAdfs", value); mWebIsScopedAdfs = value }
|
||||
private var mWebIsScopedAdfs: Boolean? = null
|
||||
val webHost
|
||||
get() = webRealmData?.host
|
||||
|
||||
var webEmail: String?
|
||||
get() { mWebEmail = mWebEmail ?: loginStore.getLoginData("webEmail", null); return mWebEmail }
|
||||
@ -359,24 +325,24 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
|
||||
* If the time passes, the certificate needs to be POSTed again (if valid)
|
||||
* or re-generated.
|
||||
*/
|
||||
var webExpiryTime: Long
|
||||
get() { mWebExpiryTime = mWebExpiryTime ?: profile?.getStudentData("webExpiryTime", 0L); return mWebExpiryTime ?: 0L }
|
||||
set(value) { profile?.putStudentData("webExpiryTime", value); mWebExpiryTime = value }
|
||||
private var mWebExpiryTime: Long? = null
|
||||
var webExpiryTime: Map<String, String?> = mapOf()
|
||||
get() { mWebExpiryTime = mWebExpiryTime ?: loginStore.getLoginData("webExpiryTime", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebExpiryTime ?: mapOf() }
|
||||
set(value) { loginStore.putLoginData("webExpiryTime", app.gson.toJson(value)); mWebExpiryTime = value }
|
||||
private var mWebExpiryTime: Map<String, String?>? = null
|
||||
|
||||
/**
|
||||
* EfebSsoAuthCookie retrieved after posting a certificate
|
||||
*/
|
||||
var webAuthCookie: String?
|
||||
get() { mWebAuthCookie = mWebAuthCookie ?: profile?.getStudentData("webAuthCookie", null); return mWebAuthCookie }
|
||||
set(value) { profile?.putStudentData("webAuthCookie", value); mWebAuthCookie = value }
|
||||
private var mWebAuthCookie: String? = null
|
||||
var webAuthCookie: Map<String, String?> = mapOf()
|
||||
get() { mWebAuthCookie = mWebAuthCookie ?: loginStore.getLoginData("webAuthCookie", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebAuthCookie ?: mapOf() }
|
||||
set(value) { loginStore.putLoginData("webAuthCookie", app.gson.toJson(value)); mWebAuthCookie = value }
|
||||
private var mWebAuthCookie: Map<String, String?>? = null
|
||||
|
||||
/**
|
||||
* Permissions needed to get JSONs from home page
|
||||
*/
|
||||
var webPermissions: String?
|
||||
get() { mWebPermissions = mWebPermissions ?: profile?.getStudentData("webPermissions", null); return mWebPermissions }
|
||||
set(value) { profile?.putStudentData("webPermissions", value); mWebPermissions = value }
|
||||
private var mWebPermissions: String? = null
|
||||
var webPermissions: Map<String, String?> = mapOf()
|
||||
get() { mWebPermissions = mWebPermissions ?: loginStore.getLoginData("webPermissions", null)?.let { app.gson.fromJson(it, field.toMutableMap()::class.java) }; return mWebPermissions ?: mapOf() }
|
||||
set(value) { loginStore.putLoginData("webPermissions", app.gson.toJson(value)); mWebPermissions = value }
|
||||
private var mWebPermissions: Map<String, String?>? = null
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api.VulcanApiAt
|
||||
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
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLogin
|
||||
import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent
|
||||
@ -135,6 +136,15 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
|
||||
}
|
||||
|
||||
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) {
|
||||
completed()
|
||||
@ -190,6 +200,14 @@ 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 = ""
|
||||
|
||||
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) { _ ->
|
||||
|
@ -19,16 +19,22 @@ 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
|
||||
const val ENDPOINT_VULCAN_HEBE_PUSH_CONFIG = 3005
|
||||
const val ENDPOINT_VULCAN_HEBE_ADDRESSBOOK = 3010
|
||||
const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020
|
||||
const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030
|
||||
const val ENDPOINT_VULCAN_HEBE_GRADES = 3040
|
||||
const val ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY = 3050
|
||||
const val ENDPOINT_VULCAN_HEBE_HOMEWORK = 3060
|
||||
const val ENDPOINT_VULCAN_HEBE_NOTICES = 3070
|
||||
const val ENDPOINT_VULCAN_HEBE_ATTENDANCE = 3080
|
||||
const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3090
|
||||
const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3100
|
||||
const val ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER = 3200
|
||||
|
||||
val VulcanFeatures = listOf(
|
||||
// timetable
|
||||
@ -51,7 +57,8 @@ val VulcanFeatures = listOf(
|
||||
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_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(
|
||||
@ -64,6 +71,9 @@ val VulcanFeatures = listOf(
|
||||
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
|
||||
@ -91,13 +101,28 @@ val VulcanFeatures = listOf(
|
||||
), 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 ->
|
||||
!data.app.config.sync.tokenVulcanList.contains(data.profileId)
|
||||
},
|
||||
|
||||
/**
|
||||
* Lucky number - using WEB Main.
|
||||
*/
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
|
||||
ENDPOINT_VULCAN_WEB_LUCKY_NUMBERS to LOGIN_METHOD_VULCAN_WEB_MAIN
|
||||
), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN)).withShouldSync { data -> data.shouldSyncLuckyNumber() },
|
||||
), listOf(LOGIN_METHOD_VULCAN_WEB_MAIN))
|
||||
.withShouldSync { data -> data.shouldSyncLuckyNumber() }
|
||||
.withPriority(2),
|
||||
/**
|
||||
* Lucky number - using Hebe API
|
||||
*/
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_LUCKY_NUMBER, listOf(
|
||||
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER to LOGIN_METHOD_VULCAN_HEBE
|
||||
), listOf(LOGIN_METHOD_VULCAN_HEBE))
|
||||
.withShouldSync { data -> data.shouldSyncLuckyNumber() }
|
||||
.withPriority(1),
|
||||
|
||||
Feature(LOGIN_TYPE_VULCAN, FEATURE_ALWAYS_NEEDED, listOf(
|
||||
ENDPOINT_VULCAN_API_UPDATE_SEMESTER to LOGIN_METHOD_VULCAN_API,
|
||||
|
@ -22,10 +22,15 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
private var firstSemesterSync = false
|
||||
private val firstSemesterSyncExclude = listOf(
|
||||
ENDPOINT_VULCAN_HEBE_MAIN,
|
||||
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG,
|
||||
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK,
|
||||
ENDPOINT_VULCAN_HEBE_TIMETABLE,
|
||||
ENDPOINT_VULCAN_HEBE_EXAMS,
|
||||
ENDPOINT_VULCAN_HEBE_HOMEWORK,
|
||||
ENDPOINT_VULCAN_HEBE_NOTICES,
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX,
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT,
|
||||
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER
|
||||
)
|
||||
|
||||
init { run {
|
||||
@ -148,6 +153,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_MAIN)
|
||||
}
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_push_config)
|
||||
VulcanHebePushConfig(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_teachers)
|
||||
VulcanHebeAddressbook(data, lastSync, onSuccess)
|
||||
@ -164,10 +173,22 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_grades)
|
||||
VulcanHebeGrades(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades)
|
||||
VulcanHebeGradeSummary(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_HOMEWORK -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_homework)
|
||||
VulcanHebeHomework(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_NOTICES -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_notices)
|
||||
VulcanHebeNotices(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_ATTENDANCE -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
|
||||
VulcanHebeAttendance(data, lastSync, onSuccess)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
|
||||
VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_RECEIVED)
|
||||
@ -176,9 +197,9 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox)
|
||||
VulcanHebeMessages(data, lastSync, onSuccess).getMessages(Message.TYPE_SENT)
|
||||
}
|
||||
ENDPOINT_VULCAN_HEBE_ATTENDANCE -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
|
||||
VulcanHebeAttendance(data, lastSync, onSuccess)
|
||||
ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER -> {
|
||||
data.startProgress(R.string.edziennik_progress_endpoint_lucky_number)
|
||||
VulcanHebeLuckyNumber(data, lastSync, onSuccess)
|
||||
}
|
||||
else -> onSuccess(endpointId)
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data
|
||||
|
||||
import android.os.Build
|
||||
@ -42,7 +46,11 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
val profile
|
||||
get() = data.profile
|
||||
|
||||
fun getDateTime(json: JsonObject?, key: String, default: Long = System.currentTimeMillis()): Long {
|
||||
fun getDateTime(
|
||||
json: JsonObject?,
|
||||
key: String,
|
||||
default: Long = System.currentTimeMillis()
|
||||
): Long {
|
||||
val date = json.getJsonObject(key)
|
||||
return date.getLong("Timestamp") ?: return default
|
||||
}
|
||||
@ -142,6 +150,18 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
2
|
||||
}
|
||||
|
||||
fun isCurrentYear(date: Date): Boolean {
|
||||
return profile?.let { profile ->
|
||||
return@let date >= profile.dateSemester1Start
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun isCurrentYear(dateTime: Long): Boolean {
|
||||
return profile?.let { profile ->
|
||||
return@let dateTime >= profile.dateSemester1Start.inMillis
|
||||
} ?: false
|
||||
}
|
||||
|
||||
inline fun <reified T> apiRequest(
|
||||
tag: String,
|
||||
endpoint: String,
|
||||
@ -193,7 +213,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
val callback = object : JsonCallbackHandler() {
|
||||
override fun onSuccess(json: JsonObject?, response: Response?) {
|
||||
if (json == null) {
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
data.error(
|
||||
ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response)
|
||||
)
|
||||
return
|
||||
@ -201,19 +222,24 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
|
||||
val status = json.getJsonObject("Status")
|
||||
if (status?.getInt("Code") != 0) {
|
||||
data.error(ApiError(tag, ERROR_VULCAN_HEBE_OTHER)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json.toString()))
|
||||
data.error(
|
||||
ApiError(tag, ERROR_VULCAN_HEBE_OTHER)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json.toString())
|
||||
)
|
||||
}
|
||||
|
||||
val envelope = when (T::class.java) {
|
||||
val envelope = if (json.get("Envelope").isJsonNull && null is T)
|
||||
null as T
|
||||
else when (T::class.java) {
|
||||
JsonObject::class.java -> json.getJsonObject("Envelope") as T
|
||||
JsonArray::class.java -> json.getJsonArray("Envelope") as T
|
||||
java.lang.Boolean::class.java -> json.getBoolean("Envelope") as T
|
||||
else -> {
|
||||
data.error(ApiError(tag, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json)
|
||||
data.error(
|
||||
ApiError(tag, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response)
|
||||
.withApiResponse(json)
|
||||
)
|
||||
return
|
||||
}
|
||||
@ -222,7 +248,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
try {
|
||||
onSuccess(envelope, response)
|
||||
} catch (e: Exception) {
|
||||
data.error(ApiError(tag, EXCEPTION_VULCAN_HEBE_REQUEST)
|
||||
data.error(
|
||||
ApiError(tag, EXCEPTION_VULCAN_HEBE_REQUEST)
|
||||
.withResponse(response)
|
||||
.withThrowable(e)
|
||||
.withApiResponse(json)
|
||||
@ -231,7 +258,8 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
}
|
||||
|
||||
override fun onFailure(response: Response?, throwable: Throwable?) {
|
||||
data.error(ApiError(tag, ERROR_REQUEST_FAILURE)
|
||||
data.error(
|
||||
ApiError(tag, ERROR_REQUEST_FAILURE)
|
||||
.withResponse(response)
|
||||
.withThrowable(throwable)
|
||||
)
|
||||
@ -347,10 +375,15 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
if (folder != null)
|
||||
query["folder"] = folder.toString()
|
||||
|
||||
val semester1Start = profile?.dateSemester1Start?.inMillis
|
||||
|
||||
query["lastId"] = "-2147483648" // don't ask, it's just Vulcan
|
||||
query["pageSize"] = "500"
|
||||
query["lastSyncDate"] = LocalDateTime
|
||||
.ofInstant(Instant.ofEpochMilli(lastSync ?: 0), ZoneId.systemDefault())
|
||||
.ofInstant(
|
||||
Instant.ofEpochMilli(lastSync ?: semester1Start ?: 0),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
apiGet(tag, url, query) { json: JsonArray, response ->
|
||||
|
@ -11,12 +11,11 @@ import im.wangchao.mhttp.Request
|
||||
import im.wangchao.mhttp.Response
|
||||
import im.wangchao.mhttp.callback.TextCallbackHandler
|
||||
import pl.droidsonroids.jspoon.Jspoon
|
||||
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.login.CufsCertificate
|
||||
import pl.szczodrzynski.edziennik.data.api.models.ApiError
|
||||
import pl.szczodrzynski.edziennik.get
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorBlank
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import java.io.File
|
||||
@ -83,10 +82,12 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
onResult(symbol, STATE_NO_REGISTER)
|
||||
return
|
||||
}
|
||||
if (!validateCallback(text, response, jsonResponse = false)) {
|
||||
if (!validateCallback(symbol, text, response, jsonResponse = false)) {
|
||||
return
|
||||
}
|
||||
data.webExpiryTime = Date.fromIso(certificate.expiryDate) / 1000L
|
||||
data.webExpiryTime = data.webExpiryTime.toMutableMap().also { map ->
|
||||
map[symbol] = (Date.fromIso(certificate.expiryDate) / 1000L).toString()
|
||||
}
|
||||
onResult(symbol, STATE_SUCCESS)
|
||||
}
|
||||
|
||||
@ -120,7 +121,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
fun getStartPage(symbol: String = data.symbol ?: "default", postErrors: Boolean = true, onSuccess: (html: String, schoolSymbols: List<String>) -> Unit) {
|
||||
val callback = object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response?) {
|
||||
if (!validateCallback(text, response, jsonResponse = false) || text == null) {
|
||||
if (!validateCallback(symbol, text, response, jsonResponse = false) || text == null) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -136,7 +137,30 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
}
|
||||
}
|
||||
|
||||
data.webPermissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
|
||||
data.webPermissions = data.webPermissions.toMutableMap().also { map ->
|
||||
val permissions = Regexes.VULCAN_WEB_PERMISSIONS.find(text)?.let { it[1] }
|
||||
if (permissions?.isNotBlank() == true) {
|
||||
val studentId = permissions.split("|")
|
||||
.getOrNull(0)
|
||||
?.base64DecodeToString()
|
||||
?.toJsonObject()
|
||||
?.getJsonArray("AuthInfos")
|
||||
?.asJsonObjectList()
|
||||
?.flatMap { authInfo ->
|
||||
authInfo.getJsonArray("UczenIds")
|
||||
?.map { it.asInt }
|
||||
?: listOf()
|
||||
}
|
||||
?.firstOrNull()
|
||||
?.toString()
|
||||
data.app.cookieJar.set(
|
||||
data.webHost ?: "vulcan.net.pl",
|
||||
"idBiezacyUczen",
|
||||
studentId
|
||||
)
|
||||
}
|
||||
map[symbol] = permissions
|
||||
}
|
||||
|
||||
val schoolSymbols = mutableListOf<String>()
|
||||
val clientUrl = "://uonetplus-uczen.${data.webHost}/$symbol/"
|
||||
@ -144,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.indexOf('"', startIndex = startIndex)
|
||||
val schoolSymbol = text.substring(startIndex, endIndex)
|
||||
schoolSymbols += schoolSymbol
|
||||
clientIndex = text.indexOf(clientUrl, startIndex = endIndex)
|
||||
@ -186,7 +210,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
private fun validateCallback(text: String?, response: Response?, jsonResponse: Boolean = true): Boolean {
|
||||
private fun validateCallback(symbol: String, text: String?, response: Response?, jsonResponse: Boolean = true): Boolean {
|
||||
if (text == null) {
|
||||
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
|
||||
.withResponse(response))
|
||||
@ -207,11 +231,13 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
|
||||
val cookies = data.app.cookieJar.getAll(data.webHost ?: "vulcan.net.pl")
|
||||
val authCookie = cookies["EfebSsoAuthCookie"]
|
||||
if ((authCookie == null || authCookie == "null") && data.webAuthCookie != null) {
|
||||
data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie)
|
||||
if ((authCookie == null || authCookie == "null") && data.webAuthCookie[symbol] != null) {
|
||||
data.app.cookieJar.set(data.webHost ?: "vulcan.net.pl", "EfebSsoAuthCookie", data.webAuthCookie[symbol])
|
||||
}
|
||||
else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie) {
|
||||
data.webAuthCookie = authCookie
|
||||
else if (authCookie.isNotNullNorBlank() && authCookie != "null" && authCookie != data.webAuthCookie[symbol]) {
|
||||
data.webAuthCookie = data.webAuthCookie.toMutableMap().also { map ->
|
||||
map[symbol] = authCookie
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -250,7 +276,7 @@ open class VulcanWebMain(open val data: DataVulcan, open val lastSync: Long?) {
|
||||
|
||||
val callback = object : TextCallbackHandler() {
|
||||
override fun onSuccess(text: String?, response: Response?) {
|
||||
if (!validateCallback(text, response))
|
||||
if (!validateCallback(data.symbol ?: "default", text, response))
|
||||
return
|
||||
|
||||
try {
|
||||
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
enum class HebeFilterType(val endpoint: String) {
|
||||
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import androidx.core.util.set
|
||||
|
@ -85,6 +85,7 @@ class VulcanHebeAttendance(
|
||||
subjectId = subjectId,
|
||||
addedDate = addedDate
|
||||
).also {
|
||||
it.lessonTopic = attendance.getString("Topic")
|
||||
it.lessonNumber = lessonNumber
|
||||
it.isCounted = isCounted
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
@ -36,6 +40,8 @@ class VulcanHebeExams(
|
||||
?: -1
|
||||
val topic = exam.getString("Content")?.trim() ?: ""
|
||||
|
||||
if (!isCurrentYear(eventDate)) return@forEach
|
||||
|
||||
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
|
||||
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
|
||||
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.DAY
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Grade
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class VulcanHebeGradeSummary(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeGradeSummary"
|
||||
}
|
||||
|
||||
init {
|
||||
val entries = mapOf(
|
||||
"Entry_1" to
|
||||
if (data.studentSemesterNumber == 1)
|
||||
Grade.TYPE_SEMESTER1_PROPOSED
|
||||
else Grade.TYPE_SEMESTER2_PROPOSED,
|
||||
"Entry_2" to
|
||||
if (data.studentSemesterNumber == 1)
|
||||
Grade.TYPE_SEMESTER1_FINAL
|
||||
else Grade.TYPE_SEMESTER2_FINAL
|
||||
)
|
||||
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_GRADE_SUMMARY,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { grade ->
|
||||
val subjectId = getSubjectId(grade, "Subject") ?: return@forEach
|
||||
val addedDate = getDateTime(grade, "DateModify")
|
||||
|
||||
entries.onEach { (key, type) ->
|
||||
val id = subjectId * -100 - type
|
||||
val entry = grade.getString(key) ?: return@onEach
|
||||
val value = Utils.getGradeValue(entry)
|
||||
val color = Utils.getVulcanGradeColor(entry)
|
||||
|
||||
val gradeObject = Grade(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
name = entry,
|
||||
type = type,
|
||||
value = value,
|
||||
weight = 0f,
|
||||
color = color,
|
||||
category = "",
|
||||
description = null,
|
||||
comment = null,
|
||||
semester = data.studentSemesterNumber,
|
||||
teacherId = -1,
|
||||
subjectId = subjectId,
|
||||
addedDate = addedDate
|
||||
)
|
||||
|
||||
data.gradeList.add(gradeObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_GRADE,
|
||||
id,
|
||||
profile?.empty ?: true,
|
||||
profile?.empty ?: true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY, 1 * DAY)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_GRADE_SUMMARY)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
|
@ -1,5 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.asJsonObjectList
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_HOMEWORK
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_HOMEWORK
|
||||
@ -7,8 +12,10 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
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.getJsonArray
|
||||
import pl.szczodrzynski.edziennik.getLong
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
|
||||
class VulcanHebeHomework(
|
||||
override val data: DataVulcan,
|
||||
@ -34,6 +41,8 @@ class VulcanHebeHomework(
|
||||
val teamId = data.teamClass?.id ?: -1
|
||||
val topic = exam.getString("Content")?.trim() ?: ""
|
||||
|
||||
if (!isCurrentYear(eventDate)) return@forEach
|
||||
|
||||
val lessonList = data.db.timetableDao().getAllForDateNow(profileId, eventDate)
|
||||
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
|
||||
|
||||
@ -50,6 +59,22 @@ class VulcanHebeHomework(
|
||||
teamId = teamId
|
||||
)
|
||||
|
||||
val attachments = exam.getJsonArray("Attachments")
|
||||
?.asJsonObjectList()
|
||||
?: return@forEach
|
||||
|
||||
for (attachment in attachments) {
|
||||
val fileName = attachment.getString("Name") ?: continue
|
||||
val url = attachment.getString("Link") ?: continue
|
||||
val attachmentName = "$fileName:$url"
|
||||
val attachmentId = Utils.crc32(attachmentName.toByteArray())
|
||||
|
||||
eventObject.addAttachment(
|
||||
id = attachmentId,
|
||||
name = attachmentName
|
||||
)
|
||||
}
|
||||
|
||||
data.eventList.add(eventObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
import pl.szczodrzynski.edziennik.getInt
|
||||
import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||
import pl.szczodrzynski.edziennik.utils.models.Week
|
||||
|
||||
class VulcanHebeLuckyNumber(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeLuckyNumber"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGet(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER,
|
||||
query = mapOf(
|
||||
"constituentId" to data.studentConstituentId.toString(),
|
||||
"day" to Date.getToday().stringY_m_d
|
||||
)
|
||||
) { lucky: JsonObject?, _ ->
|
||||
// sync tomorrow if lucky number set or is weekend or afternoon
|
||||
var nextSync = Date.getToday().stepForward(0, 0, 1).inMillis
|
||||
if (lucky == null) {
|
||||
if (Date.getToday().weekDay <= Week.FRIDAY && Time.getNow().hour < 12) {
|
||||
// working days morning, sync always
|
||||
nextSync = SYNC_ALWAYS
|
||||
}
|
||||
}
|
||||
else {
|
||||
val luckyNumberDate = Date.fromY_m_d(lucky.getString("Day")) ?: Date.getToday()
|
||||
val luckyNumber = lucky.getInt("Number") ?: -1
|
||||
val luckyNumberObject = LuckyNumber(
|
||||
profileId = profileId,
|
||||
date = luckyNumberDate,
|
||||
number = luckyNumber
|
||||
)
|
||||
|
||||
data.luckyNumberList.add(luckyNumberObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_LUCKY_NUMBER,
|
||||
luckyNumberObject.date.value.toLong(),
|
||||
true,
|
||||
profile?.empty ?: false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER, syncAt = nextSync)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import androidx.core.util.set
|
||||
@ -13,6 +17,7 @@ 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.data.db.entity.Message.Companion.TYPE_SENT
|
||||
import pl.szczodrzynski.edziennik.utils.Utils
|
||||
import pl.szczodrzynski.navlib.crc16
|
||||
import kotlin.text.replace
|
||||
|
||||
@ -76,6 +81,8 @@ class VulcanHebeMessages(
|
||||
val sentDate = getDateTime(message, "DateSent")
|
||||
val readDate = getDateTime(message, "DateRead", default = 0)
|
||||
|
||||
if (!isCurrentYear(sentDate)) return@forEach
|
||||
|
||||
val messageObject = Message(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
@ -104,6 +111,23 @@ class VulcanHebeMessages(
|
||||
data.messageRecipientList.add(messageRecipientObject)
|
||||
}
|
||||
|
||||
val attachments = message.getJsonArray("Attachments")
|
||||
?.asJsonObjectList()
|
||||
?: return@forEach
|
||||
|
||||
for (attachment in attachments) {
|
||||
val fileName = attachment.getString("Name") ?: continue
|
||||
val url = attachment.getString("Link") ?: continue
|
||||
val attachmentName = "$fileName:$url"
|
||||
val attachmentId = Utils.crc32(attachmentName.toByteArray())
|
||||
|
||||
messageObject.addAttachment(
|
||||
id = attachmentId,
|
||||
name = attachmentName,
|
||||
size = -1
|
||||
)
|
||||
}
|
||||
|
||||
data.messageList.add(messageObject)
|
||||
data.setSeenMetadataList.add(
|
||||
Metadata(
|
||||
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Kacper Ziubryniewicz 2021-2-22
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_NOTICES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_NOTICES
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Notice
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
|
||||
class VulcanHebeNotices(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeNotices"
|
||||
}
|
||||
|
||||
init {
|
||||
apiGetList(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_NOTICES,
|
||||
HebeFilterType.BY_PUPIL,
|
||||
lastSync = lastSync
|
||||
) { list, _ ->
|
||||
list.forEach { notice ->
|
||||
val id = notice.getLong("Id") ?: return@forEach
|
||||
val type = when (notice.getBoolean("Positive")) {
|
||||
true -> Notice.TYPE_POSITIVE
|
||||
else -> Notice.TYPE_NEUTRAL
|
||||
}
|
||||
val date = getDate(notice, "DateValid") ?: return@forEach
|
||||
val semester = profile?.dateToSemester(date) ?: return@forEach
|
||||
val text = notice.getString("Content") ?: ""
|
||||
val category = notice.getJsonObject("Category")?.getString("Name")
|
||||
val points = notice.getFloat("Points")
|
||||
val teacherId = getTeacherId(notice, "Creator") ?: -1
|
||||
val addedDate = getDateTime(notice, "DateModify")
|
||||
|
||||
if (!isCurrentYear(date)) return@forEach
|
||||
|
||||
val noticeObject = Notice(
|
||||
profileId = profileId,
|
||||
id = id,
|
||||
type = type,
|
||||
semester = semester,
|
||||
text = text,
|
||||
category = category,
|
||||
points = points,
|
||||
teacherId = teacherId,
|
||||
addedDate = addedDate
|
||||
)
|
||||
|
||||
data.noticeList.add(noticeObject)
|
||||
data.metadataList.add(
|
||||
Metadata(
|
||||
profileId,
|
||||
Metadata.TYPE_NOTICE,
|
||||
id,
|
||||
profile?.empty ?: true,
|
||||
profile?.empty ?: true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_NOTICES, SYNC_ALWAYS)
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_NOTICES)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonPrimitive
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_PUSH_ALL
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_PUSH_CONFIG
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
|
||||
|
||||
class VulcanHebePushConfig(
|
||||
override val data: DataVulcan,
|
||||
override val lastSync: Long?,
|
||||
val onSuccess: (endpointId: Int) -> Unit
|
||||
) : VulcanHebe(data, lastSync) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebePushConfig"
|
||||
}
|
||||
|
||||
init {
|
||||
apiPost(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_PUSH_ALL,
|
||||
payload = JsonPrimitive("on")
|
||||
) { _: Boolean, _ ->
|
||||
// sync always: this endpoint has .shouldSync set
|
||||
data.setSyncNext(ENDPOINT_VULCAN_HEBE_PUSH_CONFIG, SYNC_ALWAYS)
|
||||
data.app.config.sync.tokenVulcanList =
|
||||
data.app.config.sync.tokenVulcanList + profileId
|
||||
onSuccess(ENDPOINT_VULCAN_HEBE_PUSH_CONFIG)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-22.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_SEND
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
|
||||
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
|
||||
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 VulcanHebeSendMessage(
|
||||
override val data: DataVulcan,
|
||||
val recipients: List<Teacher>,
|
||||
val subject: String,
|
||||
val text: String,
|
||||
val onSuccess: () -> Unit
|
||||
) : VulcanHebe(data, null) {
|
||||
companion object {
|
||||
const val TAG = "VulcanHebeSendMessage"
|
||||
}
|
||||
|
||||
init {
|
||||
val recipientsArray = JsonArray()
|
||||
recipients.forEach { teacher ->
|
||||
recipientsArray += JsonObject(
|
||||
"Address" to teacher.fullNameLastFirst,
|
||||
"LoginId" to (teacher.loginId?.toIntOrNull() ?: return@forEach),
|
||||
"Initials" to teacher.initialsLastFirst,
|
||||
"AddressHash" to teacher.fullNameLastFirst.sha1Hex()
|
||||
)
|
||||
}
|
||||
|
||||
val senderName = (profile?.accountName ?: profile?.studentNameLong)
|
||||
?.swapFirstLastName() ?: ""
|
||||
val sender = JsonObject(
|
||||
"Address" to senderName,
|
||||
"LoginId" to data.studentLoginId.toString(),
|
||||
"Initials" to senderName.getNameInitials(),
|
||||
"AddressHash" to senderName.sha1Hex()
|
||||
)
|
||||
|
||||
apiPost(
|
||||
TAG,
|
||||
VULCAN_HEBE_ENDPOINT_MESSAGES_SEND,
|
||||
payload = JsonObject(
|
||||
"Status" to 1,
|
||||
"Sender" to sender,
|
||||
"DateSent" to null,
|
||||
"DateRead" to null,
|
||||
"Content" to text,
|
||||
"Receiver" to recipientsArray,
|
||||
"Id" to 0,
|
||||
"Subject" to subject,
|
||||
"Attachments" to null,
|
||||
"Self" to null
|
||||
)
|
||||
) { json: JsonObject, _ ->
|
||||
val messageId = json.getLong("Id")
|
||||
|
||||
if (messageId == null) {
|
||||
// TODO error
|
||||
return@apiPost
|
||||
}
|
||||
|
||||
VulcanHebeMessages(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()
|
||||
}.getMessages(Message.TYPE_SENT)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
|
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Copyright (c) Kuba Szczodrzyński 2021-2-20.
|
||||
*/
|
||||
|
||||
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
|
@ -13,7 +13,7 @@ import pl.szczodrzynski.edziennik.getString
|
||||
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
|
||||
import pl.szczodrzynski.edziennik.utils.models.Date
|
||||
import pl.szczodrzynski.fslogin.FSLogin
|
||||
import pl.szczodrzynski.fslogin.realm.CufsRealm
|
||||
import pl.szczodrzynski.fslogin.realm.toRealm
|
||||
|
||||
class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
companion object {
|
||||
@ -30,8 +30,7 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
}
|
||||
else {
|
||||
if (data.symbol.isNotNullNorEmpty()
|
||||
&& data.webType.isNotNullNorEmpty()
|
||||
&& data.webHost.isNotNullNorEmpty()
|
||||
&& data.webRealmData != null
|
||||
&& (data.webEmail.isNotNullNorEmpty() || data.webUsername.isNotNullNorEmpty())
|
||||
&& data.webPassword.isNotNullNorEmpty()) {
|
||||
try {
|
||||
@ -56,32 +55,28 @@ class VulcanLoginWebMain(val data: DataVulcan, val onSuccess: () -> Unit) {
|
||||
data.symbol = getString("symbol")
|
||||
remove("symbol")
|
||||
}
|
||||
// 4.6 - form inputs renamed
|
||||
if (has("email")) {
|
||||
data.webEmail = getString("email")
|
||||
remove("email")
|
||||
}
|
||||
if (has("username")) {
|
||||
data.webUsername = getString("username")
|
||||
remove("username")
|
||||
}
|
||||
if (has("password")) {
|
||||
data.webPassword = getString("password")
|
||||
remove("password")
|
||||
}
|
||||
}
|
||||
|
||||
if (data.symbol == null && data.webRealmData != null) {
|
||||
data.symbol = data.webRealmData?.symbol
|
||||
}
|
||||
}
|
||||
|
||||
private fun loginWithCredentials(): Boolean {
|
||||
val realm = when (data.webType) {
|
||||
"cufs" -> CufsRealm(
|
||||
host = data.webHost ?: return false,
|
||||
symbol = data.symbol ?: "default",
|
||||
httpCufs = data.webIsHttpCufs
|
||||
)
|
||||
"adfs" -> CufsRealm(
|
||||
host = data.webHost ?: return false,
|
||||
symbol = data.symbol ?: "default",
|
||||
httpCufs = data.webIsHttpCufs
|
||||
).toAdfsRealm(id = data.webAdfsId ?: return false)
|
||||
"adfslight" -> CufsRealm(
|
||||
host = data.webHost ?: return false,
|
||||
symbol = data.symbol ?: "default",
|
||||
httpCufs = data.webIsHttpCufs
|
||||
).toAdfsLightRealm(
|
||||
id = data.webAdfsId ?: return false,
|
||||
domain = data.webAdfsDomain ?: "adfslight",
|
||||
isScoped = data.webIsScopedAdfs
|
||||
)
|
||||
else -> return false
|
||||
}
|
||||
val realm = data.webRealmData?.toRealm() ?: return false
|
||||
|
||||
val certificate = web.readCertificate()?.let { web.parseCertificate(it) }
|
||||
if (certificate != null && Date.fromIso(certificate.expiryDate) > System.currentTimeMillis()) {
|
||||
|
@ -348,8 +348,8 @@ class SzkolnyApi(val app: App) : CoroutineScope {
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getPlatforms(registerName: String): List<LoginInfo.Platform> {
|
||||
val response = api.appLoginPlatforms(registerName).execute()
|
||||
fun getRealms(registerName: String): List<LoginInfo.Platform> {
|
||||
val response = api.fsLoginRealms(registerName).execute()
|
||||
|
||||
return parseResponse(response)
|
||||
}
|
||||
|
@ -33,12 +33,12 @@ interface SzkolnyService {
|
||||
@POST("feedbackMessage")
|
||||
fun feedbackMessage(@Body request: FeedbackMessageRequest): Call<ApiResponse<FeedbackMessageResponse>>
|
||||
|
||||
@GET("appLogin/platforms/{registerName}")
|
||||
fun appLoginPlatforms(@Path("registerName") registerName: String): Call<ApiResponse<List<LoginInfo.Platform>>>
|
||||
|
||||
@GET("firebase/token/{registerName}")
|
||||
fun firebaseToken(@Path("registerName") registerName: String): Call<ApiResponse<String>>
|
||||
|
||||
@GET("registerAvailability")
|
||||
fun registerAvailability(): Call<ApiResponse<Map<String, RegisterAvailabilityStatus>>>
|
||||
|
||||
@GET("fsLogin/{registerName}")
|
||||
fun fsLoginRealms(@Path("registerName") registerName: String): Call<ApiResponse<List<LoginInfo.Platform>>>
|
||||
}
|
||||
|
@ -46,6 +46,6 @@ object Signing {
|
||||
|
||||
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
|
||||
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
|
||||
return "$param1.MTIzNDU2Nzg5MD5LwH9bSc===.$param2".sha256()
|
||||
return "$param1.MTIzNDU2Nzg5MDAwogntL3===.$param2".sha256()
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,25 @@ open class Event(
|
||||
var attachmentIds: MutableList<Long>? = null
|
||||
var attachmentNames: MutableList<String>? = null
|
||||
|
||||
/**
|
||||
* Add an attachment
|
||||
* @param id attachment ID
|
||||
* @param name file name incl. extension
|
||||
* @return a Event to which the attachment has been added
|
||||
*/
|
||||
fun addAttachment(id: Long, name: String): Event {
|
||||
if (attachmentIds == null) attachmentIds = mutableListOf()
|
||||
if (attachmentNames == null) attachmentNames = mutableListOf()
|
||||
attachmentIds?.add(id)
|
||||
attachmentNames?.add(name)
|
||||
return this
|
||||
}
|
||||
|
||||
fun clearAttachments() {
|
||||
attachmentIds = null
|
||||
attachmentNames = null
|
||||
}
|
||||
|
||||
@Ignore
|
||||
var showAsUnseen: Boolean? = null
|
||||
|
||||
|
@ -59,6 +59,7 @@ class LoginStore(
|
||||
is Long -> putLoginData(key, o)
|
||||
is Float -> putLoginData(key, o)
|
||||
is Boolean -> putLoginData(key, o)
|
||||
is Bundle -> putLoginData(key, o.toJsonObject())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,9 @@ open class Profile(
|
||||
dateSemester2Start.year -= diff
|
||||
dateYearEnd.year -= diff
|
||||
}
|
||||
return Date.getToday() >= dateYearEnd && Date.getToday().year > studentSchoolYearStart
|
||||
return App.config.archiverEnabled
|
||||
&& Date.getToday() >= dateYearEnd
|
||||
&& Date.getToday().year > studentSchoolYearStart
|
||||
}
|
||||
fun isBeforeYear() = false && Date.getToday() < dateSemester1Start
|
||||
|
||||
|
@ -11,6 +11,7 @@ import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.fixName
|
||||
import pl.szczodrzynski.edziennik.getNameInitials
|
||||
import pl.szczodrzynski.edziennik.join
|
||||
import java.util.*
|
||||
|
||||
@ -180,6 +181,9 @@ open class Teacher {
|
||||
@delegate:Ignore
|
||||
val fullNameLastFirst by lazy { "$surname $name".fixName() }
|
||||
|
||||
@delegate:Ignore
|
||||
val initialsLastFirst by lazy { fullNameLastFirst.getNameInitials() }
|
||||
|
||||
val shortName: String
|
||||
get() = (if (name == null || name?.length == 0) "" else name!![0].toString()) + "." + surname
|
||||
|
||||
|
@ -29,6 +29,7 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val messa
|
||||
val data = message.data.getString("data")?.toJsonObject() ?: return@run
|
||||
val type = data.getString("table") ?: return@run
|
||||
val studentId = data.getInt("pupilid")
|
||||
val loginId = data.getInt("loginid")
|
||||
|
||||
/* pl.vulcan.uonetmobile.auxilary.enums.CDCPushEnum */
|
||||
val viewIdPair = when (type.toLowerCase(Locale.ROOT)) {
|
||||
@ -42,8 +43,9 @@ class SzkolnyVulcanFirebase(val app: App, val profiles: List<Profile>, val messa
|
||||
}
|
||||
|
||||
val tasks = profiles.filter {
|
||||
it.loginStoreType == LOGIN_TYPE_VULCAN &&
|
||||
it.getStudentData("studentId", 0) == studentId
|
||||
it.loginStoreType == LOGIN_TYPE_VULCAN
|
||||
&& (it.getStudentData("studentId", 0) == studentId
|
||||
|| it.getStudentData("studentLoginId", 0) == loginId)
|
||||
}.map {
|
||||
EdziennikTask.syncProfile(it.id, listOf(viewIdPair))
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ import kotlin.coroutines.CoroutineContext
|
||||
class ProfileRemoveDialog(
|
||||
val activity: MainActivity,
|
||||
val profileId: Int,
|
||||
val profileName: String
|
||||
val profileName: String,
|
||||
val noProfileRemoval: Boolean = false
|
||||
) : CoroutineScope {
|
||||
companion object {
|
||||
private const val TAG = "ProfileRemoveDialog"
|
||||
@ -52,7 +53,6 @@ class ProfileRemoveDialog(
|
||||
app.db.attendanceDao().clear(profileId)
|
||||
app.db.attendanceTypeDao().clear(profileId)
|
||||
app.db.classroomDao().clear(profileId)
|
||||
app.db.configDao().clear(profileId)
|
||||
app.db.endpointTimerDao().clear(profileId)
|
||||
app.db.eventDao().clear(profileId)
|
||||
app.db.eventTypeDao().clear(profileId)
|
||||
@ -65,23 +65,27 @@ class ProfileRemoveDialog(
|
||||
app.db.messageRecipientDao().clear(profileId)
|
||||
app.db.noticeDao().clear(profileId)
|
||||
app.db.noticeTypeDao().clear(profileId)
|
||||
app.db.noticeTypeDao().clear(profileId)
|
||||
app.db.notificationDao().clear(profileId)
|
||||
app.db.subjectDao().clear(profileId)
|
||||
app.db.teacherAbsenceDao().clear(profileId)
|
||||
app.db.teacherAbsenceDao().clear(profileId)
|
||||
app.db.teacherAbsenceTypeDao().clear(profileId)
|
||||
app.db.teacherDao().clear(profileId)
|
||||
app.db.teamDao().clear(profileId)
|
||||
app.db.timetableDao().clear(profileId)
|
||||
|
||||
app.db.metadataDao().deleteAll(profileId)
|
||||
|
||||
if (noProfileRemoval)
|
||||
return@async
|
||||
|
||||
app.db.configDao().clear(profileId)
|
||||
|
||||
val loginStoreId = profileObject.loginStoreId
|
||||
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
|
||||
if (profilesUsingLoginStore.size == 1) {
|
||||
app.db.loginStoreDao().remove(loginStoreId)
|
||||
}
|
||||
app.db.profileDao().remove(profileId)
|
||||
app.db.metadataDao().deleteAll(profileId)
|
||||
|
||||
if (App.profileId == profileId) {
|
||||
app.profileLoadLast { }
|
||||
|
@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
|
||||
import pl.szczodrzynski.edziennik.*
|
||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||
import pl.szczodrzynski.edziennik.databinding.LabFragmentBinding
|
||||
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
|
||||
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
|
||||
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
|
||||
import pl.szczodrzynski.fslogin.decode
|
||||
@ -45,6 +46,8 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
}
|
||||
|
||||
override fun onPageCreated(): Boolean {
|
||||
b.app = app
|
||||
|
||||
b.last10unseen.onClick {
|
||||
launch(Dispatchers.Default) {
|
||||
val events = app.db.eventDao().getAllNow(App.profileId)
|
||||
@ -58,6 +61,15 @@ class LabPageFragment : LazyFragment(), CoroutineScope {
|
||||
b.rodo.onClick {
|
||||
app.db.teacherDao().query(SimpleSQLiteQuery("UPDATE teachers SET teacherSurname = \"\" WHERE profileId = ${App.profileId}"))
|
||||
}
|
||||
|
||||
b.fullSync.onClick {
|
||||
app.db.query(SimpleSQLiteQuery("UPDATE profiles SET empty = 1 WHERE profileId = ${App.profileId}"))
|
||||
app.db.query(SimpleSQLiteQuery("DELETE FROM endpointTimers WHERE profileId = ${App.profileId}"))
|
||||
}
|
||||
|
||||
b.clearProfile.onClick {
|
||||
ProfileRemoveDialog(activity, App.profileId, "FAKE", noProfileRemoval = true)
|
||||
}
|
||||
|
||||
b.removeHomework.onClick {
|
||||
app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}")
|
||||
|
@ -14,7 +14,6 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.gson.JsonParser
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.utils.paddingDp
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
@ -72,7 +71,7 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
||||
val platformGuideText = arguments?.getString("platformGuideText")
|
||||
val platformDescription = arguments?.getString("platformDescription")
|
||||
val platformFormFields = arguments?.getString("platformFormFields")?.split(";")
|
||||
val platformApiData = arguments?.getString("platformApiData")?.let { JsonParser().parse(it)?.asJsonObject }
|
||||
val platformRealmData = arguments?.getString("platformRealmData")?.toJsonObject()
|
||||
|
||||
b.title.setText(R.string.login_form_title_format, app.getString(register.registerName))
|
||||
b.subTitle.text = platformName ?: app.getString(mode.name)
|
||||
@ -159,9 +158,7 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
||||
payload.putBoolean("fakeLogin", true)
|
||||
}
|
||||
|
||||
platformApiData?.entrySet()?.forEach {
|
||||
payload.putString(it.key, it.value.asString)
|
||||
}
|
||||
payload.putBundle("webRealmData", platformRealmData?.toBundle())
|
||||
|
||||
var hasErrors = false
|
||||
credentials.forEach { (credential, b) ->
|
||||
|
@ -6,12 +6,12 @@ package pl.szczodrzynski.edziennik.ui.modules.login
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.google.gson.JsonObject
|
||||
import com.mikepenz.iconics.typeface.IIcon
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import pl.szczodrzynski.edziennik.R
|
||||
import pl.szczodrzynski.edziennik.data.api.*
|
||||
import pl.szczodrzynski.edziennik.ui.modules.grades.models.ExpandableItemModel
|
||||
import pl.szczodrzynski.fslogin.realm.RealmData
|
||||
|
||||
object LoginInfo {
|
||||
|
||||
@ -191,9 +191,9 @@ object LoginInfo {
|
||||
isDevOnly = true,
|
||||
isPlatformSelection = true,
|
||||
credentials = listOf(
|
||||
getEmailCredential("webEmail"),
|
||||
getEmailCredential("email"),
|
||||
FormField(
|
||||
keyName = "webUsername",
|
||||
keyName = "username",
|
||||
name = R.string.login_hint_username,
|
||||
icon = CommunityMaterial.Icon.cmd_account_outline,
|
||||
emptyText = R.string.login_error_no_username,
|
||||
@ -203,7 +203,7 @@ object LoginInfo {
|
||||
validationRegex = "[A-Z]{7}[0-9]+",
|
||||
caseMode = FormField.CaseMode.UPPER_CASE
|
||||
),
|
||||
getPasswordCredential("webPassword")
|
||||
getPasswordCredential("password")
|
||||
),
|
||||
errorCodes = mapOf()
|
||||
)
|
||||
@ -407,15 +407,12 @@ object LoginInfo {
|
||||
|
||||
data class Platform(
|
||||
val id: Int,
|
||||
val loginType: Int,
|
||||
val loginMode: Int,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val guideText: String?,
|
||||
val icon: String,
|
||||
val screenshot: String?,
|
||||
val formFields: List<String>,
|
||||
val apiData: JsonObject
|
||||
val realmData: RealmData
|
||||
)
|
||||
|
||||
open class BaseCredential(
|
||||
|
@ -60,12 +60,12 @@ class LoginPlatformListFragment : Fragment(), CoroutineScope {
|
||||
|
||||
adapter = LoginPlatformAdapter(activity) { platform ->
|
||||
nav.navigate(R.id.loginFormFragment, Bundle(
|
||||
"loginType" to platform.loginType,
|
||||
"loginMode" to platform.loginMode,
|
||||
"loginType" to loginType,
|
||||
"loginMode" to loginMode,
|
||||
"platformName" to platform.name,
|
||||
"platformDescription" to platform.description,
|
||||
"platformFormFields" to platform.formFields.joinToString(";"),
|
||||
"platformApiData" to platform.apiData.toString()
|
||||
"platformRealmData" to app.gson.toJson(platform.realmData)
|
||||
), activity.navOptions)
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ class LoginPlatformListFragment : Fragment(), CoroutineScope {
|
||||
val platforms = LoginInfo.platformList[mode.name]
|
||||
?: run {
|
||||
api.runCatching(activity) {
|
||||
getPlatforms(register.internalName)
|
||||
getRealms(register.internalName)
|
||||
} ?: run {
|
||||
nav.navigateUp()
|
||||
return@launch
|
||||
|
@ -258,7 +258,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
|
||||
.color(IconicsColor.colorInt(iconColor))
|
||||
)
|
||||
.setOnClickAction(() -> {
|
||||
new ProfileRemoveDialog(activity, app.getProfile().getId(), app.getProfile().getName());
|
||||
new ProfileRemoveDialog(activity, app.getProfile().getId(), app.getProfile().getName(), false);
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -121,19 +121,21 @@ class AttachmentsView @JvmOverloads constructor(
|
||||
// open file by name, or first part before ':' (Vulcan OneDrive)
|
||||
Utils.openFile(context, File(Utils.getStorageDir(), attachment.name.substringBefore(":")))
|
||||
return
|
||||
}
|
||||
} else if (attachment.name.contains(":")) {
|
||||
Utils.openUrl(context, attachment.name.substringAfter(":"))
|
||||
} else {
|
||||
attachment.isDownloading = true
|
||||
(adapter as? AttachmentAdapter)?.let {
|
||||
it.notifyItemChanged(it.items.indexOf(attachment))
|
||||
}
|
||||
|
||||
attachment.isDownloading = true
|
||||
(adapter as? AttachmentAdapter)?.let {
|
||||
it.notifyItemChanged(it.items.indexOf(attachment))
|
||||
}
|
||||
|
||||
EdziennikTask.attachmentGet(
|
||||
EdziennikTask.attachmentGet(
|
||||
attachment.profileId,
|
||||
attachment.owner,
|
||||
attachment.id,
|
||||
attachment.name
|
||||
).enqueue(context)
|
||||
).enqueue(context)
|
||||
}
|
||||
}
|
||||
|
||||
private val lastUpdate: Long = 0
|
||||
|
@ -7,6 +7,10 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<data>
|
||||
<variable name="app" type="pl.szczodrzynski.edziennik.App"/>
|
||||
</data>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@ -41,6 +45,22 @@
|
||||
android:text="Set last 10 as unseen"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/fullSync"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="Full sync and empty profile"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/clearProfile"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="Clear all profile data"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/rodo"
|
||||
android:layout_width="match_parent"
|
||||
@ -66,6 +86,7 @@
|
||||
android:id="@+id/unarchive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="Unarchive this profile"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
@ -74,6 +95,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={app.config.archiverEnabled}"
|
||||
android:text="Archiver enabled" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</layout>
|
||||
|
@ -5,8 +5,8 @@ buildscript {
|
||||
kotlin_version = '1.4.30'
|
||||
|
||||
release = [
|
||||
versionName: "4.5",
|
||||
versionCode: 4050099
|
||||
versionName: "4.6-beta.1",
|
||||
versionCode: 4060001
|
||||
]
|
||||
|
||||
setup = [
|
||||
|
Reference in New Issue
Block a user