2017-03-02 14:37:32 +00:00
|
|
|
using Gee;
|
|
|
|
|
|
|
|
using Xmpp;
|
|
|
|
using Dino.Entities;
|
|
|
|
|
|
|
|
namespace Dino {
|
|
|
|
public class MucManager : StreamInteractionModule, Object {
|
2017-03-19 11:55:36 +00:00
|
|
|
public static ModuleIdentity<MucManager> IDENTITY = new ModuleIdentity<MucManager>("muc_manager");
|
|
|
|
public string id { get { return IDENTITY.id; } }
|
2017-03-02 14:37:32 +00:00
|
|
|
|
2017-04-11 16:06:01 +00:00
|
|
|
public signal void joined(Account account, Jid jid, string nick);
|
|
|
|
public signal void left(Account account, Jid jid);
|
|
|
|
public signal void subject_set(Account account, Jid jid, string? subject);
|
2017-04-23 11:50:32 +00:00
|
|
|
public signal void bookmarks_updated(Account account, Gee.List<Xep.Bookmarks.Conference> conferences);
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
private StreamInteractor stream_interactor;
|
2017-06-13 14:04:26 +00:00
|
|
|
private HashMap<Jid, Xep.Muc.MucEnterError> enter_errors = new HashMap<Jid, Xep.Muc.MucEnterError>(Jid.hash_func, Jid.equals_func);
|
2017-03-02 14:37:32 +00:00
|
|
|
|
|
|
|
public static void start(StreamInteractor stream_interactor) {
|
|
|
|
MucManager m = new MucManager(stream_interactor);
|
|
|
|
stream_interactor.add_module(m);
|
|
|
|
}
|
|
|
|
|
|
|
|
private MucManager(StreamInteractor stream_interactor) {
|
|
|
|
this.stream_interactor = stream_interactor;
|
|
|
|
stream_interactor.account_added.connect(on_account_added);
|
|
|
|
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
|
2017-04-04 13:47:00 +00:00
|
|
|
stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_received.connect(on_pre_message_received);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-04-17 20:46:12 +00:00
|
|
|
public void join(Account account, Jid jid, string? nick, string? password) {
|
2017-06-13 14:04:26 +00:00
|
|
|
if (enter_errors.has_key(jid)) return;
|
|
|
|
|
2017-06-11 11:59:24 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
2017-04-11 16:06:01 +00:00
|
|
|
if (stream == null) return;
|
2017-04-17 20:46:12 +00:00
|
|
|
string nick_ = nick ?? account.bare_jid.localpart ?? account.bare_jid.domainpart;
|
2017-04-23 11:50:32 +00:00
|
|
|
set_autojoin(stream, jid, nick_, password);
|
2017-04-17 20:46:12 +00:00
|
|
|
stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid.to_string(), nick_, password);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void part(Account account, Jid jid) {
|
2017-06-11 11:59:24 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
2017-04-23 11:50:32 +00:00
|
|
|
if (stream == null) return;
|
|
|
|
unset_autojoin(stream, jid);
|
|
|
|
stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, jid.bare_jid.to_string());
|
|
|
|
|
|
|
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account);
|
|
|
|
if (conversation != null) stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-05-30 20:47:16 +00:00
|
|
|
[CCode (has_target = false)] public delegate void OnResult(Jid jid, Xep.DataForms.DataForm data_form, Object? store);
|
|
|
|
public void get_config_form(Account account, Jid jid, OnResult on_result, Object? store) {
|
2017-06-11 11:59:24 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
2017-05-30 20:47:16 +00:00
|
|
|
if (stream == null) return;
|
|
|
|
stream.get_module(Xep.Muc.Module.IDENTITY).get_config_form(stream, jid.to_string(), (stream, jid, data_form, store) => {
|
|
|
|
Tuple<OnResult, Object?> tuple = store as Tuple<OnResult, Object?>;
|
|
|
|
tuple.a(new Jid(jid), data_form, tuple.b);
|
|
|
|
}, Tuple.create(on_result, store));
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
public void change_subject(Account account, Jid jid, string subject) {
|
2017-06-11 11:59:24 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
2017-03-11 00:40:42 +00:00
|
|
|
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_subject(stream, jid.bare_jid.to_string(), subject);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void change_nick(Account account, Jid jid, string new_nick) {
|
2017-06-11 11:59:24 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
2017-03-11 00:40:42 +00:00
|
|
|
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_nick(stream, jid.bare_jid.to_string(), new_nick);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-06-11 11:59:24 +00:00
|
|
|
public void invite(Account account, Jid muc, Jid invitee) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).invite(stream, muc.bare_jid.to_string(), invitee.bare_jid.to_string());
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
public void kick(Account account, Jid jid, string nick) {
|
2017-06-11 11:59:24 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
2017-03-11 00:40:42 +00:00
|
|
|
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).kick(stream, jid.bare_jid.to_string(), nick);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-06-11 11:59:24 +00:00
|
|
|
public bool kick_possible(Account account, Jid occupant) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) return stream.get_module(Xep.Muc.Module.IDENTITY).kick_possible(stream, occupant.to_string());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
public ArrayList<Jid>? get_occupants(Jid jid, Account account) {
|
2017-03-23 23:15:00 +00:00
|
|
|
if (is_groupchat(jid, account)) {
|
|
|
|
return stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account);
|
|
|
|
}
|
|
|
|
return null;
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public ArrayList<Jid>? get_other_occupants(Jid jid, Account account) {
|
|
|
|
ArrayList<Jid>? occupants = get_occupants(jid, account);
|
2017-06-11 11:59:24 +00:00
|
|
|
Jid? own_jid = get_own_jid(jid, account);
|
|
|
|
if (occupants != null && own_jid != null) {
|
|
|
|
occupants.remove(own_jid);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
return occupants;
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool is_groupchat(Jid jid, Account account) {
|
2017-03-19 11:55:36 +00:00
|
|
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account);
|
2017-03-09 14:34:32 +00:00
|
|
|
return !jid.is_full() && conversation != null && conversation.type_ == Conversation.Type.GROUPCHAT;
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public bool is_groupchat_occupant(Jid jid, Account account) {
|
|
|
|
return is_groupchat(jid.bare_jid, account) && jid.is_full();
|
|
|
|
}
|
|
|
|
|
2017-03-11 22:04:58 +00:00
|
|
|
public void get_bookmarks(Account account, Xep.Bookmarks.Module.OnResult listener, Object? store) {
|
2017-03-02 14:37:32 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
2017-03-11 22:04:58 +00:00
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, listener, store);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void add_bookmark(Account account, Xep.Bookmarks.Conference conference) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
2017-03-11 00:40:42 +00:00
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).add_conference(stream, conference);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void replace_bookmark(Account account, Xep.Bookmarks.Conference was, Xep.Bookmarks.Conference replace) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
2017-03-11 00:40:42 +00:00
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference(stream, was, replace);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void remove_bookmark(Account account, Xep.Bookmarks.Conference conference) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
2017-03-11 00:40:42 +00:00
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).remove_conference(stream, conference);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public string? get_groupchat_subject(Jid jid, Account account) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
2017-03-19 11:55:36 +00:00
|
|
|
return stream.get_flag(Xep.Muc.Flag.IDENTITY).get_muc_subject(jid.bare_jid.to_string());
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Jid? get_real_jid(Jid jid, Account account) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
2017-03-19 11:55:36 +00:00
|
|
|
string? real_jid = stream.get_flag(Xep.Muc.Flag.IDENTITY).get_real_jid(jid.to_string());
|
2017-03-02 14:37:32 +00:00
|
|
|
if (real_jid != null) {
|
|
|
|
return new Jid(real_jid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-06-11 11:59:24 +00:00
|
|
|
public Xep.Muc.Role? get_role(Jid jid, Account account) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) return stream.get_flag(Xep.Muc.Flag.IDENTITY).get_occupant_role(jid.to_string());
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-05-18 21:14:44 +00:00
|
|
|
public Xep.Muc.Affiliation? get_affiliation(Jid muc_jid, Jid jid, Account account) {
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
2017-06-11 11:59:24 +00:00
|
|
|
if (stream != null) return stream.get_flag(Xep.Muc.Flag.IDENTITY).get_affiliation(muc_jid.to_string(), jid.to_string());
|
2017-05-18 21:14:44 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Gee.List<Jid>? get_offline_members(Jid jid, Account account) {
|
|
|
|
Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func);
|
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
|
|
|
Gee.List<string>? members = stream.get_flag(Xep.Muc.Flag.IDENTITY).get_offline_members(jid.to_string());
|
|
|
|
if (members == null) return null;
|
|
|
|
foreach (string member in members) {
|
|
|
|
ret.add(new Jid(member));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
public Jid? get_message_real_jid(Entities.Message message) {
|
|
|
|
if (message.real_jid != null) {
|
|
|
|
return new Jid(message.real_jid);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-06-11 11:59:24 +00:00
|
|
|
public Jid? get_own_jid(Jid muc_jid, Account account) {
|
2017-03-02 14:37:32 +00:00
|
|
|
Core.XmppStream? stream = stream_interactor.get_stream(account);
|
|
|
|
if (stream != null) {
|
2017-03-19 11:55:36 +00:00
|
|
|
Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY);
|
2017-06-11 11:59:24 +00:00
|
|
|
if (flag == null) return null;
|
|
|
|
string? nick = flag.get_muc_nick(muc_jid.bare_jid.to_string());
|
|
|
|
if (nick != null) return new Jid.with_resource(muc_jid.bare_jid.to_string(), nick);
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-04-11 16:06:01 +00:00
|
|
|
public bool is_joined(Jid jid, Account account) {
|
2017-06-11 11:59:24 +00:00
|
|
|
return get_own_jid(jid, account) != null;
|
2017-04-11 16:06:01 +00:00
|
|
|
}
|
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
private void on_account_added(Account account) {
|
2017-04-11 16:06:01 +00:00
|
|
|
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_entered.connect( (stream, jid, nick) => {
|
|
|
|
joined(account, new Jid(jid), nick);
|
|
|
|
});
|
2017-06-13 14:04:26 +00:00
|
|
|
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_enter_error.connect( (stream, jid, error) => {
|
|
|
|
enter_errors[new Jid(jid)] = error;
|
|
|
|
});
|
2017-04-11 16:06:01 +00:00
|
|
|
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).self_removed_from_room.connect( (stream, jid, code) => {
|
|
|
|
left(account, new Jid(jid));
|
|
|
|
});
|
2017-03-10 20:45:56 +00:00
|
|
|
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).subject_set.connect( (stream, subject, jid) => {
|
2017-04-11 16:06:01 +00:00
|
|
|
subject_set(account, new Jid(jid), subject);
|
2017-03-02 14:37:32 +00:00
|
|
|
});
|
2017-04-23 11:50:32 +00:00
|
|
|
stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY).received_conferences.connect( (stream, conferences) => {
|
|
|
|
sync_autojoin_active(account, conferences);
|
2017-03-02 14:37:32 +00:00
|
|
|
bookmarks_updated(account, conferences);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-04-18 15:53:25 +00:00
|
|
|
private void on_stream_negotiated(Account account, Core.XmppStream stream) {
|
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences, o) => {
|
2017-04-11 16:06:01 +00:00
|
|
|
Tuple<MucManager, Account> tuple = o as Tuple<MucManager, Account>;
|
|
|
|
MucManager outer_ = tuple.a;
|
|
|
|
Account account_ = tuple.b;
|
|
|
|
foreach (Xep.Bookmarks.Conference bookmark in conferences) {
|
|
|
|
Jid jid = new Jid(bookmark.jid);
|
|
|
|
if (bookmark.autojoin) {
|
2017-04-17 20:46:12 +00:00
|
|
|
outer_.join(account_, jid, bookmark.nick, bookmark.password);
|
2017-04-11 16:06:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}, Tuple.create(this, account));
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
|
2017-03-11 00:25:45 +00:00
|
|
|
private void on_pre_message_received(Entities.Message message, Xmpp.Message.Stanza message_stanza, Conversation conversation) {
|
2017-03-09 14:34:32 +00:00
|
|
|
if (conversation.type_ != Conversation.Type.GROUPCHAT) return;
|
2017-03-02 14:37:32 +00:00
|
|
|
Core.XmppStream stream = stream_interactor.get_stream(conversation.account);
|
|
|
|
if (stream == null) return;
|
|
|
|
if (Xep.DelayedDelivery.MessageFlag.get_flag(message.stanza) == null) {
|
2017-03-19 11:55:36 +00:00
|
|
|
string? real_jid = stream.get_flag(Xep.Muc.Flag.IDENTITY).get_real_jid(message.counterpart.to_string());
|
2017-03-02 14:37:32 +00:00
|
|
|
if (real_jid != null && real_jid != message.counterpart.to_string()) {
|
|
|
|
message.real_jid = real_jid;
|
|
|
|
}
|
|
|
|
}
|
2017-03-24 21:57:05 +00:00
|
|
|
string? muc_nick = stream.get_flag(Xep.Muc.Flag.IDENTITY).get_muc_nick(conversation.counterpart.bare_jid.to_string());
|
|
|
|
if (muc_nick != null && message.from.equals(new Jid(@"$(message.from.bare_jid)/$muc_nick"))) { // TODO better from own
|
2017-04-04 13:47:00 +00:00
|
|
|
Gee.List<Entities.Message> messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation);
|
|
|
|
foreach (Entities.Message m in messages) { // TODO not here
|
|
|
|
if (m.equals(message)) {
|
|
|
|
m.marked = Entities.Message.Marked.RECEIVED;
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-23 11:50:32 +00:00
|
|
|
|
|
|
|
private void sync_autojoin_active(Account account, Gee.List<Xep.Bookmarks.Conference> conferences) {
|
|
|
|
Gee.List<Conversation> conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations();
|
|
|
|
leave_non_autojoin(account, conferences, conversations);
|
|
|
|
join_autojoin(account, conferences, conversations);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void leave_non_autojoin(Account account, Gee.List<Xep.Bookmarks.Conference> conferences, Gee.List<Conversation> conversations) {
|
|
|
|
foreach (Conversation conversation in conversations) {
|
|
|
|
if (conversation.type_ != Conversation.Type.GROUPCHAT || !conversation.account.equals(account)) continue;
|
|
|
|
bool is_autojoin = false;
|
|
|
|
foreach (Xep.Bookmarks.Conference conference in conferences) {
|
|
|
|
if (conference.jid == conversation.counterpart.to_string()) {
|
|
|
|
if (conference.autojoin) is_autojoin = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!is_autojoin) {
|
|
|
|
part(account, conversation.counterpart);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void join_autojoin(Account account, Gee.List<Xep.Bookmarks.Conference> conferences, Gee.List<Conversation> conversations) {
|
|
|
|
foreach (Xep.Bookmarks.Conference conference in conferences) {
|
|
|
|
if (!conference.autojoin) continue;
|
|
|
|
bool is_active = false;
|
|
|
|
foreach (Conversation conversation in conversations) {
|
|
|
|
if (conference.jid == conversation.counterpart.to_string()) is_active = true;
|
|
|
|
}
|
|
|
|
if (!is_active) {
|
|
|
|
join(account, new Jid(conference.jid), conference.nick, conference.password);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void set_autojoin(Core.XmppStream stream, Jid jid, string? nick, string? password) {
|
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences, storage) => {
|
|
|
|
Triple<Jid, string?, string?> triple = storage as Triple<Jid, string?, string?>;
|
|
|
|
Xep.Bookmarks.Conference changed = new Xep.Bookmarks.Conference(triple.a.to_string()) { nick=triple.b, password=triple.c, autojoin=true };
|
|
|
|
foreach (Xep.Bookmarks.Conference conference in conferences) {
|
|
|
|
if (conference.jid == triple.a.bare_jid.to_string() && conference.nick == triple.b && conference.password == triple.c) {
|
|
|
|
if (!conference.autojoin) {
|
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference(stream, conference, changed);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).add_conference(stream, changed);
|
|
|
|
}, Triple.create(jid, nick, password));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void unset_autojoin(Core.XmppStream stream, Jid jid) {
|
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).get_conferences(stream, (stream, conferences, storage) => {
|
|
|
|
Jid jid_ = storage as Jid;
|
|
|
|
foreach (Xep.Bookmarks.Conference conference in conferences) {
|
|
|
|
if (conference.jid == jid_.bare_jid.to_string()) {
|
|
|
|
if (conference.autojoin) {
|
|
|
|
Xep.Bookmarks.Conference change = new Xep.Bookmarks.Conference(conference.jid) { nick=conference.nick, password=conference.password, autojoin=false };
|
|
|
|
stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference(stream, conference, change);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, jid);
|
|
|
|
}
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|
2017-03-19 11:55:36 +00:00
|
|
|
|
2017-03-02 14:37:32 +00:00
|
|
|
}
|