diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateTypeEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateTypeEvent.java
index 96b6bd064d..51a5775e0f 100644
--- a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateTypeEvent.java
+++ b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateTypeEvent.java
@@ -32,6 +32,7 @@
*
* - of type {@link ChannelType#TEXT} is converted to type {@link ChannelType#NEWS}
* - of type {@link ChannelType#NEWS} is converted to type {@link ChannelType#TEXT}
+ * - of type {@link ChannelType#FORUM} is converted to type {@link ChannelType#MEDIA}
*
*
* @see Channel#getType()
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
index 004864d731..43bb7f071b 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
@@ -27,6 +27,7 @@
import net.dv8tion.jda.api.entities.automod.build.AutoModRuleData;
import net.dv8tion.jda.api.entities.channel.Channel;
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.AudioChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
@@ -241,6 +242,76 @@ public void invalidate()
}
}
+ public void uncacheChannel(GuildChannel channel)
+ {
+ long id = channel.getIdLong();
+ switch (channel.getType())
+ {
+ case TEXT:
+ api.getTextChannelsView().remove(id);
+ this.getTextChannelsView().remove(id);
+ break;
+ case NEWS:
+ api.getNewsChannelView().remove(id);
+ this.getNewsChannelView().remove(id);
+ break;
+ case MEDIA:
+ api.getMediaChannelsView().remove(id);
+ this.getMediaChannelsView().remove(id);
+ break;
+ case FORUM:
+ api.getForumChannelsView().remove(id);
+ this.getForumChannelsView().remove(id);
+ break;
+ case VOICE:
+ api.getVoiceChannelsView().remove(id);
+ this.getVoiceChannelsView().remove(id);
+ break;
+ case STAGE:
+ api.getStageChannelView().remove(id);
+ this.getStageChannelsView().remove(id);
+ break;
+ case CATEGORY:
+ api.getCategoriesView().remove(id);
+ this.getCategoriesView().remove(id);
+ break;
+ case GUILD_NEWS_THREAD:
+ case GUILD_PUBLIC_THREAD:
+ case GUILD_PRIVATE_THREAD:
+ api.getThreadChannelsView().remove(id);
+ this.getThreadChannelsView().remove(id);
+ break;
+ }
+
+ // Remove dangling threads
+ if (channel instanceof IThreadContainer)
+ {
+ SortedSnowflakeCacheViewImpl localView = this.getThreadChannelsView();
+ SnowflakeCacheViewImpl globalView = api.getThreadChannelsView();
+ Predicate predicate = thread -> channel.equals(thread.getParentChannel());
+
+ try (UnlockHook hook1 = localView.writeLock(); UnlockHook hook2 = globalView.writeLock())
+ {
+ localView.getMap().valueCollection().removeIf(predicate);
+ globalView.getMap().valueCollection().removeIf(predicate);
+ }
+ }
+
+ // This might be too presumptuous, Channel#getParent still returns null regardless if the category is uncached
+// if (channel instanceof Category)
+// {
+// for (Channel chan : guild.getChannels())
+// {
+// if (!(chan instanceof ICategorizableChannelMixin>))
+// continue;
+//
+// ICategorizableChannelMixin> categoizable = (ICategorizableChannelMixin>) chan;
+// if (categoizable.getParentCategoryIdLong() == id)
+// categoizable.setParentCategory(0L);
+// }
+// }
+ }
+
@Nonnull
@Override
public RestAction> retrieveCommands(boolean withLocalizations)
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java
index ce4e5fd45f..f01c5485c4 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/channel/concrete/ThreadChannelImpl.java
@@ -149,6 +149,9 @@ public List getMembers()
@Override
public IThreadContainerUnion getParentChannel()
{
+ IThreadContainer realChannel = getGuild().getChannelById(IThreadContainer.class, parentChannel.getIdLong());
+ if (realChannel != null)
+ parentChannel = (IThreadContainerUnion) realChannel;
return parentChannel;
}
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java
index 035d475d58..4dd673dcf4 100644
--- a/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java
+++ b/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java
@@ -25,11 +25,14 @@
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.IPermissionHolder;
import net.dv8tion.jda.api.entities.PermissionOverride;
+import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.ChannelFlag;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.attribute.IPostContainer;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
-import net.dv8tion.jda.api.entities.channel.concrete.*;
+import net.dv8tion.jda.api.entities.channel.concrete.Category;
+import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
+import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.entities.channel.forums.ForumTag;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.EmojiUnion;
@@ -52,12 +55,10 @@
import net.dv8tion.jda.internal.entities.GuildImpl;
import net.dv8tion.jda.internal.entities.PermissionOverrideImpl;
import net.dv8tion.jda.internal.entities.channel.concrete.ForumChannelImpl;
-import net.dv8tion.jda.internal.entities.channel.concrete.MediaChannelImpl;
-import net.dv8tion.jda.internal.entities.channel.concrete.NewsChannelImpl;
-import net.dv8tion.jda.internal.entities.channel.concrete.TextChannelImpl;
import net.dv8tion.jda.internal.entities.channel.middleman.AbstractGuildChannelImpl;
import net.dv8tion.jda.internal.entities.channel.mixin.attribute.*;
import net.dv8tion.jda.internal.entities.channel.mixin.middleman.AudioChannelMixin;
+import net.dv8tion.jda.internal.entities.channel.mixin.middleman.MessageChannelMixin;
import net.dv8tion.jda.internal.requests.WebSocketClient;
import net.dv8tion.jda.internal.utils.UnlockHook;
import net.dv8tion.jda.internal.utils.cache.SnowflakeCacheViewImpl;
@@ -191,83 +192,92 @@ private AbstractGuildChannelImpl> handleChannelTypeChange(AbstractGuildChannel
EntityBuilder builder = getJDA().getEntityBuilder();
GuildImpl guild = channel.getGuild();
- if (newChannelType == ChannelType.TEXT && channel.getType() == ChannelType.NEWS)
- {
- //This assumes that if we're moving to a TextChannel that we're transitioning from a NewsChannel
- NewsChannel newsChannel = (NewsChannel) channel;
- getJDA().getNewsChannelView().remove(newsChannel.getIdLong());
- guild.getNewsChannelView().remove(newsChannel.getIdLong());
-
- TextChannelImpl textChannel = (TextChannelImpl) builder.createTextChannel(guild, content, guild.getIdLong());
+ ChannelType oldType = channel.getType();
- //CHANNEL_UPDATE doesn't track last_message_id, so make sure to copy it over.
- textChannel.setLatestMessageIdLong(newsChannel.getLatestMessageIdLong());
-
- getJDA().handleEvent(
- new ChannelUpdateTypeEvent(
- getJDA(), responseNumber,
- textChannel, ChannelType.NEWS, ChannelType.TEXT));
-
- return textChannel;
- }
- else if (newChannelType == ChannelType.NEWS && channel.getType() == ChannelType.TEXT)
+ long id = channel.getIdLong();
+ switch (oldType)
{
- //This assumes that if we're moving to a NewsChannel that we're transitioning from a TextChannel
- TextChannel textChannel = (TextChannel) channel;
- getJDA().getTextChannelsView().remove(textChannel.getIdLong());
- guild.getTextChannelsView().remove(textChannel.getIdLong());
-
- NewsChannelImpl newsChannel = (NewsChannelImpl) builder.createNewsChannel(guild, content, guild.getIdLong());
-
- //CHANNEL_UPDATE doesn't track last_message_id, so make sure to copy it over.
- newsChannel.setLatestMessageIdLong(textChannel.getLatestMessageIdLong());
-
- getJDA().handleEvent(
- new ChannelUpdateTypeEvent(
- getJDA(), responseNumber,
- newsChannel, ChannelType.TEXT, ChannelType.NEWS));
-
- return newsChannel;
+ case TEXT:
+ getJDA().getTextChannelsView().remove(id);
+ guild.getTextChannelsView().remove(id);
+ break;
+ case NEWS:
+ getJDA().getNewsChannelView().remove(id);
+ guild.getNewsChannelView().remove(id);
+ break;
+ case MEDIA:
+ getJDA().getMediaChannelsView().remove(id);
+ guild.getMediaChannelsView().remove(id);
+ break;
+ case FORUM:
+ getJDA().getForumChannelsView().remove(id);
+ guild.getForumChannelsView().remove(id);
+ break;
+ case VOICE:
+ getJDA().getVoiceChannelsView().remove(id);
+ guild.getVoiceChannelsView().remove(id);
+ break;
+ case STAGE:
+ getJDA().getStageChannelView().remove(id);
+ guild.getStageChannelsView().remove(id);
+ break;
+ case CATEGORY:
+ getJDA().getCategoriesView().remove(id);
+ guild.getCategoriesView().remove(id);
+ break;
+ default:
+ WebSocketClient.LOG.warn("Unexpected channel type change {}->{}, discarding from cache.", channel.getType().getId(), content.getInt("type"));
+ guild.uncacheChannel(channel);
+ return null;
}
- else if (newChannelType == ChannelType.MEDIA && channel.getType() == ChannelType.FORUM)
- {
- ForumChannel forumChannel = (ForumChannel) channel;
- getJDA().getForumChannelsView().remove(forumChannel.getIdLong());
- guild.getForumChannelsView().remove(forumChannel.getIdLong());
-
- MediaChannelImpl mediaChannel = (MediaChannelImpl) builder.createMediaChannel(guild, content, guild.getIdLong());
- getJDA().handleEvent(
- new ChannelUpdateTypeEvent(
- getJDA(), responseNumber,
- mediaChannel, ChannelType.FORUM, ChannelType.MEDIA));
+ Channel newChannel;
+ switch (newChannelType)
+ {
+ case TEXT:
+ newChannel = builder.createTextChannel(guild, content, guild.getIdLong());
+ break;
+ case NEWS:
+ newChannel = builder.createNewsChannel(guild, content, guild.getIdLong());
+ break;
+ case MEDIA:
+ newChannel = builder.createMediaChannel(guild, content, guild.getIdLong());
+ break;
+ case FORUM:
+ newChannel = builder.createForumChannel(guild, content, guild.getIdLong());
+ break;
+ case VOICE:
+ newChannel = builder.createVoiceChannel(guild, content, guild.getIdLong());
+ break;
+ case STAGE:
+ newChannel = builder.createStageChannel(guild, content, guild.getIdLong());
+ break;
+ case CATEGORY:
+ newChannel = builder.createCategory(guild, content, guild.getIdLong());
+ break;
+ default:
+ WebSocketClient.LOG.warn("Unexpected channel type change {}->{}, discarding from cache.", channel.getType().getId(), content.getInt("type"));
+ guild.uncacheChannel(channel);
+ return null;
}
- else
+
+ // Potentially introduces dangling thread channels (with no parent)
+ if (channel instanceof IThreadContainer && !(newChannel instanceof IThreadContainer))
{
- WebSocketClient.LOG.warn("Received unexpected channel type change {}->{}", channel.getType(), newChannelType);
-
- // Attempt to split into delete/create events
- WebSocketClient client = getJDA().getClient();
-
- DataObject syntheticDelete = DataObject.empty()
- .put("t", "CHANNEL_DELETE")
- .put("s", responseNumber)
- .put("op", 0)
- .put("d", DataObject.empty()
- .put("type", channel.getType().getId())
- .put("guild_id", guild.getId())
- .put("id", channel.getIdLong()));
- client.getHandler("CHANNEL_DELETE").handle(responseNumber, syntheticDelete);
-
- DataObject syntheticCreate = allContent.put("t", "CHANNEL_CREATE");
- // This event does not provide last_message_id so attempt to copy it over manually
- if (channel instanceof MessageChannel)
- syntheticCreate.getObject("d").put("last_message_id", ((MessageChannel) channel).getLatestMessageIdLong());
- client.getHandler("CHANNEL_CREATE").handle(responseNumber, syntheticCreate);
+ WebSocketClient.LOG.error("ThreadContainer channel transitioned into type that is not ThreadContainer? {} -> {}", channel.getType(), newChannel.getType());
+ }
- return null;
+ if (newChannel instanceof MessageChannelMixin> && channel instanceof MessageChannel)
+ {
+ long latestMessageIdLong = ((MessageChannel) channel).getLatestMessageIdLong();
+ ((MessageChannelMixin>) channel).setLatestMessageIdLong(latestMessageIdLong);
}
+ getJDA().handleEvent(
+ new ChannelUpdateTypeEvent(
+ getJDA(), responseNumber,
+ newChannel, oldType, newChannelType));
+
return channel;
}