package com.juick.server; import com.juick.Message; import com.juick.User; import com.juick.server.api.activity.model.Link; import com.juick.server.api.activity.model.Note; import com.juick.server.api.activity.model.Person; import com.juick.server.api.activity.model.activities.Accept; import com.juick.server.api.activity.model.activities.Create; import com.juick.service.SocialService; import com.juick.service.activities.ActivityListener; import com.juick.service.activities.FollowEvent; import com.juick.service.activities.UndoFollowEvent; import com.juick.service.component.*; import com.juick.util.MessageUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.inject.Inject; import java.io.IOException; import java.net.URI; import java.util.Collections; @Component public class ActivityPubManager implements ActivityListener, NotificationListener { private static final Logger logger = LoggerFactory.getLogger(ActivityPubManager.class); @Inject SignatureManager signatureManager; @Inject ActivityPubManager activityPubManager; @Inject SocialService socialService; @Value("${ap_base_uri:http://localhost:8080/}") private String baseUri; private UriComponentsBuilder uri; @PostConstruct public void init() { uri = UriComponentsBuilder.fromUriString(baseUri); } @Override public void processFollowEvent(@Nonnull FollowEvent followEvent) { String acct = (String)followEvent.getRequest().getObject(); logger.info("received follower request to {}", acct); User followedUser = socialService.getUserByAccountUri(acct); if (!followedUser.isAnonymous()) { // automatically accept follower requests Person me = (Person) signatureManager.getContext(URI.create(acct)); Person follower = (Person) signatureManager.getContext(URI.create(followEvent.getRequest().getActor())); Accept accept = new Accept(); accept.setActor(me.getId()); accept.setObject(followEvent.getRequest()); try { signatureManager.post(me, follower, accept); socialService.addFollower(followedUser, follower.getId()); logger.info("Follower added for {}", followedUser.getName()); } catch (IOException e) { logger.info("activitypub exception", e); } } } @Override public void undoFollowEvent(UndoFollowEvent event) { String actor = event.getActor(); String me = event.getObject(); logger.info("{} stopping to follow {}", actor, me); User followedUser = socialService.getUserByAccountUri(me); if (!followedUser.isAnonymous()) { socialService.removeFollower(followedUser, actor); } } @Override public void processMessageEvent(MessageEvent messageEvent) { Message msg = messageEvent.getMessage(); User user = msg.getUser(); String userUri = personUri(user); Note note = makeNote(msg); Person me = (Person) signatureManager.getContext(URI.create(userUri)); socialService.getFollowers(user).forEach(acct -> { Person follower = (Person) signatureManager.getContext(URI.create(acct)); Create create = new Create(); create.setId(note.getId()); create.setActor(me.getId()); create.setPublished(note.getPublished()); create.setObject(note); try { logger.info("Posting to follower {}", follower.getId()); signatureManager.post(me, follower, create); } catch (IOException e) { logger.warn("activitypub exception", e); } }); } public String inboxUri() { return uri.replacePath("/api/inbox").toUriString(); } public String outboxUri(User user) { return uri.replacePath(String.format("/u/%s/blog/toc", user.getName())).toUriString(); } public String personUri(User user) { return uri.replacePath(String.format("/u/%s", user.getName())).toUriString(); } public String followersUri(User user) { return uri.replacePath(String.format("/u/%s/followers/toc", user.getName())).toUriString(); } public String followingUri(User user) { return uri.replacePath(String.format("/u/%s/following/toc", user.getName())).toUriString(); } public Note makeNote(Message msg) { Note note = new Note(); note.setId(uri.replacePath(String.format("/m/%d", msg.getMid())).toUriString()); note.setAttributedTo(personUri(msg.getUser())); if (MessageUtils.isReply(msg)) { uri.replacePath(String.format("/m/%d#%d", msg.getMid(), msg.getRid())).toUriString(); note.setInReplyTo(uri.replacePath(String.format("/m/%d#%d", msg.getMid(), msg.getReplyto())).toUriString()); } if (MessageUtils.isPM(msg)) { note.setTo(Collections.singletonList(activityPubManager.personUri(msg.getTo()))); } else { note.setTo(Collections.singletonList("https://www.w3.org/ns/activitystreams#Public")); note.setCc(Collections.singletonList(followersUri(msg.getUser()))); } note.setPublished(msg.getTimestamp()); note.setContent(MessageUtils.formatMessage(msg.getText())); if (StringUtils.isNotBlank(msg.getAttachmentType())) { Link attachment = new Link(); attachment.setHref(msg.getAttachment().getMedium().getUrl()); note.setAttachment(attachment); } return note; } @Override public void processSubscribeEvent(SubscribeEvent subscribeEvent) { } @Override public void processLikeEvent(LikeEvent likeEvent) { } @Override public void processPingEvent(PingEvent pingEvent) { } @Override public void processMessageReadEvent(MessageReadEvent messageReadEvent) { } }