Java tutorial
/* * Copyright 2015-2017 Austin Keener & Michael Ritter & Florian Spie * * 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.core.entities; import gnu.trove.map.TLongObjectMap; import net.dv8tion.jda.bot.entities.ApplicationInfo; import net.dv8tion.jda.bot.entities.impl.ApplicationInfoImpl; import net.dv8tion.jda.client.entities.*; import net.dv8tion.jda.client.entities.impl.*; import net.dv8tion.jda.core.*; import net.dv8tion.jda.core.audit.ActionType; import net.dv8tion.jda.core.audit.AuditLogChange; import net.dv8tion.jda.core.audit.AuditLogEntry; import net.dv8tion.jda.core.entities.MessageEmbed.*; import net.dv8tion.jda.core.entities.impl.*; import net.dv8tion.jda.core.exceptions.AccountTypeException; import net.dv8tion.jda.core.handle.GuildMembersChunkHandler; import net.dv8tion.jda.core.handle.ReadyHandler; import net.dv8tion.jda.core.requests.WebSocketClient; import net.dv8tion.jda.core.utils.MiscUtil; import org.apache.commons.collections4.map.CaseInsensitiveMap; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.awt.Color; import java.time.Instant; import java.time.OffsetDateTime; import java.util.*; import java.util.function.Consumer; import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; public class EntityBuilder { public static final String MISSING_CHANNEL = "MISSING_CHANNEL"; public static final String MISSING_USER = "MISSING_USER"; private static final Pattern channelMentionPattern = Pattern.compile("<#(\\d+)>"); protected final JDAImpl api; protected final TLongObjectMap<JSONObject> cachedGuildJsons = MiscUtil.newLongMap(); protected final TLongObjectMap<Consumer<Guild>> cachedGuildCallbacks = MiscUtil.newLongMap(); public EntityBuilder(JDA api) { this.api = (JDAImpl) api; } public SelfUser createSelfUser(JSONObject self) { SelfUserImpl selfUser = ((SelfUserImpl) api.getSelfUser()); if (selfUser == null) { final long id = self.getLong("id"); selfUser = new SelfUserImpl(id, api); api.setSelfUser(selfUser); } if (!api.getUserMap().containsKey(selfUser.getIdLong())) { api.getUserMap().put(selfUser.getIdLong(), selfUser); } return (SelfUser) selfUser.setVerified(self.getBoolean("verified")) .setMfaEnabled(self.getBoolean("mfa_enabled")) .setEmail(!self.isNull("email") ? self.getString("email") : null) .setName(self.getString("username")).setDiscriminator(self.getString("discriminator")) .setAvatarId(self.isNull("avatar") ? null : self.getString("avatar")) .setBot(self.has("bot") && self.getBoolean("bot")); } public void createGuildFirstPass(JSONObject guild, Consumer<Guild> secondPassCallback) { final long id = guild.getLong("id"); GuildImpl guildObj = ((GuildImpl) api.getGuildMap().get(id)); if (guildObj == null) { guildObj = new GuildImpl(api, id); api.getGuildMap().put(id, guildObj); } if (guild.has("unavailable") && guild.getBoolean("unavailable")) { guildObj.setAvailable(false); //This is used for when GuildCreateHandler receives a guild that is currently unavailable. During normal READY // loading for bots (which unavailable is always true) the secondPassCallback parameter will always // be null. if (secondPassCallback != null) secondPassCallback.accept(guildObj); api.getGuildLock().lock(id); return; } //If we make it to here, the Guild is available. This means 1 of 2 things: //Either: // 1) This is Guild provided during READY for a Client account // 2) This is a Guild received from GuildCreateHandler from a GUILD_CREATE event. // This could be triggered by joining a guild or due to discord finally // providing us with Guild information about a previously unavailable guild. // Whether it was unavailable due to Bot READY unavailability or due to an // outage within discord matters now. // // Either way, we now have enough information to fill in the general information about the Guild. // This does NOT necessarily mean that we have all information to complete the guild. // For Client accounts, we will also need to use op 12 (GUILD_SYNC) to get all presences of online users because // discord only provides Online users that we have an open PM channel with or are friends with for Client accounts. // On larger guilds we will still need to request all users using op 8 (GUILD_MEMBERS_CHUNK). // // The code below takes the information we -do- have and starts to fill in the Guild. It won't create anything // that might rely on Users that we don't have due to needing the GUILD_MEMBERS_CHUNK // This includes making VoiceStatus and PermissionOverrides guildObj.setAvailable(true).setIconId(guild.isNull("icon") ? null : guild.getString("icon")) .setSplashId(guild.isNull("splash") ? null : guild.getString("splash")) .setRegion(Region.fromKey(guild.getString("region"))).setName(guild.getString("name")) .setAfkTimeout(Guild.Timeout.fromKey(guild.getInt("afk_timeout"))) .setVerificationLevel(Guild.VerificationLevel.fromKey(guild.getInt("verification_level"))) .setDefaultNotificationLevel( Guild.NotificationLevel.fromKey(guild.getInt("default_message_notifications"))) .setRequiredMFALevel(Guild.MFALevel.fromKey(guild.getInt("mfa_level"))).setExplicitContentLevel( Guild.ExplicitContentLevel.fromKey(guild.getInt("explicit_content_filter"))); JSONArray roles = guild.getJSONArray("roles"); for (int i = 0; i < roles.length(); i++) { Role role = createRole(roles.getJSONObject(i), guildObj.getIdLong()); guildObj.getRolesMap().put(role.getIdLong(), role); if (role.getIdLong() == guildObj.getIdLong()) guildObj.setPublicRole(role); } if (!guild.isNull("emojis")) { JSONArray array = guild.getJSONArray("emojis"); TLongObjectMap<Emote> emoteMap = guildObj.getEmoteMap(); for (int i = 0; i < array.length(); i++) { JSONObject object = array.getJSONObject(i); JSONArray emoteRoles = object.getJSONArray("roles"); final long emoteId = object.getLong("id"); EmoteImpl emoteObj = new EmoteImpl(emoteId, guildObj); Set<Role> roleSet = emoteObj.getRoleSet(); for (int j = 0; j < emoteRoles.length(); j++) roleSet.add(guildObj.getRoleById(emoteRoles.getString(j))); emoteMap.put(emoteId, emoteObj.setName(object.getString("name")).setManaged(object.getBoolean("managed"))); } } if (guild.has("members")) { JSONArray members = guild.getJSONArray("members"); createGuildMemberPass(guildObj, members); } //This could be null for Client accounts. Will be fixed by GUILD_SYNC Member owner = guildObj.getMemberById(guild.getLong("owner_id")); if (owner != null) guildObj.setOwner(owner); if (guild.has("presences")) { JSONArray presences = guild.getJSONArray("presences"); for (int i = 0; i < presences.length(); i++) { JSONObject presence = presences.getJSONObject(i); final long userId = presence.getJSONObject("user").getLong("id"); MemberImpl member = (MemberImpl) guildObj.getMembersMap().get(userId); if (member == null) WebSocketClient.LOG.debug("Received a ghost presence in GuildFirstPass! Guild: " + guildObj + " UserId: " + userId); else createPresence(member, presence); } } if (guild.has("channels")) { JSONArray channels = guild.getJSONArray("channels"); for (int i = 0; i < channels.length(); i++) { JSONObject channel = channels.getJSONObject(i); ChannelType type = ChannelType.fromId(channel.getInt("type")); if (type == ChannelType.TEXT) { TextChannel newChannel = createTextChannel(channel, guildObj.getIdLong(), false); if (newChannel.getIdLong() == guildObj.getIdLong()) guildObj.setPublicChannel(newChannel); } else if (type == ChannelType.VOICE) { VoiceChannel newChannel = createVoiceChannel(channel, guildObj.getIdLong(), false); if (!guild.isNull("afk_channel_id") && newChannel.getId().equals(guild.getString("afk_channel_id"))) guildObj.setAfkChannel(newChannel); } else WebSocketClient.LOG.fatal( "Received a channel for a guild that isn't a text or voice channel. JSON: " + channel); } } //If the members that we were provided with (and loaded above) were not all of the // the members in this guild, then we need to request more users from Discord using // op 9 (GUILD_MEMBERS_CHUNK). To do so, we will cache the guild's JSON so we can properly // load stuff that relies on Users like Channels, PermissionOverrides and VoiceStatuses // after we have the rest of the users. We will request the GUILD_MEMBERS_CHUNK information // which will be sent from discord over the main Websocket and will be handled by // GuildMemberChunkHandler. After the handler has received all users as determined by the // value set using `setExpectedGuildMembers`, it will do one of the following: // 1) If this is a Bot account, immediately call EntityBuilder#createGuildSecondPass, thus finishing // the Guild object creation process. // 2) If this is a Client account, it will request op 12 (GUILD_SYNC) to make sure we have all information // about online users as GUILD_MEMBERS_CHUNK does not include presence information, and when loading the // members from GUILD_MEMBERS_CHUNK, we assume they are offline. GUILD_SYNC makes sure that we mark them // properly. After GUILD_SYNC is received by GuildSyncHandler, it will call EntityBuilder#createGuildSecondPass // //If we actually -did- get all of the users needed, then we don't need to Chunk. Furthermore, // we don't need to use GUILD_SYNC because we always get presences with users thus we have all information // needed to guild the Guild. We will skip if (guild.getJSONArray("members").length() != guild.getInt("member_count")) { cachedGuildJsons.put(id, guild); cachedGuildCallbacks.put(id, secondPassCallback); GuildMembersChunkHandler handler = api.getClient().getHandler("GUILD_MEMBERS_CHUNK"); handler.setExpectedGuildMembers(id, guild.getInt("member_count")); //If we are already past READY / RESUME, then chunk at runtime. Otherwise, pass back to the ReadyHandler // and let it send a burst chunk request. if (api.getClient().isReady()) { if (api.getAccountType() == AccountType.CLIENT) { JSONObject obj = new JSONObject().put("op", WebSocketCode.GUILD_SYNC).put("guild_id", guildObj.getId()); api.getClient().chunkOrSyncRequest(obj); } JSONObject obj = new JSONObject().put("op", WebSocketCode.MEMBER_CHUNK_REQUEST).put("d", new JSONObject().put("guild_id", id).put("query", "").put("limit", 0)); api.getClient().chunkOrSyncRequest(obj); } else { ReadyHandler readyHandler = api.getClient().getHandler("READY"); readyHandler.acknowledgeGuild(guildObj, true, true, api.getAccountType() == AccountType.CLIENT); } api.getGuildLock().lock(id); return; } //As detailed in the comment above, if we've made it this far then we have all member information needed to // create the Guild. Thus, we fill in the remaining information, unlock the guild, and provide the guild // to the callback //This should only occur on small user count guilds. JSONArray channels = guild.getJSONArray("channels"); createGuildChannelPass(guildObj, channels); //Actually creates PermissionOverrides JSONArray voiceStates = guild.getJSONArray("voice_states"); createGuildVoiceStatePass(guildObj, voiceStates); api.getGuildLock().unlock(guildObj.getIdLong()); if (secondPassCallback != null) secondPassCallback.accept(guildObj); } public void createGuildSecondPass(long guildId, List<JSONArray> memberChunks) { JSONObject guildJson = cachedGuildJsons.remove(guildId); Consumer<Guild> secondPassCallback = cachedGuildCallbacks.remove(guildId); GuildImpl guildObj = (GuildImpl) api.getGuildMap().get(guildId); if (guildObj == null) throw new IllegalStateException( "Attempted to perform a second pass on an unknown Guild. Guild not in JDA " + "mapping. GuildId: " + guildId); if (guildJson == null) throw new IllegalStateException( "Attempted to perform a second pass on an unknown Guild. No cached Guild " + "for second pass. GuildId: " + guildId); if (secondPassCallback == null) throw new IllegalArgumentException("No callback provided for the second pass on the Guild!"); for (JSONArray chunk : memberChunks) { createGuildMemberPass(guildObj, chunk); } Member owner = guildObj.getMemberById(guildJson.getLong("owner_id")); if (owner != null) guildObj.setOwner(owner); if (guildObj.getOwner() == null) WebSocketClient.LOG.fatal("Never set the Owner of the Guild: " + guildObj.getId() + " because we don't have the owner User object! How?!"); JSONArray channels = guildJson.getJSONArray("channels"); createGuildChannelPass(guildObj, channels); JSONArray voiceStates = guildJson.getJSONArray("voice_states"); createGuildVoiceStatePass(guildObj, voiceStates); secondPassCallback.accept(guildObj); api.getGuildLock().unlock(guildId); } public void handleGuildSync(GuildImpl guild, JSONArray members, JSONArray presences) { for (int i = 0; i < members.length(); i++) { JSONObject memberJson = members.getJSONObject(i); createMember(guild, memberJson); } for (int i = 0; i < presences.length(); i++) { JSONObject presenceJson = presences.getJSONObject(i); final long userId = presenceJson.getJSONObject("user").getLong("id"); MemberImpl member = (MemberImpl) guild.getMembersMap().get(userId); if (member == null) WebSocketClient.LOG .fatal("Received a Presence for a non-existent Member when dealing with GuildSync!"); else this.createPresence(member, presenceJson); } } private void createGuildMemberPass(GuildImpl guildObj, JSONArray members) { for (int i = 0; i < members.length(); i++) { JSONObject memberJson = members.getJSONObject(i); createMember(guildObj, memberJson); } } private void createGuildChannelPass(GuildImpl guildObj, JSONArray channels) { for (int i = 0; i < channels.length(); i++) { JSONObject channel = channels.getJSONObject(i); ChannelType type = ChannelType.fromId(channel.getInt("type")); Channel channelObj = null; if (type == ChannelType.TEXT) { channelObj = api.getTextChannelById(channel.getLong("id")); } else if (type == ChannelType.VOICE) { channelObj = api.getVoiceChannelById(channel.getLong("id")); } else WebSocketClient.LOG.fatal( "Received a channel for a guild that isn't a text or voice channel (ChannelPass). JSON: " + channel); if (channelObj != null) { JSONArray permissionOverwrites = channel.getJSONArray("permission_overwrites"); for (int j = 0; j < permissionOverwrites.length(); j++) { try { createPermissionOverride(permissionOverwrites.getJSONObject(j), channelObj); } catch (IllegalArgumentException e) { //Caused by Discord not properly clearing PermissionOverrides when a Member leaves a Guild. WebSocketClient.LOG.debug(e.getMessage() + ". Ignoring PermissionOverride."); } } } else { WebSocketClient.LOG .fatal("Got permission_override for unknown channel with id: " + channel.getString("id")); } } } public void createGuildVoiceStatePass(GuildImpl guildObj, JSONArray voiceStates) { for (int i = 0; i < voiceStates.length(); i++) { JSONObject voiceStateJson = voiceStates.getJSONObject(i); final long userId = voiceStateJson.getLong("user_id"); Member member = guildObj.getMembersMap().get(userId); if (member == null) { WebSocketClient.LOG.fatal("Received a VoiceState for a unknown Member! GuildId: " + guildObj.getId() + " MemberId: " + voiceStateJson.getString("user_id")); continue; } final long channelId = voiceStateJson.getLong("channel_id"); VoiceChannelImpl voiceChannel = (VoiceChannelImpl) guildObj.getVoiceChannelMap().get(channelId); voiceChannel.getConnectedMembersMap().put(member.getUser().getIdLong(), member); GuildVoiceStateImpl voiceState = (GuildVoiceStateImpl) member.getVoiceState(); voiceState.setSelfMuted(voiceStateJson.getBoolean("self_mute")) .setSelfDeafened(voiceStateJson.getBoolean("self_deaf")) .setGuildMuted(voiceStateJson.getBoolean("mute")) .setGuildDeafened(voiceStateJson.getBoolean("deaf")) .setSuppressed(voiceStateJson.getBoolean("suppress")) .setSessionId(voiceStateJson.getString("session_id")).setConnectedChannel(voiceChannel); } } public User createFakeUser(JSONObject user, boolean modifyCache) { return createUser(user, true, modifyCache); } public User createUser(JSONObject user) { return createUser(user, false, true); } private User createUser(JSONObject user, boolean fake, boolean modifyCache) { final long id = user.getLong("id"); UserImpl userObj; userObj = (UserImpl) api.getUserMap().get(id); if (userObj == null) { userObj = (UserImpl) api.getFakeUserMap().get(id); if (userObj != null) { if (!fake && modifyCache) { api.getFakeUserMap().remove(id); userObj.setFake(false); api.getUserMap().put(userObj.getIdLong(), userObj); if (userObj.hasPrivateChannel()) { PrivateChannelImpl priv = (PrivateChannelImpl) userObj.getPrivateChannel(); priv.setFake(false); api.getFakePrivateChannelMap().remove(priv.getIdLong()); api.getPrivateChannelMap().put(priv.getIdLong(), priv); } } } else { userObj = new UserImpl(id, api).setFake(fake); if (modifyCache) { if (fake) api.getFakeUserMap().put(id, userObj); else api.getUserMap().put(id, userObj); } } } return userObj.setName(user.getString("username")).setDiscriminator(user.get("discriminator").toString()) .setAvatarId(user.isNull("avatar") ? null : user.getString("avatar")) .setBot(user.has("bot") && user.getBoolean("bot")); } public Member createMember(GuildImpl guild, JSONObject memberJson) { User user = createUser(memberJson.getJSONObject("user")); MemberImpl member = (MemberImpl) guild.getMember(user); if (member == null) { member = new MemberImpl(guild, user); guild.getMembersMap().put(user.getIdLong(), member); } ((GuildVoiceStateImpl) member.getVoiceState()).setGuildMuted(memberJson.getBoolean("mute")) .setGuildDeafened(memberJson.getBoolean("deaf")); member.setJoinDate(OffsetDateTime.parse(memberJson.getString("joined_at"))).setNickname( memberJson.has("nick") && !memberJson.isNull("nick") ? memberJson.getString("nick") : null); JSONArray rolesJson = memberJson.getJSONArray("roles"); for (int k = 0; k < rolesJson.length(); k++) { final long roleId = rolesJson.getLong(k); Role r = guild.getRolesMap().get(roleId); if (r == null) { WebSocketClient.LOG.debug("Received a Member with an unknown Role. MemberId: " + member.getUser().getId() + " GuildId: " + guild.getId() + " roleId: " + roleId); } else { member.getRoleSet().add(r); } } return member; } //Effectively the same as createFriendPresence public void createPresence(Object memberOrFriend, JSONObject presenceJson) { if (memberOrFriend == null) throw new NullPointerException("Provided memberOrFriend was null!"); JSONObject gameJson = presenceJson.isNull("game") ? null : presenceJson.getJSONObject("game"); OnlineStatus onlineStatus = OnlineStatus.fromKey(presenceJson.getString("status")); Game game = null; if (gameJson != null && !gameJson.isNull("name")) { String gameName = gameJson.get("name").toString(); String url = gameJson.isNull("url") ? null : gameJson.get("url").toString(); Game.GameType gameType; try { gameType = gameJson.isNull("type") ? Game.GameType.DEFAULT : Game.GameType.fromKey(Integer.parseInt(gameJson.get("type").toString())); } catch (NumberFormatException e) { gameType = Game.GameType.DEFAULT; } game = new GameImpl(gameName, url, gameType); } if (memberOrFriend instanceof Member) { MemberImpl member = (MemberImpl) memberOrFriend; member.setOnlineStatus(onlineStatus); member.setGame(game); } else if (memberOrFriend instanceof Friend) { FriendImpl friend = (FriendImpl) memberOrFriend; friend.setOnlineStatus(onlineStatus); friend.setGame(game); OffsetDateTime lastModified = OffsetDateTime.ofInstant( Instant.ofEpochMilli(presenceJson.getLong("last_modified")), TimeZone.getTimeZone("GMT").toZoneId()); friend.setOnlineStatusModifiedTime(lastModified); } else throw new IllegalArgumentException( "An object was provided to EntityBuilder#createPresence that wasn't a Member or Friend. JSON: " + presenceJson); } public TextChannel createTextChannel(JSONObject json, long guildId) { return createTextChannel(json, guildId, true); } public TextChannel createTextChannel(JSONObject json, long guildId, boolean guildIsLoaded) { final long id = json.getLong("id"); TextChannelImpl channel = (TextChannelImpl) api.getTextChannelMap().get(id); if (channel == null) { GuildImpl guild = ((GuildImpl) api.getGuildMap().get(guildId)); channel = new TextChannelImpl(id, guild); guild.getTextChannelsMap().put(id, channel); api.getTextChannelMap().put(id, channel); } if (!json.isNull("permission_overwrites") && guildIsLoaded) { JSONArray overrides = json.getJSONArray("permission_overwrites"); for (int i = 0; i < overrides.length(); i++) { createPermissionOverride(overrides.getJSONObject(i), channel); } } return channel.setLastMessageId(json.isNull("last_message_id") ? -1 : json.getLong("last_message_id")) .setName(json.getString("name")).setTopic(json.isNull("topic") ? "" : json.getString("topic")) .setRawPosition(json.getInt("position")).setNSFW(!json.isNull("nsfw") && json.getBoolean("nsfw")); } public VoiceChannel createVoiceChannel(JSONObject json, long guildId) { return createVoiceChannel(json, guildId, true); } public VoiceChannel createVoiceChannel(JSONObject json, long guildId, boolean guildIsLoaded) { final long id = json.getLong("id"); VoiceChannelImpl channel = ((VoiceChannelImpl) api.getVoiceChannelMap().get(id)); if (channel == null) { GuildImpl guild = (GuildImpl) api.getGuildMap().get(guildId); channel = new VoiceChannelImpl(id, guild); guild.getVoiceChannelMap().put(id, channel); api.getVoiceChannelMap().put(id, channel); } if (!json.isNull("permission_overwrites") && guildIsLoaded) { JSONArray overrides = json.getJSONArray("permission_overwrites"); for (int i = 0; i < overrides.length(); i++) { createPermissionOverride(overrides.getJSONObject(i), channel); } } return channel.setName(json.getString("name")).setRawPosition(json.getInt("position")) .setUserLimit(json.getInt("user_limit")).setBitrate(json.getInt("bitrate")); } public PrivateChannel createPrivateChannel(JSONObject privatechat) { JSONObject recipient = privatechat.has("recipients") ? privatechat.getJSONArray("recipients").getJSONObject(0) : privatechat.getJSONObject("recipient"); final long userId = recipient.getLong("id"); UserImpl user = ((UserImpl) api.getUserMap().get(userId)); if (user == null) { //The API can give us private channels connected to Users that we can no longer communicate with. // As such, make a fake user and fake private channel. user = (UserImpl) createFakeUser(recipient, true); } final long channelId = privatechat.getLong("id"); PrivateChannelImpl priv = new PrivateChannelImpl(channelId, user).setLastMessageId( privatechat.isNull("last_message_id") ? -1 : privatechat.getLong("last_message_id")); user.setPrivateChannel(priv); if (user.isFake()) { priv.setFake(true); api.getFakePrivateChannelMap().put(channelId, priv); } else api.getPrivateChannelMap().put(channelId, priv); return priv; } public Role createRole(JSONObject roleJson, long guildId) { final long id = roleJson.getLong("id"); GuildImpl guild = ((GuildImpl) api.getGuildMap().get(guildId)); RoleImpl role = ((RoleImpl) guild.getRolesMap().get(id)); if (role == null) { role = new RoleImpl(id, guild); guild.getRolesMap().put(id, role); } return role.setName(roleJson.getString("name")).setRawPosition(roleJson.getInt("position")) .setRawPermissions(roleJson.getLong("permissions")).setManaged(roleJson.getBoolean("managed")) .setHoisted(roleJson.getBoolean("hoist")) .setColor(roleJson.getInt("color") != 0 ? new Color(roleJson.getInt("color")) : null) .setMentionable(roleJson.has("mentionable") && roleJson.getBoolean("mentionable")); } public Message createMessage(JSONObject jsonObject) { return createMessage(jsonObject, false); } public Message createMessage(JSONObject jsonObject, boolean exceptionOnMissingUser) { final long channelId = jsonObject.getLong("channel_id"); MessageChannel chan = api.getTextChannelById(channelId); if (chan == null) chan = api.getPrivateChannelById(channelId); if (chan == null) chan = api.getFakePrivateChannelMap().get(channelId); if (chan == null && api.getAccountType() == AccountType.CLIENT) chan = api.asClient().getGroupById(channelId); if (chan == null) throw new IllegalArgumentException(MISSING_CHANNEL); return createMessage(jsonObject, chan, exceptionOnMissingUser); } public Message createMessage(JSONObject jsonObject, MessageChannel chan, boolean exceptionOnMissingUser) { final long id = jsonObject.getLong("id"); String content = !jsonObject.isNull("content") ? jsonObject.getString("content") : ""; JSONObject author = jsonObject.getJSONObject("author"); final long authorId = author.getLong("id"); boolean fromWebhook = jsonObject.has("webhook_id"); MessageImpl message = new MessageImpl(id, chan, fromWebhook).setContent(content) .setTime(!jsonObject.isNull("timestamp") ? OffsetDateTime.parse(jsonObject.getString("timestamp")) : OffsetDateTime.now()) .setMentionsEveryone( !jsonObject.isNull("mention_everyone") && jsonObject.getBoolean("mention_everyone")) .setTTS(!jsonObject.isNull("tts") && jsonObject.getBoolean("tts")) .setPinned(!jsonObject.isNull("pinned") && jsonObject.getBoolean("pinned")); if (chan instanceof PrivateChannel) { if (authorId == api.getSelfUser().getIdLong()) message.setAuthor(api.getSelfUser()); else message.setAuthor(((PrivateChannel) chan).getUser()); } else if (chan instanceof Group) { UserImpl user = (UserImpl) api.getUserMap().get(authorId); if (user == null) user = (UserImpl) api.getFakeUserMap().get(authorId); if (user == null && fromWebhook) user = (UserImpl) createFakeUser(author, false); if (user == null) { if (exceptionOnMissingUser) throw new IllegalArgumentException(MISSING_USER); //Specifically for MESSAGE_CREATE else user = (UserImpl) createFakeUser(author, false); //Any message creation that isn't MESSAGE_CREATE } message.setAuthor(user); //If the message was sent by a cached fake user, lets update it. if (user.isFake() && !fromWebhook) { user.setName(author.getString("username")).setDiscriminator(author.get("discriminator").toString()) .setAvatarId(author.isNull("avatar") ? null : author.getString("avatar")) .setBot(author.has("bot") && author.getBoolean("bot")); } } else { GuildImpl guild = (GuildImpl) ((TextChannel) chan).getGuild(); Member member = guild.getMembersMap().get(authorId); User user = member != null ? member.getUser() : null; if (user != null) message.setAuthor(user); else if (fromWebhook || !exceptionOnMissingUser) message.setAuthor(createFakeUser(author, false)); else throw new IllegalArgumentException(MISSING_USER); } List<Message.Attachment> attachments = new LinkedList<>(); if (!jsonObject.isNull("attachments")) { JSONArray jsonAttachments = jsonObject.getJSONArray("attachments"); for (int i = 0; i < jsonAttachments.length(); i++) { JSONObject jsonAttachment = jsonAttachments.getJSONObject(i); attachments.add(new Message.Attachment(jsonAttachment.getString("id"), jsonAttachment.getString("url"), jsonAttachment.getString("proxy_url"), jsonAttachment.getString("filename"), jsonAttachment.getInt("size"), jsonAttachment.has("height") ? jsonAttachment.getInt("height") : 0, jsonAttachment.has("width") ? jsonAttachment.getInt("width") : 0, api)); } } message.setAttachments(attachments); List<MessageEmbed> embeds = new LinkedList<>(); JSONArray jsonEmbeds = jsonObject.getJSONArray("embeds"); for (int i = 0; i < jsonEmbeds.length(); i++) { embeds.add(createMessageEmbed(jsonEmbeds.getJSONObject(i))); } message.setEmbeds(embeds); if (!jsonObject.isNull("edited_timestamp")) message.setEditedTime(OffsetDateTime.parse(jsonObject.getString("edited_timestamp"))); if (jsonObject.has("reactions")) { JSONArray reactions = jsonObject.getJSONArray("reactions"); List<MessageReaction> list = new LinkedList<>(); for (int i = 0; i < reactions.length(); i++) { JSONObject obj = reactions.getJSONObject(i); JSONObject emoji = obj.getJSONObject("emoji"); final Long emojiId = emoji.isNull("id") ? null : emoji.getLong("id"); String emojiName = emoji.getString("name"); boolean self = obj.has("self") && obj.getBoolean("self"); int count = obj.getInt("count"); Emote emote = null; if (emojiId != null) { emote = api.getEmoteById(emojiId); if (emote == null) emote = new EmoteImpl(emojiId, api).setName(emojiName); } MessageReaction.ReactionEmote reactionEmote; if (emote == null) reactionEmote = new MessageReaction.ReactionEmote(emojiName, null, api); else reactionEmote = new MessageReaction.ReactionEmote(emote); list.add(new MessageReaction(chan, reactionEmote, message.getIdLong(), self, count)); } message.setReactions(list); } if (message.isFromType(ChannelType.TEXT)) { TextChannel textChannel = message.getTextChannel(); TreeMap<Integer, User> mentionedUsers = new TreeMap<>(); if (!jsonObject.isNull("mentions")) { JSONArray mentions = jsonObject.getJSONArray("mentions"); for (int i = 0; i < mentions.length(); i++) { JSONObject mention = mentions.getJSONObject(i); User u = api.getUserById(mention.getLong("id")); if (u != null) { //We do this to properly order the mentions. The array given by discord is out of order sometimes. String mentionId = mention.getString("id"); int index = content.indexOf("<@" + mentionId + ">"); if (index < 0) index = content.indexOf("<@!" + mentionId + ">"); mentionedUsers.put(index, u); } } } message.setMentionedUsers(new LinkedList<User>(mentionedUsers.values())); TreeMap<Integer, Role> mentionedRoles = new TreeMap<>(); if (!jsonObject.isNull("mention_roles")) { JSONArray roleMentions = jsonObject.getJSONArray("mention_roles"); for (int i = 0; i < roleMentions.length(); i++) { String roleId = roleMentions.getString(i); Role r = textChannel.getGuild().getRoleById(roleId); if (r != null) { int index = content.indexOf("<@&" + roleId + ">"); mentionedRoles.put(index, r); } } } message.setMentionedRoles(new LinkedList<Role>(mentionedRoles.values())); List<TextChannel> mentionedChannels = new LinkedList<>(); TLongObjectMap<TextChannel> chanMap = ((GuildImpl) textChannel.getGuild()).getTextChannelsMap(); Matcher matcher = channelMentionPattern.matcher(content); while (matcher.find()) { TextChannel channel = chanMap.get(Long.parseLong(matcher.group(1))); if (channel != null && !mentionedChannels.contains(channel)) { mentionedChannels.add(channel); } } message.setMentionedChannels(mentionedChannels); } return message; } public MessageEmbed createMessageEmbed(JSONObject messageEmbed) { if (messageEmbed.isNull("type")) throw new JSONException( "Encountered embed object with missing/null type field for Json: " + messageEmbed); EmbedType type = EmbedType.fromKey(messageEmbed.getString("type")); /* if (type == EmbedType.UNKNOWN) throw new JSONException("Discord provided us an unknown embed type. Json: " + messageEmbed);*/ MessageEmbedImpl embed = new MessageEmbedImpl().setType(type) .setUrl(messageEmbed.isNull("url") ? null : messageEmbed.getString("url")) .setTitle(messageEmbed.isNull("title") ? null : messageEmbed.getString("title")) .setDescription(messageEmbed.isNull("description") ? null : messageEmbed.getString("description")) .setColor(messageEmbed.isNull("color") || messageEmbed.getInt("color") == 0 ? null : new Color(messageEmbed.getInt("color"))) .setTimestamp(messageEmbed.isNull("timestamp") ? null : OffsetDateTime.parse(messageEmbed.getString("timestamp"))); if (messageEmbed.has("thumbnail")) { JSONObject thumbnailJson = messageEmbed.getJSONObject("thumbnail"); embed.setThumbnail(new Thumbnail(thumbnailJson.getString("url"), thumbnailJson.getString("proxy_url"), thumbnailJson.getInt("width"), thumbnailJson.getInt("height"))); } else embed.setThumbnail(null); if (messageEmbed.has("provider")) { JSONObject providerJson = messageEmbed.getJSONObject("provider"); embed.setSiteProvider(new Provider(providerJson.isNull("name") ? null : providerJson.getString("name"), providerJson.isNull("url") ? null : providerJson.getString("url"))); } else embed.setSiteProvider(null); if (messageEmbed.has("author")) { JSONObject authorJson = messageEmbed.getJSONObject("author"); embed.setAuthor(new AuthorInfo(authorJson.isNull("name") ? null : authorJson.getString("name"), authorJson.isNull("url") ? null : authorJson.getString("url"), authorJson.isNull("icon_url") ? null : authorJson.getString("icon_url"), authorJson.isNull("proxy_icon_url") ? null : authorJson.getString("proxy_icon_url"))); } else embed.setAuthor(null); if (messageEmbed.has("image")) { JSONObject imageJson = messageEmbed.getJSONObject("image"); embed.setImage(new ImageInfo(imageJson.isNull("url") ? null : imageJson.getString("url"), imageJson.isNull("proxy_url") ? null : imageJson.getString("proxy_url"), imageJson.isNull("width") ? -1 : imageJson.getInt("width"), imageJson.isNull("height") ? -1 : imageJson.getInt("height"))); } else embed.setImage(null); if (messageEmbed.has("footer")) { JSONObject footerJson = messageEmbed.getJSONObject("footer"); embed.setFooter(new Footer(footerJson.isNull("text") ? null : footerJson.getString("text"), footerJson.isNull("icon_url") ? null : footerJson.getString("icon_url"), footerJson.isNull("proxy_icon_url") ? null : footerJson.getString("proxy_icon_url"))); } else embed.setFooter(null); if (messageEmbed.has("fields")) { JSONArray fieldsJson = messageEmbed.getJSONArray("fields"); List<Field> fields = new LinkedList<>(); for (int index = 0; index < fieldsJson.length(); index++) { JSONObject fieldJson = fieldsJson.getJSONObject(index); fields.add(new Field(fieldJson.isNull("name") ? null : fieldJson.getString("name"), fieldJson.isNull("value") ? null : fieldJson.getString("value"), !fieldJson.isNull("inline") && fieldJson.getBoolean("inline"), false)); // unchecked field instantiation } embed.setFields(fields); } else embed.setFields(Collections.emptyList()); if (messageEmbed.has("video")) { JSONObject videoJson = messageEmbed.getJSONObject("video"); embed.setVideoInfo(new MessageEmbed.VideoInfo(videoJson.getString("url"), videoJson.isNull("width") ? -1 : videoJson.getInt("width"), videoJson.isNull("height") ? -1 : videoJson.getInt("height"))); } return embed; } public PermissionOverride createPermissionOverride(JSONObject override, Channel chan) { PermissionOverrideImpl permOverride = null; final long id = override.getLong("id"); long allow = override.getLong("allow"); long deny = override.getLong("deny"); switch (override.getString("type")) { case "member": Member member = chan.getGuild().getMemberById(id); if (member == null) throw new IllegalArgumentException( "Attempted to create a PermissionOverride for a non-existent user. Guild: " + chan.getGuild() + ", Channel: " + chan + ", JSON: " + override); permOverride = (PermissionOverrideImpl) chan.getPermissionOverride(member); if (permOverride == null) { permOverride = new PermissionOverrideImpl(chan, member.getUser().getIdLong(), member); ((AbstractChannelImpl<?>) chan).getOverrideMap().put(member.getUser().getIdLong(), permOverride); } break; case "role": Role role = ((GuildImpl) chan.getGuild()).getRolesMap().get(id); if (role == null) throw new IllegalArgumentException( "Attempted to create a PermissionOverride for a non-existent role! JSON: " + override); permOverride = (PermissionOverrideImpl) chan.getPermissionOverride(role); if (permOverride == null) { permOverride = new PermissionOverrideImpl(chan, role.getIdLong(), role); ((AbstractChannelImpl<?>) chan).getOverrideMap().put(role.getIdLong(), permOverride); } break; default: throw new IllegalArgumentException( "Provided with an unknown PermissionOverride type! JSON: " + override); } return permOverride.setAllow(allow).setDeny(deny); } public Webhook createWebhook(JSONObject object) { final long id = object.getLong("id"); final long guildId = object.getLong("guild_id"); final long channelId = object.getLong("channel_id"); String token = !object.isNull("token") ? object.getString("token") : null; TextChannel channel = api.getTextChannelById(channelId); if (channel == null) throw new NullPointerException(String.format( "Tried to create Webhook for an un-cached TextChannel! WebhookId: %s ChannelId: %s GuildId: %s", id, channelId, guildId)); Object name = !object.isNull("name") ? object.get("name") : JSONObject.NULL; Object avatar = !object.isNull("avatar") ? object.get("avatar") : JSONObject.NULL; JSONObject fakeUser = new JSONObject().put("username", name).put("discriminator", "0000").put("id", id) .put("avatar", avatar); User defaultUser = createFakeUser(fakeUser, false); JSONObject ownerJson = object.getJSONObject("user"); final long userId = ownerJson.getLong("id"); User owner = api.getUserById(userId); if (owner == null) { ownerJson.put("id", userId); owner = createFakeUser(ownerJson, false); } return new WebhookImpl(channel, id).setToken(token).setOwner(channel.getGuild().getMember(owner)) .setUser(defaultUser); } public Relationship createRelationship(JSONObject relationshipJson) { if (api.getAccountType() != AccountType.CLIENT) throw new AccountTypeException(AccountType.CLIENT, "Attempted to create a Relationship but the logged in account is not a CLIENT!"); RelationshipType type = RelationshipType.fromKey(relationshipJson.getInt("type")); User user; if (type == RelationshipType.FRIEND) user = createUser(relationshipJson.getJSONObject("user")); else user = createFakeUser(relationshipJson.getJSONObject("user"), true); Relationship relationship = api.asClient().getRelationshipById(user.getIdLong(), type); if (relationship == null) { switch (type) { case FRIEND: relationship = new FriendImpl(user); break; case BLOCKED: relationship = new BlockedUserImpl(user); break; case INCOMING_FRIEND_REQUEST: relationship = new IncomingFriendRequestImpl(user); break; case OUTGOING_FRIEND_REQUEST: relationship = new OutgoingFriendRequestImpl(user); break; default: return null; } ((JDAClientImpl) api.asClient()).getRelationshipMap().put(user.getIdLong(), relationship); } return relationship; } public Group createGroup(JSONObject groupJson) { if (api.getAccountType() != AccountType.CLIENT) throw new AccountTypeException(AccountType.CLIENT, "Attempted to create a Group but the logged in account is not a CLIENT!"); final long groupId = groupJson.getLong("id"); JSONArray recipients = groupJson.getJSONArray("recipients"); final long ownerId = groupJson.getLong("owner_id"); String name = !groupJson.isNull("name") ? groupJson.getString("name") : null; String iconId = !groupJson.isNull("icon") ? groupJson.getString("icon") : null; long lastMessage = !groupJson.isNull("last_message_id") ? groupJson.getLong("last_message_id") : -1; GroupImpl group = (GroupImpl) api.asClient().getGroupById(groupId); if (group == null) { group = new GroupImpl(groupId, api); ((JDAClientImpl) api.asClient()).getGroupMap().put(groupId, group); } TLongObjectMap<User> groupUsers = group.getUserMap(); groupUsers.put(api.getSelfUser().getIdLong(), api.getSelfUser()); for (int i = 0; i < recipients.length(); i++) { JSONObject groupUser = recipients.getJSONObject(i); groupUsers.put(groupUser.getLong("id"), createFakeUser(groupUser, true)); } User owner = api.getUserMap().get(ownerId); if (owner == null) owner = api.getFakeUserMap().get(ownerId); if (owner == null) throw new IllegalArgumentException( "Attempted to build a Group, but could not find user by provided owner id." + "This should not be possible because the owner should be IN the group!"); return group.setOwner(owner).setLastMessageId(lastMessage).setName(name).setIconId(iconId); } public Invite createInvite(JSONObject object) { final String code = object.getString("code"); final User inviter = object.has("inviter") ? this.createFakeUser(object.getJSONObject("inviter"), false) : null; final JSONObject channelObject = object.getJSONObject("channel"); final ChannelType channelType = ChannelType.fromId(channelObject.getInt("type")); final long channelId = channelObject.getLong("id"); final String channelName = channelObject.getString("name"); final Invite.Channel channel = new InviteImpl.ChannelImpl(channelId, channelName, channelType); final JSONObject guildObject = object.getJSONObject("guild"); final String guildIconId = guildObject.isNull("icon") ? null : guildObject.getString("icon"); final long guildId = guildObject.getLong("id"); final String guildName = guildObject.getString("name"); final String guildSplashId = guildObject.isNull("splash") ? null : guildObject.getString("splash"); final Invite.Guild guild = new InviteImpl.GuildImpl(guildId, guildIconId, guildName, guildSplashId); final int maxAge; final int maxUses; final boolean temporary; final OffsetDateTime timeCreated; final int uses; final boolean expanded; if (object.has("max_uses")) { expanded = true; maxAge = object.getInt("max_age"); maxUses = object.getInt("max_uses"); uses = object.getInt("uses"); temporary = object.getBoolean("temporary"); timeCreated = OffsetDateTime.parse(object.getString("created_at")); } else { expanded = false; maxAge = -1; maxUses = -1; uses = -1; temporary = false; timeCreated = null; } return new InviteImpl(api, code, expanded, inviter, maxAge, maxUses, temporary, timeCreated, uses, channel, guild); } public void clearCache() { cachedGuildJsons.clear(); cachedGuildCallbacks.clear(); } public ApplicationInfo createApplicationInfo(JSONObject object) { final String description = object.getString("description"); final boolean doesBotRequireCodeGrant = object.getBoolean("bot_require_code_grant"); final String iconId = !object.isNull("icon") ? object.getString("icon") : null; final long id = object.getLong("id"); final String name = object.getString("name"); final boolean isBotPublic = object.getBoolean("bot_public"); final User owner = createFakeUser(object.getJSONObject("owner"), false); return new ApplicationInfoImpl(api, description, doesBotRequireCodeGrant, iconId, id, isBotPublic, name, owner); } public Application createApplication(JSONObject object) { return new ApplicationImpl(api, object); } public AuthorizedApplication createAuthorizedApplication(JSONObject object) { final long authId = object.getLong("id"); JSONArray scopeArray = object.getJSONArray("scopes"); List<String> scopes = new ArrayList<>(scopeArray.length()); for (int i = 0; i < scopeArray.length(); i++) { scopes.add(scopeArray.getString(i)); } JSONObject application = object.getJSONObject("application"); final String description = application.getString("description"); final String iconId = application.has("icon") ? application.getString("icon") : null; final long id = application.getLong("id"); final String name = application.getString("name"); return new AuthorizedApplicationImpl(api, authId, description, iconId, id, name, scopes); } public AuditLogEntry createAuditLogEntry(GuildImpl guild, JSONObject entryJson, JSONObject userJson) { final long targetId = entryJson.isNull("target_id") ? 0 : entryJson.getLong("target_id"); final long id = entryJson.getLong("id"); final int typeKey = entryJson.getInt("action_type"); final JSONArray changes = entryJson.isNull("changes") ? null : entryJson.getJSONArray("changes"); final JSONObject options = entryJson.isNull("options") ? null : entryJson.getJSONObject("options"); final String reason = entryJson.isNull("reason") ? null : entryJson.getString("reason"); final UserImpl user = (UserImpl) createFakeUser(userJson, false); final Set<AuditLogChange> changesList; final ActionType type = ActionType.from(typeKey); if (changes != null) { changesList = new HashSet<>(changes.length()); for (int i = 0; i < changes.length(); i++) { final JSONObject object = changes.getJSONObject(i); AuditLogChange change = createAuditLogChange(object); changesList.add(change); } } else { changesList = Collections.emptySet(); } CaseInsensitiveMap<String, AuditLogChange> changeMap = new CaseInsensitiveMap<>(changeToMap(changesList)); CaseInsensitiveMap<String, Object> optionMap = options != null ? new CaseInsensitiveMap<>(options.toMap()) : null; return new AuditLogEntry(type, id, targetId, guild, user, reason, changeMap, optionMap); } public AuditLogChange createAuditLogChange(JSONObject change) { final String key = change.getString("key"); Object oldValue = change.isNull("old_value") ? null : change.get("old_value"); Object newValue = change.isNull("new_value") ? null : change.get("new_value"); // Don't confront users with JSON if (oldValue instanceof JSONArray || newValue instanceof JSONArray) { oldValue = oldValue instanceof JSONArray ? ((JSONArray) oldValue).toList() : oldValue; newValue = newValue instanceof JSONArray ? ((JSONArray) newValue).toList() : newValue; } else if (oldValue instanceof JSONObject || newValue instanceof JSONObject) { oldValue = oldValue instanceof JSONObject ? ((JSONObject) oldValue).toMap() : oldValue; newValue = newValue instanceof JSONObject ? ((JSONObject) newValue).toMap() : newValue; } return new AuditLogChange(oldValue, newValue, key); } private Map<String, AuditLogChange> changeToMap(Set<AuditLogChange> changesList) { return changesList.stream().collect(Collectors.toMap(AuditLogChange::getKey, UnaryOperator.identity())); } }