[API/Vulcan] Migrate to new MessageBox API. (#134)

* Implement fetching vulcan messages from new api

* Bump kotlin version and fix timetable card lessons size display

* Revert formatting changes

* Revert disabling message composing

* Revert MultiDex changes and dependency upgrades

* Add missing MultiDex dependency, update Google Services

* Separate MessageBoxes sync, revert API data behavior changes

* Revert migrating MessageRecipient to Kotlin

* Use loginId from MessageBox address book

* Revert using compatible HTML mode in Vulcan

* Implement message meta and read status changing

* Always set attachment lists

* Fix setting tutor role description

* Implement sending messages

* Replace millis constant with WEEK

* Revert timetable changes

* Remove unused DataVulcan properties

* Ensure UUID-format recipient IDs

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
This commit is contained in:
Antoni Czaplicki 2022-09-17 12:31:35 +02:00 committed by GitHub
parent 54a61c6254
commit bb44fa066c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 297 additions and 102 deletions

View File

@ -142,6 +142,7 @@ dependencies {
// Language cores
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.multidex:multidex:2.0.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
// Android Jetpack

View File

@ -92,7 +92,7 @@ val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT
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)"
const val VULCAN_HEBE_APP_VERSION = "22.09.02 (G)"
private const val VULCAN_API_DEVICE_NAME_PREFIX = "Szkolny.eu "
private const val VULCAN_API_DEVICE_NAME_SUFFIX = " - nie usuwać"
val VULCAN_API_DEVICE_NAME by lazy {
@ -116,9 +116,11 @@ 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_MESSAGEBOX = "api/mobile/messagebox"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_ADDRESSBOOK = "api/mobile/messagebox/addressbook"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES = "api/mobile/messagebox/message"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_STATUS = "api/mobile/messagebox/message/status"
const val VULCAN_HEBE_ENDPOINT_MESSAGEBOX_SEND = "api/mobile/messagebox/message"
const val VULCAN_HEBE_ENDPOINT_LUCKY_NUMBER = "api/mobile/school/lucky"
const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}"

View File

@ -20,6 +20,10 @@ object Regexes {
"""<br\s?/?>""".toRegex()
}
val MESSAGE_META by lazy {
"""^\[META:([A-z0-9-&=]+)]""".toRegex()
}
val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy {

View File

@ -222,15 +222,15 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext }
set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value }
private var mSenderAddressHash: String? = null
var senderAddressHash: String?
get() { mSenderAddressHash = mSenderAddressHash ?: profile?.getStudentData("senderAddressHash", null); return mSenderAddressHash }
set(value) { profile?.putStudentData("senderAddressHash", value) ?: return; mSenderAddressHash = value }
private var mMessageBoxKey: String? = null
var messageBoxKey: String?
get() { mMessageBoxKey = mMessageBoxKey ?: profile?.getStudentData("messageBoxKey", null); return mMessageBoxKey }
set(value) { profile?.putStudentData("messageBoxKey", value) ?: return; mMessageBoxKey = value }
private var mSenderAddressName: String? = null
var senderAddressName: String?
get() { mSenderAddressName = mSenderAddressName ?: profile?.getStudentData("senderAddressName", null); return mSenderAddressName }
set(value) { profile?.putStudentData("senderAddressName", value) ?: return; mSenderAddressName = value }
private var mMessageBoxName: String? = null
var messageBoxName: String?
get() { mMessageBoxName = mMessageBoxName ?: profile?.getStudentData("messageBoxName", null); return mMessageBoxName }
set(value) { profile?.putStudentData("messageBoxName", value) ?: return; mMessageBoxName = value }
val apiUrl: String?
get() {

View File

@ -12,6 +12,7 @@ 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_ADDRESSBOOK_2 = 3011
const val ENDPOINT_VULCAN_HEBE_TIMETABLE = 3020
const val ENDPOINT_VULCAN_HEBE_EXAMS = 3030
const val ENDPOINT_VULCAN_HEBE_GRADES = 3040
@ -19,10 +20,11 @@ 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_TEACHERS = 3110
const val ENDPOINT_VULCAN_HEBE_LUCKY_NUMBER = 3200
const val ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES = 3500
const val ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX = 3510
const val ENDPOINT_VULCAN_HEBE_MESSAGES_SENT = 3520
val VulcanFeatures = listOf(
// timetable
@ -85,6 +87,8 @@ val VulcanFeatures = listOf(
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,
ENDPOINT_VULCAN_HEBE_TEACHERS to LOGIN_METHOD_VULCAN_HEBE
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2 to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_TEACHERS to LOGIN_METHOD_VULCAN_HEBE,
ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES to LOGIN_METHOD_VULCAN_HEBE,
), listOf(LOGIN_METHOD_VULCAN_HEBE))
)

View File

@ -21,10 +21,12 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
ENDPOINT_VULCAN_HEBE_MAIN,
ENDPOINT_VULCAN_HEBE_PUSH_CONFIG,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK,
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2,
ENDPOINT_VULCAN_HEBE_TIMETABLE,
ENDPOINT_VULCAN_HEBE_EXAMS,
ENDPOINT_VULCAN_HEBE_HOMEWORK,
ENDPOINT_VULCAN_HEBE_NOTICES,
ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES,
ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX,
ENDPOINT_VULCAN_HEBE_MESSAGES_SENT,
ENDPOINT_VULCAN_HEBE_TEACHERS,
@ -107,6 +109,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_addressbook)
VulcanHebeAddressbook(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2 -> {
data.startProgress(R.string.edziennik_progress_endpoint_addressbook)
VulcanHebeAddressbook2(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_TEACHERS -> {
data.startProgress(R.string.edziennik_progress_endpoint_teachers)
VulcanHebeTeachers(data, lastSync, onSuccess)
@ -139,6 +145,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_attendance)
VulcanHebeAttendance(data, lastSync, onSuccess)
}
ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages)
VulcanHebeMessageBoxes(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)

View File

@ -14,7 +14,6 @@ import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler
import io.github.wulkanowy.signer.hebe.getSignatureHeaders
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.hebe.HebeFilterType
@ -55,6 +54,15 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
return date.getLong("Timestamp") ?: return default
}
fun buildDateTime(): JsonObject {
return JsonObject(
"Timestamp" to System.currentTimeMillis(),
"Date" to Date.getToday().stringY_m_d,
"DateDisplay" to Date.getToday().stringDmy,
"Time" to Time.getNow().stringHMS,
)
}
fun getDate(json: JsonObject?, key: String): Date? {
val date = json.getJsonObject(key)
return date.getString("Date")?.let { Date.fromY_m_d(it) }
@ -74,6 +82,22 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
return teacherId
}
fun getTeacherRecipient(json: JsonObject): Teacher? {
val globalKey = json.getString("GlobalKey") ?: return null
if (globalKey == data.messageBoxKey)
return null
var name = json.getString("Name") ?: return null
val group = json.getString("Group", "P")
val loginId = "${globalKey};${group};${name}"
val pattern = " - $group - (${data.schoolShort})"
if (name.endsWith(pattern))
name = name.substringBefore(pattern)
val teacher = data.getTeacherByFirstLast(name, loginId)
if (teacher.type == 0)
teacher.type = Teacher.TYPE_OTHER
return teacher
}
fun getSubjectId(json: JsonObject?, key: String): Long? {
val subject = json.getJsonObject(key)
val subjectId = subject.getLong("Id") ?: return null
@ -89,7 +113,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
}
fun getTeamId(json: JsonObject?, key: String): Long? {
val team = json.getJsonObject(key)
val team = json.getJsonObject(key) ?: return null
val teamId = team.getLong("Id")
var teamName = team.getString("Shortcut")
?: team.getString("Name")
@ -104,7 +128,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
}
fun getClassId(json: JsonObject?, key: String): Long? {
val team = json.getJsonObject(key)
val team = json.getJsonObject(key) ?: return null
val teamId = team.getLong("Id")
val teamName = data.profile?.studentClassName
?: team.getString("Name")
@ -148,7 +172,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
fun isCurrentYear(dateTime: Long): Boolean {
return profile?.let { profile ->
return@let dateTime >= profile.dateSemester1Start.inMillis
return@let dateTime >= profile.dateSemester1Start.inMillis - WEEK * MS
} ?: false
}
@ -355,6 +379,7 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
dateTo: Date? = null,
lastSync: Long? = null,
folder: Int? = null,
messageBox: String? = null,
params: Map<String, String> = mapOf(),
includeFilterType: Boolean = true,
onSuccess: (data: List<JsonObject>, response: Response?) -> Unit
@ -378,6 +403,9 @@ open class VulcanHebe(open val data: DataVulcan, open val lastSync: Long?) {
query["periodId"] = data.studentSemesterId.toString()
query["pupilId"] = data.studentId.toString()
}
HebeFilterType.BY_MESSAGEBOX -> {
query["box"] = messageBox ?: data.messageBoxKey ?: ""
}
}
if (dateFrom != null)

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
enum class HebeFilterType(val endpoint: String) {
BY_MESSAGEBOX("byBox"),
BY_PUPIL("byPupil"),
BY_PERSON("byPerson"),
BY_PERIOD("byPeriod")

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.core.util.set
import androidx.room.OnConflictStrategy
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_ADDRESSBOOK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
@ -44,7 +45,6 @@ class VulcanHebeAddressbook(
) { list, _ ->
list.forEach { person ->
val id = person.getString("Id") ?: return@forEach
val loginId = person.getString("LoginId") ?: return@forEach
val idType = id.split("-")
.getOrNull(0)
@ -69,7 +69,7 @@ class VulcanHebeAddressbook(
idLong,
name,
surname,
loginId
null
).also {
data.teacherList[idLong] = it
}
@ -108,13 +108,14 @@ class VulcanHebeAddressbook(
}
teacher.setTeacherType(personType)
if (roleText != null)
teacher.typeDescription = roleText
}
if (teacher.type == 0)
teacher.setTeacherType(typeBase)
}
data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK, 2 * DAY)
onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK)
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-2-21.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.room.OnConflictStrategy
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_ADDRESSBOOK
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_OTHER
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_PARENT
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_STUDENT
import pl.szczodrzynski.edziennik.data.db.entity.Teacher.Companion.TYPE_TEACHER
import pl.szczodrzynski.edziennik.ext.DAY
import pl.szczodrzynski.edziennik.ext.getString
class VulcanHebeAddressbook2(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeAddressbook2"
}
init {
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX_ADDRESSBOOK,
HebeFilterType.BY_MESSAGEBOX,
messageBox = data.messageBoxKey,
lastSync = lastSync,
includeFilterType = false
) { list, _ ->
list.forEach { person ->
val teacher = getTeacherRecipient(person) ?: return@forEach
val group = person.getString("Group", "P")
if (teacher.type == TYPE_OTHER) {
teacher.type = when (group) {
"P" -> TYPE_TEACHER // Pracownik
"O" -> TYPE_PARENT // Opiekun
"U" -> TYPE_STUDENT // Uczeń
else -> TYPE_OTHER
}
}
}
data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE
data.setSyncNext(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2, 2 * DAY)
onSuccess(ENDPOINT_VULCAN_HEBE_ADDRESSBOOK_2)
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Kuba Szczodrzyński 2022-9-16.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.ext.DAY
import pl.szczodrzynski.edziennik.ext.getString
class VulcanHebeMessageBoxes(
override val data: DataVulcan,
override val lastSync: Long?,
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeMessageBoxes"
}
init {
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX,
lastSync = lastSync
) { list, _ ->
for (messageBox in list) {
val name = messageBox.getString("Name") ?: continue
val studentName = profile?.studentNameLong ?: continue
if (!name.startsWith(studentName))
continue
data.messageBoxKey = messageBox.getString("GlobalKey")
data.messageBoxName = name
break
}
data.setSyncNext(ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES, 7 * DAY)
onSuccess(ENDPOINT_VULCAN_HEBE_MESSAGE_BOXES)
}
}
}

View File

@ -4,22 +4,21 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import androidx.core.util.set
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Message
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.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.navlib.crc16
class VulcanHebeMessages(
override val data: DataVulcan,
@ -27,29 +26,7 @@ class VulcanHebeMessages(
val onSuccess: (endpointId: Int) -> Unit
) : VulcanHebe(data, lastSync) {
companion object {
const val TAG = "VulcanHebeMessagesInbox"
}
private fun getPersonId(json: JsonObject): Long {
val senderLoginId = json.getInt("LoginId") ?: return -1
/*if (senderLoginId == data.studentLoginId)
return -1*/
val senderName = json.getString("Address") ?: return -1
val senderNameSplit = senderName.splitName()
val senderLoginIdStr = senderLoginId.toString()
val teacher = data.teacherList.singleOrNull { it.loginId == senderLoginIdStr }
?: Teacher(
profileId,
-1 * crc16(senderName).toLong(),
senderNameSplit?.second ?: "",
senderNameSplit?.first ?: "",
senderLoginIdStr
).also {
it.setTeacherType(Teacher.TYPE_OTHER)
data.teacherList[it.id] = it
}
return teacher.id
const val TAG = "VulcanHebeMessages"
}
fun getMessages(messageType: Int) {
@ -64,17 +41,28 @@ class VulcanHebeMessages(
TYPE_SENT -> ENDPOINT_VULCAN_HEBE_MESSAGES_SENT
else -> ENDPOINT_VULCAN_HEBE_MESSAGES_INBOX
}
val messageBox = data.messageBoxKey
if (messageBox == null) {
onSuccess(endpointId)
return
}
apiGetList(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES,
HebeFilterType.BY_PERSON,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX_MESSAGES,
HebeFilterType.BY_MESSAGEBOX,
messageBox = data.messageBoxKey,
folder = folder,
lastSync = lastSync
) { list, _ ->
list.forEach { message ->
val id = message.getLong("Id") ?: return@forEach
val uuid = message.getString("Id") ?: return@forEach
val id = Utils.crc32(uuid.toByteArray())
val globalKey = message.getString("GlobalKey", "")
val threadKey = message.getString("ThreadKey", "")
val subject = message.getString("Subject") ?: return@forEach
val body = message.getString("Content") ?: return@forEach
var body = message.getString("Content") ?: return@forEach
val sender = message.getJsonObject("Sender") ?: return@forEach
@ -83,13 +71,27 @@ class VulcanHebeMessages(
if (!isCurrentYear(sentDate)) return@forEach
val senderId = if (messageType == TYPE_RECEIVED)
getTeacherRecipient(sender)?.id
else
null
val meta = mutableMapOf(
"uuid" to uuid,
"globalKey" to globalKey,
"threadKey" to threadKey,
)
val metaString = meta.map { "${it.key}=${it.value}" }.join("&")
body = "[META:${metaString}]" + body
body = body.replace("\n", "<br>")
val messageObject = Message(
profileId = profileId,
id = id,
type = messageType,
subject = subject,
body = body.replace("\n", "<br>"),
senderId = if (messageType == TYPE_RECEIVED) getPersonId(sender) else null,
body = body,
senderId = senderId,
addedDate = sentDate
)
@ -101,9 +103,14 @@ class VulcanHebeMessages(
else -1
for (receiver in receivers) {
val recipientId = if (messageType == TYPE_SENT)
getTeacherRecipient(receiver)?.id ?: -1
else
-1
val messageRecipientObject = MessageRecipient(
profileId,
if (messageType == TYPE_SENT) getPersonId(receiver) else -1,
recipientId,
-1,
receiverReadDate,
id
@ -115,6 +122,9 @@ class VulcanHebeMessages(
?.asJsonObjectList()
?: return@forEach
messageObject.attachmentIds = mutableListOf()
messageObject.attachmentNames = mutableListOf()
messageObject.attachmentSizes = mutableListOf()
for (attachment in attachments) {
val fileName = attachment.getString("Name") ?: continue
val url = attachment.getString("Link") ?: continue

View File

@ -5,7 +5,7 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_STATUS
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.MessageGetEvent
@ -23,13 +23,19 @@ class VulcanHebeMessagesChangeStatus(
const val TAG = "VulcanHebeMessagesChangeStatus"
}
init {
init { let {
val messageKey = messageObject.body?.let { data.parseMessageMeta(it) }?.get("globalKey") ?: run {
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
return@let
}
apiPost(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES_STATUS,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX_STATUS,
payload = JsonObject(
"MessageId" to messageObject.id,
"LoginId" to data.studentLoginId,
"BoxKey" to data.messageBoxKey,
"MessageKey" to messageKey,
"Status" to 1
)
) { _: Boolean, _ ->
@ -61,5 +67,5 @@ class VulcanHebeMessagesChangeStatus(
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
}
}
}}
}

View File

@ -4,17 +4,20 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.ERROR_MESSAGE_NOT_SENT
import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_SEND
import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGEBOX_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.Teacher
import pl.szczodrzynski.edziennik.ext.*
import java.util.UUID
class VulcanHebeSendMessage(
override val data: DataVulcan,
@ -28,9 +31,9 @@ class VulcanHebeSendMessage(
}
init {
if (data.senderAddressName == null || data.senderAddressHash == null) {
VulcanHebeMain(data).getStudents(data.profile, null) {
if (data.senderAddressName == null || data.senderAddressHash == null) {
if (data.messageBoxKey == null || data.messageBoxName == null) {
VulcanHebeMessageBoxes(data, 0) {
if (data.messageBoxKey == null || data.messageBoxName == null) {
data.error(TAG, ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY)
}
else {
@ -44,47 +47,64 @@ class VulcanHebeSendMessage(
}
private fun sendMessage() {
val uuid = UUID.randomUUID().toString()
val globalKey = UUID.randomUUID().toString()
val partition = "${data.symbol}-${data.schoolSymbol}"
val recipientsArray = JsonArray()
recipients.forEach { teacher ->
val loginId = teacher.loginId?.split(";", limit = 3) ?: return@forEach
val key = loginId.getOrNull(0) ?: teacher.loginId
val group = loginId.getOrNull(1)
val name = loginId.getOrNull(2)
if (key?.toIntOrNull() != null) {
// raise error for old-format (non-UUID) login IDs
data.error(TAG, ERROR_MESSAGE_NOT_SENT)
return
}
recipientsArray += JsonObject(
"Address" to teacher.fullNameLastFirst,
"LoginId" to (teacher.loginId?.toIntOrNull() ?: return@forEach),
"Initials" to teacher.initialsLastFirst,
"AddressHash" to teacher.fullNameLastFirst.sha1Hex()
"Id" to "${data.messageBoxKey}-${key}",
"Partition" to partition,
"Owner" to data.messageBoxKey,
"GlobalKey" to key,
"Name" to name,
"Group" to group,
"Initials" to "",
"HasRead" to 0,
)
}
val senderName = (profile?.accountName ?: profile?.studentNameLong)
?.swapFirstLastName() ?: ""
val sender = JsonObject(
"Address" to data.senderAddressName,
"LoginId" to data.studentLoginId.toString(),
"Initials" to senderName.getNameInitials(),
"AddressHash" to data.senderAddressHash
"Id" to "0",
"Partition" to partition,
"Owner" to data.messageBoxKey,
"GlobalKey" to data.messageBoxKey,
"Name" to data.messageBoxName,
"Group" to "",
"Initials" to "",
"HasRead" to 0,
)
apiPost(
TAG,
VULCAN_HEBE_ENDPOINT_MESSAGES_SEND,
VULCAN_HEBE_ENDPOINT_MESSAGEBOX_SEND,
payload = JsonObject(
"Status" to 1,
"Sender" to sender,
"DateSent" to null,
"DateRead" to null,
"Content" to text,
"Receiver" to recipientsArray,
"Id" to 0,
"Id" to uuid,
"GlobalKey" to globalKey,
"Partition" to partition,
"ThreadKey" to globalKey, // TODO correct threadKey for reply messages
"Subject" to subject,
"Attachments" to null,
"Self" to null
"Content" to text,
"Status" to 1,
"Owner" to data.messageBoxKey,
"DateSent" to buildDateTime(),
"DateRead" to null,
"Sender" to sender,
"Receiver" to recipientsArray,
"Attachments" to JsonArray(),
)
) { json: JsonObject, _ ->
val messageId = json.getLong("Id")
if (messageId == null) {
// TODO error
return@apiPost
}
) { _: JsonObject, _ ->
// TODO handle errors
VulcanHebeMessages(data, null) {
val message = data.messageList.firstOrNull { it.isSent && it.subject == subject }

View File

@ -45,6 +45,7 @@ class VulcanHebeTeachers(
when (subjectName) {
"Pedagog" -> teacher.setTeacherType(Teacher.TYPE_PEDAGOGUE)
"Dyrektor" -> teacher.setTeacherType(Teacher.TYPE_PRINCIPAL)
else -> {
val subjectId = data.getSubject(null, subjectName).id
if (!teacher.subjects.contains(subjectId))

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.ERROR_REQUEST_FAILURE
import pl.szczodrzynski.edziennik.data.api.Regexes.MESSAGE_META
import pl.szczodrzynski.edziennik.data.api.interfaces.EndpointCallback
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.*
@ -489,11 +490,19 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
teacherList[id] = this
}
return obj.also {
if (loginId != null && it.loginId != null)
if (loginId != null)
it.loginId = loginId
if (firstName.length > 1)
it.name = firstName
it.surname = lastName
}
}
fun parseMessageMeta(body: String): Map<String, String>? {
val match = MESSAGE_META.find(body) ?: return null
return match[1].split("&").associateBy(
{ it.substringBefore("=") },
{ it.substringAfter("=") },
)
}
}

View File

@ -77,7 +77,7 @@ class MessagesComposeFragment : Fragment(), CoroutineScope {
private lateinit var stylingConfig: StylingConfig
private lateinit var uiConfig: UIConfig
private val enableTextStyling
get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_VULCAN && app.profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS
get() = app.profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS
private var changedRecipients = false
private var changedSubject = false
private var changedBody = false

View File

@ -15,6 +15,7 @@ import android.text.style.*
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.graphics.ColorUtils
import androidx.core.text.HtmlCompat
import pl.szczodrzynski.edziennik.data.api.Regexes.MESSAGE_META
import pl.szczodrzynski.edziennik.ext.dp
import pl.szczodrzynski.edziennik.ext.getWordBounds
import pl.szczodrzynski.edziennik.ext.resolveAttr
@ -45,7 +46,7 @@ object BetterHtml {
.toRegex(RegexOption.IGNORE_CASE)
var text = html
.replace("\\[META:[A-z0-9]+;[0-9-]+]".toRegex(), "")
.replace(MESSAGE_META, "")
.replace("background-color: ?$hexPattern;".toRegex(), "")
// treat paragraphs as if they had no margin
.replace("<p", "<span")

View File

@ -859,7 +859,7 @@
<string name="settings_about_licenses_text">Open-source licenses</string>
<string name="settings_about_privacy_policy_text">Privacy policy</string>
<string name="settings_card_register_title">E-register</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - 2022</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 2022</string>
<string name="settings_about_update_subtext">Click to check for updates</string>
<string name="settings_about_update_text">Update</string>
<string name="settings_about_version_text">Version</string>

View File

@ -23,8 +23,8 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.13'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1'
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
}
}