diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 659eb6af..9c82d6ce 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -76,4 +76,6 @@ jobs:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
- category: "/language:${{matrix.language}}"
+ category: "/language:${{matrix.language}}"
+
+
diff --git a/.github/workflows/qodana.yml b/.github/workflows/qodana.yml
new file mode 100644
index 00000000..bd58e15d
--- /dev/null
+++ b/.github/workflows/qodana.yml
@@ -0,0 +1,25 @@
+name: Qodana
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+ - 'releases/*'
+
+jobs:
+ qodana:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ checks: write
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
+ fetch-depth: 0 # a full history is required for pull request analysis
+ - name: 'Qodana Scan'
+ uses: JetBrains/qodana-action@v2023.2
+ env:
+ QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
diff --git a/pom.xml b/pom.xml
index 0e9bee32..7ef1bda5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,7 +94,7 @@
org.json
json
- 20230618
+ 20231013
com.google.code.gson
@@ -105,7 +105,7 @@
org.springframework.boot
spring-boot-starter-websocket
- 3.1.4
+ 3.1.5
@@ -129,12 +129,12 @@
org.jsoup
jsoup
- 1.16.1
+ 1.16.2
com.squareup.okhttp3
okhttp
- 4.11.0
+ 4.12.0
joda-time
diff --git a/src/main/java/com/seailz/discordjar/DiscordJar.java b/src/main/java/com/seailz/discordjar/DiscordJar.java
index d48dbb7c..463b19f0 100644
--- a/src/main/java/com/seailz/discordjar/DiscordJar.java
+++ b/src/main/java/com/seailz/discordjar/DiscordJar.java
@@ -1,6 +1,7 @@
package com.seailz.discordjar;
import com.seailz.discordjar.action.guild.GetCurrentUserGuildsAction;
+import com.seailz.discordjar.action.sku.ListEntitlementRequest;
import com.seailz.discordjar.cache.Cache;
import com.seailz.discordjar.cache.CacheType;
import com.seailz.discordjar.cache.JsonCache;
@@ -34,6 +35,7 @@
import com.seailz.discordjar.model.guild.Member;
import com.seailz.discordjar.model.invite.Invite;
import com.seailz.discordjar.model.invite.internal.InviteImpl;
+import com.seailz.discordjar.model.monetization.SKU;
import com.seailz.discordjar.model.status.Status;
import com.seailz.discordjar.model.user.User;
import com.seailz.discordjar.utils.Checker;
@@ -304,7 +306,6 @@ public DiscordJar(String token, EnumSet intents, APIVersion version, boo
.setTransportCompressionType(gwCompressionType)
.build();
}
-
}
/**
@@ -1507,6 +1508,36 @@ public void deleteInvite(String code) {
}
}
+ public List getApplicationSkus() {
+ DiscordRequest req = new DiscordRequest(
+ new JSONObject(),
+ new HashMap<>(),
+ URLS.GET.APPLICATION.GET_APPLICATION_SKUS.replace("{application.id}", getSelfInfo().id()),
+ this,
+ URLS.GET.APPLICATION.GET_APPLICATION_SKUS,
+ RequestMethod.GET
+ );
+ JSONArray res = null;
+ try {
+ res = req.invoke().arr();
+ } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) {
+ throw new DiscordRequest.DiscordAPIErrorException(e);
+ }
+ List skus = new ArrayList<>();
+ for (int i = 0; i < res.length(); i++) {
+ skus.add(SKU.decompile(res.getJSONObject(i), this));
+ }
+ return skus;
+ }
+
+ public SKU getSKUById(String id) {
+ return getApplicationSkus().stream().filter(s -> s.id().equals(id)).findFirst().orElse(null);
+ }
+
+ public ListEntitlementRequest getEntitlements() {
+ return new ListEntitlementRequest(this);
+ }
+
public boolean isDebug() {
return debug;
}
diff --git a/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java b/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java
index e2a6fcf0..6a1bd2a8 100644
--- a/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java
+++ b/src/main/java/com/seailz/discordjar/action/interaction/followup/InteractionFollowupAction.java
@@ -12,6 +12,7 @@
import com.seailz.discordjar.utils.rest.Response;
import org.springframework.web.bind.annotation.RequestMethod;
+import java.io.File;
import java.util.HashMap;
import java.util.List;
@@ -129,6 +130,11 @@ public InteractionFollowupAction setAttachments(List attachments) {
return this;
}
+ public InteractionFollowupAction addFiles(File... files) {
+ this.getReply().addFiles(files);
+ return this;
+ }
+
public InteractionFollowupAction setAllowedMentions(AllowedMentions allowedMentions) {
this.getReply().setAllowedMentions(allowedMentions);
return this;
@@ -146,7 +152,7 @@ public InteractionMessageResponse getReply() {
public Response run() {
Response response = new Response<>();
try {
- new DiscordRequest(
+ DiscordRequest req = new DiscordRequest(
getReply().compile(),
new HashMap<>(),
URLS.POST.INTERACTIONS.FOLLOWUP
@@ -155,7 +161,11 @@ public Response run() {
discordJar,
URLS.POST.INTERACTIONS.FOLLOWUP,
RequestMethod.POST
- ).invoke();
+ );
+
+ if (getReply().useFiles()) req.invokeWithFiles(getReply().getFiles().toArray(new File[0]));
+ else req.invoke();
+
response.complete(InteractionHandler.from(token, id, discordJar));
} catch (DiscordRequest.UnhandledDiscordAPIErrorException e) {
response.completeError(new Response.Error(e.getCode(), e.getMessage(), e.getBody()));
diff --git a/src/main/java/com/seailz/discordjar/action/sku/ListEntitlementRequest.java b/src/main/java/com/seailz/discordjar/action/sku/ListEntitlementRequest.java
new file mode 100644
index 00000000..4600619e
--- /dev/null
+++ b/src/main/java/com/seailz/discordjar/action/sku/ListEntitlementRequest.java
@@ -0,0 +1,138 @@
+package com.seailz.discordjar.action.sku;
+
+import com.seailz.discordjar.DiscordJar;
+import com.seailz.discordjar.model.monetization.Entitlement;
+import com.seailz.discordjar.utils.URLS;
+import com.seailz.discordjar.utils.rest.DiscordRequest;
+import com.seailz.discordjar.utils.rest.DiscordResponse;
+import com.seailz.discordjar.utils.rest.Response;
+import org.json.JSONObject;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class ListEntitlementRequest {
+
+ private final DiscordJar jar;
+ private String userId;
+ private List skuIds;
+ private String before;
+ private String after;
+ private int limit = 100;
+ private String guildId;
+ private boolean excludeEnded = false;
+
+ public ListEntitlementRequest(DiscordJar jar) {
+ this.jar = jar;
+ }
+
+ /**
+ * Sets the user ID to get entitlements for.
+ */
+ public ListEntitlementRequest setUserId(String userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ /**
+ * Sets the SKU IDs to get entitlements for.
+ */
+ public ListEntitlementRequest setSkuIds(List skuIds) {
+ this.skuIds = skuIds;
+ return this;
+ }
+
+ public ListEntitlementRequest addSkuId(String skuId) {
+ if (this.skuIds == null) this.skuIds = new ArrayList<>();
+ this.skuIds.add(skuId);
+ return this;
+ }
+
+ /**
+ * Limits entitlements to before this ID.
+ */
+ public ListEntitlementRequest setBefore(String before) {
+ this.before = before;
+ return this;
+ }
+
+ /**
+ * Limits entitlements to after this ID.
+ */
+ public ListEntitlementRequest setAfter(String after) {
+ this.after = after;
+ return this;
+ }
+
+ /**
+ * Limits entitlements to this amount. 1-100
+ */
+ public ListEntitlementRequest setLimit(int limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ /**
+ * Limits entitlements to this guild ID.
+ */
+ public ListEntitlementRequest setGuildId(String guildId) {
+ this.guildId = guildId;
+ return this;
+ }
+
+ /**
+ * Whether or not to exclude ended entitlements.
+ */
+ public ListEntitlementRequest setExcludeEnded(boolean excludeEnded) {
+ this.excludeEnded = excludeEnded;
+ return this;
+ }
+
+ public Response> run() {
+ Response> response = new Response<>();
+ if (limit < 1 || limit > 100) throw new IllegalArgumentException("Limit must be between 1 and 100");
+ new Thread(() -> {
+ String urlWithQuery = URLS.GET.APPLICATION.LIST_ENTITLEMENTS;
+ urlWithQuery += "?";
+ if (userId != null) urlWithQuery += "user_id=" + userId + "&";
+ if (skuIds != null) {
+ StringBuilder skuIdsString = new StringBuilder();
+ for (String skuId : skuIds) {
+ skuIdsString.append(skuId).append(",");
+ }
+ skuIdsString = new StringBuilder(skuIdsString.substring(0, skuIdsString.length() - 1));
+ urlWithQuery += "sku_ids=" + skuIdsString + "&";
+ }
+ if (before != null) urlWithQuery += "before=" + before + "&";
+ if (after != null) urlWithQuery += "after=" + after + "&";
+ urlWithQuery += "limit=" + limit + "&";
+ if (guildId != null) urlWithQuery += "guild_id=" + guildId + "&";
+ urlWithQuery += "exclude_ended=" + excludeEnded;
+
+ DiscordResponse req = null;
+ try {
+ req = new DiscordRequest(
+ new JSONObject(),
+ new HashMap<>(),
+ urlWithQuery.replace("{application.id}", jar.getSelfInfo().id()),
+ jar,
+ URLS.GET.APPLICATION.LIST_ENTITLEMENTS,
+ RequestMethod.GET
+ ).invoke();
+ } catch (DiscordRequest.UnhandledDiscordAPIErrorException e) {
+ response.completeError(new Response.Error(e));
+ return;
+ }
+
+ List entitlements = new ArrayList<>();
+ for (Object o : req.arr()) {
+ entitlements.add(Entitlement.decompile(jar, (JSONObject) o));
+ }
+ response.complete(entitlements);
+ }).start();
+ return response;
+ }
+
+}
diff --git a/src/main/java/com/seailz/discordjar/model/component/select/AutoPopulatedSelect.java b/src/main/java/com/seailz/discordjar/model/component/select/AutoPopulatedSelect.java
new file mode 100644
index 00000000..fbd79894
--- /dev/null
+++ b/src/main/java/com/seailz/discordjar/model/component/select/AutoPopulatedSelect.java
@@ -0,0 +1,14 @@
+package com.seailz.discordjar.model.component.select;
+
+import java.util.List;
+
+/**
+ * Represents a select menu that is auto populated, such as a {@link com.seailz.discordjar.model.component.select.entity.ChannelSelectMenu ChannelSelectMenu}.
+ * @author Seailz
+ * @see AutoPopulatedSelect
+*/
+public interface AutoPopulatedSelect extends SelectMenu {
+
+ List defaultValues();
+
+}
diff --git a/src/main/java/com/seailz/discordjar/model/component/select/entity/ChannelSelectMenu.java b/src/main/java/com/seailz/discordjar/model/component/select/entity/ChannelSelectMenu.java
index 66354f1c..5fafb287 100644
--- a/src/main/java/com/seailz/discordjar/model/component/select/entity/ChannelSelectMenu.java
+++ b/src/main/java/com/seailz/discordjar/model/component/select/entity/ChannelSelectMenu.java
@@ -5,6 +5,7 @@
import com.seailz.discordjar.model.channel.utils.ChannelType;
import com.seailz.discordjar.model.component.ActionComponent;
import com.seailz.discordjar.model.component.ComponentType;
+import com.seailz.discordjar.model.component.select.AutoPopulatedSelect;
import com.seailz.discordjar.model.component.select.SelectMenu;
import com.seailz.discordjar.model.component.select.string.StringSelectMenu;
import com.seailz.discordjar.utils.registry.components.ChannelSelectRegistry;
@@ -13,6 +14,7 @@
import org.json.JSONObject;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -23,7 +25,7 @@
* @see com.seailz.discordjar.model.component.select.SelectMenu
* @since 1.0
*/
-public class ChannelSelectMenu implements SelectMenu {
+public class ChannelSelectMenu implements AutoPopulatedSelect {
private String customId;
private String placeholder;
@@ -31,6 +33,7 @@ public class ChannelSelectMenu implements SelectMenu {
private int maxValues;
private List channelTypes;
private boolean disabled;
+ private List defaultValues;
private JSONObject raw;
@@ -52,7 +55,7 @@ public ChannelSelectMenu(String customId) {
* @param maxValues The maximum amount of values that can be selected
* @param channelTypes The channel types that can be selected
*/
- public ChannelSelectMenu(String customId, String placeholder, int minValues, int maxValues, List channelTypes, boolean disabled, JSONObject raw) {
+ public ChannelSelectMenu(String customId, String placeholder, int minValues, int maxValues, List channelTypes, boolean disabled, JSONObject raw, List defaultValues) {
this.customId = customId;
this.placeholder = placeholder;
this.minValues = minValues;
@@ -60,6 +63,7 @@ public ChannelSelectMenu(String customId, String placeholder, int minValues, int
this.channelTypes = channelTypes;
this.disabled = disabled;
this.raw = raw;
+ this.defaultValues = defaultValues;
}
/**
@@ -72,7 +76,7 @@ public ChannelSelectMenu(String customId, String placeholder, int minValues, int
* @param channelTypes The channel types that can be selected
*/
public ChannelSelectMenu(String customId, String placeholder, int minValues, int maxValues, boolean disabled, JSONObject raw, ChannelType... channelTypes) {
- this(customId, placeholder, minValues, maxValues, List.of(channelTypes), disabled, raw);
+ this(customId, placeholder, minValues, maxValues, List.of(channelTypes), disabled, raw, new ArrayList<>());
}
@Override
@@ -131,6 +135,26 @@ public ActionComponent setDisabled(boolean disabled) {
);
}
+
+ @Override
+ public List defaultValues() {
+ return defaultValues;
+ }
+
+ public ChannelSelectMenu setDefaultValues(List defaultValues) {
+ if (this.defaultValues == null)
+ this.defaultValues = new ArrayList<>();
+ this.defaultValues = defaultValues;
+ return this;
+ }
+
+ public ChannelSelectMenu addDefaultValue(String... defaultValue) {
+ if (this.defaultValues == null)
+ this.defaultValues = new ArrayList<>();
+ this.defaultValues.addAll(Arrays.asList(defaultValue));
+ return this;
+ }
+
/**
* Sets the action of the select.
*
@@ -166,6 +190,13 @@ public JSONObject compile() {
if (maxValues != 0) obj.put("max_values", maxValues);
if (disabled) obj.put("disabled", true);
if (this.channelTypes != null) obj.put("channel_types", channelTypes);
+ if (defaultValues != null && !defaultValues.isEmpty()) {
+ JSONArray values = new JSONArray();
+ for (String value : defaultValues) {
+ values.put(new JSONObject().put("id", value).put("type", "channel"));
+ }
+ obj.put("default_values", values);
+ }
return obj;
}
@@ -187,7 +218,14 @@ public static ChannelSelectMenu decompile(JSONObject json) {
});
}
- return new ChannelSelectMenu(customId, placeholder, minValues, maxValues, channelTypesDecompiled, disabled, json);
+ List defaultValues = new ArrayList<>();
+ if (json.has("default_values")) {
+ for (Object o : json.getJSONArray("default_values")) {
+ defaultValues.add(o.toString());
+ }
+ }
+
+ return new ChannelSelectMenu(customId, placeholder, minValues, maxValues, channelTypesDecompiled, disabled, json, defaultValues);
}
@Override
diff --git a/src/main/java/com/seailz/discordjar/model/component/select/entity/MentionableSelectMenu.java b/src/main/java/com/seailz/discordjar/model/component/select/entity/MentionableSelectMenu.java
index 18d7a562..bccb5d97 100644
--- a/src/main/java/com/seailz/discordjar/model/component/select/entity/MentionableSelectMenu.java
+++ b/src/main/java/com/seailz/discordjar/model/component/select/entity/MentionableSelectMenu.java
@@ -2,9 +2,17 @@
import com.seailz.discordjar.model.component.ActionComponent;
import com.seailz.discordjar.model.component.ComponentType;
+import com.seailz.discordjar.model.component.select.AutoPopulatedSelect;
import com.seailz.discordjar.model.component.select.SelectMenu;
+import com.seailz.discordjar.utils.Mentionable;
+import org.json.JSONArray;
import org.json.JSONObject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
/**
* Represents a {@link com.seailz.discordjar.utils.Mentionable} select menu ({@link com.seailz.discordjar.model.role.Role} & {@link com.seailz.discordjar.model.user.User})
*
@@ -19,6 +27,7 @@ public class MentionableSelectMenu implements SelectMenu {
private int minValues;
private int maxValues;
private boolean disabled;
+ private HashMap defaultValues;
private JSONObject raw;
@@ -39,13 +48,14 @@ public MentionableSelectMenu(String customId) {
* @param minValues The minimum amount of values that can be selected
* @param maxValues The maximum amount of values that can be selected
*/
- public MentionableSelectMenu(String customId, String placeholder, int minValues, int maxValues, boolean disabled, JSONObject raw) {
+ public MentionableSelectMenu(String customId, String placeholder, int minValues, int maxValues, boolean disabled, JSONObject raw, HashMap defaultValues) {
this.customId = customId;
this.placeholder = placeholder;
this.minValues = minValues;
this.maxValues = maxValues;
this.disabled = disabled;
this.raw = raw;
+ this.defaultValues = defaultValues;
}
@Override
@@ -87,7 +97,7 @@ public void setMaxValues(int maxValues) {
@Override
public ActionComponent setDisabled(boolean disabled) {
return new MentionableSelectMenu(
- customId, placeholder, minValues, maxValues, disabled, raw
+ customId, placeholder, minValues, maxValues, disabled, raw, defaultValues
);
}
@@ -96,6 +106,27 @@ public ComponentType type() {
return ComponentType.MENTIONABLE_SELECT;
}
+
+ public HashMap defaultValues() {
+ return defaultValues;
+ }
+
+
+ public MentionableSelectMenu setDefaultValues(HashMap defaultValues) {
+ if (this.defaultValues == null)
+ this.defaultValues = new HashMap<>();
+ this.defaultValues = defaultValues;
+ return this;
+ }
+
+ public MentionableSelectMenu addDefaultValue(MentionableType type, String defaultValue) {
+ if (this.defaultValues == null)
+ this.defaultValues = new HashMap<>();
+ this.defaultValues.put(type, defaultValue);
+ return this;
+ }
+
+
@Override
public boolean isSelect() {
return true;
@@ -113,6 +144,11 @@ public JSONObject compile() {
if (minValues != 0) obj.put("min_values", minValues);
if (maxValues != 0) obj.put("max_values", maxValues);
if (disabled) obj.put("disabled", true);
+ if (defaultValues != null && !defaultValues.isEmpty()) {
+ JSONArray values = new JSONArray();
+ defaultValues.forEach((type, value) -> values.put(new JSONObject().put("id", value).put("type", type.getId())));
+ obj.put("default_values", values);
+ }
return obj;
}
@@ -123,7 +159,17 @@ public static MentionableSelectMenu decompile(JSONObject json) {
int maxValues = json.has("max_values") ? json.getInt("max_values") : 25;
boolean disabled = json.has("disabled") && json.getBoolean("disabled");
- return new MentionableSelectMenu(customId, placeholder, minValues, maxValues, disabled, json);
+ HashMap defaultValues = new HashMap<>();
+
+ if (json.has("default_values")) {
+ JSONArray values = json.getJSONArray("default_values");
+ for (int i = 0; i < values.length(); i++) {
+ JSONObject value = values.getJSONObject(i);
+ defaultValues.put(MentionableType.fromId(value.getString("type")), value.getString("id"));
+ }
+ }
+
+ return new MentionableSelectMenu(customId, placeholder, minValues, maxValues, disabled, json, defaultValues);
}
@Override
diff --git a/src/main/java/com/seailz/discordjar/model/component/select/entity/MentionableType.java b/src/main/java/com/seailz/discordjar/model/component/select/entity/MentionableType.java
new file mode 100644
index 00000000..4949f5b8
--- /dev/null
+++ b/src/main/java/com/seailz/discordjar/model/component/select/entity/MentionableType.java
@@ -0,0 +1,15 @@
+package com.seailz.discordjar.model.component.select.entity;
+
+import org.jetbrains.annotations.NotNull;
+
+public enum MentionableType {
+ ROLE, USER, CHANNEL;
+
+ public @NotNull String getId() {
+ return this.name().toLowerCase();
+ }
+
+ public static @NotNull MentionableType fromId(@NotNull String id) {
+ return MentionableType.valueOf(id.toUpperCase());
+ }
+}
diff --git a/src/main/java/com/seailz/discordjar/model/component/select/entity/RoleSelectMenu.java b/src/main/java/com/seailz/discordjar/model/component/select/entity/RoleSelectMenu.java
index d36a6a17..0fa9902e 100644
--- a/src/main/java/com/seailz/discordjar/model/component/select/entity/RoleSelectMenu.java
+++ b/src/main/java/com/seailz/discordjar/model/component/select/entity/RoleSelectMenu.java
@@ -2,9 +2,15 @@
import com.seailz.discordjar.model.component.ActionComponent;
import com.seailz.discordjar.model.component.ComponentType;
+import com.seailz.discordjar.model.component.select.AutoPopulatedSelect;
import com.seailz.discordjar.model.component.select.SelectMenu;
+import org.json.JSONArray;
import org.json.JSONObject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Represents a role select menu
*
@@ -12,13 +18,14 @@
* @see com.seailz.discordjar.model.component.select.SelectMenu
* @since 1.0
*/
-public class RoleSelectMenu implements SelectMenu {
+public class RoleSelectMenu implements AutoPopulatedSelect {
private String customId;
private String placeholder;
private int minValues;
private int maxValues;
private boolean isDisabled;
+ private List defaultValues;
private JSONObject raw;
@@ -40,13 +47,14 @@ public RoleSelectMenu(String customId) {
* @param maxValues The maximum amount of values that can be selected
* @param disabled If the select menu is disabled
*/
- public RoleSelectMenu(String customId, String placeholder, int minValues, int maxValues, boolean disabled, JSONObject raw) {
+ public RoleSelectMenu(String customId, String placeholder, int minValues, int maxValues, boolean disabled, JSONObject raw, List defaultValues) {
this.customId = customId;
this.placeholder = placeholder;
this.minValues = minValues;
this.maxValues = maxValues;
this.isDisabled = disabled;
this.raw = raw;
+ this.defaultValues = defaultValues;
}
@Override
@@ -92,7 +100,7 @@ public RoleSelectMenu setMaxValues(int maxValues) {
@Override
public ActionComponent setDisabled(boolean disabled) {
return new RoleSelectMenu(
- customId, placeholder, minValues, maxValues, disabled, raw
+ customId, placeholder, minValues, maxValues, disabled, raw, defaultValues
);
}
@@ -106,6 +114,28 @@ public boolean isSelect() {
return true;
}
+
+ @Override
+ public List defaultValues() {
+ return defaultValues;
+ }
+
+
+ public RoleSelectMenu setDefaultValues(List defaultValues) {
+ if (this.defaultValues == null)
+ this.defaultValues = new ArrayList<>();
+ this.defaultValues = defaultValues;
+ return this;
+ }
+
+ public RoleSelectMenu addDefaultValue(String... defaultValue) {
+ if (this.defaultValues == null)
+ this.defaultValues = new ArrayList<>();
+ this.defaultValues.addAll(Arrays.asList(defaultValue));
+ return this;
+ }
+
+
@Override
public JSONObject compile() {
if (minValues > maxValues)
@@ -118,6 +148,13 @@ public JSONObject compile() {
if (minValues != 0) obj.put("min_values", minValues);
if (maxValues != 0) obj.put("max_values", maxValues);
if (isDisabled) obj.put("disabled", true);
+ if (defaultValues != null && !defaultValues.isEmpty()) {
+ JSONArray values = new JSONArray();
+ for (String value : defaultValues) {
+ values.put(new JSONObject().put("id", value).put("type", "role"));
+ }
+ obj.put("default_values", values);
+ }
return obj;
}
@@ -127,8 +164,14 @@ public static RoleSelectMenu decompile(JSONObject json) {
int minValues = json.has("min_values") ? json.getInt("min_values") : 0;
int maxValues = json.has("max_values") ? json.getInt("max_values") : 25;
boolean disabled = json.has("disabled") && json.getBoolean("disabled");
-
- return new RoleSelectMenu(customId, placeholder, minValues, maxValues, disabled, json);
+ List defaultValues = new ArrayList<>();
+ if (json.has("default_values")) {
+ for (Object o : json.getJSONArray("default_values")) {
+ defaultValues.add(o.toString());
+ }
+ }
+
+ return new RoleSelectMenu(customId, placeholder, minValues, maxValues, disabled, json, defaultValues);
}
diff --git a/src/main/java/com/seailz/discordjar/model/component/select/entity/UserSelectMenu.java b/src/main/java/com/seailz/discordjar/model/component/select/entity/UserSelectMenu.java
index 581ecd1e..a7b7970b 100644
--- a/src/main/java/com/seailz/discordjar/model/component/select/entity/UserSelectMenu.java
+++ b/src/main/java/com/seailz/discordjar/model/component/select/entity/UserSelectMenu.java
@@ -2,9 +2,15 @@
import com.seailz.discordjar.model.component.ActionComponent;
import com.seailz.discordjar.model.component.ComponentType;
+import com.seailz.discordjar.model.component.select.AutoPopulatedSelect;
import com.seailz.discordjar.model.component.select.SelectMenu;
+import org.json.JSONArray;
import org.json.JSONObject;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Represents a user select menu
*
@@ -12,13 +18,14 @@
* @see com.seailz.discordjar.model.component.select.SelectMenu
* @since 1.0
*/
-public class UserSelectMenu implements SelectMenu {
+public class UserSelectMenu implements AutoPopulatedSelect {
private String customId;
private String placeholder;
private int minValues;
private int maxValues;
private boolean isDisabled;
+ private List defaultValues;
private JSONObject raw;
@@ -39,13 +46,14 @@ public UserSelectMenu(String customId) {
* @param minValues The minimum amount of values that can be selected
* @param maxValues The maximum amount of values that can be selected
*/
- public UserSelectMenu(String customId, String placeholder, int minValues, int maxValues, boolean isDisabled, JSONObject raw) {
+ public UserSelectMenu(String customId, String placeholder, int minValues, int maxValues, boolean isDisabled, JSONObject raw, List defaultValues) {
this.customId = customId;
this.placeholder = placeholder;
this.minValues = minValues;
this.maxValues = maxValues;
this.isDisabled = isDisabled;
this.raw = raw;
+ this.defaultValues = defaultValues;
}
@Override
@@ -88,10 +96,24 @@ public UserSelectMenu setMaxValues(int maxValues) {
return this;
}
+ public UserSelectMenu setDefaultValues(List defaultValues) {
+ if (this.defaultValues == null)
+ this.defaultValues = new ArrayList<>();
+ this.defaultValues = defaultValues;
+ return this;
+ }
+
+ public UserSelectMenu addDefaultValue(String... defaultValue) {
+ if (this.defaultValues == null)
+ this.defaultValues = new ArrayList<>();
+ this.defaultValues.addAll(Arrays.asList(defaultValue));
+ return this;
+ }
+
@Override
public ActionComponent setDisabled(boolean disabled) {
return new UserSelectMenu(
- customId, placeholder, minValues, maxValues, disabled, raw
+ customId, placeholder, minValues, maxValues, disabled, raw, defaultValues
);
}
@@ -105,6 +127,11 @@ public boolean isSelect() {
return true;
}
+ @Override
+ public List defaultValues() {
+ return defaultValues;
+ }
+
@Override
public JSONObject compile() {
if (minValues > maxValues)
@@ -117,6 +144,13 @@ public JSONObject compile() {
if (minValues != 0) obj.put("min_values", minValues);
if (maxValues != 0) obj.put("max_values", maxValues);
if (isDisabled) obj.put("disabled", true);
+ if (defaultValues != null && !defaultValues.isEmpty()) {
+ JSONArray values = new JSONArray();
+ for (String value : defaultValues) {
+ values.put(new JSONObject().put("id", value).put("type", "user"));
+ }
+ obj.put("default_values", values);
+ }
return obj;
}
@@ -126,8 +160,14 @@ public static UserSelectMenu decompile(JSONObject json) {
int minValues = json.has("min_values") ? json.getInt("min_values") : 0;
int maxValues = json.has("max_values") ? json.getInt("max_values") : 25;
boolean isDisabled = json.has("disabled") && json.getBoolean("disabled");
-
- return new UserSelectMenu(customId, placeholder, minValues, maxValues, isDisabled, json);
+ List defaultValues = new ArrayList<>();
+ if (json.has("default_values")) {
+ for (Object o : json.getJSONArray("default_values")) {
+ defaultValues.add(o.toString());
+ }
+ }
+
+ return new UserSelectMenu(customId, placeholder, minValues, maxValues, isDisabled, json, defaultValues);
}
@Override
diff --git a/src/main/java/com/seailz/discordjar/model/interaction/InteractionData.java b/src/main/java/com/seailz/discordjar/model/interaction/InteractionData.java
index 6237bf71..29fde8e9 100644
--- a/src/main/java/com/seailz/discordjar/model/interaction/InteractionData.java
+++ b/src/main/java/com/seailz/discordjar/model/interaction/InteractionData.java
@@ -23,7 +23,7 @@ public static InteractionData decompile(InteractionType type, JSONObject obj, Di
return switch (type) {
case APPLICATION_COMMAND, APPLICATION_COMMAND_AUTOCOMPLETE ->
new ApplicationCommandInteractionData(obj, jv);
- case MESSAGE_COMPONENT -> new MessageComponentInteractionData(obj);
+ case MESSAGE_COMPONENT -> new MessageComponentInteractionData(obj, jv);
case MODAL_SUBMIT -> new ModalSubmitInteractionData(obj);
default -> null;
};
diff --git a/src/main/java/com/seailz/discordjar/model/interaction/data/message/MessageComponentInteractionData.java b/src/main/java/com/seailz/discordjar/model/interaction/data/message/MessageComponentInteractionData.java
index adaaa629..f6abfa4e 100644
--- a/src/main/java/com/seailz/discordjar/model/interaction/data/message/MessageComponentInteractionData.java
+++ b/src/main/java/com/seailz/discordjar/model/interaction/data/message/MessageComponentInteractionData.java
@@ -1,8 +1,10 @@
package com.seailz.discordjar.model.interaction.data.message;
+import com.seailz.discordjar.DiscordJar;
import com.seailz.discordjar.model.component.ComponentType;
import com.seailz.discordjar.model.component.select.SelectOption;
import com.seailz.discordjar.model.interaction.InteractionData;
+import com.seailz.discordjar.model.interaction.data.ResolvedData;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -25,15 +27,17 @@ public class MessageComponentInteractionData extends InteractionData {
// The selected options if this is a select menu
private SelectOption.ResolvedSelectOption[] selectOptions;
private List snowflakes;
+ private ResolvedData resolved;
- public MessageComponentInteractionData(String customId, ComponentType componentType, SelectOption.ResolvedSelectOption[] selectOptions, List snowflakes) {
+ public MessageComponentInteractionData(String customId, ComponentType componentType, SelectOption.ResolvedSelectOption[] selectOptions, List snowflakes, ResolvedData resolved) {
this.customId = customId;
this.componentType = componentType;
this.selectOptions = selectOptions;
this.snowflakes = snowflakes;
+ this.resolved = resolved;
}
- public MessageComponentInteractionData(JSONObject obj) {
+ public MessageComponentInteractionData(JSONObject obj, DiscordJar discordJar) {
this.customId = obj.getString("custom_id");
this.componentType = ComponentType.getType(obj.getInt("component_type"));
@@ -48,6 +52,8 @@ public MessageComponentInteractionData(JSONObject obj) {
obj.getJSONArray("values").toList().forEach(snowflake -> snowflakes.add(snowflake.toString()));
}
}
+
+ resolved = obj.has("resolved") ? ResolvedData.decompile(obj.getJSONObject("resolved"), discordJar) : null;
}
public String customId() {
@@ -88,4 +94,8 @@ public List snowflakes() {
return snowflakes;
}
+ public ResolvedData resolved() {
+ return resolved;
+ }
+
}
diff --git a/src/main/java/com/seailz/discordjar/model/monetization/Entitlement.java b/src/main/java/com/seailz/discordjar/model/monetization/Entitlement.java
new file mode 100644
index 00000000..deec47d9
--- /dev/null
+++ b/src/main/java/com/seailz/discordjar/model/monetization/Entitlement.java
@@ -0,0 +1,165 @@
+package com.seailz.discordjar.model.monetization;
+
+import com.seailz.discordjar.DiscordJar;
+import com.seailz.discordjar.core.Compilerable;
+import com.seailz.discordjar.model.guild.Guild;
+import com.seailz.discordjar.model.user.User;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.json.JSONObject;
+
+/**
+ * Represents an entitlement, which is generally defined as a purchase of a {@link SKU}.
+ */
+public class Entitlement implements Compilerable {
+
+ private String id;
+ private String skuId;
+ private String guildId;
+ private String userId;
+ private String applicationId;
+ private Type type;
+
+ private DiscordJar jar;
+ private Guild guild;
+ private SKU sku;
+ private User user;
+
+ private Entitlement(String id, String skuId, String guildId, String userId, String applicationId, Type type, DiscordJar jar) {
+ this.id = id;
+ this.skuId = skuId;
+ this.guildId = guildId;
+ this.userId = userId;
+ this.applicationId = applicationId;
+ this.type = type;
+ this.jar = jar;
+ }
+
+ /**
+ * Returns the ID of this entitlement.
+ */
+ @NotNull
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the ID of the SKU that this entitlement is for.
+ */
+ @NotNull
+ public String skuId() {
+ return skuId;
+ }
+
+ /**
+ * Returns the {@link SKU} that this entitlement is for.
+ */
+ @NotNull
+ public SKU sku() {
+ if (sku == null) sku = jar.getSKUById(skuId);
+ return sku;
+ }
+
+ /**
+ * Returns the {@link User user} that this entitlement is for, or null if this entitlement is not for a user.
+ */
+ @Nullable
+ public User getUser() {
+ if (userId == null) return null;
+ if (user == null) user = jar.getUserById(userId);
+ return user;
+ }
+
+ /**
+ * Returns the ID of the user that is granted access to this entitlement's SKU, or null if this entitlement is not for a user.
+ */
+ @Nullable
+ public String userId() {
+ return userId;
+ }
+
+ /**
+ * Returns the {@link Guild guild} that is granted access to this entitlement's SKU, or null if this entitlement is not for a guild.
+ */
+ @Nullable
+ public Guild getGuild() {
+ if (guildId == null) return null;
+ if (guild == null) guild = jar.getGuildById(guildId);
+ return guild;
+ }
+
+ /**
+ * Returns the ID of the guild that is granted access to this entitlement's SKU, or null if this entitlement is not for a guild.
+ */
+ @Nullable
+ public String guildId() {
+ return guildId;
+ }
+
+ /**
+ * Returns the target ID of this entitlement. This is either the guild ID or the user ID, depending on the type of entitlement.
+ */
+ @NotNull
+ public String getTargetId() {
+ return guildId == null ? userId : guildId;
+ }
+
+ /**
+ * Returns the class of the target of this entitlement. This is either {@link User} or {@link Guild}, depending on the type of entitlement.
+ */
+ @NotNull
+ public Class> getTargetClass() {
+ return guildId == null ? User.class : Guild.class;
+ }
+
+ @NotNull
+ @Override
+ public JSONObject compile() {
+ JSONObject obj = new JSONObject();
+ obj.put("id", id);
+ obj.put("sku_id", skuId);
+ obj.put("guild_id", guildId);
+ obj.put("user_id", userId);
+ obj.put("application_id", applicationId);
+ obj.put("type", type.code());
+ return obj;
+ }
+
+ @NotNull
+ @Contract("_, _ -> new")
+ public static Entitlement decompile(@NotNull DiscordJar jar, @NotNull JSONObject obj) {
+ return new Entitlement(
+ obj.getString("id"),
+ obj.getString("sku_id"),
+ obj.has("guild_id") ? obj.getString("guild_id") : null,
+ obj.has("user_id") ? obj.getString("user_id") : null,
+ obj.getString("application_id"),
+ Type.fromCode(obj.getInt("type")),
+ jar
+ );
+ }
+
+ public enum Type {
+ APPLICATION_SUBSCRIPTION(0),
+ UNKNOWN(-1);
+
+ private int code;
+
+ Type(int code) {
+ this.code = code;
+ }
+
+ public int code() {
+ return code;
+ }
+
+ public static Type fromCode(int code) {
+ for (Type type : values()) {
+ if (type.code() == code) return type;
+ }
+ return UNKNOWN;
+ }
+ }
+
+}
diff --git a/src/main/java/com/seailz/discordjar/model/monetization/SKU.java b/src/main/java/com/seailz/discordjar/model/monetization/SKU.java
new file mode 100644
index 00000000..eaee7ef2
--- /dev/null
+++ b/src/main/java/com/seailz/discordjar/model/monetization/SKU.java
@@ -0,0 +1,183 @@
+package com.seailz.discordjar.model.monetization;
+
+import com.seailz.discordjar.DiscordJar;
+import com.seailz.discordjar.action.sku.ListEntitlementRequest;
+import com.seailz.discordjar.core.Compilerable;
+import com.seailz.discordjar.utils.flag.Bitwiseable;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * Represents an application SKU (stock keeping unit).
+ */
+public class SKU implements Compilerable {
+ private String id;
+ private Type type;
+ private String applicationId;
+ private String name;
+ private String slug;
+ private List flags;
+ private DiscordJar discordJar;
+
+ private SKU(String id, Type type, String applicationId, String name, String slug, List flags, DiscordJar discordJar) {
+ this.id = id;
+ this.type = type;
+ this.applicationId = applicationId;
+ this.name = name;
+ this.slug = slug;
+ this.flags = flags;
+ this.discordJar = discordJar;
+ }
+
+ /**
+ * Returns the ID of this SKU.
+ */
+ @NotNull
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the type of this SKU.
+ */
+ @NotNull
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the ID of the parent application for this SKU.
+ */
+ @NotNull
+ public String applicationId() {
+ return applicationId;
+ }
+
+ /**
+ * Returns the name of this SKU.
+ */
+ @NotNull
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the system-generated URL slug for this SKU.
+ */
+ @NotNull
+ public String slug() {
+ return slug;
+ }
+
+ /**
+ * Returns the flags for this SKU.
+ */
+ @NotNull
+ public List flags() {
+ return flags;
+ }
+
+ public boolean isGuildSubscription() {
+ return flags.contains(Flag.GUILD_SUB);
+ }
+
+ public boolean isUserSubscription() {
+ return flags.contains(Flag.USER_SUB);
+ }
+
+ public boolean hasUserPurchased(String userId) {
+ return !new ListEntitlementRequest(discordJar)
+ .setUserId(userId)
+ .setLimit(1)
+ .setExcludeEnded(true)
+ .setSkuIds(List.of(id))
+ .run().awaitCompleted().isEmpty();
+ }
+
+ public boolean hasGuildPurchased(String guildId) {
+ return !new ListEntitlementRequest(discordJar)
+ .setGuildId(guildId)
+ .setLimit(1)
+ .setExcludeEnded(true)
+ .setSkuIds(List.of(id))
+ .run().awaitCompleted().isEmpty();
+ }
+
+ @Override
+ public JSONObject compile() {
+ JSONObject object = new JSONObject();
+ object.put("id", id);
+ object.put("type", type.getCode());
+ object.put("application_id", applicationId);
+ object.put("name", name);
+ object.put("slug", slug);
+
+ int flags = 0;
+ for (Flag flag : this.flags) {
+ flags |= flag.getLeftShiftId();
+ }
+ object.put("flags", flags);
+ return object;
+ }
+
+ @NotNull
+ @Contract("_ -> new")
+ public static SKU decompile(@NotNull JSONObject obj, @NotNull DiscordJar discordJar) {
+ return new SKU(
+ obj.getString("id"),
+ Type.fromCode(obj.getInt("type")),
+ obj.getString("application_id"),
+ obj.getString("name"),
+ obj.getString("slug"),
+ (List) Flag.UNKNOWN.decode(obj.getInt("flags")),
+ discordJar
+ );
+ }
+
+ public enum Type {
+ SUBSCRIPTION(5),
+ SUBSCRIPTION_GROUP(6),
+ UNKNOWN(-1);
+ private int code;
+
+ Type(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public static Type fromCode(int code) {
+ for (Type type : values()) {
+ if (type.getCode() == code) return type;
+ }
+ return UNKNOWN;
+ }
+ }
+ public enum Flag implements Bitwiseable {
+
+ GUILD_SUB(7),
+ USER_SUB(8),
+ UNKNOWN(-1);
+
+ private final int id;
+
+ Flag(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public int getLeftShiftId() {
+ return 1 << id;
+ }
+
+ @Override
+ public int id() {
+ return id;
+ }
+ }
+}
diff --git a/src/main/java/com/seailz/discordjar/utils/URLS.java b/src/main/java/com/seailz/discordjar/utils/URLS.java
index dac71d82..93ca1080 100644
--- a/src/main/java/com/seailz/discordjar/utils/URLS.java
+++ b/src/main/java/com/seailz/discordjar/utils/URLS.java
@@ -133,6 +133,8 @@ public static class APPLICATION {
* Returns the bot's application object
*/
public static String APPLICATION_INFORMATION = "/applications/@me";
+ public static String GET_APPLICATION_SKUS = "/applications/{application.id}/skus";
+ public static String LIST_ENTITLEMENTS = "/applications/{application.id}/entitlements";
public static class COMMANDS {
public static String GET_GLOBAL_APPLICATION_COMMANDS = "/applications/{application.id}/commands";
diff --git a/src/main/java/com/seailz/discordjar/utils/flag/BitwiseUtil.java b/src/main/java/com/seailz/discordjar/utils/flag/BitwiseUtil.java
index fdde69b0..04788139 100644
--- a/src/main/java/com/seailz/discordjar/utils/flag/BitwiseUtil.java
+++ b/src/main/java/com/seailz/discordjar/utils/flag/BitwiseUtil.java
@@ -12,13 +12,13 @@
*/
public class BitwiseUtil & Bitwiseable> {
- public EnumSet get(long flags, Class clazz) {
- EnumSet set = EnumSet.noneOf(clazz);
+ public EnumSet get(long flags, Class extends Bitwiseable> clazz) {
+ EnumSet set = EnumSet.noneOf((Class) clazz);
if (flags == 0)
return set;
- for (T flag : clazz.getEnumConstants()) {
+ for (Bitwiseable flag : clazz.getEnumConstants()) {
if ((flag.getLeftShiftId() & flags) == flag.getLeftShiftId())
- set.add(flag);
+ set.add((T) flag);
}
return set;
}
diff --git a/src/main/java/com/seailz/discordjar/utils/flag/Bitwiseable.java b/src/main/java/com/seailz/discordjar/utils/flag/Bitwiseable.java
index 5dbac170..87e9cc79 100644
--- a/src/main/java/com/seailz/discordjar/utils/flag/Bitwiseable.java
+++ b/src/main/java/com/seailz/discordjar/utils/flag/Bitwiseable.java
@@ -1,8 +1,16 @@
package com.seailz.discordjar.utils.flag;
-public interface Bitwiseable {
+import java.util.EnumSet;
+import java.util.List;
+
+public interface Bitwiseable & Bitwiseable> {
int getLeftShiftId();
int id();
+ default EnumSet decode(long val) {
+ return new BitwiseUtil()
+ .get(val, this.getClass());
+ }
+
}