1 module dscord.state; 2 3 import std.functional, 4 std.stdio, 5 std.algorithm.iteration, 6 std.experimental.logger; 7 8 import vibe.core.sync : createManualEvent, ManualEvent; 9 import std.algorithm.searching : canFind, countUntil; 10 import std.algorithm.mutation : remove; 11 12 import dscord.api, 13 dscord.types, 14 dscord.client, 15 dscord.gateway, 16 dscord.util.emitter; 17 18 /** 19 The State class is used to track and maintain client state. 20 */ 21 class State : Emitter { 22 // Client 23 Client client; 24 APIClient api; 25 GatewayClient gw; 26 27 /// Currently logged in user, recieved from READY payload. 28 User me; 29 30 /* 31 TODO: all of these should contain weakrefs too the objects. 32 */ 33 34 /// All users we've seen 35 UserMap users; 36 37 /// All currently loaded guilds 38 GuildMap guilds; 39 40 /// All currently loaded DMs 41 ChannelMap directMessages; 42 43 /// All currently loaded channels 44 ChannelMap channels; 45 46 /// All voice states 47 VoiceStateMap voiceStates; 48 49 /// Event triggered when all guilds are synced 50 ManualEvent ready; 51 52 bool requestOfflineMembers = true; 53 54 private { 55 Snowflake[] awaitingCreate; 56 57 Logger log; 58 EventListenerArray listeners; 59 } 60 61 this(Client client) { 62 this.client = client; 63 this.log = client.log; 64 this.api = client.api; 65 this.gw = client.gw; 66 67 this.users = new UserMap; 68 this.guilds = new GuildMap; 69 this.directMessages = new ChannelMap; 70 this.channels = new ChannelMap; 71 this.voiceStates = new VoiceStateMap; 72 73 this.ready = createManualEvent(); 74 75 // Finally bind all events we want 76 this.bindListeners(); 77 } 78 79 private void listen(Ty...)() { 80 foreach (T; Ty) { 81 this.listeners ~= this.client.events.listen!T(mixin("&this.on" ~ T.stringof)); 82 } 83 } 84 85 private void bindListeners() { 86 // Unbind all listeners 87 this.listeners.each!((l) => l.unbind()); 88 89 // Always listen for ready payload 90 this.listen!( 91 Ready, GuildCreate, GuildUpdate, GuildDelete, GuildMemberAdd, GuildMemberRemove, 92 GuildMemberUpdate, GuildMembersChunk, GuildRoleCreate, GuildRoleUpdate, GuildRoleDelete, 93 GuildEmojisUpdate, ChannelCreate, ChannelUpdate, ChannelDelete, VoiceStateUpdate, MessageCreate, 94 PresenceUpdate 95 ); 96 } 97 98 private void onReady(Ready r) { 99 this.me = r.me; 100 101 foreach (guild; r.guilds) { 102 this.awaitingCreate ~= guild.id; 103 } 104 105 foreach (dm; r.dms) { 106 this.directMessages[dm.id] = dm; 107 } 108 } 109 110 private void onGuildCreate(GuildCreate c) { 111 // If this guild is "coming online" and we're awaiting its creation, clear that state here 112 if (!c.unavailable && this.awaitingCreate.canFind(c.guild.id)) { 113 this.awaitingCreate.remove(this.awaitingCreate.countUntil(c.guild.id)); 114 115 // If no other guilds are awaiting, emit the event 116 if (this.awaitingCreate.length == 0) { 117 this.ready.emit(); 118 } 119 } 120 121 this.guilds[c.guild.id] = c.guild; 122 123 c.guild.channels.each((c) { 124 this.channels[c.id] = c; 125 }); 126 127 c.guild.members.each((m) { 128 this.users[m.user.id] = m.user; 129 }); 130 131 c.guild.voiceStates.each((v) { 132 this.voiceStates[v.sessionID] = v; 133 }); 134 135 if (this.requestOfflineMembers) { 136 c.guild.requestOfflineMembers(); 137 } 138 } 139 140 private void onGuildUpdate(GuildUpdate c) { 141 if (!this.guilds.has(c.guild.id)) return; 142 // TODO: handle updates, iterate over raw data 143 // this.guilds[c.guild.id].fromUpdate(c); 144 } 145 146 private void onGuildDelete(GuildDelete c) { 147 if (!this.guilds.has(c.guildID)) return; 148 149 /* 150 this._guilds[c.guildID].channels.each((c) { 151 destroy(c.id); 152 this._channels.remove(c.id); 153 }); 154 */ 155 156 this.guilds.remove(c.guildID); 157 } 158 159 private void onGuildMemberAdd(GuildMemberAdd c) { 160 if (this.users.has(c.member.user.id)) { 161 this.users[c.member.user.id] = c.member.user; 162 } 163 164 if (this.guilds.has(c.member.guild.id)) { 165 this.guilds[c.member.guild.id].members[c.member.user.id] = c.member; 166 } 167 } 168 169 private void onGuildMemberRemove(GuildMemberRemove c) { 170 if (!this.guilds.has(c.guildID)) return; 171 if (!this.guilds[c.guildID].members.has(c.user.id)) return; 172 this.guilds[c.guildID].members.remove(c.user.id); 173 } 174 175 private void onGuildMemberUpdate(GuildMemberUpdate c) { 176 if (!this.guilds.has(c.member.guildID)) return; 177 if (!this.guilds[c.member.guildID].members.has(c.member.user.id)) return; 178 // TODO: handle updates 179 // this._guilds[c.guildID].members[c.user.id].fromUpdate(c); 180 } 181 182 private void onGuildRoleCreate(GuildRoleCreate c) { 183 if (!this.guilds.has(c.guildID)) return; 184 this.guilds[c.guildID].roles[c.role.id] = c.role; 185 } 186 187 private void onGuildRoleDelete(GuildRoleDelete c) { 188 if (!this.guilds.has(c.guildID)) return; 189 if (!this.guilds[c.guildID].roles.has(c.role.id)) return; 190 this.guilds[c.guildID].roles.remove(c.role.id); 191 } 192 193 private void onGuildRoleUpdate(GuildRoleUpdate c) { 194 if (!this.guilds.has(c.guildID)) return; 195 if (!this.guilds[c.guildID].roles.has(c.role.id)) return; 196 this.guilds[c.guildID].roles[c.role.id] = c.role; 197 } 198 199 private void onChannelCreate(ChannelCreate c) { 200 this.channels[c.channel.id] = c.channel; 201 } 202 203 private void onChannelUpdate(ChannelUpdate c) { 204 this.channels[c.channel.id] = c.channel; 205 } 206 207 private void onChannelDelete(ChannelDelete c) { 208 if (this.channels.has(c.channel.id)) { 209 this.channels.remove(c.channel.id); 210 } 211 } 212 213 private void onVoiceStateUpdate(VoiceStateUpdate u) { 214 // TODO: shallow tracking, don't require guilds 215 auto guild = this.guilds.get(u.state.guildID); 216 if (!guild) return; 217 218 if (!u.state.channelID) { 219 this.voiceStates.remove(u.state.sessionID); 220 guild.voiceStates.remove(u.state.sessionID); 221 } else { 222 this.voiceStates[u.state.sessionID] = u.state; 223 guild.voiceStates[u.state.sessionID] = u.state; 224 } 225 } 226 227 private void onGuildMembersChunk(GuildMembersChunk c) { 228 // TODO 229 } 230 231 private void onGuildEmojisUpdate(GuildEmojisUpdate c) { 232 // TODO 233 } 234 235 private void onMessageCreate(MessageCreate mc) { 236 // TODO 237 } 238 239 private void onPresenceUpdate(PresenceUpdate p) { 240 // TODO 241 } 242 }