Skip to content

Commit

Permalink
Feature/add emote is available (#5)
Browse files Browse the repository at this point in the history
* Fix description for RestAction#flatMap

* Remove invalid NSFW info

* Fix bug in buffer allocation for ZlibDecompressor

* Don't insert null into override cache

* Keep a strong reference to user from members

Fake members will use fake users that are not cached

* Add functionality to set/get state of embed suppression (discord-jda#1117)

* Add functionality to set/get state of embed suppression

* Provide Enum for MessageFlags

* Improve handling of events for unavailable guilds (discord-jda#1184)

* Improve handling of events for unavailable guilds
* Add UnavailableGuildLeaveEvent
* Update docs for Guild#isAvailable

* Improve ratelimit bucket handling (discord-jda#1103)

* Let ChannelUpdateHandler add overrides to cache

This is a bug since the override shouldn't be in the interface
if the member is not actually in the cache. With the current
implementation we have a cache inconsistency that could NPE.

* Change handling of GuildDeleteHandler to avoid deadlock

There really is no point in holding a write-lock here

* Bump version

* Add South Korea Region

* Add getPermissionHolder method to PermissionOverride (discord-jda#1183)

* Make ignored events for unavailable guilds a debug log

* Add ErrorHandler utility class (discord-jda#1200)

* Add ErrorHandler utility class
* Improve logging of 429 warnings

* Add clearReactions(String) (discord-jda#1192)

* Add clearReactions(String)
* Add MessageReactionRemoveEmoteEvent
* Fix some outdated documentation
* Log and rethrow errors in BotRateLimiter workers

* Properly encode reaction emoji for clearReactionsById(...)

* Updated all copyright headers for 2020 (discord-jda#1198)

* Add (Guild) Invite Events (discord-jda#1196)

Adds GuildInviteCreateEvent and GuildInviteDeleteEvent

* Allow queued requests to finish

* Support gateway intents and configurable member caching (discord-jda#1190)

* First pass on member cache policy
* Add INVALID_INTENTS to close code enum
* Deprecate AccountType.CLIENT
* Add Guild#unloadMembers
* Update README with better examples
* Improve handling of channel overrides
* Add documentation for intents and member cache policy
* Remove references to guild subscriptions
* Add GuildMemberRemoveEvent
* Improve retrieveMemberById and retrieveUserById
* Changes to the builders, we use factory methods now :)
* Disable presence cache by default
* Add JDA#getGatewayIntents

* Fix NPE for GUILD_UPDATE handling

* Fix GuildChannel#createCopy for uncached members (discord-jda#1229)

* Deprecate GuildUpdateBannerEvent#getNewBannerIdUrl (discord-jda#1217)

* Allow to open private channel with user id (discord-jda#1224)

* Change defaults for requester to be non-daemon and have a shorter timeout (discord-jda#1215)

* Added Server Insights permission (discord-jda#1221)

* Add RestAction#timeout and RestAction#deadline (discord-jda#1220)

* Fix NPE in PermissionOverrideAction constructor

* Fix another NPE in success handler

* Documentation updates (discord-jda#1222)

* Swapped manage server and manage channel in Invite#isExpanded docs (discord-jda#1210)
* Document another possible null return case of Message#getMember (discord-jda#1204)
* Fix EmbedBuilder constructor javadocs (discord-jda#1214)
* Update example for jda-nas
* Add more details to modifyMemberRoles docs
* Fix typo

* Added getGuildChannelById methods to ShardManager (discord-jda#1234)

* Add better configuration methods to the builders (discord-jda#1240)

* Update README
* Add enableCache and disableCache
* Add enableIntents and disableIntents
* Update docs for events to mention required intents when necessary.
* Deprecate the old setters for cache flags

* Include voice server endpoint in connection failure logs

* Initialize SelfUser for all shards before logging them in

* Fix bug with new channel overrides having incorrect permissions

* Add the close code to the disconnect warn

* Adding a check in reaction methods before calling getTextChannel() (discord-jda#1216)

* Add JDA#cancelRequests (discord-jda#1223)

* Fix boost time not being handled in guild create

* Add GuildVoiceStreamEvent

* Fix IllegalStateException in Requester

Closes discord-jda#1254

* Change reconnect code to 4900 to avoid confusion (discord-jda#1250)

* Change reconnect code to 4900 to avoid confusion
* And update the disconnect handling to better log things
* Make resume reconnect log on debug
* Log server error on error level
* Add catch in gateway worker

* Add Guild#getMaxFileSize and Guild.BoostTier#getMaxFileSize (discord-jda#1244)

Fixes discord-jda#1243

* Add fallback operators onErrorMap and onErrorFlatMap to RestAction (discord-jda#1219)

* Fix incorrect file size check

* Documentation updates (discord-jda#1241)

* Add note about presence update and member intents
* Fix typo in readme
* Add MessageChannel#retrieveReactionUsersById and update reaction related docs (discord-jda#1236)
* Add requirements section to member/user events

* Json Parsing Optimizations (discord-jda#1267)

* Remove initial reconnect delay for resumes

* Add prune overload to disable wait parameter

* Add Guild#retrieveMetaData (discord-jda#1171)

* Lazy load member limits for guilds
* Improve implementation of update handler
* Add Guild#retrieveMetaData

* Add check for heartbeats in WebSocketClient (discord-jda#1282)

* Add retrieveMembersByPrefix (discord-jda#1276)

* Add retrieveMemberByPrefix
* Add event pool configuration
* Fix javadoc errors
* Rename chunkSyncQueue -> chunkQueue

* Create file if it doesn't exist in downloadToFile

* Add members to cache from retrieveMember methods

* Improve handling of join times for members

* Add Message.JUMP_URL_PATTERN

* Replace disconnect handling to recover from strange thread states (discord-jda#1295)

* Shutdown the WebSocket connection after everything else

* Use executor to shutdown in DefaultShardManager

* Add methods to bulk retrieve members (discord-jda#1292)

* Add methods to bulk retrieve members
* Add documentation
* Add missing annotations to Task interface
* Use random number as nonce

* Improvements to handling of Error (discord-jda#1285)

* Remove usage of AssertionError
* Improve handling of Errors

* Change domain to discord.com (discord-jda#1288)

* Fix NPE in ChannelManager#sync

* Add User#getFlags (discord-jda#1271)

* Add Emote#isAvailable() method

Co-authored-by: Florian Spieß <business@minnced.club>
Co-authored-by: Michael Ritter <ritter.michael92@gmail.com>
Co-authored-by: horyu1234 <horyu1234@naver.com>
Co-authored-by: DMan <DManstrator@users.noreply.github.com>
Co-authored-by: Austin Keener <keeneraustin@yahoo.com>
Co-authored-by: averen <31487840+averen@users.noreply.github.com>
Co-authored-by: Mathéo CIMBARO <25774021+DiscowZombie@users.noreply.github.com>
Co-authored-by: Artuto <artutogamer@gmail.com>
Co-authored-by: Nik <nik@monsterlyrics.co>
Co-authored-by: Sebo Molnár <git@mlnr.dev>
Co-authored-by: gpluscb <20143778+gpluscb@users.noreply.github.com>
Co-authored-by: Napster <napster@npstr.space>
  • Loading branch information
13 people committed May 21, 2020
1 parent 395928d commit d82688f
Show file tree
Hide file tree
Showing 559 changed files with 11,598 additions and 3,388 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
Copyright 2015-2020 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.
Expand Down
61 changes: 33 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ This library is a helpful tool that provides the functionality to create a disco
## 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. We will however continue support
for the usage of JDA through the client account type through `JDABuilder(AccountType.CLIENT)`. This does not mean
it is encouraged or recommended to create applications such as selfbots or custom clients which are prohibited by
the Discord Terms of Service.
are undocumented API endpoints or protocols that are not available to bot-accounts.

_Please see the [Discord docs](https://discordapp.com/developers/docs/reference) for more information about bot accounts._

Expand All @@ -51,8 +48,7 @@ _Please see the [Discord docs](https://discordapp.com/developers/docs/reference)
## UserBots and SelfBots

Discord is currently prohibiting creation and usage of automated client accounts (AccountType.CLIENT).
We however still have support to login with these accounts due to legacy support. That does not mean it is allowed or
welcome to use.
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://discordapp.com/developers/applications).
Expand All @@ -70,13 +66,9 @@ Note that this method is blocking and will cause the thread to sleep until start
**Example**:

```java
JDA jda = new JDABuilder("token").build();
JDA jda = JDABuilder.createDefault("token").build();
```

**Note**: By default this will use the `AccountType.BOT` as that is the recommended type of account.
You can change this to use `AccountType.CLIENT`, however you will be risking account termination.
Use `new JDABuilder(AccountType)` to change to a different account type.

### Configuration

Both the `JDABuilder` and the `DefaultShardManagerBuilder` allow a set of configurations to improve the experience.
Expand All @@ -85,10 +77,10 @@ Both the `JDABuilder` and the `DefaultShardManagerBuilder` allow a set of config

```java
public static void main(String[] args) {
JDABuilder builder = new JDABuilder(args[0]);
JDABuilder builder = JDABuilder.createDefault(args[0]);

// Disable parts of the cache
builder.setDisabledCacheFlags(EnumSet.of(CacheFlag.ACTIVITY, CacheFlag.VOICE_STATE));
builder.disableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE);
// Enable the bulk delete event
builder.setBulkDeleteSplittingEnabled(false);
// Disable compression (not recommended)
Expand All @@ -104,18 +96,25 @@ public static void main(String[] args) {
and [DefaultShardManagerBuilder](https://ci.dv8tion.net/job/JDA/javadoc/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` or disabling `guild_subscriptions`.
Additionally, you can change the handling of member/user cache by setting either a `ChunkingFilter`, disabling **intents**, or changing the **member cache policy**.

```java
public void configureMemoryUsage(JDABuilder builder) {
// Disable cache for member activities (streaming/games/spotify)
builder.setDisabledCacheFlags(
EnumSet.of(CacheFlag.ACTIVITY)
);
// Disable user/member cache and related events
builder.setGuildSubscriptionsEnabled(false);
// Disable member chunking on startup (ignored if guild subscriptions are turned off)
builder.disableCache(CacheFlag.ACTIVITY);

// Only cache members who are either in a voice channel or owner of the guild
builder.setMemberCachePolicy(MemberCachePolicy.VOICE.or(MemberCachePolicy.OWNER));

// Disable member chunking on startup
builder.setChunkingFilter(ChunkingFilter.NONE);

// Disable presence updates and typing events
builder.disableIntents(GatewayIntent.GUILD_PRESENCE, GatewayIntent.GUILD_MESSAGE_TYPING);

// Consider guilds with more than 50 members as "large".
// Large guilds will only provide online members in their setup and thus reduce bandwidth if chunking is disabled.
builder.setLargeThreshold(50);
}
```

Expand Down Expand Up @@ -143,7 +142,7 @@ public class ReadyListener implements EventListener
throws LoginException, InterruptedException
{
// Note: It is important to register your ReadyListener before building
JDA jda = new JDABuilder("token")
JDA jda = JDABuilder.createDefault("token")
.addEventListeners(new ReadyListener())
.build();

Expand All @@ -168,7 +167,7 @@ public class MessageListener extends ListenerAdapter
public static void main(String[] args)
throws LoginException
{
JDA jda = new JDABuilder("token").build();
JDA jda = JDABuilder.createDefault("token").build();
//You can also add event listeners to the already built JDA instance
// Note that some events may not be received if the listener is added after calling build()
// This includes events such as the ReadyEvent
Expand Down Expand Up @@ -200,7 +199,14 @@ public class Bot extends ListenerAdapter
{
public static void main(String[] args) throws LoginException
{
new JDABuilder(args[0])
if (args.length < 1) {
System.out.println("You have to provide a token as first argument!");
System.exit(1);
}
// args[0] should be the token
// We only need 2 intents in this bot. We only respond to messages in guilds and private channels.
// All other events will be disabled.
JDABuilder.createLight(args[0], GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES)
.addEventListeners(new Bot())
.setActivity(Activity.playing("Type !ping"))
.build();
Expand Down Expand Up @@ -288,7 +294,7 @@ Since version **3.4.0** JDA provides a `ShardManager` which automates this build
```java
public static void main(String[] args) throws Exception
{
JDABuilder shardBuilder = new JDABuilder(args[0]);
JDABuilder shardBuilder = JDABuilder.createDefault(args[0]);
//register your listeners here using shardBuilder.addEventListeners(...)
shardBuilder.addEventListeners(new MessageListener());
for (int i = 0; i < 10; i++)
Expand All @@ -305,8 +311,7 @@ public static void main(String[] args) throws Exception
```java
public static void main(String[] args) throws Exception
{
DefaultShardManagerBuilder builder = new DefaultShardManagerBuilder();
builder.setToken(args[0]);
DefaultShardManagerBuilder builder = DefaultShardManagerBuilder.createDefault(args[0]);
builder.addEventListeners(new MessageListener());
builder.build();
}
Expand Down Expand Up @@ -574,7 +579,7 @@ Note that this send system creates an extra UDP-Client which causes audio receiv
since discord identifies the sending UDP-Client as the receiver.

```java
JDABuilder builder = new JDABuilder(BOT_TOKEN)
JDABuilder builder = JDABuilder.createDefault(BOT_TOKEN)
.setAudioSendFactory(new NativeAudioSendFactory());
```

Expand All @@ -592,7 +597,7 @@ fun main() {
.filter { it.message.contentRaw == "!ping" }
.subscribe { it.channel.sendMessage("Pong!").queue() }

val jda = JDABuilder(BOT_TOKEN)
val jda = JDABuilder.createDefault(BOT_TOKEN)
.setEventManager(manager)
.build()
}
Expand Down
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand Down Expand Up @@ -33,7 +33,7 @@ plugins {
id("com.github.johnrengelman.shadow") version "5.1.0"
}

val versionObj = Version(major = "4", minor = "1", revision = "0")
val versionObj = Version(major = "4", minor = "1", revision = "1")

project.group = "net.dv8tion"
project.version = "$versionObj"
Expand Down
19 changes: 16 additions & 3 deletions src/examples/java/AudioEchoExample.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand All @@ -23,9 +23,12 @@
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.managers.AudioManager;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.cache.CacheFlag;

import javax.security.auth.login.LoginException;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
Expand All @@ -41,12 +44,21 @@ public static void main(String[] args) throws LoginException
}
String token = args[0];

new JDABuilder(token) // Use provided token from command line arguments
// We only need 2 gateway intents enabled for this example:
EnumSet<GatewayIntent> intents = EnumSet.of(
// We need messages in guilds to accept commands from users
GatewayIntent.GUILD_MESSAGES,
// We need voice states to connect to the voice channel
GatewayIntent.GUILD_VOICE_STATES
);

// Start the JDA session with light mode (minimal cache)
JDABuilder.createLight(token, intents) // Use provided token from command line arguments
.addEventListeners(new AudioEchoExample()) // Start listening with this listener
.setActivity(Activity.listening("to jams")) // Inform users that we are jammin' it out
.setStatus(OnlineStatus.DO_NOT_DISTURB) // Please don't disturb us while we're jammin'
.enableCache(CacheFlag.VOICE_STATE) // Enable the VOICE_STATE cache to find a user's connected voice channel
.build(); // Login with these options
// Note that its not needed to explicitly enable audio here
}

@Override
Expand Down Expand Up @@ -80,6 +92,7 @@ else if (content.equals("!echo"))
*/
private void onEchoCommand(GuildMessageReceivedEvent event)
{
// Note: None of these can be null due to our configuration with the JDABuilder!
Member member = event.getMember(); // Member is the context of the user for the specific guild, containing voice state and roles
GuildVoiceState voiceState = member.getVoiceState(); // Check the current voice state of the user
VoiceChannel channel = voiceState.getChannel(); // Use the channel the user is currently connected to
Expand Down
62 changes: 62 additions & 0 deletions src/examples/java/LRUCachePolicy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2015-2020 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).
* <br>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<Long> 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;
}
}
6 changes: 3 additions & 3 deletions src/examples/java/MessageListenerExample.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand Down Expand Up @@ -39,8 +39,8 @@ public static void main(String[] args)
// we would use AccountType.CLIENT
try
{
JDA jda = new JDABuilder("Your-Token-Goes-Here") // The token of the account that is logging in.
.addEventListeners(new MessageListenerExample()) // An instance of a class that will handle events.
JDA jda = JDABuilder.createDefault("Your-Token-Goes-Here") // The token of the account that is logging in.
.addEventListeners(new MessageListenerExample()) // An instance of a class that will handle events.
.build();
jda.awaitReady(); // Blocking guarantees that JDA will be completely loaded.
System.out.println("Finished Building JDA!");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand All @@ -23,7 +23,7 @@
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
public @interface DeprecatedSince
{
String value();
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/net/dv8tion/jda/annotations/ForRemoval.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand All @@ -24,7 +24,7 @@
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
public @interface ForRemoval
{
}
2 changes: 1 addition & 1 deletion src/main/java/net/dv8tion/jda/annotations/Incubating.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/net/dv8tion/jda/annotations/ReplaceWith.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand All @@ -24,7 +24,7 @@
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
public @interface ReplaceWith
{
String value();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
* Copyright 2015-2020 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.
Expand Down
Loading

0 comments on commit d82688f

Please sign in to comment.