diff options
Diffstat (limited to 'src/main/java/com/juick/server/ActivityPubManager.java')
-rw-r--r-- | src/main/java/com/juick/server/ActivityPubManager.java | 407 |
1 files changed, 0 insertions, 407 deletions
diff --git a/src/main/java/com/juick/server/ActivityPubManager.java b/src/main/java/com/juick/server/ActivityPubManager.java deleted file mode 100644 index 50af506b..00000000 --- a/src/main/java/com/juick/server/ActivityPubManager.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) 2008-2020, Juick - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -package com.juick.server; - -import com.juick.model.Message; -import com.juick.model.Reaction; -import com.juick.model.User; -import com.juick.formatters.PlainTextFormatter; -import com.juick.model.Tag; -import com.juick.www.api.SystemActivity.ActivityType; -import com.juick.www.api.activity.model.Context; -import com.juick.www.api.activity.model.activities.*; -import com.juick.www.api.activity.model.objects.Hashtag; -import com.juick.www.api.activity.model.objects.Image; -import com.juick.www.api.activity.model.objects.Mention; -import com.juick.www.api.activity.model.objects.Note; -import com.juick.www.api.activity.model.objects.Person; -import com.juick.server.util.HttpBadRequestException; -import com.juick.server.util.HttpUtils; -import com.juick.service.MessagesService; -import com.juick.service.SocialService; -import com.juick.service.UserService; -import com.juick.service.activities.*; -import com.juick.service.component.*; -import com.juick.util.MessageUtils; -import com.mitchellbosecke.pebble.PebbleEngine; -import com.mitchellbosecke.pebble.template.PebbleTemplate; -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.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.annotation.Nonnull; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -@Component -public class ActivityPubManager implements ActivityListener, NotificationListener { - private static final Logger logger = LoggerFactory.getLogger("ActivityPub"); - @Inject - private SignatureManager signatureManager; - @Inject - private SocialService socialService; - @Inject - private UserService userService; - @Inject - private MessagesService messagesService; - @Inject - private PebbleEngine pebbleEngine; - @Value("${ap_base_uri:http://localhost:8080/}") - private String baseUri; - @Value("${service_user:juick}") - private String serviceUsername; - - private User serviceUser; - - @PostConstruct - public void init() { - serviceUser = userService.getUserByName(serviceUsername); - } - - @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)).get(); - Person follower = (Person) signatureManager.getContext(URI.create(followEvent.getRequest().getActor())).get(); - 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 deleteUserEvent(DeleteUserEvent event) { - String acct = event.getUserUri(); - logger.info("Deleting {} from followers", acct); - socialService.removeAccount(acct); - } - - @Override - public void deleteMessageEvent(DeleteMessageEvent event) { - Message msg = event.getMessage(); - User user = msg.getUser(); - String userUri = personUri(user); - Note note = makeNote(msg); - Person me = (Person) signatureManager.getContext(URI.create(userUri)).get(); - socialService.getFollowers(user).forEach(acct -> { - Person follower = (Person) signatureManager.getContext(URI.create(acct)).get(); - Delete delete = new Delete(); - delete.setId(note.getId()); - delete.setActor(me.getId()); - delete.setPublished(note.getPublished()); - delete.setObject(note); - try { - logger.info("Deletion to follower {}", follower.getId()); - signatureManager.post(me, follower, delete); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - }); - } - - @Override - public void processAnnounceEvent(AnnounceEvent event) { - UriComponents uriComponents = UriComponentsBuilder.fromUriString(event.getMessageUri()).build(); - List<String> segments = uriComponents.getPathSegments(); - if (segments.get(0).equals("n")) { - String[] ids = segments.get(1).split("-", 2); - if (ids.length == 2 && Integer.parseInt(ids[1]) == 0) { - // only messages - logger.info("{} recommends {}", event.getActorUri(), Integer.valueOf(ids[0])); - messagesService.likeMessage(Integer.parseInt(ids[0]), 0, Reaction.LIKE, event.getActorUri()); - } - } - } - - @Override - public void undoAnnounceEvent(UndoAnnounceEvent event) { - UriComponents uriComponents = UriComponentsBuilder.fromUriString(event.getMessageUri()).build(); - List<String> segments = uriComponents.getPathSegments(); - if (segments.get(0).equals("n")) { - String[] ids = segments.get(1).split("-", 2); - if (ids.length == 2 && Integer.parseInt(ids[1]) == 0) { - // only messages - logger.info("{} stop recommending {}", event.getActorUri(), Integer.valueOf(ids[0])); - messagesService.likeMessage(Integer.parseInt(ids[0]), 0, null, event.getActorUri()); - } - } - } - - @Override - public void processUpdateEvent(UpdateEvent event) { - String objectUri = event.getMessageUri(); - User user = event.getUser(); - String userUri = personUri(user); - Person me = (Person) signatureManager.getContext(URI.create(userUri)).get(); - socialService.getFollowers(user).forEach(acct -> { - Person follower = (Person) signatureManager.getContext(URI.create(acct)).get(); - Update update = new Update(); - update.setId(objectUri + "#update"); - update.setActor(me.getId()); - update.setObject(objectUri); - try { - logger.info("Update to follower {}", follower.getId()); - signatureManager.post(me, follower, update); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - }); - } - - @Override - public void processSystemEvent(SystemEvent systemEvent) { - ActivityType type = systemEvent.getActivity().getType(); - if (type.equals(ActivityType.message)) { - processMessage(systemEvent.getActivity().getMessage()); - } else if (type.equals(ActivityType.like)) { - if (systemEvent.getActivity().getFrom().equals(serviceUser)) { - processTop(systemEvent.getActivity().getMessage()); - } - } - } - private void processMessage(Message msg) { - if (MessageUtils.isPM(msg) || msg.isService()) { - return; - } - User user = msg.getUser(); - String userUri = personUri(user); - Note note = makeNote(msg); - Person me = (Person) signatureManager.getContext(URI.create(userUri)).get(); - Set<String> subscribers = new HashSet<>(socialService.getFollowers(user)); - if (MessageUtils.isReply(msg) && msg.getTo().getUri().toASCIIString().length() > 0) { - String replier = msg.getTo().getUri().toASCIIString(); - subscribers.add(replier); - List<String> cc = new ArrayList<>(note.getCc()); - cc.add(replier); - note.setCc(cc); - } - subscribers.addAll(note.getCc()); - subscribers.forEach(acct -> { - Optional<Context> context = signatureManager.getContext(URI.create(acct)); - if (context.isPresent() && context.get() instanceof Person) { - Person follower = (Person)context.get(); - Create create = new Create(); - create.setId(note.getId()); - create.setActor(me.getId()); - create.setPublished(note.getPublished()); - create.setObject(note); - try { - signatureManager.post(me, follower, create); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - } - }); - } - - public String inboxUri() { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath("/api/inbox").toUriString(); - } - - public String outboxUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s/blog/toc", user.getName())).toUriString(); - } - - public String personUri(User user) { - if (user.getUri().toString().length() > 0) { - return user.getUri().toASCIIString(); - } - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s", user.getName())).toUriString(); - } - public String personWebUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/%s/", user.getName())).toUriString(); - } - - public String followersUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s/followers/toc", user.getName())).toUriString(); - } - - public String followingUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s/following/toc", user.getName())).toUriString(); - } - public String messageUri(Message msg) { - return messageUri(msg.getMid(), msg.getRid()); - } - public String messageUri(int mid, int rid) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - uri.replacePath(String.format("/n/%d-%d", mid, rid)); - return uri.toUriString(); - } - public String tagUri(Tag tag) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/t/%s", tag.getName())).toUriString(); - } - - public String postId(String messageUri) { - UriComponents uri = UriComponentsBuilder.fromUriString(messageUri).build(); - return uri.getPath().substring(uri.getPath().lastIndexOf('/') + 1).replace("-", "/"); - } - - public Note makeNote(Message msg) { - Note note = new Note(); - note.setId(messageUri(msg)); - note.setUrl(PlainTextFormatter.formatUrl(msg)); - note.setAttributedTo(personUri(msg.getUser())); - if (MessageUtils.isReply(msg)) { - if (msg.getReplyToUri().toASCIIString().length() > 0) { - note.setInReplyTo(msg.getReplyToUri().toASCIIString()); - } else { - note.setInReplyTo(messageUri(msg.getMid(), msg.getReplyto())); - } - } - if (MessageUtils.isPM(msg)) { - note.setTo(Collections.singletonList(personUri(msg.getTo()))); - } else { - note.setTo(Collections.singletonList(Context.ACTIVITYSTREAMS_PUBLIC)); - note.setCc(Collections.singletonList(followersUri(msg.getUser()))); - } - note.setPublished(msg.getCreated()); - if (StringUtils.isNotBlank(msg.getAttachmentType())) { - Image attachment = new Image(); - attachment.setId(msg.getAttachment().getMedium().getUrl()); - attachment.setUrl(msg.getAttachment().getMedium().getUrl()); - attachment.setMediaType(HttpUtils.mediaType(msg.getAttachmentType())); - note.setAttachment(Collections.singletonList(attachment)); - } - note.setTags(msg.getTags().stream().map(t -> new Hashtag(tagUri(t), t.getName())).collect(Collectors.toList())); - if (msg.getReplyToUri() != null && msg.getReplyToUri().toASCIIString().length() > 0) { - Optional<Context> noteContext = signatureManager.getContext(msg.getReplyToUri()); - if (noteContext.isPresent()) { - Note activity = (Note) noteContext.get(); - Optional<Context> personContext = signatureManager.getContext(URI.create(activity.getAttributedTo())); - if (personContext.isPresent()) { - Person person = (Person) personContext.get(); - note.getTags().add(new Mention(person.getUrl(), person.getPreferredUsername())); - msg.getTo().setName(person.getPreferredUsername()); - note.setInReplyTo(activity.getInReplyTo()); - } - } - } else if (MessageUtils.isReply(msg)) { - note.getTags().add(new Mention(personWebUri(msg.getTo()), msg.getTo().getName())); - } - MessageUtils.getGlobalMentions(msg).forEach(m -> { - // @user@server.tld -> user@server.tld - Optional<Context> personContext = signatureManager.discoverPerson(m.substring(1)); - if (personContext.isPresent()) { - Person person = (Person) personContext.get(); - note.getTags().add(new Mention(person.getUrl(), person.getPreferredUsername())); - List<String> cc = new ArrayList<>(note.getCc()); - cc.add(person.getId()); - note.setCc(cc); - } - }); - if (msg.isHtml()) { - note.setContent(msg.getText()); - } else { - PebbleTemplate noteTemplate = pebbleEngine.getTemplate("layouts/note"); - Map<String, Object> context = new HashMap<>(); - context.put("msg", msg); - context.put("baseUri", baseUri); - try { - Writer writer = new StringWriter(); - noteTemplate.evaluate(writer, context); - note.setContent(writer.toString()); - } catch (IOException e) { - logger.warn("template not rendered, falling back"); - note.setContent(MessageUtils.formatMessage(StringUtils.defaultString(msg.getText()))); - } - } - return note; - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - - } - - private void processTop(Message message) { - Note note = makeNote(message); - Announce announce = new Announce(); - announce.setId(note.getId() + "#top"); - announce.setActor(personUri(serviceUser)); - announce.setTo(Collections.singletonList(Context.ACTIVITYSTREAMS_PUBLIC)); - announce.setObject(note); - Person me = (Person) signatureManager.getContext(URI.create(announce.getActor())).get(); - socialService.getFollowers(serviceUser).forEach(acct -> { - var follower = signatureManager.getContext(URI.create(acct)); - follower.ifPresentOrElse((person) -> { - try { - logger.info("Announcing top: {}", message.getMid()); - signatureManager.post(me, (Person)person, announce); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - }, () -> logger.warn("Follower not found: {}", acct)); - }); - } - public User personToUser(URI uri) throws HttpBadRequestException { - Person person = (Person) signatureManager.getContext(uri).orElseThrow(HttpBadRequestException::new); - User user = new User(); - user.setUri(URI.create(person.getId())); - user.setName(person.getPreferredUsername()); - if (person.getIcon() != null) { - user.setAvatar(person.getIcon().getUrl()); - } - return user; - } -} |