From 0e20ca44eee0cb2726b575aed88a495206481973 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 4 Jan 2023 07:18:16 +0300 Subject: ActivityPub: handle Note updates --- src/main/java/com/juick/TelegramBotManager.java | 2 +- .../java/com/juick/service/MessagesService.java | 2 +- .../com/juick/service/MessagesServiceImpl.java | 6 +-- src/main/java/com/juick/www/api/Post.java | 2 +- .../java/com/juick/www/api/activity/Profile.java | 57 +++++++++++++--------- .../java/com/juick/server/tests/ServerTests.java | 6 ++- src/test/resources/friendica_update.json | 1 + 7 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 src/test/resources/friendica_update.json (limited to 'src') diff --git a/src/main/java/com/juick/TelegramBotManager.java b/src/main/java/com/juick/TelegramBotManager.java index 1dcc7edb..8064fc58 100644 --- a/src/main/java/com/juick/TelegramBotManager.java +++ b/src/main/java/com/juick/TelegramBotManager.java @@ -147,7 +147,7 @@ public class TelegramBotManager implements NotificationListener { User author = originalMessage.getUser(); String newMessageText = StringUtils.defaultString(message.text()); if (user_from.equals(author) && canUpdateMessage(originalMessage, newMessageText)) { - if (messagesService.updateMessage(mid, rid, newMessageText)) { + if (messagesService.updateMessage(mid, rid, newMessageText, false)) { telegramNotify(message.chat().id(), "Message updated", new com.juick.model.Message()); return; } diff --git a/src/main/java/com/juick/service/MessagesService.java b/src/main/java/com/juick/service/MessagesService.java index dda1cfd3..b9ee612a 100644 --- a/src/main/java/com/juick/service/MessagesService.java +++ b/src/main/java/com/juick/service/MessagesService.java @@ -154,7 +154,7 @@ public interface MessagesService { List getUnread(User user); @CacheEvict(value = { "discover", "discussions", "messages", "replies" }, allEntries = true) - boolean updateMessage(Integer mid, Integer rid, String body); + boolean updateMessage(Integer mid, Integer rid, String body, boolean foreign); @CacheEvict(value = { "discover", "discussions", "messages", "replies" }, allEntries = true) boolean updateReplyUri(Message reply, URI replyUri); diff --git a/src/main/java/com/juick/service/MessagesServiceImpl.java b/src/main/java/com/juick/service/MessagesServiceImpl.java index 3a4c3767..68d47429 100644 --- a/src/main/java/com/juick/service/MessagesServiceImpl.java +++ b/src/main/java/com/juick/service/MessagesServiceImpl.java @@ -1099,14 +1099,14 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ @Transactional @Override - public boolean updateMessage(Integer mid, Integer rid, String body) { + public boolean updateMessage(Integer mid, Integer rid, String body, boolean foreign) { Instant now = Instant.now(); Instant messageEditingWindow = now.minus(15, ChronoUnit.MINUTES); if (rid == 0) { Optional message = getMessage(mid); if (message.isPresent()) { Instant ts = message.get().getUpdatedAt(); - if (ts.compareTo(messageEditingWindow) >= 0) { + if (ts.compareTo(messageEditingWindow) >= 0 || foreign) { return jdbcTemplate.update( "UPDATE messages_txt SET txt=?, updated_at=? WHERE messages_txt.message_id=?", body, Timestamp.from(now), mid) > 0; @@ -1117,7 +1117,7 @@ public class MessagesServiceImpl extends BaseJdbcService implements MessagesServ Message reply = getReply(mid, rid); if (reply != null) { Instant ts = reply.getUpdatedAt(); - if (ts.compareTo(messageEditingWindow) >= 0) { + if (ts.compareTo(messageEditingWindow) >= 0 || foreign) { return jdbcTemplate.update( "UPDATE replies SET txt=?, updated_at=? WHERE message_id=? AND reply_id=?", body, Timestamp.from(now), mid, rid) > 0; diff --git a/src/main/java/com/juick/www/api/Post.java b/src/main/java/com/juick/www/api/Post.java index 2a92178d..2dba6e07 100644 --- a/src/main/java/com/juick/www/api/Post.java +++ b/src/main/java/com/juick/www/api/Post.java @@ -225,7 +225,7 @@ public class Post { @RequestParam String body) { User author = rid == 0 ? messagesService.getMessageAuthor(mid) : messagesService.getReply(mid, rid).getUser(); if (visitor.equals(author)) { - if (messagesService.updateMessage(mid, rid, body)) { + if (messagesService.updateMessage(mid, rid, body, false)) { Message result = rid == 0 ? messagesService.getMessage(mid).orElseThrow(IllegalStateException::new) : messagesService.getReply(mid, rid); diff --git a/src/main/java/com/juick/www/api/activity/Profile.java b/src/main/java/com/juick/www/api/activity/Profile.java index 7a3fdf29..2ba9b0a0 100644 --- a/src/main/java/com/juick/www/api/activity/Profile.java +++ b/src/main/java/com/juick/www/api/activity/Profile.java @@ -132,7 +132,7 @@ public class Profile { @GetMapping(value = "/u/{userName}/blog", produces = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE }) public OrderedCollectionPage getOutboxPage(@ModelAttribute User visitor, @PathVariable String userName, - @RequestParam(required = false, defaultValue = "0") int before) { + @RequestParam(required = false, defaultValue = "0") int before) { User user = userService.getUserByName(userName); if (!user.isAnonymous() && !user.isBanned()) { UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); @@ -265,10 +265,23 @@ public class Profile { throw new HttpNotFoundException(); } + private String formatNote(Note note) { + String markdown = remarkConverter.convertFragment((String) note.getContent()); + // combine note text with attachment urls + return note.getAttachment() == null ? markdown + : note.getAttachment().stream().map(attachment -> { + String attachmentUrl = attachment.getUrl(); + String attachmentName = attachment.getName(); + return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); + }).reduce(markdown, + (currentUrl, nextUrl) -> String.format("%s\n%s", currentUrl, nextUrl)); + } + @CacheEvict(cacheNames = "profiles", key = "{ #visitor.uri }") @PostMapping(value = "/api/inbox", consumes = { Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE }) - public ResponseEntity processInbox(@ModelAttribute User visitor, InputStream inboxData) throws Exception { + public ResponseEntity processInbox(@ModelAttribute User visitor, InputStream inboxData) + throws Exception { String inbox = IOUtils.toString(inboxData, StandardCharsets.UTF_8); Activity activity = jsonMapper.readValue(inbox, Activity.class); if ((StringUtils.isNotEmpty(visitor.getUri().toString()) @@ -305,17 +318,9 @@ public class Profile { String postId = profileUriBuilder.postId(inReplyTo); User user = new User(); user.setUri(URI.create(activity.getActor())); - String markdown = remarkConverter.convertFragment((String) note.getContent()); - String commandBody = note.getAttachment() == null ? markdown - : note.getAttachment().stream().map(attachment -> { - String attachmentUrl = attachment.getUrl(); - String attachmentName = attachment.getName(); - return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); - }).reduce(markdown, - (currentUrl, nextUrl) -> String.format("%s\n%s", currentUrl, nextUrl)); - CommandResult result = commandsManager.processCommand(user, - String.format("#%s %s", postId, commandBody), URI.create(StringUtils.EMPTY)); + String.format("#%s %s", postId, formatNote(note)), + URI.create(StringUtils.EMPTY)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { messagesService.updateReplyUri(result.getNewMessage().get(), noteId); @@ -329,17 +334,9 @@ public class Profile { if (reply != null) { User user = new User(); user.setUri(URI.create(activity.getActor())); - String markdown = remarkConverter.convertFragment((String) note.getContent()); - // combine note text with attachment urls - String commandBody = note.getAttachment() == null ? markdown - : note.getAttachment().stream().map(attachment -> { - String attachmentUrl = attachment.getUrl(); - String attachmentName = attachment.getName(); - return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); - }).reduce(markdown, (currentUrl, nextUrl) -> String.format("%s\n%s", - currentUrl, nextUrl)); CommandResult result = commandsManager.processCommand(user, - String.format("#%d/%d %s", reply.getMid(), reply.getRid(), commandBody), + String.format("#%d/%d %s", reply.getMid(), reply.getRid(), + formatNote(note)), URI.create(StringUtils.EMPTY)); logger.info(jsonMapper.writeValueAsString(result)); if (result.getNewMessage().isPresent()) { @@ -386,6 +383,22 @@ public class Profile { logger.info("{} update they profile"); return new ResponseEntity<>(CommandResult.fromString("Update accepted"), HttpStatus.ACCEPTED); } + if (activity.getObject() instanceof Note) { + Note note = (Note) activity.getObject(); + if (activity.getActor().equals(note.getAttributedTo())) { + Message reply = messagesService.getReplyByUri(activity.getObject().getUrl()); + if (messagesService.updateMessage(reply.getMid(), reply.getRid(), formatNote(note), true)) { + logger.info("{} update they message {}", activity.getActor(), note.getId()); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + ; + logger.warn("Unable to update {}", activity.getObject().getId()); + return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE); + } else { + logger.warn("Invalid Update: {}", jsonMapper.writeValueAsString(activity)); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + } } logger.warn("Unknown activity: {}", jsonMapper.writeValueAsString(activity)); return new ResponseEntity<>(CommandResult.fromString("Unknown activity"), HttpStatus.NOT_IMPLEMENTED); diff --git a/src/test/java/com/juick/server/tests/ServerTests.java b/src/test/java/com/juick/server/tests/ServerTests.java index 5eeeab3d..6e4bbcb5 100644 --- a/src/test/java/com/juick/server/tests/ServerTests.java +++ b/src/test/java/com/juick/server/tests/ServerTests.java @@ -255,6 +255,8 @@ public class ServerTests { private Resource sapeOutput; @Value("classpath:flag.json") private Resource flagPayload; + @Value("classpath:friendica_update.json") + private Resource friendicaUpdate; @Inject AppleClientSecretGenerator clientSecretGenerator; @Inject @@ -2449,7 +2451,7 @@ public class ServerTests { } @Test - public void hubzillaAndHonkActor() throws Exception { + public void hubzillaAndHonkActorAndFriendicaUpdate() throws Exception { String activity = IOUtils.toString(hubzillaActivity.getInputStream(), StandardCharsets.UTF_8); Create create = jsonMapper.readValue(activity, Create.class); String followData = IOUtils.toString(hubzillaFollow.getInputStream(), StandardCharsets.UTF_8); @@ -2458,6 +2460,8 @@ public class ServerTests { String honkData = IOUtils.toString(honkFollow.getInputStream(), StandardCharsets.UTF_8); Follow hfollow = jsonMapper.readValue(honkData, Follow.class); assertThat(hfollow.getTo().get(0), is("https://juick.com/u/vt")); + String updateString = IOUtils.toString(friendicaUpdate.getInputStream(), StandardCharsets.UTF_8); + Update update = jsonMapper.readValue(updateString, Update.class); } @Test diff --git a/src/test/resources/friendica_update.json b/src/test/resources/friendica_update.json new file mode 100644 index 00000000..6700261c --- /dev/null +++ b/src/test/resources/friendica_update.json @@ -0,0 +1 @@ +{"id":"https://friendica.ironbug.org/objects/3217dd65-1863-b450-5692-633751828658/Update","published":"2023-01-03T15:57:10Z","to":["https://s.zholnay.name/users/kirill","https://friendica.ironbug.org/followers/iron_bug","https://www.w3.org/ns/activitystreams#Public","https://juick.com/u/Strephil"],"actor":"https://friendica.ironbug.org/profile/iron_bug","object":{"id":"https://friendica.ironbug.org/objects/3217dd65-1863-b450-5692-633751828658","published":"2023-01-03T15:57:10Z","url":"https://friendica.ironbug.org/display/3217dd65-1863-b450-5692-633751828658","to":["https://s.zholnay.name/users/kirill","https://friendica.ironbug.org/followers/iron_bug","https://www.w3.org/ns/activitystreams#Public","https://juick.com/u/Strephil"],"content":"@kirill @Strephil вот разве что спиногрызы. а так, если никто не мешает ходить дома без штанов, то это самый комфортный вариант.
я когда ушла работать на удалёнку и избавилась от необходимости сидеть в одежде полдня, я стала просто счастлива. это было самым ужасным мучением из всех неприятностей работы в офисе. дома я одежду не ношу, если совсем не холодно.","attributedTo":"https://friendica.ironbug.org/profile/iron_bug","inReplyTo":"https://s.zholnay.name/users/kirill/statuses/109625978748200980","cc":["https://friends.deko.cloud/profile/shuro"],"type":"Note","tag":[{"href":"https://friendica.ironbug.org/profile/iron_bug","name":"@@iron_bug@friendica.ironbug.org@friendica.ironbug.org","type":"Mention"},{"href":"https://s.zholnay.name/users/kirill","name":"@@kirill@s.zholnay.name@s.zholnay.name","type":"Mention"},{"href":"https://juick.com/u/Strephil","name":"@@Strephil@juick.com@juick.com","type":"Mention"}]},"type":"Update","@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"vcard":"http://www.w3.org/2006/vcard/ns#","dfrn":"http://purl.org/macgirvin/dfrn/1.0/","diaspora":"https://diasporafoundation.org/ns/","litepub":"http://litepub.social/ns#","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","Hashtag":"as:Hashtag","directMessage":"litepub:directMessage"}]} -- cgit v1.2.3