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