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() || self.getRole().ranks(Role.PARTICIPANT);
286 }
287
288 public boolean membersOnly() {
289 return hasFeature("muc_membersonly");
290 }
291
292 public boolean mamSupport() {
293 // Update with "urn:xmpp:mam:1" once we support it
294 return hasFeature("urn:xmpp:mam:0");
295 }
296
297 public boolean nonanonymous() {
298 return hasFeature("muc_nonanonymous");
299 }
300
301 public boolean persistent() {
302 return hasFeature("muc_persistent");
303 }
304
305 public boolean moderated() {
306 return hasFeature("muc_moderated");
307 }
308
309 public User deleteUser(String name) {
310 return this.users.remove(name);
311 }
312
313 public void addUser(User user) {
314 this.users.put(user.getName(), user);
315 }
316
317 public User findUser(String name) {
318 return this.users.get(name);
319 }
320
321 public boolean isUserInRoom(String name) {
322 return findUser(name) != null;
323 }
324
325 public void setError(int error) {
326 this.isOnline = isOnline && error == ERROR_NO_ERROR;
327 this.error = error;
328 }
329
330 public void setOnline() {
331 this.isOnline = true;
332 }
333
334 public ArrayList<User> getUsers() {
335 return new ArrayList<>(users.values());
336 }
337
338 public List<User> getUsers(int max) {
339 ArrayList<User> users = new ArrayList<>();
340 int i = 1;
341 for(User user : this.users.values()) {
342 users.add(user);
343 if (i >= max) {
344 break;
345 } else {
346 ++i;
347 }
348 }
349 return users;
350 }
351
352 public int getUserCount() {
353 return this.users.size();
354 }
355
356 public String getProposedNick() {
357 if (conversation.getBookmark() != null
358 && conversation.getBookmark().getNick() != null
359 && !conversation.getBookmark().getNick().isEmpty()) {
360 return conversation.getBookmark().getNick();
361 } else if (!conversation.getJid().isBareJid()) {
362 return conversation.getJid().getResourcepart();
363 } else {
364 return account.getUsername();
365 }
366 }
367
368 public String getActualNick() {
369 if (this.self.getName() != null) {
370 return this.self.getName();
371 } else {
372 return this.getProposedNick();
373 }
374 }
375
376 public boolean online() {
377 return this.isOnline;
378 }
379
380 public int getError() {
381 return this.error;
382 }
383
384 public void setOnRenameListener(OnRenameListener listener) {
385 this.onRenameListener = listener;
386 }
387
388 public void setOffline() {
389 this.users.clear();
390 this.error = ERROR_NO_RESPONSE;
391 this.isOnline = false;
392 }
393
394 public User getSelf() {
395 return self;
396 }
397
398 public void setSubject(String content) {
399 this.subject = content;
400 }
401
402 public String getSubject() {
403 return this.subject;
404 }
405
406 public String createNameFromParticipants() {
407 if (users.size() >= 2) {
408 List<String> names = new ArrayList<>();
409 for (User user : getUsers(5)) {
410 Contact contact = user.getContact();
411 if (contact != null && !contact.getDisplayName().isEmpty()) {
412 names.add(contact.getDisplayName().split("\\s+")[0]);
413 } else {
414 names.add(user.getName());
415 }
416 }
417 StringBuilder builder = new StringBuilder();
418 for (int i = 0; i < names.size(); ++i) {
419 builder.append(names.get(i));
420 if (i != names.size() - 1) {
421 builder.append(", ");
422 }
423 }
424 return builder.toString();
425 } else {
426 return null;
427 }
428 }
429
430 public long[] getPgpKeyIds() {
431 List<Long> ids = new ArrayList<>();
432 for (User user : this.users.values()) {
433 if (user.getPgpKeyId() != 0) {
434 ids.add(user.getPgpKeyId());
435 }
436 }
437 ids.add(account.getPgpId());
438 long[] primitiveLongArray = new long[ids.size()];
439 for (int i = 0; i < ids.size(); ++i) {
440 primitiveLongArray[i] = ids.get(i);
441 }
442 return primitiveLongArray;
443 }
444
445 public boolean pgpKeysInUse() {
446 for (User user : this.users.values()) {
447 if (user.getPgpKeyId() != 0) {
448 return true;
449 }
450 }
451 return false;
452 }
453
454 public boolean everybodyHasKeys() {
455 for (User user : this.users.values()) {
456 if (user.getPgpKeyId() == 0) {
457 return false;
458 }
459 }
460 return true;
461 }
462
463 public Jid createJoinJid(String nick) {
464 try {
465 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
466 } catch (final InvalidJidException e) {
467 return null;
468 }
469 }
470
471 public Jid getTrueCounterpart(String name) {
472 User user = findUser(name);
473 return user == null ? null : user.getJid();
474 }
475
476 public String getPassword() {
477 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
478 if (this.password == null && conversation.getBookmark() != null
479 && conversation.getBookmark().getPassword() != null) {
480 return conversation.getBookmark().getPassword();
481 } else {
482 return this.password;
483 }
484 }
485
486 public void setPassword(String password) {
487 if (conversation.getBookmark() != null) {
488 conversation.getBookmark().setPassword(password);
489 } else {
490 this.password = password;
491 }
492 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
493 }
494
495 public Conversation getConversation() {
496 return this.conversation;
497 }
498}