1 module dscord.types.guild;
2 
3 import std.stdio,
4        std.algorithm,
5        std.array,
6        std.conv;
7 
8 import dscord.types,
9        dscord.client,
10        dscord.gateway;
11 
12 alias GuildMap = ModelMap!(Snowflake, Guild);
13 alias RoleMap = ModelMap!(Snowflake, Role);
14 alias GuildMemberMap = ModelMap!(Snowflake, GuildMember);
15 alias EmojiMap = ModelMap!(Snowflake, Emoji);
16 
17 enum VerificationLevel : ushort {
18   NONE = 0,
19   LOW = 1,
20   MEDIUM = 2,
21   HIGH = 3,
22   EXTREME = 4,
23 }
24 
25 class Role : IModel {
26   mixin Model;
27 
28   Snowflake   id;
29   Snowflake   guildID;
30 
31   string      name;
32   bool        hoist;
33   bool        managed;
34   uint        color;
35   Permission  permissions;
36   short       position;
37   bool        mentionable;
38 
39   @property Guild guild() {
40     return this.client.state.guilds.get(this.guildID);
41   }
42 }
43 
44 class Emoji : IModel {
45   mixin Model;
46 
47   Snowflake  id;
48   Snowflake  guildID;
49   string     name;
50   bool       requireColons;
51   bool       managed;
52 
53   Snowflake[]  roles;
54 
55   @property Guild guild() {
56     return this.client.state.guilds.get(this.guildID);
57   }
58 
59   bool matches(string usage) {
60     if (this.name == ":" ~ usage ~ ":") {
61       return true;
62     } else if (this.requireColons) {
63       return false;
64     } else {
65       return this.name == usage;
66     }
67   }
68 }
69 
70 class GuildMember : IModel {
71   mixin Model;
72 
73   User       user;
74   Snowflake  guildID;
75   string     nick;
76   string     joinedAt;
77   bool       mute;
78   bool       deaf;
79 
80   Snowflake[]  roles;
81 
82   @property Snowflake id() {
83     return this.user.id;
84   }
85 
86   @property Guild guild() {
87     return this.client.state.guilds.get(this.guildID);
88   }
89 
90   override string toString() {
91     return format("<GuildMember %s#%s (%s / %s)>",
92       this.user.username,
93       this.user.discriminator,
94       this.id,
95       this.guild.id);
96   }
97 
98   bool hasRole(Role role) {
99     return this.hasRole(role.id);
100   }
101 
102   bool hasRole(Snowflake id) {
103     return this.roles.canFind(id);
104   }
105 }
106 
107 class Guild : IModel, IPermissible {
108   mixin Model;
109   mixin Permissible;
110 
111   Snowflake  id;
112   Snowflake  ownerID;
113   Snowflake  afkChannelID;
114   Snowflake  embedChannelID;
115   string     name;
116   string     icon;
117   string     splash;
118   string     region;
119   uint       afkTimeout;
120   bool       embedEnabled;
121   ushort     verificationLevel;
122   ushort     mfaLevel;
123   string[]   features;
124 
125   bool  unavailable;
126 
127   @JSONListToMap("id")
128   GuildMemberMap  members;
129 
130   @JSONListToMap("sessionID")
131   VoiceStateMap   voiceStates;
132 
133   @JSONListToMap("id")
134   ChannelMap      channels;
135 
136   @JSONListToMap("id")
137   RoleMap         roles;
138 
139   @JSONListToMap("id")
140   EmojiMap        emojis;
141 
142   override void initialize() {
143     // It's possible these are not created
144     if (!this.members) return;
145 
146     this.members.each((m) { m.guildID = this.id; });
147     this.voiceStates.each((vc) { vc.guildID = this.id; });
148     this.channels.each((c) { c.guildID = this.id; });
149     this.roles.each((r) { r.guildID = this.id; });
150     this.emojis.each((e) { e.guildID = this.id; });
151   }
152 
153   override string toString() {
154     return format("<Guild %s (%s)>", this.name, this.id);
155   }
156 
157   /// Returns a URL to the guild icon
158   string getIconURL(string fmt = "webp", size_t size = 1024) {
159     if (this.icon == "") {
160       return "";
161     }
162     return format("https://cdn.discordapp.com/icons/%s/%s.%s?size=%s", this.id, this.icon, fmt, size);
163   }
164 
165   /// Returns a GuildMember for a given user object
166   GuildMember getMember(User obj) {
167     return this.getMember(obj.id);
168   }
169 
170   /// Returns a GuildMember for a given user/member id
171   GuildMember getMember(Snowflake id) {
172     return this.members.get(id);
173   }
174 
175   /// Kick a given GuildMember
176   void kick(GuildMember member) {
177     this.kick(member.user);
178   }
179 
180   /// Kick a given User
181   void kick(User user) {
182     this.client.api.guildsMembersKick(this.id, user.id);
183   }
184 
185   /// Default role for this Guild
186   @property Role defaultRole() {
187     return this.roles.pick((r) {
188       return r.id == this.id;
189     });
190   }
191 
192   /// Default channel for this Guild
193   @property Channel defaultChannel() {
194     return this.channels.pick((c) {
195       return c.id == this.id;
196     });
197   }
198 
199   /// Request offline members for this guild
200   void requestOfflineMembers() {
201     this.client.gw.send(new RequestGuildMembers(this.id));
202   }
203 
204   override Permission getPermissions(Snowflake user) {
205     // If we're the owner, we have alllll the permissions
206     if (this.ownerID == user) {
207       return Permissions.ADMINISTRATOR;
208     }
209 
210     // Otherwise grab the member object
211     GuildMember member = this.getMember(user);
212     Permission perm;
213     auto roles = member.roles.map!((rid) => this.roles.get(rid));
214 
215     // Iterate over roles and add permissions
216     foreach (role; roles) {
217       perm |= role.permissions;
218     }
219 
220     return perm;
221   }
222 
223   /// Set this servers name
224   void setName(string name) {
225     this.client.api.guildsModify(this.id, VibeJSON(["name" : VibeJSON(name)]));
226   }
227 
228   /// Set this servers region
229   void setRegion(string region) {
230     this.client.api.guildsModify(this.id, VibeJSON(["region" : VibeJSON(region)]));
231   }
232 }