Skip to content

Commit

Permalink
[WIP] add misskey support
Browse files Browse the repository at this point in the history
  • Loading branch information
Tlaster committed Aug 6, 2023
1 parent 69f7e3d commit fa10175
Show file tree
Hide file tree
Showing 346 changed files with 21,987 additions and 267 deletions.
9 changes: 4 additions & 5 deletions app/src/main/java/dev/dimension/flare/common/AppDeepLink.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ const val AppSchema = "flare"
object AppDeepLink {
object Callback {
const val Mastodon = "$AppSchema://Callback/SignIn/Mastodon"
const val Misskey = "$AppSchema://Callback/SignIn/Misskey"
const val Twitter = "$AppSchema://Callback/SignIn/Twitter"
}

object Mastodon {
object Hashtag {
const val route = "$AppSchema://Mastodon/Hashtag/{keyword}"
operator fun invoke(keyword: String) = "$AppSchema://Mastodon/Hashtag/${java.net.URLEncoder.encode(keyword, "UTF-8")}"
}
object Search {
const val route = "$AppSchema://Search/{keyword}"
operator fun invoke(keyword: String) = "$AppSchema://Search/${java.net.URLEncoder.encode(keyword, "UTF-8")}"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ interface PagingTimelineDao {

@Delete
suspend fun delete(timeline: List<DbPagingTimeline>)

@Query("DELETE FROM paging_timeline WHERE pagingKey == :pagingKey AND accountKey == :accountKey")
suspend fun delete(
pagingKey: String,
accountKey: MicroBlogKey
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(user: List<DbUser>)

@Query("SELECT * FROM user WHERE userKey IN (:userKeys)")
suspend fun findByKeys(userKeys: List<MicroBlogKey>): List<DbUser>

@Query("SELECT * FROM user WHERE userKey = :userKey")
fun getUser(userKey: MicroBlogKey): Flow<DbUser?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ suspend fun save(
val data = toDbPagingTimeline(accountKey, pagingKey, sortIdProvider)
withTransaction {
(
data.map { it.status.status.user } + data.flatMap { it.status.references }
.map { it.status.user }
data.mapNotNull { it.status.status.user } + data.flatMap { it.status.references }
.mapNotNull { it.status.user }
).let {
userDao().insertAll(it)
}
Expand All @@ -56,8 +56,8 @@ suspend fun saveNotification(
val data = toDb(accountKey, pagingKey)
withTransaction {
(
data.map { it.status.status.user } + data.flatMap { it.status.references }
.map { it.status.user }
data.mapNotNull { it.status.status.user } + data.flatMap { it.status.references }
.mapNotNull { it.status.user }
).let {
userDao().insertAll(it)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
package dev.dimension.flare.data.database.cache.mapper

import androidx.room.withTransaction
import dev.dimension.flare.data.database.cache.CacheDatabase
import dev.dimension.flare.data.database.cache.model.DbPagingTimeline
import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus
import dev.dimension.flare.data.database.cache.model.DbStatus
import dev.dimension.flare.data.database.cache.model.DbStatusWithReference
import dev.dimension.flare.data.database.cache.model.DbStatusWithUser
import dev.dimension.flare.data.database.cache.model.DbUser
import dev.dimension.flare.data.database.cache.model.StatusContent
import dev.dimension.flare.data.database.cache.model.UserContent
import dev.dimension.flare.data.network.misskey.api.model.Note
import dev.dimension.flare.data.network.misskey.api.model.Notification
import dev.dimension.flare.data.network.misskey.api.model.User
import dev.dimension.flare.data.network.misskey.api.model.UserLite
import dev.dimension.flare.model.MicroBlogKey
import dev.dimension.flare.model.PlatformType
import dev.dimension.flare.model.ReferenceType
import java.util.UUID
import kotlinx.datetime.toInstant

context(CacheDatabase, List<Note>)
suspend fun save(
accountKey: MicroBlogKey,
pagingKey: String,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
sortIdProvider: (Note) -> Long = { it.createdAt.toInstant().toEpochMilliseconds() }
) {
val data = toDbPagingTimeline(accountKey, pagingKey, emojis, sortIdProvider)
with(data) {
saveDbPagingTimelineWithStatus()
}
}


context(CacheDatabase, List<Notification>)
suspend fun saveNotification(
accountKey: MicroBlogKey,
pagingKey: String,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
) {
val data = toDb(accountKey, pagingKey, emojis)
with(data) {
saveDbPagingTimelineWithStatus()
}
}

context(CacheDatabase, List<DbPagingTimelineWithStatus>)
suspend fun saveDbPagingTimelineWithStatus() {
withTransaction {
(
mapNotNull { it.status.status.user } + flatMap { it.status.references }
.mapNotNull { it.status.user }
).let { allUsers ->
val exsitingUsers = userDao()
.findByKeys(allUsers.map { it.userKey })
.filter {
it.content is UserContent.Misskey
}.map {
val content = it.content as UserContent.Misskey
val user = allUsers.find { user ->
user.userKey == it.userKey
}

if (user != null && user.content is UserContent.MisskeyLite) {
it.copy(
content = content.copy(
data = content.data.copy(
name = user.content.data.name,
username = user.content.data.username,
avatarUrl = user.content.data.avatarUrl,
)
)
)
} else {
it
}
}

val result = (allUsers + exsitingUsers).distinctBy { it.userKey }
userDao().insertAll(result)
}
(
map { it.status.status.data } + flatMap { it.status.references }
.map { it.status.data }
).let {
statusDao().insertAll(it)
}
flatMap { it.status.references }.map { it.reference }.let {
statusReferenceDao().insertAll(it)
}
pagingTimelineDao().insertAll(map { it.timeline })
}
}

fun List<Notification>.toDb(
accountKey: MicroBlogKey,
pagingKey: String,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
): List<DbPagingTimelineWithStatus> {
return this.map {
it.toDbPagingTimeline(accountKey, pagingKey, emojis)
}
}

private fun Notification.toDbPagingTimeline(
accountKey: MicroBlogKey,
pagingKey: String,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
): DbPagingTimelineWithStatus {
val status = this.toDbStatusWithReference(accountKey, emojis)
val sortId = this.createdAt.toInstant().toEpochMilliseconds()
return DbPagingTimelineWithStatus(
timeline = DbPagingTimeline(
_id = UUID.randomUUID().toString(),
accountKey = accountKey,
statusKey = MicroBlogKey(
this.id,
accountKey.host
),
pagingKey = pagingKey,
sortId = sortId
),
status = status
)
}

private fun Notification.toDbStatusWithReference(
accountKey: MicroBlogKey,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
): DbStatusWithReference {
val status = this.toDbStatusWithUser(accountKey, emojis)
val retweet = this.note?.toDbStatusWithUser(accountKey, emojis)
return DbStatusWithReference(
status = status,
references = listOfNotNull(
retweet?.toDbStatusReference(status.data.statusKey, ReferenceType.MisskeyNotification)
)
)
}

private fun Notification.toDbStatusWithUser(
accountKey: MicroBlogKey,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
): DbStatusWithUser {
val user = this.user?.toDbUser(accountKey.host, emojis)
val status = this.toDbStatus(accountKey, emojis)
return DbStatusWithUser(
data = status,
user = user
)
}

private fun Notification.toDbStatus(
accountKey: MicroBlogKey,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
): DbStatus {
val user = this.user?.toDbUser(accountKey.host, emojis)
return DbStatus(
statusKey = MicroBlogKey(
this.id,
accountKey.host
),
platformType = PlatformType.Misskey,
userKey = user?.userKey,
content = StatusContent.MisskeyNotification(this, emojis),
accountKey = accountKey
)
}

private fun List<Note>.toDbPagingTimeline(
accountKey: MicroBlogKey,
pagingKey: String,
emoji: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
sortIdProvider: (Note) -> Long = { it.createdAt.toInstant().toEpochMilliseconds() }
): List<DbPagingTimelineWithStatus> {
return this.map {
it.toDbPagingTimeline(accountKey, pagingKey, emoji, sortIdProvider)
}
}


private fun Note.toDbPagingTimeline(
accountKey: MicroBlogKey,
pagingKey: String,
emoji: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
sortIdProvider: (Note) -> Long = { it.createdAt.toInstant().toEpochMilliseconds() }
): DbPagingTimelineWithStatus {
val status = this.toDbStatusWithReference(accountKey, emoji)
val sortId = sortIdProvider(this)
return DbPagingTimelineWithStatus(
timeline = DbPagingTimeline(
_id = UUID.randomUUID().toString(),
accountKey = accountKey,
statusKey = status.status.data.statusKey,
pagingKey = pagingKey,
sortId = sortId
),
status = status
)
}

private fun Note.toDbStatusWithReference(
accountKey: MicroBlogKey,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
): DbStatusWithReference {
val status = this.toDbStatusWithUser(accountKey, emojis)
val retweet = this.renote?.toDbStatusWithUser(accountKey, emojis)
val reply = this.reply?.toDbStatusWithUser(accountKey, emojis)
return DbStatusWithReference(
status = status,
references = listOfNotNull(
retweet?.toDbStatusReference(status.data.statusKey, ReferenceType.Retweet),
reply?.toDbStatusReference(status.data.statusKey, ReferenceType.Reply)
)
)
}

private fun Note.toDbStatusWithUser(
accountKey: MicroBlogKey,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
): DbStatusWithUser {
val user = user.toDbUser(accountKey.host, emojis)
val status = DbStatus(
statusKey = MicroBlogKey(
id = id,
host = user.userKey.host
),
platformType = PlatformType.Misskey,
content = StatusContent.Misskey(this, emojis),
userKey = user.userKey,
accountKey = accountKey
)
return DbStatusWithUser(
data = status,
user = user
)
}

private fun UserLite.toDbUser(
host: String,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
) = DbUser(
userKey = MicroBlogKey(
id = id,
host = host,
),
platformType = dev.dimension.flare.model.PlatformType.Misskey,
name = name ?: "",
handle = username,
content = UserContent.MisskeyLite(this, emojis)
)

fun User.toDbUser(
host: String,
emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
) = DbUser(
userKey = MicroBlogKey(
id = id,
host = host,
),
platformType = dev.dimension.flare.model.PlatformType.Misskey,
name = name ?: "",
handle = username,
content = UserContent.Misskey(this, emojis)
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import dev.dimension.flare.data.network.mastodon.api.model.Notification
import dev.dimension.flare.data.network.mastodon.api.model.Status
import dev.dimension.flare.model.MicroBlogKey
import dev.dimension.flare.model.PlatformType
import java.util.UUID
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.util.UUID

@Entity(
tableName = "status",
Expand All @@ -23,7 +23,7 @@ data class DbStatus(
val id: String = UUID.randomUUID().toString(),
val statusKey: MicroBlogKey,
val accountKey: MicroBlogKey,
val userKey: MicroBlogKey,
val userKey: MicroBlogKey?,
val platformType: PlatformType,
val content: StatusContent
)
Expand All @@ -37,13 +37,27 @@ sealed interface StatusContent {
@Serializable
@SerialName("mastodon-notification")
data class MastodonNotification(val data: Notification) : StatusContent

@Serializable
@SerialName("misskey")
data class Misskey(
val data: dev.dimension.flare.data.network.misskey.api.model.Note,
val emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
) : StatusContent

@Serializable
@SerialName("misskey-notification")
data class MisskeyNotification(
val data: dev.dimension.flare.data.network.misskey.api.model.Notification,
val emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
) : StatusContent
}

data class DbStatusWithUser(
@Embedded
val data: DbStatus,
@Relation(parentColumn = "userKey", entityColumn = "userKey")
val user: DbUser
val user: DbUser?
)

data class DbStatusReferenceWithStatus(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,18 @@ sealed interface UserContent {
@Serializable
@SerialName("Mastodon")
data class Mastodon(val data: Account) : UserContent

@Serializable
@SerialName("Misskey")
data class Misskey(
val data: dev.dimension.flare.data.network.misskey.api.model.User,
val emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
) : UserContent

@Serializable
@SerialName("MisskeyLite")
data class MisskeyLite(
val data: dev.dimension.flare.data.network.misskey.api.model.UserLite,
val emojis: List<dev.dimension.flare.data.network.misskey.api.model.EmojiSimple>,
) : UserContent
}
Loading

0 comments on commit fa10175

Please sign in to comment.