From ac4aa8a2316f36dd4a801aece09d9dd2b23b9c44 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:43:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=81=9D=E3=81=AE=E4=BB=96=E3=81=AEAct?= =?UTF-8?q?ivityPub=E3=83=97=E3=83=AD=E3=82=BB=E3=83=83=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activity/accept/APAcceptService.kt | 1 + .../activity/accept/ApAcceptProcessor.kt | 50 +++++++++++++++++ .../activity/delete/APDeleteProcessor.kt | 35 ++++++++++++ .../activity/follow/APFollowProcessor.kt | 35 ++++++++++++ .../follow/APReceiveFollowJobService.kt | 1 + .../follow/APReceiveFollowJobServiceImpl.kt | 1 + .../service/activity/like/APLikeProcessor.kt | 54 +++++++++++++++++++ .../service/activity/undo/APUndoProcessor.kt | 53 ++++++++++++++++++ .../common/AbstractActivityPubProcessor.kt | 2 +- 9 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/APAcceptService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/APAcceptService.kt index 2e845715..b702a791 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/APAcceptService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/APAcceptService.kt @@ -10,6 +10,7 @@ import dev.usbharu.hideout.core.service.user.UserService import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +@Deprecated("use activitypub processor") interface APAcceptService { suspend fun receiveAccept(accept: Accept) } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt new file mode 100644 index 00000000..6060531f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt @@ -0,0 +1,50 @@ +package dev.usbharu.hideout.activitypub.service.activity.accept + +import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException +import dev.usbharu.hideout.activitypub.domain.model.Accept +import dev.usbharu.hideout.activitypub.domain.model.Follow +import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor +import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext +import dev.usbharu.hideout.activitypub.service.common.ActivityType +import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.query.FollowerQueryService +import dev.usbharu.hideout.core.query.UserQueryService +import dev.usbharu.hideout.core.service.user.UserService + +class ApAcceptProcessor( + private val transaction: Transaction, + private val userQueryService: UserQueryService, + private val followerQueryService: FollowerQueryService, + private val userService: UserService +) : + AbstractActivityPubProcessor(transaction) { + + override suspend fun internalProcess(activity: ActivityPubProcessContext) { + val value = activity.activity.`object` ?: throw IllegalActivityPubObjectException("object is null") + + if (value.type.contains("Follow").not()) { + logger.warn("FAILED Activity type is not Follow.") + throw IllegalActivityPubObjectException("Invalid type ${value.type}") + } + + val follow = value as Follow + + val userUrl = follow.`object` ?: throw IllegalActivityPubObjectException("object is null") + val followerUrl = follow.actor ?: throw IllegalActivityPubObjectException("actor is null") + + val user = userQueryService.findByUrl(userUrl) + val follower = userQueryService.findByUrl(followerUrl) + + if (followerQueryService.alreadyFollow(user.id, follower.id)) { + logger.debug("END User already follow from ${follower.url} to ${user.url}.") + return + } + + userService.follow(user.id, follower.id) + logger.debug("SUCCESS Follow from ${follower.url} to ${user.url}.") + } + + override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Accept + + override fun type(): Class = Accept::class.java +} diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt new file mode 100644 index 00000000..fee94bad --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt @@ -0,0 +1,35 @@ +package dev.usbharu.hideout.activitypub.service.activity.delete + +import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException +import dev.usbharu.hideout.activitypub.domain.model.Delete +import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor +import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext +import dev.usbharu.hideout.activitypub.service.common.ActivityType +import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException +import dev.usbharu.hideout.core.domain.model.post.PostRepository +import dev.usbharu.hideout.core.query.PostQueryService + +class APDeleteProcessor( + transaction: Transaction, + private val postQueryService: PostQueryService, + private val postRepository: PostRepository +) : + AbstractActivityPubProcessor(transaction) { + override suspend fun internalProcess(activity: ActivityPubProcessContext) { + val deleteId = activity.activity.`object`?.id ?: throw IllegalActivityPubObjectException("object.id is null") + + val post = try { + postQueryService.findByApId(deleteId) + } catch (e: FailedToGetResourcesException) { + logger.warn("FAILED delete id: {} is not found.", deleteId) + return + } + + postRepository.delete(post.id) + } + + override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete + + override fun type(): Class = Delete::class.java +} diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt new file mode 100644 index 00000000..92ad3e28 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt @@ -0,0 +1,35 @@ +package dev.usbharu.hideout.activitypub.service.activity.follow + +import com.fasterxml.jackson.databind.ObjectMapper +import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException +import dev.usbharu.hideout.activitypub.domain.model.Follow +import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor +import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext +import dev.usbharu.hideout.activitypub.service.common.ActivityType +import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.external.job.ReceiveFollowJob +import dev.usbharu.hideout.core.external.job.ReceiveFollowJobParam +import dev.usbharu.hideout.core.service.job.JobQueueParentService + +class APFollowProcessor( + transaction: Transaction, + private val jobQueueParentService: JobQueueParentService, + private val objectMapper: ObjectMapper +) : + AbstractActivityPubProcessor(transaction) { + override suspend fun internalProcess(activity: ActivityPubProcessContext) { + logger.info("FOLLOW from: {} to {}", activity.activity.actor, activity.activity.`object`) + + // inboxをジョブキューに乗せているので既に不要だが、フォロー承認制アカウントを実装する際に必要なので残す + val jobProps = ReceiveFollowJobParam( + activity.activity.actor ?: throw IllegalActivityPubObjectException("actor is null"), + objectMapper.writeValueAsString(activity.activity), + activity.activity.`object` ?: throw IllegalActivityPubObjectException("object is null") + ) + jobQueueParentService.scheduleTypeSafe(ReceiveFollowJob, jobProps) + } + + override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Follow + + override fun type(): Class = Follow::class.java +} diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobService.kt index 2b7a84d4..d3c106b9 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobService.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.activitypub.service.activity.follow import dev.usbharu.hideout.core.external.job.ReceiveFollowJob import kjob.core.job.JobProps +@Deprecated("use activitypub processor") interface APReceiveFollowJobService { suspend fun receiveFollowJob(props: JobProps) } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobServiceImpl.kt index 02a466ab..86056b69 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowJobServiceImpl.kt @@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component @Component +@Deprecated("use activitypub processor") class APReceiveFollowJobServiceImpl( private val apUserService: APUserService, private val userQueryService: UserQueryService, diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt new file mode 100644 index 00000000..470eaee7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt @@ -0,0 +1,54 @@ +package dev.usbharu.hideout.activitypub.service.activity.like + +import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException +import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException +import dev.usbharu.hideout.activitypub.domain.model.Like +import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor +import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext +import dev.usbharu.hideout.activitypub.service.common.ActivityType +import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService +import dev.usbharu.hideout.activitypub.service.objects.user.APUserService +import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.query.PostQueryService +import dev.usbharu.hideout.core.service.reaction.ReactionService + +class APLikeProcessor( + transaction: Transaction, + private val apUserService: APUserService, + private val apNoteService: APNoteService, + private val postQueryService: PostQueryService, + private val reactionService: ReactionService +) : + AbstractActivityPubProcessor(transaction) { + override suspend fun internalProcess(activity: ActivityPubProcessContext) { + val actor = activity.activity.actor ?: throw IllegalActivityPubObjectException("actor is null") + val content = activity.activity.content ?: throw IllegalActivityPubObjectException("content is null") + + val target = activity.activity.`object` ?: throw IllegalActivityPubObjectException("object is null") + + val personWithEntity = apUserService.fetchPersonWithEntity(actor) + + try { + apNoteService.fetchNoteAsync(target).await() + } catch (e: FailedToGetActivityPubResourceException) { + logger.debug("FAILED failed to get {}", target) + logger.trace("", e) + return + } + + val post = postQueryService.findByUrl(target) + + reactionService.receiveReaction( + content, + actor.substringAfter("://").substringBefore("/"), + personWithEntity.second.id, + post.id + ) + + logger.debug("SUCCESS Add Like($content) from ${personWithEntity.second.url} to ${post.url}") + } + + override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Like + + override fun type(): Class = Like::class.java +} diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt new file mode 100644 index 00000000..8c09b54f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt @@ -0,0 +1,53 @@ +package dev.usbharu.hideout.activitypub.service.activity.undo + +import dev.usbharu.hideout.activitypub.domain.model.Follow +import dev.usbharu.hideout.activitypub.domain.model.Undo +import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor +import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext +import dev.usbharu.hideout.activitypub.service.common.ActivityType +import dev.usbharu.hideout.activitypub.service.objects.user.APUserService +import dev.usbharu.hideout.application.external.Transaction +import dev.usbharu.hideout.core.query.UserQueryService +import dev.usbharu.hideout.core.service.user.UserService + +class APUndoProcessor( + transaction: Transaction, + private val apUserService: APUserService, + private val userQueryService: UserQueryService, + private val userService: UserService +) : + AbstractActivityPubProcessor(transaction) { + override suspend fun internalProcess(activity: ActivityPubProcessContext) { + val undo = activity.activity + if (undo.actor == null) { + return + } + + val type = + undo.`object`?.type.orEmpty() + .firstOrNull { it == "Block" || it == "Follow" || it == "Like" || it == "Announce" || it == "Accept" } + ?: return + + when (type) { + "Follow" -> { + val follow = undo.`object` as Follow + + if (follow.`object` == null) { + return + } + apUserService.fetchPerson(undo.actor!!, follow.`object`) + val follower = userQueryService.findByUrl(undo.actor!!) + val target = userQueryService.findByUrl(follow.`object`!!) + userService.unfollow(target.id, follower.id) + return + } + + else -> {} + } + TODO() + } + + override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Undo + + override fun type(): Class = Undo::class.java +} diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt index 8cddae81..bfc7d24e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt @@ -11,7 +11,7 @@ abstract class AbstractActivityPubProcessor( private val transaction: Transaction, private val allowUnauthorized: Boolean = false ) : ActivityPubProcessor { - private val logger = LoggerFactory.getLogger(this::class.java) + protected val logger = LoggerFactory.getLogger(this::class.java) override suspend fun process(activity: ActivityPubProcessContext) { if (activity.isAuthorized.not() && allowUnauthorized.not()) {