diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 32d3fb8445..14140121ad 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -14,7 +14,7 @@ Based on the current `master` changes! - Follow the guides provided at [JDA Structure Guide](https://jda.wiki/contributing/structure-guide/) - Compare your code style to the one used all over JDA and ensure you do not break the consistency (if you find issues in the JDA style you can include and update it) - - Do not remove existing functionality, use deprecation instead (for reference [deprecation policy](https://github.com/DV8FromTheWorld/JDA#deprecation-policy)) + - Do not remove existing functionality, use deprecation instead (for reference [deprecation policy](https://github.com/discord-jda/JDA?tab=readme-ov-file#versioning-and-deprecation-policy)) 2. Making a Commit - While having multiple commits can help the reader understand your changes, it might sometimes be @@ -23,8 +23,8 @@ Based on the current `master` changes! 3. Updating your Fork - Before you start committing make sure your fork is updated. - (See [Syncing a Fork](https://help.github.com/articles/syncing-a-fork/) - or [Keeping a Fork Updated](https://robots.thoughtbot.com/keeping-a-github-fork-updated)) + (See [Syncing a Fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) + or [Keeping a Fork Updated](https://thoughtbot.com/blog/keeping-a-github-fork-updated)) 4. Only open Pull Requests to master - Look at the [Repository Structure](https://jda.wiki/contributing/repository-structure/) for further details diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 33e1778a22..9c0adbd26e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,10 +4,10 @@ body: - type: markdown attributes: value: |- - Please join the [Discord Server](https://discord.gg/0hMr4ce0tIl3SLv5) for questions or ask them in [our Discussions](https://github.com/DV8FromTheWorld/JDA/discussions). + Please join the [Discord Server](https://discord.gg/0hMr4ce0tIl3SLv5) for questions or ask them in [our Discussions](https://github.com/discord-jda/JDA/discussions). Keep in mind that this isn't the place to learn Java. - Please head over to [StackOverflow](https://stackoverflow.com/questions/tagged/java) for your general programming questions. + Please head over to [Stack Overflow](https://stackoverflow.com/questions/tagged/java) for your general programming questions. - type: checkboxes attributes: label: General Troubleshooting @@ -15,10 +15,19 @@ body: options: - label: I have checked for similar issues on the Issue-tracker. required: true - - label: I have updated to the [latest JDA version](https://ci.dv8tion.net/job/JDA/lastSuccessfulBuild/) - required: true - - label: I have checked the branches or the maintainers' PRs for upcoming bug fixes. + - label: I have checked for PRs that might already address this issue. required: true +- type: input + attributes: + label: "Version of JDA" + description: | + Please let us know, which specific version of JDA you used. + If your version is outdated, maybe try updating and see if that already fixes your problem. + You can see the latest version in the [Releases](https://github.com/discord-jda/JDA/releases). + To find out your version, you can add `System.out.println(JDAInfo.VERSION);` in your code. + placeholder: "For example 5.0.0-beta.7" + validations: + required: true - type: textarea attributes: label: "Expected Behaviour" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 849c2f380a..5727387dce 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -3,18 +3,21 @@ contact_links: - name: Discord url: https://discord.gg/0hMr4ce0tIk3pSjp about: The JDA Discord is the best place to receive fast support for your questions. + - name: Questions + url: https://github.com/discord-jda/JDA/discussions + about: You can ask questions about JDA and find useful information in our Discussions. - name: Javadoc - url: https://ci.dv8tion.net/job/JDA/javadoc/ + url: https://docs.jda.wiki/ about: The Javadoc is the perfect place to retrieve information about various methods within JDA. - - name: Jenkins - url: https://ci.dv8tion.net/job/JDA/ - about: You can find the latest Dev Builds on the Jenkins CI Server. - name: Wiki - url: https://jda.wiki + url: https://jda.wiki/ about: You can find answers to common questions on our Wiki. Make sure to read the FAQ page! - name: Releases - url: https://github.com/DV8FromTheWorld/JDA/releases + url: https://github.com/discord-jda/JDA/releases about: You can find the latest releases here on GitHub and on Bintray. - - name: Questions - url: https://github.com/DV8FromTheWorld/JDA/discussions - about: You can ask questions about JDA and find useful information in our Discussions. + - name: Snapshots + url: https://jitpack.io/#discord-jda/JDA + about: You can find the latest dev builds on JitPack. + - name: Jenkins + url: https://ci.dv8tion.net/job/JDA5/ + about: Dev builds also available on the Jenkins CI Server. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index d41e5bb9c1..a7f02e91c0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -4,10 +4,10 @@ body: - type: markdown attributes: value: |- - Please join the [Discord Server](https://discord.gg/0hMr4ce0tIl3SLv5) for questions or ask them in [our Discussions](https://github.com/DV8FromTheWorld/JDA/discussions). + Please join the [Discord Server](https://discord.gg/0hMr4ce0tIl3SLv5) for questions or ask them in [our Discussions](https://github.com/discord-jda/JDA/discussions). Keep in mind that this isn't the place to learn Java. - Please head over to [StackOverflow](https://stackoverflow.com/questions/tagged/java) for your general programming questions. + Please head over to [Stack Overflow](https://stackoverflow.com/questions/tagged/java) for your general programming questions. - type: checkboxes attributes: label: General Troubleshooting @@ -15,7 +15,7 @@ body: options: - label: I have checked for similar issues on the Issue-tracker. required: true - - label: I have updated to the [latest JDA version](https://ci.dv8tion.net/job/JDA/lastSuccessfulBuild/) + - label: I have updated to the [latest JDA version](https://github.com/discord-jda/JDA/releases/latest) required: true - label: I have checked the branches or the maintainers' PRs for upcoming features. required: true diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c1dafb9f44..0f00e0e5af 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,5 +1,5 @@ # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle name: Pull Request Validation diff --git a/README.md b/README.md index d998c6a809..bcf76265f7 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [license]: https://github.com/discord-jda/JDA/tree/master/LICENSE [faq]: https://jda.wiki/introduction/faq/ [docs]: https://docs.jda.wiki/index.html +[wiki]: https://jda.wiki/introduction/jda/ [troubleshooting]: https://jda.wiki/using-jda/troubleshooting/ [discord-shield]: https://discord.com/api/guilds/125227483518861312/widget.png [faq-shield]: https://img.shields.io/badge/Wiki-FAQ-blue.svg @@ -32,16 +33,11 @@ # JDA (Java Discord API) -JDA strives to provide a clean and full wrapping of the Discord REST api and its Websocket-Events for Java. -This library is a helpful tool that provides the functionality to create a discord bot in java. +JDA strives to provide a clean and full wrapping of the Discord REST API and its WebSocket-Events for Java. +This library is a helpful tool that provides the functionality to create a Discord bot in Java. ## Summary -Due to official statements made by the Discord developers we will no longer support unofficial features. These features -are undocumented API endpoints or protocols that are not available to bot-accounts. - -_Please see the [Discord docs](https://discord.com/developers/docs/reference) for more information about bot accounts._ - 1. [Introduction](#creating-the-jda-object) 2. [Sharding](#sharding-a-bot) 3. [Entity Lifetimes](#entity-lifetimes) @@ -57,8 +53,6 @@ _Please see the [Discord docs](https://discord.com/developers/docs/reference) fo Discord is currently prohibiting creation and usage of automated client accounts (AccountType.CLIENT). We have officially dropped support for client login as of version **4.2.0**! -Note that JDA is not a good tool to build a custom discord client as it loads all servers/guilds on startup unlike -a client which does this via lazy loading instead. If you need a bot, use a bot account from the [Application Dashboard](https://discord.com/developers/applications). [Read More](https://support.discord.com/hc/en-us/articles/115002192352-Automated-user-accounts-self-bots-) @@ -102,7 +96,8 @@ public static void main(String[] args) { and [DefaultShardManagerBuilder](https://docs.jda.wiki/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.html) You can configure the memory usage by changing enabled `CacheFlags` on the `JDABuilder`. -Additionally, you can change the handling of member/user cache by setting either a `ChunkingFilter`, disabling **intents**, or changing the **member cache policy**. +Additionally, you can change the handling of the member/user cache by disabling **intents** or changing the **member cache policy**. +To learn more about intents and member loading/caching, read the [Gateway Intents Guide](https://jda.wiki/using-jda/gateway-intents-and-member-cache-policy/). ```java public void configureMemoryUsage(JDABuilder builder) { @@ -124,16 +119,16 @@ public void configureMemoryUsage(JDABuilder builder) { } ``` -### Listening to Events +### Listening for Events The event system in JDA is configured through a hierarchy of classes/interfaces. We offer two implementations for the `IEventManager`: - **InterfacedEventManager** which uses an `EventListener` interface and the `ListenerAdapter` abstract class -- **AnnotatedEventManager** which uses the `@SubscribeEvent` annotation that can be applied to methods +- **AnnotatedEventManager** which uses the `@SubscribeEvent` annotation which can be applied to methods -By default the **InterfacedEventManager** is used. -Since you can create your own implementation of `IEventManager` this is a very versatile and configurable system. +By default, the **InterfacedEventManager** is used. +Since you can create your own implementation of `IEventManager`, this is a very versatile and configurable system. If the aforementioned implementations don't suit your use-case you can simply create a custom implementation and configure it on the `JDABuilder` with `setEventManager(...)`. @@ -142,11 +137,8 @@ configure it on the `JDABuilder` with `setEventManager(...)`. **Using EventListener**: ```java -public class ReadyListener implements EventListener -{ - public static void main(String[] args) - throws InterruptedException - { +public class ReadyListener implements EventListener { + public static void main(String[] args) throws InterruptedException { // Note: It is important to register your ReadyListener before building JDA jda = JDABuilder.createDefault("token") .addEventListeners(new ReadyListener()) @@ -157,10 +149,10 @@ public class ReadyListener implements EventListener } @Override - public void onEvent(GenericEvent event) - { - if (event instanceof ReadyEvent) + public void onEvent(GenericEvent event) { + if (event instanceof ReadyEvent) { System.out.println("API is ready!"); + } } } ``` @@ -168,10 +160,8 @@ public class ReadyListener implements EventListener **Using ListenerAdapter**: ```java -public class MessageListener extends ListenerAdapter -{ - public static void main(String[] args) - { +public class MessageListener extends ListenerAdapter { + public static void main(String[] args) { JDA jda = JDABuilder.createDefault("token") .enableIntents(GatewayIntent.MESSAGE_CONTENT) // enables explicit access to message.getContentDisplay() .build(); @@ -182,15 +172,11 @@ public class MessageListener extends ListenerAdapter } @Override - public void onMessageReceived(MessageReceivedEvent event) - { - if (event.isFromType(ChannelType.PRIVATE)) - { + public void onMessageReceived(MessageReceivedEvent event) { + if (event.isFromType(ChannelType.PRIVATE)) { System.out.printf("[PM] %s: %s\n", event.getAuthor().getName(), event.getMessage().getContentDisplay()); - } - else - { + } else { System.out.printf("[%s][%s] %s: %s\n", event.getGuild().getName(), event.getTextChannel().getName(), event.getMember().getEffectiveName(), event.getMessage().getContentDisplay()); @@ -202,10 +188,8 @@ public class MessageListener extends ListenerAdapter **Slash-Commands**: ```java -public class Bot extends ListenerAdapter -{ - public static void main(String[] args) - { +public class Bot extends ListenerAdapter { + public static void main(String[] args) { if (args.length < 1) { System.out.println("You have to provide a token as first argument!"); System.exit(1); @@ -229,8 +213,7 @@ public class Bot extends ListenerAdapter } @Override - public void onSlashCommandInteraction(SlashCommandInteractionEvent event) - { + public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { // make sure we handle the right command switch (event.getName()) { case "ping": @@ -241,7 +224,7 @@ public class Bot extends ListenerAdapter ).queue(); // Queue both reply and edit break; case "ban": - // double check permissions, don't trust discord on this! + // double check permissions, don't trust Discord on this! if (!event.getMember().hasPermission(Permission.BAN_MEMBERS)) { event.reply("You cannot ban members! Nice try ;)").setEphemeral(true).queue(); break; @@ -268,6 +251,9 @@ public class Bot extends ListenerAdapter event.getHook().editOriginal("Some error occurred, try again!").queue(); error.printStackTrace(); }); + break; + default: + System.out.printf("Unknown command %s used by %#s%n", event.getName(), event.getUser()); } } } @@ -309,52 +295,27 @@ public RestAction selfDestruct(MessageChannel channel, String content) { We provide a small set of Examples in the [Example Directory](https://github.com/discord-jda/JDA/tree/master/src/examples/java). - - ## Sharding a Bot -Discord allows Bot-accounts to share load across sessions by limiting them to a fraction of the total connected Guilds/Servers of the bot. -
This can be done using **sharding** which will limit JDA to only a certain amount of Guilds/Servers including events and entities. -Sharding will limit the amount of Guilds/Channels/Users visible to the JDA session so it is recommended to have some kind of elevated management to -access information of other shards. +When your bot joins over 2500 guilds, it is required to perform **Sharding**. +This means, your connection is split up into multiple **shards**, each only accessing a fraction of your total available guilds. +A shard can at most contain 2500 guilds when starting up the bot. -To use sharding in JDA you will need to use `JDABuilder.useSharding(int shardId, int shardTotal)`. The **shardId** is 0-based which means the first shard -has the ID 0. The **shardTotal** is the total amount of shards (not 0-based) which can be seen similar to the length of an array, the last shard has the ID of -`shardTotal - 1`. +Each shard is assigned a **shard id** and **shard total** (usually shown as `id / total`), which uniquely identifies which guilds are accessible on that shard. +For instance, the first of 2 shards would be `0 / 2` and the second would be `1 / 2`. -The [`SessionController`](https://docs.jda.wiki/net/dv8tion/jda/api/utils/SessionController.html) is a tool of the JDABuilder -that allows to control state and behaviour between shards (sessions). When using multiple builders to build shards you have to create one instance -of this controller and add the same instance to each builder: `builder.setSessionController(controller)` +If you want to use sharding with your bot, make use of the [DefaultShardManager](https://docs.jda.wiki/net/dv8tion/jda/api/sharding/DefaultShardManager.html) as seen in the example below. +This manager automatically assigns the right number of shards to your bot, so you do not need to do any math yourself. -Since version **3.4.0** JDA provides a `ShardManager` which automates this building process. +Internally, this shard manager also handles the proper scaling of threads for connections and handles the login rate-limit (Identify Rate-Limit) to properly startup without issues. -### Example Sharding - Using JDABuilder - -```java -public static void main(String[] args) throws Exception -{ - JDABuilder shardBuilder = JDABuilder.createDefault(args[0]); - //register your listeners here using shardBuilder.addEventListeners(...) - shardBuilder.addEventListeners(new MessageListener()); - for (int i = 0; i < 10; i++) - { - shardBuilder.useSharding(i, 10) - .build(); - } -} -``` +If you do not want to use the shard manager, and instead manage sharding yourself, you can use [JDABuilder#useSharding](https://docs.jda.wiki/net/dv8tion/jda/api/JDABuilder.html#useSharding(int,int)) and [ConcurrentSessionController](https://docs.jda.wiki/net/dv8tion/jda/api/utils/ConcurrentSessionController.html). -> When the `useSharding` method is invoked for the first time, the builder automatically sets a SessionController internally (if none is present) ### Example Sharding - Using DefaultShardManager + ```java -public static void main(String[] args) throws Exception -{ +public static void main(String[] args) { DefaultShardManagerBuilder builder = DefaultShardManagerBuilder.createDefault(args[0]); builder.addEventListeners(new MessageListener()); builder.build(); @@ -391,17 +352,14 @@ Discord may request that a client (the JDA session) invalidates its entire cache #### Example ```java -public class UserLogger extends ListenerAdapter -{ +public class UserLogger extends ListenerAdapter { private final User user; - public UserLogger(User user) - { + public UserLogger(User user) { this.user = user; } - private User getUser(JDA api) - { + private User getUser(JDA api) { // Acquire a reference to the User instance through the id User newUser = api.getUserById(this.user.getIdLong()); if (newUser != null) @@ -410,12 +368,10 @@ public class UserLogger extends ListenerAdapter } @Override - public void onMessageReceived(MessageReceivedEvent event) - { + public void onMessageReceived(MessageReceivedEvent event) { User author = event.getAuthor(); Message message = event.getMessage(); - if (author.getIdLong() == this.user.getIdLong()) - { + if (author.getIdLong() == this.user.getIdLong()) { // Update user from message instance (likely more up-to-date) this.user = author; // Print the message of the user @@ -424,12 +380,10 @@ public class UserLogger extends ListenerAdapter } @Override - public void onGuildJoin(GuildJoinEvent event) - { + public void onGuildJoin(GuildJoinEvent event) { JDA api = event.getJDA(); User user = getUser(); // use getter to refresh user automatically on access - user.openPrivateChannel().queue((channel) -> - { + user.openPrivateChannel().queue((channel) -> { // Send a private message to the user channel.sendMessageFormat("I have joined a new guild: **%s**", event.getGuild().getName()).queue(); }); @@ -499,7 +453,7 @@ If you do not need any opus de-/encoding done by JDA (voice receive/send with PC This can be done if you only send audio with an `AudioSendHandler` which only sends opus (`isOpus() = true`). (See [lavaplayer](https://github.com/sedmelluq/lavaplayer)) If you want to use a custom opus library you can provide the absolute path to `OpusLibrary.loadFrom(String)` before using -the audio api of JDA. This works without `opus-java-natives` as it only requires `opus-java-api`. +the audio API of JDA. This works without `opus-java-natives` as it only requires `opus-java-api`.
_For this setup you should only exclude `opus-java-natives` as `opus-java-api` is a requirement for en-/decoding._ See [opus-java](https://github.com/discord-java/opus-java) @@ -523,8 +477,8 @@ There is a guide for logback-classic available in our wiki: [Logging Setup](http ## Documentation -Docs can be found on the [Jenkins][jenkins] or directly [here](https://docs.jda.wiki/) -
A simple Wiki can also be found at [jda.wiki](https://jda.wiki/) +Docs can be found on the [GitHub Pages][docs] +
We also have a wiki filled with information and troubleshooting guides at [jda.wiki][wiki] ### Annotations @@ -556,22 +510,22 @@ and [Setup](https://jda.wiki/setup/intellij/) Pages. ## Third Party Recommendations -### [LavaPlayer](https://github.com/sedmelluq/lavaplayer) +### [Lavaplayer](https://github.com/lavalink-devs/lavaplayer) -Created and maintained by [sedmelluq](https://github.com/sedmelluq) -
LavaPlayer is the most popular library used by Music Bots created in Java. +Created by [sedmelluq](https://github.com/sedmelluq) and now maintained by the [lavalink community](https://github.com/lavalink-devs) +
Lavaplayer is the most popular library used by Music Bots created in Java. It is highly compatible with JDA and Discord4J and allows to play audio from -Youtube, Soundcloud, Twitch, Bandcamp and [more providers](https://github.com/sedmelluq/lavaplayer#supported-formats). +Youtube, Soundcloud, Twitch, Bandcamp and [more providers](https://github.com/lavalink-devs/lavaplayer#supported-formats).
The library can easily be expanded to more services by implementing your own AudioSourceManager and registering it. -It is recommended to read the [Usage](https://github.com/sedmelluq/lavaplayer#usage) section of LavaPlayer +It is recommended to read the [Usage](https://github.com/lavalink-devs/lavaplayer#usage) section of Lavaplayer to understand a proper implementation.
Sedmelluq provided a demo in his repository which presents an example implementation for JDA: -https://github.com/sedmelluq/lavaplayer/tree/master/demo-jda +https://github.com/lavalink-devs/lavaplayer/tree/master/demo-jda -### [Lavalink](https://github.com/freyacodes/Lavalink) +### [Lavalink](https://github.com/lavalink-devs/Lavalink) -Maintained by [Freya Arbjerg](https://github.com/freyacodes). +Created by [Freya Arbjerg](https://github.com/freyacodes) and now maintained by the [lavalink community](https://github.com/lavalink-devs). Lavalink is a popular standalone audio sending node based on Lavaplayer. Lavalink was built with scalability in mind, and allows streaming music via many servers. It supports most of Lavaplayer's features. @@ -583,13 +537,13 @@ as it is easier. [Lavalink-Client](https://github.com/FredBoat/Lavalink-Client) is the official Lavalink client for JDA. -### [jda-nas](https://github.com/sedmelluq/jda-nas) +### [jda-nas](https://github.com/sedmelluq/jda-nas) and [udpqueue](https://github.com/MinnDevelopment/udpqueue.rs) -Created and maintained by [sedmelluq](https://github.com/sedmelluq) +Created and maintained by [sedmelluq](https://github.com/sedmelluq) and extended by [MinnDevelopment](https://github.com/MinnDevelopment)
Provides a native implementation for the JDA Audio Send-System to avoid GC pauses. -Note that this send system creates an extra UDP-Client which causes audio receive to no longer function properly -since discord identifies the sending UDP-Client as the receiver. +Note that this send system creates an extra UDP-Client which causes audio receive to no longer function properly, +since Discord identifies the sending UDP-Client as the receiver. ```java JDABuilder builder = JDABuilder.createDefault(BOT_TOKEN) @@ -623,36 +577,42 @@ More can be found in our github organization: [JDA-Applications](https://github. ## Contributing to JDA -If you want to contribute to JDA, make sure to base your branch off of our **development** branch (or a feature-branch) -and create your PR into that **same** branch. **We will be rejecting any PRs between branches or into release branches!** -It is very possible that your change might already be in development or you missed something. +If you want to contribute to JDA, make sure to base your branch off of our **master** branch (or a feature-branch) +and create your PR into that **same** branch. -More information can be found at the wiki page [Contributing](https://github.com/discord-jda/JDA/wiki/5\)-Contributing) +More information can be found at the wiki page [Contributing](https://jda.wiki/contributing/contributing/). -### Deprecation Policy +## Versioning and Deprecation Policy -When a feature is introduced to replace or enhance existing functionality we might deprecate old functionality. +Since the Discord API is in itself a moving standard, the stability is never guaranteed. For this reason, JDA does not follow the common semver versioning strategy. -A deprecated method/class usually has a replacement mentioned in its documentation which should be switched to. Deprecated -functionality might or might not exist in the next minor release. (Hint: The minor version is the `MM` of `XX.MM.RR` in our version format) +The JDA version is structured with a looser definition, where the version change indicates the significance of changes. +For instance, using `5.1.2` as a baseline: -It is possible that some features are deprecated without replacement, in this case the functionality is no longer supported by either the JDA structure -due to fundamental changes (for example automation of a feature) or due to Discord API changes that cause it to be removed. +- A change to the major like `6.0.0` indicates that a lot of code has to be adjusted due to major changes to the interfaces. A change like this always comes with a full migration guide like [Migrating from 4.X to 5.X](https://jda.wiki/introduction/migration-v4-v5/). +- A change to the minor like `5.2.0` indicates some code may need to be adjusted due to the removal or change of interfaces. You can usually find the necessary changes in the release documentation. +- A change to the patch like `5.1.3` indicates bug fixes and new feature additions that are backwards compatible. + +If a feature is marked as deprecated, it usually also indicates an alternative. For instance: + +```java +@Deprecated +@DeprecatedSince("5.1.2") +@ForRemoval(deadline="5.2.0") +@ReplaceWith("setFoo(foo)") +public void changeFoo(Foo foo) { ... } +``` -We highly recommend discontinuing usage of deprecated functionality and update by going through each minor release instead of jumping. -For instance, when updating from version 3.3.0 to version 3.5.1 you should do the following: +The method `changeFoo` was deprecated in release `5.1.2` and is going to be removed in `5.2.0`. Your change should replace all usage of `changeFoo(foo)` with `setFoo(foo)`. -- Update to `3.4.RR` and check for deprecation, replace -- Update to `3.5.1` and check for deprecation, replace +Sometimes, a feature might be removed without a replacement. This will be clearly explained in the documentation. -The `RR` in version `3.4.RR` should be replaced by the latest version that was published for `3.4`, you can find out which the latest -version was by looking at the [release page](https://github.com/discord-jda/JDA/releases) ## Dependencies: This project requires **Java 8+**.
All dependencies are managed automatically by Gradle. - * NV Websocket Client + * NV WebSocket Client * Version: **2.14** * [Github](https://github.com/TakahikoKawasaki/nv-websocket-client) * OkHttp diff --git a/build.gradle.kts b/build.gradle.kts index 1067728eb8..0d3d18f3da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,7 +40,7 @@ plugins { } val javaVersion = JavaVersion.current() -val versionObj = Version(major = "5", minor = "0", revision = "0", classifier = "beta.12") +val versionObj = Version(major = "5", minor = "0", revision = "0", classifier = "beta.17") val isCI = System.getProperty("BUILD_NUMBER") != null // jenkins || System.getenv("BUILD_NUMBER") != null || System.getProperty("GIT_COMMIT") != null // jitpack @@ -134,6 +134,7 @@ dependencies { } testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") + testImplementation("org.reflections:reflections:0.10.2") } val compileJava: JavaCompile by tasks diff --git a/src/examples/java/LRUCachePolicy.java b/src/examples/java/LRUCachePolicy.java deleted file mode 100644 index 8a04779982..0000000000 --- a/src/examples/java/LRUCachePolicy.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.utils.MemberCachePolicy; - -import java.util.LinkedList; - -/** - * Implements a Least-Recently-Used (LRU) cache (independent of guilds). - *
The cache policy will keep track of how many members are currently cached and will removed the least recently - * cached member once the maximum is reached. - */ -public class LRUCachePolicy implements MemberCachePolicy -{ - private final int max; - private final LinkedList cached = new LinkedList<>(); - - public LRUCachePolicy(int max) - { - this.max = max; - } - - @Override - public synchronized boolean cacheMember(Member member) - { - long id = member.getIdLong(); - int index = cached.indexOf(id); - - // Special case, the member is the most recently used already - if (index == 0) - return true; - - if (index > 0) - { - // Promote member to most recently used - cached.remove(index); - } - else if (cached.size() >= max) - { - // Unload LRU user - long remove = cached.removeLast(); - member.getJDA().unloadUser(remove); - } - - cached.addFirst(id); - return true; - } -} diff --git a/src/main/java/net/dv8tion/jda/api/EmbedBuilder.java b/src/main/java/net/dv8tion/jda/api/EmbedBuilder.java index acb18c0bdd..16b83d98c8 100644 --- a/src/main/java/net/dv8tion/jda/api/EmbedBuilder.java +++ b/src/main/java/net/dv8tion/jda/api/EmbedBuilder.java @@ -712,8 +712,7 @@ public EmbedBuilder setAuthor(@Nullable String name, @Nullable String url) @Nonnull public EmbedBuilder setAuthor(@Nullable String name, @Nullable String url, @Nullable String iconUrl) { - //We only check if the name is null because its presence is what determines if the - // the author will appear in the embed. + // We only check if the name is null because its presence is what determines if the author will appear in the embed. if (name == null) { this.author = null; diff --git a/src/main/java/net/dv8tion/jda/api/JDA.java b/src/main/java/net/dv8tion/jda/api/JDA.java index 7251566654..30b18a5bca 100644 --- a/src/main/java/net/dv8tion/jda/api/JDA.java +++ b/src/main/java/net/dv8tion/jda/api/JDA.java @@ -1584,6 +1584,9 @@ default PrivateChannel getPrivateChannelById(long id) *
This will fail with {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER UNKNOWN_USER} * if the user does not exist. * + *

If the channel is cached, this will directly return the channel in a completed {@link RestAction} without making a request. + * You can use {@link CacheRestAction#useCache(boolean) action.useCache(false)} to force an update. + * *

Example
*

{@code
      * public void sendMessage(JDA jda, String userId, String content) {
@@ -1607,7 +1610,7 @@ default PrivateChannel getPrivateChannelById(long id)
      */
     @Nonnull
     @CheckReturnValue
-    default RestAction openPrivateChannelById(@Nonnull String userId)
+    default CacheRestAction openPrivateChannelById(@Nonnull String userId)
     {
         return openPrivateChannelById(MiscUtil.parseSnowflake(userId));
     }
diff --git a/src/main/java/net/dv8tion/jda/api/audio/hooks/ConnectionListener.java b/src/main/java/net/dv8tion/jda/api/audio/hooks/ConnectionListener.java
index 14521e811f..2c67861570 100644
--- a/src/main/java/net/dv8tion/jda/api/audio/hooks/ConnectionListener.java
+++ b/src/main/java/net/dv8tion/jda/api/audio/hooks/ConnectionListener.java
@@ -16,8 +16,11 @@
 
 package net.dv8tion.jda.api.audio.hooks;
 
+import net.dv8tion.jda.annotations.ForRemoval;
+import net.dv8tion.jda.annotations.ReplaceWith;
 import net.dv8tion.jda.api.audio.SpeakingMode;
 import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.entities.UserSnowflake;
 
 import javax.annotation.Nonnull;
 import java.util.EnumSet;
@@ -46,8 +49,6 @@ public interface ConnectionListener
      */
     void onStatusChange(@Nonnull ConnectionStatus status);
 
-    // TODO: Deprecate and replace these onUserSpeaking methods.
-
     /**
      * This method is an easy way to detect if a user is talking. Discord sends us an event when a user starts or stops
      * talking and it is parallel to the audio socket, so this event could come milliseconds before or after audio begins
@@ -69,8 +70,16 @@ public interface ConnectionListener
      *         Never-null {@link net.dv8tion.jda.api.entities.User User} who's talking status has changed.
      * @param  speaking
      *         If true, the user has begun transmitting audio.
+     *
+     * @deprecated This method no longer represents the actual speaking state of the user.
+     *             Discord does not send updates when a user starts and stops speaking anymore.
+     *             You can use {@link #onUserSpeakingModeUpdate(UserSnowflake, EnumSet)} to see when a user changes their speaking mode,
+     *             or use an {@link net.dv8tion.jda.api.audio.AudioReceiveHandler AudioReceiveHandler} to check when a user is speaking.
      */
-    void onUserSpeaking(@Nonnull User user, boolean speaking);
+    @Deprecated
+    @ForRemoval
+    @ReplaceWith("onUserSpeakingModeUpdate(User, EnumSet)")
+    default void onUserSpeaking(@Nonnull User user, boolean speaking) {}
 
     /**
      * This method is an easy way to detect if a user is talking. Discord sends us an event when a user starts or stops
@@ -97,7 +106,15 @@ public interface ConnectionListener
      *
      * @see    java.util.EnumSet EnumSet
      * @see    net.dv8tion.jda.api.audio.SpeakingMode SpeakingMode
+     *
+     * @deprecated This method no longer represents the actual speaking state of the user.
+     *             Discord does not send updates when a user starts and stops speaking anymore.
+     *             You can use {@link #onUserSpeakingModeUpdate(UserSnowflake, EnumSet)} to see when a user changes their speaking mode,
+     *             or use an {@link net.dv8tion.jda.api.audio.AudioReceiveHandler AudioReceiveHandler} to check when a user is speaking.
      */
+    @Deprecated
+    @ForRemoval
+    @ReplaceWith("onUserSpeakingModeUpdate(User, EnumSet)")
     default void onUserSpeaking(@Nonnull User user, @Nonnull EnumSet modes) {}
 
 
@@ -124,6 +141,46 @@ default void onUserSpeaking(@Nonnull User user, @Nonnull EnumSet m
      *         If true, the user has begun transmitting audio.
      * @param  soundshare
      *         If true, the user is using soundshare
+     *
+     * @deprecated This method no longer represents the actual speaking state of the user.
+     *             Discord does not send updates when a user starts and stops speaking anymore.
+     *             You can use {@link #onUserSpeakingModeUpdate(UserSnowflake, EnumSet)} to see when a user changes their speaking mode,
+     *             or use an {@link net.dv8tion.jda.api.audio.AudioReceiveHandler AudioReceiveHandler} to check when a user is speaking.
      */
+    @Deprecated
+    @ForRemoval
+    @ReplaceWith("onUserSpeakingModeUpdate(User, EnumSet)")
     default void onUserSpeaking(@Nonnull User user, boolean speaking, boolean soundshare) {}
+
+    /**
+     * This method is used to listen for users changing their speaking mode.
+     * 

Whenever a user joins a voice channel, this is fired once to define the initial speaking modes. + * + *

To detect when a user is speaking, a {@link net.dv8tion.jda.api.audio.AudioReceiveHandler AudioReceiveHandler} should be used instead. + * + *

Note: This requires the user to be currently in the cache. + * You can use {@link net.dv8tion.jda.api.utils.MemberCachePolicy#VOICE MemberCachePolicy.VOICE} to cache currently connected users. + * Alternatively, use {@link #onUserSpeakingModeUpdate(UserSnowflake, EnumSet)} to avoid cache. + * + * @param user + * The user who changed their speaking mode + * @param modes + * The new speaking modes of the user + */ + default void onUserSpeakingModeUpdate(@Nonnull User user, @Nonnull EnumSet modes) {} + + /** + * This method is used to listen for users changing their speaking mode. + *

Whenever a user joins a voice channel, this is fired once to define the initial speaking modes. + * + *

To detect when a user is speaking, a {@link net.dv8tion.jda.api.audio.AudioReceiveHandler AudioReceiveHandler} should be used instead. + * + *

This method works independently of the user cache. The provided user might not be cached. + * + * @param user + * The user who changed their speaking mode + * @param modes + * The new speaking modes of the user + */ + default void onUserSpeakingModeUpdate(@Nonnull UserSnowflake user, @Nonnull EnumSet modes) {} } diff --git a/src/main/java/net/dv8tion/jda/api/audio/hooks/ListenerProxy.java b/src/main/java/net/dv8tion/jda/api/audio/hooks/ListenerProxy.java index f6c9417783..3872154b94 100644 --- a/src/main/java/net/dv8tion/jda/api/audio/hooks/ListenerProxy.java +++ b/src/main/java/net/dv8tion/jda/api/audio/hooks/ListenerProxy.java @@ -18,6 +18,7 @@ import net.dv8tion.jda.api.audio.SpeakingMode; import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.UserSnowflake; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,7 +95,27 @@ public void onUserSpeaking(@Nonnull User user, @Nonnull EnumSet mo } @Override - public void onUserSpeaking(@Nonnull User user, boolean speaking) {} + public void onUserSpeakingModeUpdate(@Nonnull UserSnowflake user, @Nonnull EnumSet modes) + { + if (listener == null) + return; + ConnectionListener listener = this.listener; + try + { + if (listener != null) + { + listener.onUserSpeakingModeUpdate(user, modes); + if (user instanceof User) + listener.onUserSpeakingModeUpdate((User) user, modes); + } + } + catch (Throwable t) + { + log.error("The ConnectionListener encountered and uncaught exception", t); + if (t instanceof Error) + throw (Error) t; + } + } public void setListener(ConnectionListener listener) { diff --git a/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java b/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java index 848a42eb88..e4e6831ed6 100644 --- a/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java +++ b/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java @@ -321,13 +321,13 @@ public enum AuditLogKey */ CHANNEL_ID("channel_id"), -// /** -// * The {@link ForumChannel#getDefaultSortOrder()} value. -// *
Only for {@link ChannelType#FORUM}. -// * -// *

Expected type: Integer -// */ -// CHANNEL_DEFAULT_SORT_ORDER("default_sort_order"), + /** + * The {@link ForumChannel#getDefaultSortOrder()} value. + *
Only for {@link ChannelType#FORUM} and {@link ChannelType#MEDIA}. + * + *

Expected type: Integer + */ + CHANNEL_DEFAULT_SORT_ORDER("default_sort_order"), /** * The {@link ForumChannel#getDefaultLayout()} value. diff --git a/src/main/java/net/dv8tion/jda/api/entities/Activity.java b/src/main/java/net/dv8tion/jda/api/entities/Activity.java index fe21821120..6858ccf5e9 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Activity.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Activity.java @@ -15,13 +15,13 @@ */ package net.dv8tion.jda.api.entities; -import net.dv8tion.jda.annotations.Incubating; import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.emoji.EmojiUnion; import net.dv8tion.jda.internal.entities.EntityBuilder; import net.dv8tion.jda.internal.utils.Checks; import net.dv8tion.jda.internal.utils.EntityString; import net.dv8tion.jda.internal.utils.Helpers; +import org.jetbrains.annotations.Contract; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -50,6 +50,12 @@ public interface Activity /** The Pattern used for {@link #isValidStreamingUrl(String)} */ Pattern STREAMING_URL = Pattern.compile("https?://(www\\.)?(twitch\\.tv/|youtube\\.com/watch\\?v=).+", Pattern.CASE_INSENSITIVE); + /** Maximum length for an activity name */ + int MAX_ACTIVITY_NAME_LENGTH = 128; + + /** Maximum length for an activity state */ + int MAX_ACTIVITY_STATE_LENGTH = 128; + /** * Whether this is a Rich Presence *
If {@code false} the result of {@link #asRichPresence()} is {@code null} @@ -76,6 +82,29 @@ public interface Activity @Nonnull String getName(); + /** + * The user's activity state + *
Example: "Looking to Play", "Playing Solo", "In a Group" + * + *

This shows below the normal activity information in the profile. + * + *

Example
+ * Code: + *

{@code
+     * Activity.playing("Trivia")
+     *     .withState("Question 20")
+     * }
+ * Display: + *
+     * Playing Trivia
+     * Question 20
+     * 
+ * + * @return The user's current party status + */ + @Nullable + String getState(); + /** * The URL of the {@link Activity Activity} if the game is actually a Stream. *
This will return null for regular games. @@ -109,6 +138,22 @@ public interface Activity @Nullable EmojiUnion getEmoji(); + /** + * Adds the provided state to the activity. + *
The state is shown below the activity, unless it is a {@link #customStatus(String) custom status}. + * + * @param state + * The activity state, or null to unset + * + * @throws IllegalArgumentException + * If the state is longer than {@value #MAX_ACTIVITY_STATE_LENGTH} characters + * + * @return New activity instance with the provided state + */ + @Nonnull + @Contract("_->new") + Activity withState(@Nullable String state); + /** * Creates a new Activity instance with the specified name. *
In order to appear as "streaming" in the official client you must @@ -118,7 +163,7 @@ public interface Activity * The not-null name of the newly created game * * @throws IllegalArgumentException - * if the specified name is null, empty, blank or longer than 128 characters + * if the specified name is null, empty, blank or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters * * @return A valid Activity instance with the provided name with {@link net.dv8tion.jda.api.entities.Activity.ActivityType#PLAYING} */ @@ -127,7 +172,7 @@ static Activity playing(@Nonnull String name) { Checks.notBlank(name, "Name"); name = name.trim(); - Checks.notLonger(name, 128, "Name"); + Checks.notLonger(name, MAX_ACTIVITY_NAME_LENGTH, "Name"); return EntityBuilder.createActivity(name, null, ActivityType.PLAYING); } @@ -142,7 +187,7 @@ static Activity playing(@Nonnull String name) * The streaming url to use, required to display as "streaming" * * @throws IllegalArgumentException - * If the specified name is null, empty or longer than 128 characters + * If the specified name is null, empty or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters * * @return A valid Activity instance with the provided name and url * @@ -153,7 +198,7 @@ static Activity streaming(@Nonnull String name, @Nullable String url) { Checks.notEmpty(name, "Provided game name"); name = Helpers.isBlank(name) ? name : name.trim(); - Checks.notLonger(name, 128, "Name"); + Checks.notLonger(name, MAX_ACTIVITY_NAME_LENGTH, "Name"); ActivityType type; if (isValidStreamingUrl(url)) type = ActivityType.STREAMING; @@ -170,7 +215,7 @@ static Activity streaming(@Nonnull String name, @Nullable String url) * The not-null name of the newly created game * * @throws IllegalArgumentException - * if the specified name is null, empty, blank or longer than 128 characters + * if the specified name is null, empty, blank or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters * * @return A valid Activity instance with the provided name with {@link net.dv8tion.jda.api.entities.Activity.ActivityType#LISTENING} */ @@ -179,7 +224,7 @@ static Activity listening(@Nonnull String name) { Checks.notBlank(name, "Name"); name = name.trim(); - Checks.notLonger(name, 128, "Name"); + Checks.notLonger(name, MAX_ACTIVITY_NAME_LENGTH, "Name"); return EntityBuilder.createActivity(name, null, ActivityType.LISTENING); } @@ -191,19 +236,16 @@ static Activity listening(@Nonnull String name) * The not-null name of the newly created game * * @throws IllegalArgumentException - * if the specified name is null, empty, blank or longer than 128 characters + * if the specified name is null, empty, blank or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters * * @return A valid Activity instance with the provided name with {@link net.dv8tion.jda.api.entities.Activity.ActivityType#WATCHING} - * - * @incubating This feature is not yet confirmed for the official bot API */ @Nonnull - @Incubating static Activity watching(@Nonnull String name) { Checks.notBlank(name, "Name"); name = name.trim(); - Checks.notLonger(name, 128, "Name"); + Checks.notLonger(name, MAX_ACTIVITY_NAME_LENGTH, "Name"); return EntityBuilder.createActivity(name, null, ActivityType.WATCHING); } @@ -215,7 +257,7 @@ static Activity watching(@Nonnull String name) * The not-null name of the newly created game * * @throws IllegalArgumentException - * If the specified name is null, empty, blank or longer than 128 characters + * If the specified name is null, empty, blank or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters * * @return A valid Activity instance with the provided name with {@link net.dv8tion.jda.api.entities.Activity.ActivityType#COMPETING} * @@ -226,10 +268,31 @@ static Activity competing(@Nonnull String name) { Checks.notBlank(name, "Name"); name = name.trim(); - Checks.notLonger(name, 128, "Name"); + Checks.notLonger(name, MAX_ACTIVITY_NAME_LENGTH, "Name"); return EntityBuilder.createActivity(name, null, ActivityType.COMPETING); } + /** + * Creates a new Activity instance with the specified name. + *
This will display without a prefix in the official client + * + * @param name + * The not-null name of the newly created status + * + * @throws IllegalArgumentException + * If the specified name is null, empty, blank or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters + * + * @return A valid Activity instance with the provided name with {@link net.dv8tion.jda.api.entities.Activity.ActivityType#CUSTOM_STATUS} + */ + @Nonnull + static Activity customStatus(@Nonnull String name) + { + Checks.notBlank(name, "Name"); + name = name.trim(); + Checks.notLonger(name, MAX_ACTIVITY_NAME_LENGTH, "Name"); + return EntityBuilder.createActivity(name, null, ActivityType.CUSTOM_STATUS); + } + /** * Creates a new Activity instance with the specified name. * @@ -241,7 +304,7 @@ static Activity competing(@Nonnull String name) * @throws IllegalArgumentException *
    *
  • If the specified ActivityType is null or unsupported
  • - *
  • If the specified name is null, empty or longer than 128 characters
  • + *
  • If the specified name is null, empty or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters
  • *
* * @return A valid Activity instance with the provided name @@ -260,14 +323,14 @@ static Activity of(@Nonnull ActivityType type, @Nonnull String name) * @param type * The {@link net.dv8tion.jda.api.entities.Activity.ActivityType ActivityType} to use * @param name - * The not-null name of the newly created game + * The not-null name of the newly created game or custom status text * @param url * The streaming url to use, required to display as "streaming". * * @throws IllegalArgumentException *
    *
  • If the specified ActivityType is null or unsupported
  • - *
  • If the specified name is null, empty or longer than 128 characters
  • + *
  • If the specified name is null, empty or longer than {@value #MAX_ACTIVITY_NAME_LENGTH} characters
  • *
* * @return A valid Activity instance with the provided name and url @@ -290,6 +353,8 @@ static Activity of(@Nonnull ActivityType type, @Nonnull String name, @Nullable S return watching(name); case COMPETING: return competing(name); + case CUSTOM_STATUS: + return customStatus(name); default: throw new IllegalArgumentException("ActivityType " + type + " is not supported!"); } @@ -331,18 +396,12 @@ enum ActivityType /** * Used to indicate that the {@link Activity Activity} should display * as {@code Watching...} in the official client. - * - * @incubating This feature is not yet confirmed for the official bot API */ - @Incubating WATCHING(3), /** * Used to indicate that the {@link Activity Activity} should display as a custom status * in the official client. - * - * @incubating This Activity type is read-only for bots */ - @Incubating CUSTOM_STATUS(4), /** diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java index 1775542b6e..10af96368b 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java @@ -4539,6 +4539,70 @@ default ChannelAction createForumChannel(@Nonnull String name) @CheckReturnValue ChannelAction createForumChannel(@Nonnull String name, @Nullable Category parent); + /** + * Creates a new {@link MediaChannel} in this Guild. + * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link RestAction RestAction} include the following: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
    The channel could not be created due to a permission discrepancy
  • + * + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS} + *
    The maximum number of channels were exceeded
  • + *
+ * + * @param name + * The name of the MediaChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission + * @throws IllegalArgumentException + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters + * + * @return A specific {@link ChannelAction ChannelAction} + *
This action allows to set fields for the new MediaChannel before creating it + */ + @Nonnull + @CheckReturnValue + default ChannelAction createMediaChannel(@Nonnull String name) + { + return createMediaChannel(name, null); + } + + /** + * Creates a new {@link MediaChannel} in this Guild. + * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link RestAction RestAction} include the following: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS} + *
    The channel could not be created due to a permission discrepancy
  • + * + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS} + *
    The maximum number of channels were exceeded
  • + *
+ * + * @param name + * The name of the MediaChannel to create (up to {@value Channel#MAX_NAME_LENGTH} characters) + * @param parent + * The optional parent category for this channel, or null + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission + * @throws IllegalArgumentException + * If the provided name is {@code null}, blank, or longer than {@value Channel#MAX_NAME_LENGTH} characters; + * or the provided parent is not in the same guild. + * + * @return A specific {@link ChannelAction ChannelAction} + *
This action allows to set fields for the new MediaChannel before creating it + */ + @Nonnull + @CheckReturnValue + ChannelAction createMediaChannel(@Nonnull String name, @Nullable Category parent); + /** * Creates a new {@link Category Category} in this Guild. * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission. diff --git a/src/main/java/net/dv8tion/jda/api/entities/IncomingWebhookClient.java b/src/main/java/net/dv8tion/jda/api/entities/IncomingWebhookClient.java new file mode 100644 index 0000000000..796a555752 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/IncomingWebhookClient.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.api.entities; + +/** + * Specialization of {@link WebhookClient} for incoming webhooks. + *
An incoming webhook, is a webhook that sends messages with a customizable name and avatar. + * + *

These webhooks are primarily used to integrate external services. + */ +public interface IncomingWebhookClient extends WebhookClient +{ +} diff --git a/src/main/java/net/dv8tion/jda/api/entities/Message.java b/src/main/java/net/dv8tion/jda/api/entities/Message.java index 334c071747..b0062f494f 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Message.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Message.java @@ -23,6 +23,7 @@ import net.dv8tion.jda.api.entities.channel.ChannelType; import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer; import net.dv8tion.jda.api.entities.channel.concrete.*; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.unions.GuildMessageChannelUnion; @@ -77,7 +78,7 @@ /** * Represents a Text message received from Discord. - *
This represents messages received from {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel MessageChannels}. + *
This represents messages received from {@link MessageChannel MessageChannels}. * *

This type is not updated. JDA does not keep track of changes to messages, it is advised to do this via events such * as {@link net.dv8tion.jda.api.events.message.MessageUpdateEvent MessageUpdateEvent} and similar. @@ -307,7 +308,7 @@ default Message getReferencedMessage() /** * The {@link Mentions} used in this message. * - *

This includes {@link Member Members}, {@link net.dv8tion.jda.api.entities.channel.middleman.GuildChannel GuildChannels}, {@link Role Roles}, and {@link CustomEmoji CustomEmojis}. + *

This includes {@link Member Members}, {@link GuildChannel GuildChannels}, {@link Role Roles}, and {@link CustomEmoji CustomEmojis}. * Can also be used to check if a message mentions {@code @everyone} or {@code @here}. * *

Example
@@ -353,7 +354,7 @@ default Message getReferencedMessage() *
You can check the type of channel this message was sent from using {@link #isFromType(ChannelType)} or {@link #getChannelType()}. * *

Discord does not provide a member object for messages returned by {@link RestAction RestActions} of any kind. - * This will return null if the message was retrieved through {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel#retrieveMessageById(long)} or similar means, + * This will return null if the message was retrieved through {@link MessageChannel#retrieveMessageById(long)} or similar means, * unless the member is already cached. * * @return Message author, or {@code null} if the message was not sent in a GuildMessageChannel, or if the message was sent by a Webhook. @@ -399,7 +400,7 @@ default Message getReferencedMessage() *

This includes resolving: *
{@link User Users} / {@link net.dv8tion.jda.api.entities.Member Members} * to their @Username/@Nickname format, - *
{@link net.dv8tion.jda.api.entities.channel.middleman.GuildChannel GuildChannels} to their #ChannelName format, + *
{@link GuildChannel GuildChannels} to their #ChannelName format, *
{@link net.dv8tion.jda.api.entities.Role Roles} to their @RoleName format *
{@link CustomEmoji Custom Emojis} (not unicode emojis!) to their {@code :name:} format. * @@ -480,15 +481,12 @@ default Message getReferencedMessage() boolean isFromType(@Nonnull ChannelType type); /** - * Whether this message was sent in a {@link net.dv8tion.jda.api.entities.Guild Guild}. + * Whether this message was sent in a {@link Guild Guild}. *
If this is {@code false} then {@link #getGuild()} will throw an {@link java.lang.IllegalStateException}. * * @return True, if {@link #getChannelType()}.{@link ChannelType#isGuild() isGuild()} is true. */ - default boolean isFromGuild() - { - return getChannelType().isGuild(); - } + boolean isFromGuild(); /** * Gets the {@link net.dv8tion.jda.api.entities.channel.ChannelType ChannelType} that this message was received from. @@ -530,21 +528,59 @@ default String getApplicationId() long getApplicationIdLong(); /** - * Returns the {@link net.dv8tion.jda.api.entities.channel.middleman.MessageChannel MessageChannel} that this message was sent in. + * Whether this message instance has an available {@link #getChannel()}. + * + *

This can be {@code false} for messages sent via webhooks, or in the context of interactions. + * + * @return True, if {@link #getChannel()} is available + */ + boolean hasChannel(); + + /** + * The ID for the channel this message was sent in. + *
This is useful when {@link #getChannel()} is unavailable, for instance on webhook messages. + * + * @return The channel id + */ + long getChannelIdLong(); + + /** + * The ID for the channel this message was sent in. + *
This is useful when {@link #getChannel()} is unavailable, for instance on webhook messages. + * + * @return The channel id + */ + @Nonnull + default String getChannelId() + { + return Long.toUnsignedString(getChannelIdLong()); + } + + /** + * Returns the {@link MessageChannel} that this message was sent in. + * + * @throws IllegalStateException + * If the channel is not available (see {@link #hasChannel()}) * * @return The MessageChannel of this Message + * + * @see #hasChannel() + * @see #getChannelIdLong() */ @Nonnull MessageChannelUnion getChannel(); /** - * Returns the {@link net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel GuildMessageChannel} that this message was sent in + * Returns the {@link GuildMessageChannel} that this message was sent in * if it was sent in a Guild. * * @throws java.lang.IllegalStateException - * If this was not sent in a {@link net.dv8tion.jda.api.entities.Guild}. + * If this was not sent in a {@link Guild} or the channel is not available (see {@link #hasChannel()}). * * @return The MessageChannel of this Message + * + * @see #hasChannel() + * @see #getChannelIdLong() */ @Nonnull GuildMessageChannelUnion getGuildChannel(); @@ -552,7 +588,7 @@ default String getApplicationId() /** * The {@link Category Category} this * message was sent in. This will always be {@code null} for DMs. - *
Equivalent to {@code getGuildChannel().getParentCategory()} if this was sent in a {@link net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel}. + *
Equivalent to {@code getGuildChannel().getParentCategory()} if this was sent in a {@link GuildMessageChannel}. * * @return {@link net.dv8tion.jda.api.entities.channel.concrete.Category Category} for this message */ @@ -560,13 +596,44 @@ default String getApplicationId() Category getCategory(); /** - * Returns the {@link net.dv8tion.jda.api.entities.Guild Guild} that this message was sent in. - *
This is just a shortcut to {@link #getGuildChannel()}{@link net.dv8tion.jda.api.entities.channel.middleman.GuildChannel#getGuild() .getGuild()}. + * Whether this message instance provides a guild instance via {@link #getGuild()}. + *
This is different from {@link #isFromGuild()}, which checks whether the message was sent in a guild. + * This method describes whether {@link #getGuild()} is usable. + * + *

This can be {@code false} for messages sent via webhooks, or in the context of interactions. + * + * @return True, if {@link #getGuild()} is provided + */ + boolean hasGuild(); + + /** + * The ID for the guild this message was sent in. + *
This is useful when {@link #getGuild()} is not provided, for instance on webhook messages. + * + * @return The guild id, or 0 if this message was not sent in a guild + */ + long getGuildIdLong(); + + /** + * The ID for the guild this message was sent in. + *
This is useful when {@link #getGuild()} is not provided, for instance on webhook messages. + * + * @return The guild id, or null if this message was not sent in a guild + */ + @Nullable + default String getGuildId() + { + return isFromGuild() ? Long.toUnsignedString(getGuildIdLong()) : null; + } + + /** + * Returns the {@link Guild Guild} that this message was sent in. + *
This is just a shortcut to {@link #getGuildChannel()}{@link GuildChannel#getGuild() .getGuild()}. *
This is only valid if the Message was actually sent in a GuildMessageChannel. *
You can check the type of channel this message was sent from using {@link #isFromType(ChannelType)} or {@link #getChannelType()}. * * @throws java.lang.IllegalStateException - * If this was not sent in a {@link net.dv8tion.jda.api.entities.channel.middleman.GuildChannel}. + * If this was not sent in a {@link GuildChannel} or the guild instance is not provided * * @return The Guild this message was sent in * @@ -743,9 +810,9 @@ default List