aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/juick/www/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/juick/www/api')
-rw-r--r--src/main/java/com/juick/www/api/Mastodon.java175
-rw-r--r--src/main/java/com/juick/www/api/activity/Profile.java2
2 files changed, 147 insertions, 30 deletions
diff --git a/src/main/java/com/juick/www/api/Mastodon.java b/src/main/java/com/juick/www/api/Mastodon.java
index ff7f2e8c..b23ec9c1 100644
--- a/src/main/java/com/juick/www/api/Mastodon.java
+++ b/src/main/java/com/juick/www/api/Mastodon.java
@@ -17,12 +17,20 @@
package com.juick.www.api;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import com.juick.model.Chat;
+import com.juick.model.Message;
import com.juick.model.User;
+import com.juick.service.ChatService;
+import com.juick.service.TagService;
import com.juick.service.UserService;
import com.juick.www.WebApp;
+import com.juick.www.api.webfinger.model.Account;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -31,11 +39,15 @@ import org.springframework.security.oauth2.server.authorization.settings.ClientS
import org.springframework.web.bind.annotation.*;
import javax.inject.Inject;
+
+import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
@RestController
public class Mastodon {
@@ -47,26 +59,34 @@ public class Mastodon {
UserService userService;
@Inject
RegisteredClientRepository registeredClientRepository;
+ @Inject
+ TagService tagService;
+ @Inject
+ ChatService chatService;
- public record ApplicationRequest(@JsonProperty("client_name") String clientName,
- @JsonProperty("redirect_uris") String redirectUris,
- @JsonProperty("scopes") String scopes) {
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+ public record ApplicationRequest(String clientName,
+ String redirectUris,
+ String scopes) {
}
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record ApplicationResponse(String id,
- @JsonProperty("client_name") String name,
- @JsonProperty("redirect_uri") String redirectUri,
- @JsonProperty("client_id") String clientId,
- @JsonProperty("client_secret") String clientSecret) {
+ String name,
+ String redirectUri,
+ String clientId,
+ String clientSecret) {
}
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record CredentialAccount(String id, String username, String acct,
- @JsonProperty("display_name") String displayName,
- @JsonProperty("followers_count") Integer followersCount,
- @JsonProperty("following_count") Integer followingCount,
- String avatar) {
+ String displayName,
+ Integer followersCount,
+ Integer followingCount,
+ String avatar) {
}
+
private Collection<String> parseScopes(String s) {
return s != null ? Arrays.asList(s.split(" ")) : Collections.emptyList();
}
@@ -76,19 +96,19 @@ public class Mastodon {
return apps(application.clientName(), application.redirectUris(), application.scopes());
}
- @PostMapping(value = "/api/v1/apps", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE })
+ @PostMapping(value = "/api/v1/apps", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE,
+ MediaType.MULTIPART_FORM_DATA_VALUE })
public ApplicationResponse apps(@RequestParam("client_name") String clientName,
- @RequestParam("redirect_uris") String redirectUris,
- @RequestParam("scopes") String scopes) {
+ @RequestParam("redirect_uris") String redirectUris,
+ @RequestParam("scopes") String scopes) {
var secret = UUID.randomUUID().toString();
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(UUID.randomUUID().toString())
.clientSecret("{noop}" + secret)
.clientName(clientName)
.clientAuthenticationMethods(coll -> coll.addAll(List.of(
- ClientAuthenticationMethod.CLIENT_SECRET_POST,
- ClientAuthenticationMethod.CLIENT_SECRET_BASIC
- )))
+ ClientAuthenticationMethod.CLIENT_SECRET_POST,
+ ClientAuthenticationMethod.CLIENT_SECRET_BASIC)))
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri(redirectUris)
.scopes((coll) -> coll.addAll(parseScopes(scopes)))
@@ -99,24 +119,121 @@ public class Mastodon {
registeredClient.getClientName(),
String.join(",", registeredClient.getRedirectUris()),
registeredClient.getClientId(),
- secret
- );
+ secret);
}
- public record Instance(String domain) {}
- @GetMapping("/api/v1/instance")
+
+ public record Instance(String domain) {
+ }
+
+ @GetMapping({ "/api/v1/instance", "/api/v2/instance" })
public Instance getInstance() {
return new Instance(domain);
}
+
@GetMapping("/api/v1/accounts/verify_credentials")
public CredentialAccount account(@ModelAttribute User visitor) {
+ return toAccount(visitor);
+ }
+
+ @GetMapping({ "/api/v1/announcements", "/api/v1/lists", "/api/v1/custom_emojis" })
+ public ResponseEntity<List<Void>> arrayStubs() {
+ return new ResponseEntity<>(List.of(), HttpStatus.OK);
+ }
+
+ public record Preferences() {
+ };
+
+ @GetMapping("/api/v1/preferences")
+ public Preferences preferences() {
+ return new Preferences();
+ }
+
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+ public record Filter(String id, String phrase, List<String> context,
+ Instant expiresAt, Boolean irreversible,
+ Boolean wholeWord) {
+
+ }
+
+ @GetMapping("/api/v1/filters")
+ public List<Filter> filters() {
+ return List.of();
+ }
+
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+ public record Relationship(String id, Boolean following, Boolean showingReblogs,
+ Boolean notyfing, Boolean followingBy, Boolean blocked, Boolean blockedBy,
+ Boolean muting, Boolean mutingNotifications, Boolean requested, Boolean domainBlocking,
+ Boolean endorsed, String note) {
+
+ }
+
+ private Relationship findRelationships(String visitorId, String userId) {
+ User user = userService.getUserByUID(Integer.valueOf(userId)).orElseThrow();
+ var readers = userService.getUserReaders(Integer.valueOf(visitorId));
+ var friends = userService.getUserFriends(Integer.valueOf(visitorId));
+ var bl = userService.getUserBLUsers(Integer.valueOf(visitorId));
+ var isFriend = friends.contains(user);
+ var isReader = readers.contains(user);
+ var isMuting = bl.contains(user);
+ return new Relationship(userId,
+ isFriend, isFriend, isFriend, isReader, false, false,
+ isMuting, isMuting, false,
+ false, false, "");
+ }
+
+ @GetMapping("/api/v1/accounts/relationships")
+ public List<Relationship> relationships(@ModelAttribute User visitor, @RequestParam(value="id[]") String[] ids) {
+ return Stream.of(ids).map(
+ id -> findRelationships(String.valueOf(visitor.getUid()), id)
+ ).collect(Collectors.toList());
+ }
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+ public record Status(String id, Instant createdAt, String inReplyToId, String inReplyToAccountId,
+ Boolean sensitive, String spoilerText, String visibility, String content) {}
+ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+ public record Conversation(
+ String id,
+ Boolean unread,
+ List<CredentialAccount> accounts,
+ Status lastStatus
+ ) {}
+ public CredentialAccount toAccount(User user) {
return new CredentialAccount(
- String.valueOf(visitor.getUid()),
- visitor.getName(),
- visitor.getName(),
- visitor.getFullName(),
- userService.getUserReaders(visitor.getUid()).size(),
- userService.getUserFriends(visitor.getUid()).size(),
- webApp.getAvatarUrl(visitor)
+ String.valueOf(user.getUid()),
+ user.getName(),
+ user.getName(),
+ user.getFullName(),
+ userService.getUserReaders(user.getUid()).size(),
+ userService.getUserFriends(user.getUid()).size(),
+ webApp.getAvatarUrl(user));
+ }
+
+ public Status mapLastMessage(Chat chat) {
+ return new Status(
+ String.valueOf(chat.getLastMessageTimestamp().toEpochMilli()),
+ chat.getLastMessageTimestamp(),
+ null,
+ null,
+ false,
+ "",
+ "direct",
+ chat.getLastMessageText()
);
}
+
+ public Conversation toConversation(User user, Chat chat) {
+ return new Conversation(
+ String.valueOf(chat.getUid()),
+ chat.getUnread() != null && chat.getUnread().size() > 0,
+ List.of(toAccount(user), toAccount(chat)),
+ mapLastMessage(chat));
+ }
+
+ @GetMapping("/api/v1/conversations")
+ public List<Conversation> conversations(@ModelAttribute User visitor) {
+ return chatService.getLastChats(visitor).stream().map(
+ chat -> toConversation(visitor, chat)
+ ).collect(Collectors.toList());
+ }
}
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 fba39954..b0a39a76 100644
--- a/src/main/java/com/juick/www/api/activity/Profile.java
+++ b/src/main/java/com/juick/www/api/activity/Profile.java
@@ -422,7 +422,7 @@ public class Profile {
return new ResponseEntity<>(CommandResult.fromString("Can not authenticate"), HttpStatus.UNAUTHORIZED);
}
- @PostMapping(value = { "/u/", "/api/u/" }, produces = MediaType.APPLICATION_JSON_VALUE)
+ @PostMapping(value = "/api/u/", produces = MediaType.APPLICATION_JSON_VALUE)
public User fetchUser(@RequestParam URI uri) throws JsonProcessingException, HttpBadRequestException {
return activityPubManager.actorToUser(uri);
}