1package eu.siacs.conversations.entities;
2
3import android.annotation.SuppressLint;
4
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.LinkedHashMap;
8import java.util.List;
9import java.util.Map;
10
11import eu.siacs.conversations.R;
12import eu.siacs.conversations.xmpp.forms.Data;
13import eu.siacs.conversations.xmpp.forms.Field;
14import eu.siacs.conversations.xmpp.jid.InvalidJidException;
15import eu.siacs.conversations.xmpp.jid.Jid;
16import eu.siacs.conversations.xmpp.pep.Avatar;
17
18@SuppressLint("DefaultLocale")
19public class MucOptions {
20
21 public Account getAccount() {
22 return this.conversation.getAccount();
23 }
24
25 public void setSelf(User user) {
26 this.self = user;
27 }
28
29 public enum Affiliation {
30 OWNER("owner", 4, R.string.owner),
31 ADMIN("admin", 3, R.string.admin),
32 MEMBER("member", 2, R.string.member),
33 OUTCAST("outcast", 0, R.string.outcast),
34 NONE("none", 1, R.string.no_affiliation);
35
36 Affiliation(String string, int rank, int resId) {
37 this.string = string;
38 this.resId = resId;
39 this.rank = rank;
40 }
41
42 private String string;
43 private int resId;
44 private int rank;
45
46 public int getResId() {
47 return resId;
48 }
49
50 @Override
51 public String toString() {
52 return this.string;
53 }
54
55 public boolean outranks(Affiliation affiliation) {
56 return rank > affiliation.rank;
57 }
58
59 public boolean ranks(Affiliation affiliation) {
60 return rank >= affiliation.rank;
61 }
62 }
63
64 public enum Role {
65 MODERATOR("moderator", R.string.moderator,3),
66 VISITOR("visitor", R.string.visitor,1),
67 PARTICIPANT("participant", R.string.participant,2),
68 NONE("none", R.string.no_role,0);
69
70 Role(String string, int resId, int rank) {
71 this.string = string;
72 this.resId = resId;
73 this.rank = rank;
74 }
75
76 private String string;
77 private int resId;
78 private int rank;
79
80 public int getResId() {
81 return resId;
82 }
83
84 @Override
85 public String toString() {
86 return this.string;
87 }
88
89 public boolean ranks(Role role) {
90 return rank >= role.rank;
91 }
92 }
93
94 public enum Error {
95 NO_RESPONSE,
96 NONE,
97 NICK_IN_USE,
98 PASSWORD_REQUIRED,
99 BANNED,
100 MEMBERS_ONLY,
101 KICKED,
102 SHUTDOWN,
103 UNKNOWN
104 }
105
106 public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
107 public static final String STATUS_CODE_SELF_PRESENCE = "110";
108 public static final String STATUS_CODE_BANNED = "301";
109 public static final String STATUS_CODE_CHANGED_NICK = "303";
110 public static final String STATUS_CODE_KICKED = "307";
111 public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
112 public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
113 public static final String STATUS_CODE_SHUTDOWN = "332";
114
115 private interface OnEventListener {
116 void onSuccess();
117
118 void onFailure();
119 }
120
121 public interface OnRenameListener extends OnEventListener {
122
123 }
124
125 public static class User {
126 private Role role = Role.NONE;
127 private Affiliation affiliation = Affiliation.NONE;
128 private Jid jid;
129 private Jid fullJid;
130 private long pgpKeyId = 0;
131 private Avatar avatar;
132 private MucOptions options;
133
134 public User(MucOptions options, Jid from) {
135 this.options = options;
136 this.fullJid = from;
137 }
138
139 public String getName() {
140 return this.fullJid.getResourcepart();
141 }
142
143 public void setJid(Jid jid) {
144 this.jid = jid;
145 }
146
147 public Jid getJid() {
148 return this.jid;
149 }
150
151 public Role getRole() {
152 return this.role;
153 }
154
155 public void setRole(String role) {
156 role = role.toLowerCase();
157 switch (role) {
158 case "moderator":
159 this.role = Role.MODERATOR;
160 break;
161 case "participant":
162 this.role = Role.PARTICIPANT;
163 break;
164 case "visitor":
165 this.role = Role.VISITOR;
166 break;
167 default:
168 this.role = Role.NONE;
169 break;
170 }
171 }
172
173 @Override
174 public boolean equals(Object other) {
175 if (this == other) {
176 return true;
177 } else if (!(other instanceof User)) {
178 return false;
179 } else {
180 User o = (User) other;
181 return getName() != null && getName().equals(o.getName())
182 && jid != null && jid.equals(o.jid)
183 && affiliation == o.affiliation
184 && role == o.role;
185 }
186 }
187
188 public Affiliation getAffiliation() {
189 return this.affiliation;
190 }
191
192 public void setAffiliation(String affiliation) {
193 affiliation = affiliation.toLowerCase();
194 switch (affiliation) {
195 case "admin":
196 this.affiliation = Affiliation.ADMIN;
197 break;
198 case "owner":
199 this.affiliation = Affiliation.OWNER;
200 break;
201 case "member":
202 this.affiliation = Affiliation.MEMBER;
203 break;
204 case "outcast":
205 this.affiliation = Affiliation.OUTCAST;
206 break;
207 default:
208 this.affiliation = Affiliation.NONE;
209 }
210 }
211
212 public void setPgpKeyId(long id) {
213 this.pgpKeyId = id;
214 }
215
216 public long getPgpKeyId() {
217 return this.pgpKeyId;
218 }
219
220 public Contact getContact() {
221 return getAccount().getRoster().getContactFromRoster(getJid());
222 }
223
224 public boolean setAvatar(Avatar avatar) {
225 if (this.avatar != null && this.avatar.equals(avatar)) {
226 return false;
227 } else {
228 this.avatar = avatar;
229 return true;
230 }
231 }
232
233 public String getAvatar() {
234 return avatar == null ? null : avatar.getFilename();
235 }
236
237 public Account getAccount() {
238 return options.getAccount();
239 }
240
241 public Jid getFullJid() {
242 return fullJid;
243 }
244 }
245
246 private Account account;
247 private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>());
248 private List<String> features = new ArrayList<>();
249 private Data form = new Data();
250 private Conversation conversation;
251 private boolean isOnline = false;
252 private Error error = Error.NONE;
253 public OnRenameListener onRenameListener = null;
254 private User self;
255 private String subject = null;
256 private String password = null;
257 public boolean mNickChangingInProgress = false;
258
259 public MucOptions(Conversation conversation) {
260 this.account = conversation.getAccount();
261 this.conversation = conversation;
262 this.self = new User(this,createJoinJid(getProposedNick()));
263 }
264
265 public void updateFeatures(ArrayList<String> features) {
266 this.features.clear();
267 this.features.addAll(features);
268 }
269
270 public void updateFormData(Data form) {
271 this.form = form;
272 }
273
274 public boolean hasFeature(String feature) {
275 return this.features.contains(feature);
276 }
277
278 public boolean canInvite() {
279 Field field = this.form.getFieldByName("muc#roomconfig_allowinvites");
280 return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
281 }
282
283 public boolean canChangeSubject() {
284 Field field = this.form.getFieldByName("muc#roomconfig_changesubject");
285 return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
286 }
287
288 public boolean participating() {
289 return !online()
290 || self.getRole().ranks(Role.PARTICIPANT)
291 || hasFeature("muc_unmoderated");
292 }
293
294 public boolean membersOnly() {
295 return hasFeature("muc_membersonly");
296 }
297
298 public boolean mamSupport() {
299 // Update with "urn:xmpp:mam:1" once we support it
300 return hasFeature("urn:xmpp:mam:0");
301 }
302
303 public boolean nonanonymous() {
304 return hasFeature("muc_nonanonymous");
305 }
306
307 public boolean persistent() {
308 return hasFeature("muc_persistent");
309 }
310
311 public boolean moderated() {
312 return hasFeature("muc_moderated");
313 }
314
315 public User deleteUser(String name) {
316 return this.users.remove(name);
317 }
318
319 public void addUser(User user) {
320 this.users.put(user.getName(), user);
321 }
322
323 public User findUser(String name) {
324 return this.users.get(name);
325 }
326
327 public boolean isUserInRoom(String name) {
328 return findUser(name) != null;
329 }
330
331 public void setError(Error error) {
332 this.isOnline = isOnline && error == Error.NONE;
333 this.error = error;
334 }
335
336 public void setOnline() {
337 this.isOnline = true;
338 }
339
340 public ArrayList<User> getUsers() {
341 return new ArrayList<>(users.values());
342 }
343
344 public List<User> getUsers(int max) {
345 ArrayList<User> users = new ArrayList<>();
346 int i = 1;
347 for(User user : this.users.values()) {
348 users.add(user);
349 if (i >= max) {
350 break;
351 } else {
352 ++i;
353 }
354 }
355 return users;
356 }
357
358 public int getUserCount() {
359 return this.users.size();
360 }
361
362 public String getProposedNick() {
363 if (conversation.getBookmark() != null
364 && conversation.getBookmark().getNick() != null
365 && !conversation.getBookmark().getNick().isEmpty()) {
366 return conversation.getBookmark().getNick();
367 } else if (!conversation.getJid().isBareJid()) {
368 return conversation.getJid().getResourcepart();
369 } else {
370 return account.getUsername();
371 }
372 }
373
374 public String getActualNick() {
375 if (this.self.getName() != null) {
376 return this.self.getName();
377 } else {
378 return this.getProposedNick();
379 }
380 }
381
382 public boolean online() {
383 return this.isOnline;
384 }
385
386 public Error getError() {
387 return this.error;
388 }
389
390 public void setOnRenameListener(OnRenameListener listener) {
391 this.onRenameListener = listener;
392 }
393
394 public void setOffline() {
395 this.users.clear();
396 this.error = Error.NO_RESPONSE;
397 this.isOnline = false;
398 }
399
400 public User getSelf() {
401 return self;
402 }
403
404 public void setSubject(String content) {
405 this.subject = content;
406 }
407
408 public String getSubject() {
409 return this.subject;
410 }
411
412 public String createNameFromParticipants() {
413 if (users.size() >= 2) {
414 List<String> names = new ArrayList<>();
415 for (User user : getUsers(5)) {
416 Contact contact = user.getContact();
417 if (contact != null && !contact.getDisplayName().isEmpty()) {
418 names.add(contact.getDisplayName().split("\\s+")[0]);
419 } else {
420 names.add(user.getName());
421 }
422 }
423 StringBuilder builder = new StringBuilder();
424 for (int i = 0; i < names.size(); ++i) {
425 builder.append(names.get(i));
426 if (i != names.size() - 1) {
427 builder.append(", ");
428 }
429 }
430 return builder.toString();
431 } else {
432 return null;
433 }
434 }
435
436 public long[] getPgpKeyIds() {
437 List<Long> ids = new ArrayList<>();
438 for (User user : this.users.values()) {
439 if (user.getPgpKeyId() != 0) {
440 ids.add(user.getPgpKeyId());
441 }
442 }
443 ids.add(account.getPgpId());
444 long[] primitiveLongArray = new long[ids.size()];
445 for (int i = 0; i < ids.size(); ++i) {
446 primitiveLongArray[i] = ids.get(i);
447 }
448 return primitiveLongArray;
449 }
450
451 public boolean pgpKeysInUse() {
452 for (User user : this.users.values()) {
453 if (user.getPgpKeyId() != 0) {
454 return true;
455 }
456 }
457 return false;
458 }
459
460 public boolean everybodyHasKeys() {
461 for (User user : this.users.values()) {
462 if (user.getPgpKeyId() == 0) {
463 return false;
464 }
465 }
466 return true;
467 }
468
469 public Jid createJoinJid(String nick) {
470 try {
471 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
472 } catch (final InvalidJidException e) {
473 return null;
474 }
475 }
476
477 public Jid getTrueCounterpart(String name) {
478 User user = findUser(name);
479 return user == null ? null : user.getJid();
480 }
481
482 public String getPassword() {
483 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
484 if (this.password == null && conversation.getBookmark() != null
485 && conversation.getBookmark().getPassword() != null) {
486 return conversation.getBookmark().getPassword();
487 } else {
488 return this.password;
489 }
490 }
491
492 public void setPassword(String password) {
493 if (conversation.getBookmark() != null) {
494 conversation.getBookmark().setPassword(password);
495 } else {
496 this.password = password;
497 }
498 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
499 }
500
501 public Conversation getConversation() {
502 return this.conversation;
503 }
504}