Skip to content

Commit

Permalink
Implemented basic message receiving functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
aeoliux committed Dec 6, 2024
1 parent 0d8134e commit ba0f03c
Show file tree
Hide file tree
Showing 21 changed files with 572 additions and 51 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ and from [here](https://nightly.link/aeoliux/Violet/workflows/build/main/release
- Lucky number
- Timetable
- Attendance
- Agenda
- Agenda
- School notices
- Basic message receiving (links and other HTML stuff are not parsed **for now**)
3 changes: 2 additions & 1 deletion composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ kotlin {
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.kotlinx.serialization.json)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ksoup)

// both
implementation(libs.kotlinx.datetime)
Expand All @@ -81,7 +82,7 @@ android {
defaultConfig {
applicationId = "com.github.aeoliux.violet"
minSdk = 32
targetSdk = 34
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ actual class Keychain(private val context: Context) {

@OptIn(ExperimentalEncodingApi::class)
actual fun savePass(password: String) {
println(password)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key)
val iv = cipher.iv
Expand Down Expand Up @@ -73,7 +72,6 @@ actual class Keychain(private val context: Context) {
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec)

val final = String(cipher.doFinal(cipherText), Charsets.UTF_8)
println(final)
return final
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import io.ktor.http.parameters
import io.ktor.serialization.kotlinx.json.json
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.isoDayNumber
Expand All @@ -45,7 +46,7 @@ class ApiClient {
var colors = LinkedHashMap<UInt, String>()
var classrooms = LinkedHashMap<UInt, String>()

private val client = HttpClient {
val client = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.github.aeoliux.violet.api.scraping.messages

import com.fleeksoft.ksoup.Ksoup
import com.github.aeoliux.violet.api.ApiClient
import com.github.aeoliux.violet.api.localDateTimeFormat
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.datetime.LocalDateTime

data class MessageLabel(
val url: String,
val sender: String,
val topic: String,
val sentAt: LocalDateTime?,
val hasAttachment: Boolean,
)

typealias MessagesList = LinkedHashMap<MessageCategories, List<MessageLabel>>
//fun MessagesList.init() {
// this[MessageCategories.Received] = emptyList()
// this[MessageCategories.Sent] = emptyList()
// this[MessageCategories.Bin] = emptyList()
//}

suspend fun ApiClient.getMessages(): MessagesList {
val categories = listOf(
MessageCategories.Received,
MessageCategories.Sent,
MessageCategories.Bin
)

return categories.fold(MessagesList()) { acc, category ->
val url = "https://synergia.librus.pl/wiadomosci/${category.categoryId}"
val body = client.get(url).bodyAsText()
val soup = Ksoup.parse(body)

val labels = soup.select(
"#formWiadomosci > div > div > table > tbody > tr > td:nth-child(2) > table:nth-child(2) > tbody > tr"
).fold(emptyList<MessageLabel>()) { list, label ->
if (label.getAllElements().size > 2) {
val hasAttachment = label.select("td:nth-child(2)").html().startsWith("<img ")
val date = label.select("td:nth-child(5)").html()

val senderData = label.select("td:nth-child(3) > a")
val messageUrl = senderData.attr("href")
val sender = senderData.html()

val topic = label.select("td:nth-child(4) > a").html()

val messageLabel = MessageLabel(
url = messageUrl,
sender = sender,
topic = topic,
sentAt = LocalDateTime.parse(date, localDateTimeFormat),
hasAttachment = hasAttachment
)

list.plus(messageLabel)
} else
list
}

acc[category] = labels
acc
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.github.aeoliux.violet.api.scraping.messages

import com.fleeksoft.ksoup.Ksoup
import com.github.aeoliux.violet.api.ApiClient
import com.github.aeoliux.violet.api.localDateTimeFormat
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.datetime.LocalDateTime


suspend fun ApiClient.getMessage(url: String): Message {
val url = "https://synergia.librus.pl$url"
val soup = Ksoup.parse(
client.get(url).bodyAsText()
)

val messageData = soup.select("#formWiadomosci > div > div > table > tbody > tr > td:nth-child(2)")
val meta = messageData.select("table:nth-child(2) > tbody > tr > td:nth-child(2)")
val content = messageData.select("div").html().replace("<br>", "")

var topic = ""
var date = ""
var sender: String? = null
if (meta.size == 2) {
topic = meta[0].html()
date = meta[1].html()
} else {
sender = meta[0].html()
topic = meta[1].html()
date = meta[2].html()
}

return Message(
sender = sender,
date = LocalDateTime.parse(date, localDateTimeFormat),
topic = topic,
content = content,
attachments = emptyList()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.aeoliux.violet.api.scraping.messages

import kotlinx.datetime.LocalDateTime

data class Message(
val sender: String?,
val date: LocalDateTime,
val topic: String,
val content: String,
val attachments: List<String>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.aeoliux.violet.api.scraping.messages

import kotlin.enums.EnumEntries

enum class MessageCategories(val categoryId: Int) {
Received(5),
Sent(6),
Bin(7);

companion object {
fun fromInt(n: Int): MessageCategories {
return MessageCategories.entries.first { n == it.categoryId }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.github.aeoliux.violet.app.appState

import com.github.aeoliux.violet.api.scraping.messages.getMessages
import com.github.aeoliux.violet.app.storage.Database
import com.github.aeoliux.violet.app.storage.insertAgenda
import com.github.aeoliux.violet.app.storage.insertAttendances
import com.github.aeoliux.violet.app.storage.insertGrades
import com.github.aeoliux.violet.app.storage.insertLessons
import com.github.aeoliux.violet.app.storage.insertMessageIds
import com.github.aeoliux.violet.app.storage.insertSchoolNotices
import com.github.aeoliux.violet.app.storage.selectClassInfo
import com.github.aeoliux.violet.app.storage.setAboutMe
Expand Down Expand Up @@ -68,6 +70,12 @@ suspend fun AppState.fetchData(login: String? = null, password: String? = null)
)
}

setFetchStatus("Did you get a message?") {
Database.insertMessageIds(
client.value.getMessages()
)
}

statusMessage.value = "Saving some data and refreshing the view..."
semester.value = classInfo.semester
databaseUpdated.value = !databaseUpdated.value
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.github.aeoliux.violet.app.appState

import com.github.aeoliux.violet.api.scraping.messages.Message
import com.github.aeoliux.violet.api.scraping.messages.getMessage

suspend fun AppState.fetchMessage(url: String): Message? {
this.logIn()
return try {
this.client.value.getMessage(url)
} catch (e: Exception) {
this.alertTitle.value = "Fetching message error"
this.alertMessage.value = e.toString()
this.showAlert.value = true

null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ suspend fun AppState.logIn(login: String? = null, password: String? = null) {
if (login == null || password == null) {
val usernameAndPass = keychain.getPass()
?: throw IllegalStateException("Username and password not found in keychain")
println(usernameAndPass)
val indexOfSpace = usernameAndPass.indexOfFirst { it == ' ' }
login = usernameAndPass.slice(0..<indexOfSpace)
password = usernameAndPass.slice(indexOfSpace + 1..<usernameAndPass.length)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.github.aeoliux.violet.app.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun <T> ComboBox(
options: List<String>,
values: List<T>,
selectedIndex: Int = 0,
onChange: (opt: T) -> Unit = {}
) {
var selectedIndex by remember { mutableStateOf(selectedIndex) }

Card(
Modifier.fillMaxWidth()
.wrapContentHeight()
.padding(start = 10.dp, end = 10.dp, top = 2.dp, bottom = 2.dp)
) {
ExpandableList(
header = { Text(
text = options[selectedIndex],
modifier = Modifier.padding(15.dp)
) },
) {
options.forEachIndexed { index, it ->
Row(
Modifier
.fillMaxSize()
.clickable {
selectedIndex = index
onChange(values[selectedIndex])
},
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.padding(15.dp),
text = it
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ fun MainView() {
IconButton({ vm.showOrHideSettings() }) {
Icon(
imageVector = Icons.Filled.Settings,
tint = MaterialTheme.colorScheme.onBackground,
contentDescription = "Show/hide settings"
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package com.github.aeoliux.violet.app.main
import androidx.compose.runtime.Composable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.aeoliux.violet.api.scraping.messages.MessageLabel
import com.github.aeoliux.violet.app.agenda.AgendaView
import com.github.aeoliux.violet.app.appState.AppState
import com.github.aeoliux.violet.app.appState.fetchData
import com.github.aeoliux.violet.app.appState.runBackgroundTask
import com.github.aeoliux.violet.app.attendance.AttendanceView
import com.github.aeoliux.violet.app.grades.GradesView
import com.github.aeoliux.violet.app.home.HomeView
import com.github.aeoliux.violet.app.messages.MessagesView
import com.github.aeoliux.violet.app.schoolNotices.SchoolNoticesView
import com.github.aeoliux.violet.app.timetable.TimetableView
import kotlinx.coroutines.flow.MutableSharedFlow
Expand All @@ -29,7 +31,8 @@ class MainViewModel(
TabItem("Timetable") { TimetableView() },
TabItem("Attendance") { AttendanceView() },
TabItem("Agenda") { AgendaView() },
TabItem("School notices") { SchoolNoticesView() }
TabItem("School notices") { SchoolNoticesView() },
TabItem("Messages") { MessagesView() }
)

private var _isRefreshing = MutableStateFlow(false)
Expand Down
Loading

0 comments on commit ba0f03c

Please sign in to comment.