diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java index 408dd6ad..d3e51c6c 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/ChannelRequest.java @@ -6,9 +6,9 @@ import com.javadiscord.jdi.core.api.builders.*; import com.javadiscord.jdi.core.models.channel.Channel; import com.javadiscord.jdi.core.models.channel.ThreadMember; +import com.javadiscord.jdi.core.models.emoji.Emoji; import com.javadiscord.jdi.core.models.invite.Invite; import com.javadiscord.jdi.core.models.message.Message; -import com.javadiscord.jdi.core.models.message.MessageReaction; import com.javadiscord.jdi.core.models.user.User; import com.javadiscord.jdi.internal.api.channel.*; @@ -49,13 +49,13 @@ public AsyncResponse createMessage(CreateMessageBuilder builder) { return responseParser.callAndParse(Message.class, builder.build()); } - public AsyncResponse createReaction( + public AsyncResponse createReaction( long channelId, long messageId, - String emoji + Emoji emoji ) { return responseParser.callAndParse( - MessageReaction.class, new CreateReactionRequest(channelId, messageId, emoji) + Void.class, new CreateReactionRequest(channelId, messageId, emoji) ); } @@ -211,14 +211,8 @@ public AsyncResponse> listThreadMembers(ListThreadMembersBuil return responseParser.callAndParseList(ThreadMember.class, builder.build()); } - public AsyncResponse modifyChannel( - long channelId, - String name, - String base64EncodedIcon - ) { - return responseParser.callAndParse( - Channel.class, new ModifyChannelRequest(channelId, name, base64EncodedIcon) - ); + public AsyncResponse modifyChannel(ModifyChannelBuilder builder) { + return responseParser.callAndParse(Channel.class, builder.build()); } public AsyncResponse pinMessage(long channelId, long messageId) { diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/EmojiRequest.java b/api/src/main/java/com/javadiscord/jdi/core/api/EmojiRequest.java index 6869f696..1482eab2 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/EmojiRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/EmojiRequest.java @@ -29,8 +29,8 @@ public AsyncResponse getEmoji(long emojiId) { return responseParser.callAndParse(Emoji.class, new GetEmojiRequest(guildId, emojiId)); } - public AsyncResponse getEmojis() { - return responseParser.callAndParse(Emoji.class, new GetEmojisRequest(guildId)); + public AsyncResponse> getEmojis() { + return responseParser.callAndParseList(Emoji.class, new GetEmojisRequest(guildId)); } public AsyncResponse modifyEmoji(ModifyEmojiBuilder builder) { diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java index ac64c441..dedd41a0 100644 --- a/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/CreateMessageBuilder.java @@ -1,7 +1,6 @@ package com.javadiscord.jdi.core.api.builders; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -22,8 +21,7 @@ public class CreateMessageBuilder { private Optional> components; private Optional> stickerIds; private Optional> files; - private Optional payloadJson; - private List attachments; + private Optional> attachments; private Optional flags; private Optional enforceNonce; @@ -38,8 +36,7 @@ public CreateMessageBuilder(long channelId) { this.components = Optional.empty(); this.stickerIds = Optional.empty(); this.files = Optional.empty(); - this.payloadJson = Optional.empty(); - this.attachments = new ArrayList<>(); + this.attachments = Optional.empty(); this.flags = Optional.empty(); this.enforceNonce = Optional.empty(); } @@ -89,13 +86,8 @@ public CreateMessageBuilder files(List files) { return this; } - public CreateMessageBuilder payloadJson(String payloadJson) { - this.payloadJson = Optional.of(payloadJson); - return this; - } - public CreateMessageBuilder attachments(List attachments) { - this.attachments = attachments; + this.attachments = Optional.of(attachments); return this; } @@ -121,7 +113,6 @@ public CreateMessageRequest build() { components, stickerIds, files, - payloadJson, attachments, flags, enforceNonce diff --git a/api/src/main/java/com/javadiscord/jdi/core/api/builders/ModifyChannelBuilder.java b/api/src/main/java/com/javadiscord/jdi/core/api/builders/ModifyChannelBuilder.java new file mode 100644 index 00000000..e1749128 --- /dev/null +++ b/api/src/main/java/com/javadiscord/jdi/core/api/builders/ModifyChannelBuilder.java @@ -0,0 +1,31 @@ +package com.javadiscord.jdi.core.api.builders; + +import java.util.Optional; + +import com.javadiscord.jdi.internal.api.channel.ModifyChannelRequest; + +public class ModifyChannelBuilder { + private final long channelId; + private Optional name; + private Optional base64EncodedIcon; + + public ModifyChannelBuilder(long channelId) { + this.channelId = channelId; + this.name = Optional.empty(); + this.base64EncodedIcon = Optional.empty(); + } + + public ModifyChannelBuilder name(String name) { + this.name = Optional.of(name); + return this; + } + + public ModifyChannelBuilder base64EncodedIcon(String base64EncodedIcon) { + this.base64EncodedIcon = Optional.of(base64EncodedIcon); + return this; + } + + public ModifyChannelRequest build() { + return new ModifyChannelRequest(channelId, name, base64EncodedIcon); + } +} diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java index 7bce7bc1..db9ac121 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateMessageRequest.java @@ -1,11 +1,13 @@ package com.javadiscord.jdi.internal.api.channel; +import java.io.FileNotFoundException; import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import com.javadiscord.jdi.core.api.utils.DiscordImageUtil; import com.javadiscord.jdi.core.models.channel.ChannelMention; import com.javadiscord.jdi.core.models.message.MessageAttachment; import com.javadiscord.jdi.core.models.message.MessageReference; @@ -13,6 +15,7 @@ import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; +import com.github.mizosoft.methanol.MediaType; import com.github.mizosoft.methanol.MultipartBodyPublisher; public record CreateMessageRequest( @@ -26,8 +29,7 @@ public record CreateMessageRequest( Optional> components, Optional> stickerIds, Optional> files, - Optional payloadJson, - List attachments, + Optional> attachments, Optional flags, Optional enforceNonce ) implements DiscordRequest { @@ -35,9 +37,10 @@ public record CreateMessageRequest( /** TODO: Add functionality for the enforceNonce flag */ @Override public DiscordRequestBuilder create() { - MultipartBodyPublisher.Builder bodyBuilder = MultipartBodyPublisher.newBuilder(); + MultipartBodyPublisher.Builder multiPartBody = MultipartBodyPublisher.newBuilder(); Map body = new HashMap<>(); + content.ifPresent(val -> body.put("content", val)); nonce.ifPresent(val -> body.put("nonce", val)); tts.ifPresent(val -> body.put("tts", val)); @@ -47,16 +50,39 @@ public DiscordRequestBuilder create() { components.ifPresent(val -> body.put("components", val)); stickerIds.ifPresent(val -> body.put("sticker_ids", val)); flags.ifPresent(val -> body.put("flags", val)); + + attachments.ifPresent(val -> body.put("attachments", val)); + enforceNonce.ifPresent(val -> body.put("enforce_nonce", val)); - if (payloadJson.isPresent()) { + if (files.isPresent()) { + List f = files.get(); + for (int i = 0; i < f.size(); i++) { + try { + Path path = f.get(i); + String name = "files[" + i + "]"; + + String extension = DiscordImageUtil.getExtension(path); + + switch (extension) { + case "png" -> multiPartBody.filePart(name, path, MediaType.IMAGE_PNG); + case "jpg", "jpeg" -> + multiPartBody.filePart(name, path, MediaType.IMAGE_JPEG); + case "gif" -> multiPartBody.filePart(name, path, MediaType.IMAGE_GIF); + default -> multiPartBody.filePart(name, path, MediaType.ANY); + } + + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } - bodyBuilder.textPart("payload_json", body); + body.forEach(multiPartBody::textPart); return new DiscordRequestBuilder() .post() .path("/channels/%s/messages".formatted(channelId)) - .multipartBody(bodyBuilder.build()); + .multipartBody(multiPartBody.build()); } return new DiscordRequestBuilder() diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java index c0223985..0f5508b8 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/CreateReactionRequest.java @@ -1,12 +1,13 @@ package com.javadiscord.jdi.internal.api.channel; +import com.javadiscord.jdi.core.models.emoji.Emoji; import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; public record CreateReactionRequest( long channelId, long messageId, - String emoji + Emoji emoji ) implements DiscordRequest { @Override public DiscordRequestBuilder create() { @@ -14,7 +15,7 @@ public DiscordRequestBuilder create() { .put() .path( "/channels/%s/messages/%s/reactions/%s/@me" - .formatted(channelId, messageId, emoji) + .formatted(channelId, messageId, ":%s:%d".formatted(emoji.name(), emoji.id())) ); } } diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/FetchChannelMessagesRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/FetchChannelMessagesRequest.java index e14c3752..181dd910 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/FetchChannelMessagesRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/FetchChannelMessagesRequest.java @@ -32,7 +32,7 @@ public DiscordRequestBuilder create() { after.ifPresent(val -> requestBuilder.queryParam("after", val)); requestBuilder.queryParam("limit", limit); - requestBuilder.path("/channels/%s".formatted(channelId)); + requestBuilder.path("/channels/%s/messages".formatted(channelId)); return requestBuilder; } } diff --git a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/ModifyChannelRequest.java b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/ModifyChannelRequest.java index db685912..57bb7749 100644 --- a/api/src/main/java/com/javadiscord/jdi/internal/api/channel/ModifyChannelRequest.java +++ b/api/src/main/java/com/javadiscord/jdi/internal/api/channel/ModifyChannelRequest.java @@ -1,26 +1,28 @@ package com.javadiscord.jdi.internal.api.channel; +import java.util.HashMap; import java.util.Map; +import java.util.Optional; import com.javadiscord.jdi.internal.api.DiscordRequest; import com.javadiscord.jdi.internal.api.DiscordRequestBuilder; public record ModifyChannelRequest( long channelId, - String name, - String base64EncodedIcon + Optional name, + Optional base64EncodedIcon ) implements DiscordRequest { @Override public DiscordRequestBuilder create() { + Map body = new HashMap<>(); + + name.ifPresent(val -> body.put("name", val)); + base64EncodedIcon.ifPresent(val -> body.put("icon", val)); + return new DiscordRequestBuilder() .patch() .path("/channels/%s".formatted(channelId)) - .body( - Map.of( - "name", name, - "icon", base64EncodedIcon - ) - ); + .body(body); } } diff --git a/api/src/test/integration/com/javadiscord/jdi/core/api/ChannelRequestTest.java b/api/src/test/integration/com/javadiscord/jdi/core/api/ChannelRequestTest.java new file mode 100644 index 00000000..fb65b707 --- /dev/null +++ b/api/src/test/integration/com/javadiscord/jdi/core/api/ChannelRequestTest.java @@ -0,0 +1,308 @@ +package com.javadiscord.jdi.core.api; + +import com.javadiscord.jdi.core.Guild; +import com.javadiscord.jdi.core.api.builders.CreateMessageBuilder; +import com.javadiscord.jdi.core.api.builders.FetchChannelMessagesBuilder; +import com.javadiscord.jdi.core.models.channel.Channel; +import com.javadiscord.jdi.core.models.emoji.Emoji; +import com.javadiscord.jdi.core.models.invite.Invite; +import com.javadiscord.jdi.core.models.message.Message; +import helpers.LiveDiscordHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +class ChannelRequestTest { + private static Guild guild; + + @BeforeAll + public static void setup() throws InterruptedException { + guild = new LiveDiscordHelper().getGuild(); + } + + @Test + void testSendFile() throws InterruptedException, URISyntaxException { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + URL url = ChannelRequestTest.class.getResource("/cat.jpg"); + + if (url == null) { + fail("/cat.jpg not found"); + return; + } + + Path path = Paths.get(url.toURI()); + + AsyncResponse asyncResponse = guild + .channel() + .createMessage(new CreateMessageBuilder(testChannelId) + .files(List.of(path))); + + asyncResponse.onSuccess(res -> { + assertEquals(testChannelId, res.channelId()); + assertEquals("cat.jpg", res.messageAttachments().getFirst().filename()); + assertEquals("image/jpeg", res.messageAttachments().getFirst().contentType()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + @Test + void testSendMessageWithFile() throws InterruptedException, URISyntaxException { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + URL url = ChannelRequestTest.class.getResource("/cat.jpg"); + + if (url == null) { + fail("/cat.jpg not found"); + return; + } + + Path path = Paths.get(url.toURI()); + + AsyncResponse asyncResponse = guild + .channel() + .createMessage(new CreateMessageBuilder(testChannelId) + .files(List.of(path)) + .content("Hello, World!")); + + asyncResponse.onSuccess(res -> { + System.out.println(res); + assertEquals(testChannelId, res.channelId()); + assertEquals("cat.jpg", res.messageAttachments().getFirst().filename()); + assertEquals("image/jpeg", res.messageAttachments().getFirst().contentType()); + assertEquals("Hello, World!", res.content()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + @Test + void testCreateInvite() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse asyncResponse = guild.channel().createInvite(testChannelId, 10000, 10, true); + + asyncResponse.onSuccess(res -> { + assertEquals(10000, res.maxAge()); + assertEquals(10, res.maxUses()); + assertTrue(res.temporary()); + assertNotNull(res.code()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + @Test + void testChannelInvites() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse> asyncResponse = guild.channel().channelInvites(testChannelId); + + asyncResponse.onSuccess(res -> { + assertFalse(res.isEmpty()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + @Test + void testTypingIndicator() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse asyncResponse = guild.channel().typingIndicatorRequest(testChannelId); + + asyncResponse.onSuccess(res -> latch.countDown()); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + @Test + void testPinAndUnpinMessage() throws InterruptedException { + AtomicReference messageId = new AtomicReference<>(); + + { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse asyncResponse = guild + .channel() + .createMessage(new CreateMessageBuilder(testChannelId) + .content("Hello, World!")); + + asyncResponse.onSuccess(res -> { + messageId.set(res.id()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse asyncResponse = guild + .channel() + .pinMessage(testChannelId, messageId.get()); + + asyncResponse.onSuccess(res -> latch.countDown()); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse asyncResponse = guild + .channel() + .unpinMessage(testChannelId, messageId.get()); + + asyncResponse.onSuccess(res -> latch.countDown()); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + } + + @Test + void testFetchChannelMessages() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse> asyncResponse = guild + .channel() + .fetchChannelMessages(new FetchChannelMessagesBuilder(testChannelId, 100)); + + asyncResponse.onSuccess(res -> { + assertFalse(res.isEmpty()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + @Test + void testFetchChannel() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + + long testChannelId = 1242792813700055134L; + + AsyncResponse asyncResponse = guild + .channel() + .fetchChannel(testChannelId); + + asyncResponse.onSuccess(res -> { + assertEquals("general", res.name()); + assertEquals(testChannelId, res.id()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + + + @Test + void testCreateReaction() throws InterruptedException { + AtomicReference messageId = new AtomicReference<>(); + long testChannelId = 1242792813700055134L; + + { + CountDownLatch latch = new CountDownLatch(1); + + AsyncResponse asyncResponse = guild + .channel() + .createMessage(new CreateMessageBuilder(testChannelId) + .content("Hello, World!")); + + asyncResponse.onSuccess(res -> { + messageId.set(res.id()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + AtomicReference emoji = new AtomicReference<>(); + { + CountDownLatch latch = new CountDownLatch(1); + AsyncResponse> asyncResponse = guild + .emoji() + .getEmojis(); + + asyncResponse.onSuccess(res -> { + assertFalse(res.isEmpty()); + emoji.set(res.getFirst()); + latch.countDown(); + }); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + + { + CountDownLatch latch = new CountDownLatch(1); + AsyncResponse asyncResponse = guild + .channel().createReaction(testChannelId, messageId.get(), emoji.get()); + + asyncResponse.onSuccess(res -> latch.countDown()); + + asyncResponse.onError(Assertions::fail); + + assertTrue(latch.await(30, TimeUnit.SECONDS)); + } + } +} diff --git a/api/src/test/resources/cat.jpg b/api/src/test/resources/cat.jpg new file mode 100644 index 00000000..c90c2562 Binary files /dev/null and b/api/src/test/resources/cat.jpg differ