Skip to content

Commit 45617ce

Browse files
authored
implement Webhook API (#87)
Implement webhook API endpoints
1 parent 98f045a commit 45617ce

15 files changed

+423
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import java.util.Optional;
9+
10+
public record CreateWebhookRequest(
11+
long channelId, String name, Optional<String> avatar, Optional<String> reason)
12+
implements DiscordRequest {
13+
14+
public CreateWebhookRequest {
15+
if (name.length() > 80 || name.isEmpty())
16+
throw new IllegalArgumentException("Name must be <= 80 characters");
17+
18+
// Must follow Discord's naming guidelines
19+
String[] disallowedSubstrings = {"clyde", "discord", "@", "#", ":", "```"};
20+
for (String s : disallowedSubstrings) {
21+
if (name.toLowerCase()
22+
.contains(s.toLowerCase())) { // Crude way of checking case insensitivity
23+
throw new IllegalArgumentException(
24+
"Cannot include one of the following characters: clyde, discord, @, #, :,"
25+
+ " ```");
26+
}
27+
}
28+
}
29+
30+
@Override
31+
public DiscordRequestBuilder create() {
32+
Map<String, Object> body = new HashMap<>();
33+
body.put("name", name);
34+
avatar.ifPresent(val -> body.put("avatar", val));
35+
36+
DiscordRequestBuilder discordRequestBuilder =
37+
new DiscordRequestBuilder()
38+
.post()
39+
.path("/channels/%s/webhooks".formatted(channelId))
40+
.body(body);
41+
42+
reason.ifPresent(reason -> discordRequestBuilder.putHeader("X-Audit-Log-Reason", reason));
43+
44+
return discordRequestBuilder;
45+
}
46+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
import java.util.Optional;
7+
8+
public record DeleteWebhookMessageRequest(
9+
long webhookId, String webhookToken, long messageId, Optional<Long> threadId)
10+
implements DiscordRequest {
11+
@Override
12+
public DiscordRequestBuilder create() {
13+
DiscordRequestBuilder discordRequestBuilder =
14+
new DiscordRequestBuilder()
15+
.delete()
16+
.path(
17+
"/webhooks/%s/%s/messages/%s"
18+
.formatted(webhookId, webhookToken, messageId));
19+
20+
threadId.ifPresent(val -> discordRequestBuilder.queryParam("thread_id", val));
21+
22+
return discordRequestBuilder;
23+
}
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
import java.util.Optional;
7+
8+
public record DeleteWebhookRequest(String webhookId, Optional<String> reason)
9+
implements DiscordRequest {
10+
11+
@Override
12+
public DiscordRequestBuilder create() {
13+
DiscordRequestBuilder discordRequestBuilder =
14+
new DiscordRequestBuilder().delete().path("/webhooks/%s".formatted(webhookId));
15+
reason.ifPresent(reason -> discordRequestBuilder.putHeader("X-Audit-Log-Reason", reason));
16+
17+
return discordRequestBuilder;
18+
}
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
import java.util.Optional;
7+
8+
public record DeleteWebhookWithTokenRequest(
9+
long webhookId, String webhookToken, Optional<String> reason) implements DiscordRequest {
10+
11+
@Override
12+
public DiscordRequestBuilder create() {
13+
DiscordRequestBuilder discordRequestBuilder =
14+
new DiscordRequestBuilder()
15+
.delete()
16+
.path("/webhooks/%s/%s".formatted(webhookId, webhookToken));
17+
reason.ifPresent(reason -> discordRequestBuilder.putHeader("X-Audit-Log-Reason", reason));
18+
19+
return discordRequestBuilder;
20+
}
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.github.mizosoft.methanol.MultipartBodyPublisher;
4+
import com.javadiscord.jdi.core.models.message.MessageAttachment;
5+
import com.javadiscord.jdi.core.models.message.embed.Embed;
6+
import com.javadiscord.jdi.internal.api.DiscordRequest;
7+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
8+
9+
import java.util.List;
10+
import java.util.Optional;
11+
12+
public record EditWebhookMessageRequest(
13+
long webhookId,
14+
String webhookToken,
15+
long messageId,
16+
Optional<Long> threadId,
17+
Optional<String> content,
18+
Optional<List<Embed>> embeds,
19+
Optional<Object> allowedMentions,
20+
Optional<List<Object>> components,
21+
Optional<Object> files,
22+
Optional<String> payloadJson,
23+
Optional<List<MessageAttachment>> attachments)
24+
implements DiscordRequest {
25+
26+
@Override
27+
public DiscordRequestBuilder create() {
28+
29+
DiscordRequestBuilder discordRequestBuilder =
30+
new DiscordRequestBuilder()
31+
.patch()
32+
.path(
33+
"/webhooks/%s/%s/messages/%s"
34+
.formatted(webhookId, webhookToken, messageId))
35+
.multipartBody(
36+
MultipartBodyPublisher.newBuilder()
37+
.textPart("content", content)
38+
.textPart("embeds", embeds)
39+
.textPart("allowed_mentions", allowedMentions)
40+
.textPart("components", components)
41+
.textPart("files", files)
42+
.textPart("payload_json", payloadJson)
43+
.textPart("attachments", attachments)
44+
.build());
45+
46+
threadId.ifPresent(val -> discordRequestBuilder.queryParam("thread_id", val));
47+
return discordRequestBuilder;
48+
}
49+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
import java.util.Optional;
7+
8+
public record ExecuteGithubCompatibleWebhookRequest(
9+
long webhookId, String webhookToken, Optional<Long> threadId, Optional<Boolean> waits)
10+
implements DiscordRequest {
11+
@Override
12+
public DiscordRequestBuilder create() {
13+
DiscordRequestBuilder discordRequestBuilder =
14+
new DiscordRequestBuilder()
15+
.post()
16+
.path("/webhooks/%s/%s/github".formatted(webhookId, webhookToken));
17+
18+
threadId.ifPresent(val -> discordRequestBuilder.queryParam("thread_id", val));
19+
waits.ifPresent(val -> discordRequestBuilder.queryParam("wait", val));
20+
21+
return discordRequestBuilder;
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
import java.util.Optional;
7+
8+
public record ExecuteSlackCompatibleWebhookRequest(
9+
long webhookId, String webhookToken, Optional<Long> threadId, Optional<Boolean> waits)
10+
implements DiscordRequest {
11+
@Override
12+
public DiscordRequestBuilder create() {
13+
DiscordRequestBuilder discordRequestBuilder =
14+
new DiscordRequestBuilder()
15+
.post()
16+
.path("/webhooks/%s/%s/slack".formatted(webhookId, webhookToken));
17+
18+
threadId.ifPresent(val -> discordRequestBuilder.queryParam("thread_id", val));
19+
waits.ifPresent(val -> discordRequestBuilder.queryParam("wait", val));
20+
21+
return discordRequestBuilder;
22+
}
23+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.github.mizosoft.methanol.MultipartBodyPublisher;
4+
import com.javadiscord.jdi.core.models.message.MessageAttachment;
5+
import com.javadiscord.jdi.core.models.poll.Poll;
6+
import com.javadiscord.jdi.internal.api.DiscordRequest;
7+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
8+
9+
import java.util.HashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
import java.util.stream.Stream;
14+
15+
public record ExecuteWebhookRequest(
16+
long webhookId,
17+
String webhookToken,
18+
Optional<Boolean> waits,
19+
Optional<Long> threadId,
20+
Optional<String> content,
21+
Optional<String> username,
22+
Optional<String> avatarUrl,
23+
Optional<Boolean> tts,
24+
Optional<List<Object>> embeds,
25+
Optional<Object> allowedMentions,
26+
Optional<List<Object>> components,
27+
Optional<Object> files,
28+
String payloadJson,
29+
Optional<List<MessageAttachment>> attachments,
30+
Optional<Integer> flags,
31+
Optional<String> threadName,
32+
Optional<List<Long>> appliedTags,
33+
Optional<Poll> poll)
34+
implements DiscordRequest {
35+
36+
public ExecuteWebhookRequest {
37+
if (Stream.of(content, embeds, components, files, poll).noneMatch(Optional::isPresent)) {
38+
throw new IllegalArgumentException(
39+
"At least one of content, embeds, components, files, poll should be present");
40+
}
41+
}
42+
43+
@Override
44+
public DiscordRequestBuilder create() {
45+
Map<String, Object> body = new HashMap<>();
46+
content.ifPresent(val -> body.put("content", val));
47+
username.ifPresent(val -> body.put("username", val));
48+
avatarUrl.ifPresent(val -> body.put("avatar_url", val));
49+
tts.ifPresent(val -> body.put("tts", val));
50+
embeds.ifPresent(val -> body.put("embeds", val));
51+
allowedMentions.ifPresent(val -> body.put("allowed_mentions", val));
52+
components.ifPresent(val -> body.put("components", val));
53+
files.ifPresent(val -> body.put("files", val));
54+
body.put("payload_json", payloadJson);
55+
attachments.ifPresent(val -> body.put("attachments", val));
56+
flags.ifPresent(val -> body.put("flags", val));
57+
threadName.ifPresent(val -> body.put("thread_name", val));
58+
appliedTags.ifPresent(val -> body.put("applied_tags", val));
59+
poll.ifPresent(val -> body.put("poll", val));
60+
61+
DiscordRequestBuilder discordRequestBuilder =
62+
new DiscordRequestBuilder()
63+
.post()
64+
.path("/webhooks/%s/%s".formatted(webhookId, webhookToken))
65+
.multipartBody(
66+
MultipartBodyPublisher.newBuilder()
67+
.textPart("payload_json", payloadJson)
68+
.build())
69+
.body(body);
70+
71+
waits.ifPresent(val -> discordRequestBuilder.queryParam("wait", val));
72+
threadId.ifPresent(val -> discordRequestBuilder.queryParam("thread_id", val));
73+
74+
return discordRequestBuilder;
75+
}
76+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
public record GetChannelWebhooksRequest(long channelId) implements DiscordRequest {
7+
8+
@Override
9+
public DiscordRequestBuilder create() {
10+
return new DiscordRequestBuilder().get().path("/channels/%s/webhooks".formatted(channelId));
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.javadiscord.jdi.internal.api.webhook;
2+
3+
import com.javadiscord.jdi.internal.api.DiscordRequest;
4+
import com.javadiscord.jdi.internal.api.DiscordRequestBuilder;
5+
6+
public record GetGuildWebhooksRequest(long guildId) implements DiscordRequest {
7+
8+
@Override
9+
public DiscordRequestBuilder create() {
10+
return new DiscordRequestBuilder().get().path("/guilds/%s/webhooks".formatted(guildId));
11+
}
12+
}

0 commit comments

Comments
 (0)