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