feat: Mastodon 互換APIでカスタム絵文字を使用できるように

This commit is contained in:
usbharu 2023-12-22 14:32:11 +09:00
parent 2d84ad073f
commit 8af489c93c
8 changed files with 45 additions and 12 deletions

View File

@ -21,5 +21,6 @@ data class Timeline(
val sensitive: Boolean, val sensitive: Boolean,
val isLocal: Boolean, val isLocal: Boolean,
val isPureRepost: Boolean = false, val isPureRepost: Boolean = false,
val mediaIds: List<Long> = emptyList() val mediaIds: List<Long> = emptyList(),
val emojiIds: List<Long> = emptyList()
) )

View File

@ -37,6 +37,7 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService
it[isLocal] = timeline.isLocal it[isLocal] = timeline.isLocal
it[isPureRepost] = timeline.isPureRepost it[isPureRepost] = timeline.isPureRepost
it[mediaIds] = timeline.mediaIds.joinToString(",") it[mediaIds] = timeline.mediaIds.joinToString(",")
it[emojiIds] = timeline.emojiIds.joinToString(",")
} }
} else { } else {
Timelines.update({ Timelines.id eq timeline.id }) { Timelines.update({ Timelines.id eq timeline.id }) {
@ -52,6 +53,7 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService
it[isLocal] = timeline.isLocal it[isLocal] = timeline.isLocal
it[isPureRepost] = timeline.isPureRepost it[isPureRepost] = timeline.isPureRepost
it[mediaIds] = timeline.mediaIds.joinToString(",") it[mediaIds] = timeline.mediaIds.joinToString(",")
it[emojiIds] = timeline.emojiIds.joinToString(",")
} }
} }
return@query timeline return@query timeline
@ -72,6 +74,7 @@ class ExposedTimelineRepository(private val idGenerateService: IdGenerateService
this[Timelines.isLocal] = it.isLocal this[Timelines.isLocal] = it.isLocal
this[Timelines.isPureRepost] = it.isPureRepost this[Timelines.isPureRepost] = it.isPureRepost
this[Timelines.mediaIds] = it.mediaIds.joinToString(",") this[Timelines.mediaIds] = it.mediaIds.joinToString(",")
this[Timelines.emojiIds] = it.emojiIds.joinToString(",")
} }
return@query timelines return@query timelines
} }
@ -104,7 +107,8 @@ fun ResultRow.toTimeline(): Timeline {
sensitive = this[Timelines.sensitive], sensitive = this[Timelines.sensitive],
isLocal = this[Timelines.isLocal], isLocal = this[Timelines.isLocal],
isPureRepost = this[Timelines.isPureRepost], isPureRepost = this[Timelines.isPureRepost],
mediaIds = this[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() } mediaIds = this[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() },
emojiIds = this[Timelines.emojiIds].split(",").mapNotNull { it.toLongOrNull() }
) )
} }
@ -122,6 +126,7 @@ object Timelines : Table("timelines") {
val isLocal = bool("is_local") val isLocal = bool("is_local")
val isPureRepost = bool("is_pure_repost") val isPureRepost = bool("is_pure_repost")
val mediaIds = varchar("media_ids", 255) val mediaIds = varchar("media_ids", 255)
val emojiIds = varchar("emoji_ids", 255)
override val primaryKey: PrimaryKey = PrimaryKey(id) override val primaryKey: PrimaryKey = PrimaryKey(id)

View File

@ -45,7 +45,8 @@ class ExposedGenerateTimelineService(private val statusQueryService: StatusQuery
it[Timelines.postId], it[Timelines.postId],
it[Timelines.replyId], it[Timelines.replyId],
it[Timelines.repostId], it[Timelines.repostId],
it[Timelines.mediaIds].split(",").mapNotNull { s -> s.toLongOrNull() } it[Timelines.mediaIds].split(",").mapNotNull { s -> s.toLongOrNull() },
it[Timelines.emojiIds].split(",").mapNotNull { s -> s.toLongOrNull() }
) )
} }

View File

@ -57,7 +57,8 @@ class MongoGenerateTimelineService(
it.postId, it.postId,
it.replyId, it.replyId,
it.repostId, it.repostId,
it.mediaIds it.mediaIds,
it.emojiIds
) )
} }
) )

View File

@ -37,7 +37,8 @@ class TimelineService(
sensitive = post.sensitive, sensitive = post.sensitive,
isLocal = isLocal, isLocal = isLocal,
isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()),
mediaIds = post.mediaIds mediaIds = post.mediaIds,
emojiIds = post.emojiIds
) )
}.toMutableList() }.toMutableList()
if (post.visibility == Visibility.PUBLIC) { if (post.visibility == Visibility.PUBLIC) {
@ -55,7 +56,8 @@ class TimelineService(
sensitive = post.sensitive, sensitive = post.sensitive,
isLocal = isLocal, isLocal = isLocal,
isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()),
mediaIds = post.mediaIds mediaIds = post.mediaIds,
emojiIds = post.emojiIds
) )
) )
} }

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.mastodon.infrastructure.exposedquery package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments
import dev.usbharu.hideout.core.infrastructure.exposedrepository.* import dev.usbharu.hideout.core.infrastructure.exposedrepository.*
import dev.usbharu.hideout.domain.mastodon.model.generated.Account import dev.usbharu.hideout.domain.mastodon.model.generated.Account
@ -12,6 +13,7 @@ import org.jetbrains.exposed.sql.andWhere
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.time.Instant import java.time.Instant
import dev.usbharu.hideout.domain.mastodon.model.generated.CustomEmoji as MastodonEmoji
@Suppress("IncompleteDestructuring") @Suppress("IncompleteDestructuring")
@Repository @Repository
@ -23,6 +25,10 @@ class StatusQueryServiceImpl : StatusQueryService {
postIdSet.addAll(statusQueries.flatMap { listOfNotNull(it.postId, it.replyId, it.repostId) }) postIdSet.addAll(statusQueries.flatMap { listOfNotNull(it.postId, it.replyId, it.repostId) })
val mediaIdSet = mutableSetOf<Long>() val mediaIdSet = mutableSetOf<Long>()
mediaIdSet.addAll(statusQueries.flatMap { it.mediaIds }) mediaIdSet.addAll(statusQueries.flatMap { it.mediaIds })
val emojiIdSet = mutableSetOf<Long>()
emojiIdSet.addAll(statusQueries.flatMap { it.emojiIds })
val postMap = Posts val postMap = Posts
.leftJoin(Actors) .leftJoin(Actors)
.select { Posts.id inList postIdSet } .select { Posts.id inList postIdSet }
@ -32,12 +38,16 @@ class StatusQueryServiceImpl : StatusQueryService {
it[Media.id] to it.toMedia().toMediaAttachments() it[Media.id] to it.toMedia().toMediaAttachments()
} }
val emojiMap = CustomEmojis.select { CustomEmojis.id inList emojiIdSet }.associate {
it[CustomEmojis.id] to it.toCustomEmoji().toMastodonEmoji()
}
return statusQueries.mapNotNull { statusQuery -> return statusQueries.mapNotNull { statusQuery ->
postMap[statusQuery.postId]?.copy( postMap[statusQuery.postId]?.copy(
inReplyToId = statusQuery.replyId?.toString(), inReplyToId = statusQuery.replyId?.toString(),
inReplyToAccountId = postMap[statusQuery.replyId]?.account?.id, inReplyToAccountId = postMap[statusQuery.replyId]?.account?.id,
reblog = postMap[statusQuery.repostId], reblog = postMap[statusQuery.repostId],
mediaAttachments = statusQuery.mediaIds.mapNotNull { mediaMap[it] } mediaAttachments = statusQuery.mediaIds.mapNotNull { mediaMap[it] },
emojis = statusQuery.emojiIds.mapNotNull { emojiMap[it] }
) )
} }
} }
@ -120,6 +130,8 @@ class StatusQueryServiceImpl : StatusQueryService {
private suspend fun findByPostIdsWithMedia(ids: List<Long>): List<Status> { private suspend fun findByPostIdsWithMedia(ids: List<Long>): List<Status> {
val pairs = Posts val pairs = Posts
.leftJoin(PostsMedia) .leftJoin(PostsMedia)
.leftJoin(PostsEmojis)
.leftJoin(CustomEmojis)
.leftJoin(Actors) .leftJoin(Actors)
.leftJoin(Media) .leftJoin(Media)
.select { Posts.id inList ids } .select { Posts.id inList ids }
@ -129,13 +141,22 @@ class StatusQueryServiceImpl : StatusQueryService {
toStatus(it.first()).copy( toStatus(it.first()).copy(
mediaAttachments = it.mapNotNull { resultRow -> mediaAttachments = it.mapNotNull { resultRow ->
resultRow.toMediaOrNull()?.toMediaAttachments() resultRow.toMediaOrNull()?.toMediaAttachments()
} },
emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmoji()?.toMastodonEmoji() }
) to it.first()[Posts.repostId] ) to it.first()[Posts.repostId]
} }
return resolveReplyAndRepost(pairs) return resolveReplyAndRepost(pairs)
} }
} }
private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji(
shortcode = this.name,
url = this.url,
staticUrl = this.url,
visibleInPicker = true,
category = this.category.orEmpty()
)
private fun toStatus(it: ResultRow) = Status( private fun toStatus(it: ResultRow) = Status(
id = it[Posts.id].toString(), id = it[Posts.id].toString(),
uri = it[Posts.apId], uri = it[Posts.apId],

View File

@ -4,5 +4,6 @@ data class StatusQuery(
val postId: Long, val postId: Long,
val replyId: Long?, val replyId: Long?,
val repostId: Long?, val repostId: Long?,
val mediaIds: List<Long> val mediaIds: List<Long>,
val emojiIds: List<Long>
) )

View File

@ -158,7 +158,8 @@ create table if not exists timelines
"sensitive" boolean not null, "sensitive" boolean not null,
is_local boolean not null, is_local boolean not null,
is_pure_repost boolean not null, is_pure_repost boolean not null,
media_ids varchar(255) not null media_ids varchar(255) not null,
emoji_ids varchar(255) not null
); );
create table if not exists application_authorization create table if not exists application_authorization