voice handling in moderated groups (#788)

This commit is contained in:
Anmol 2020-04-22 23:34:03 +05:30 committed by GitHub
parent 51a2372869
commit 2631a9bdba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 207 additions and 2 deletions

View file

@ -149,11 +149,13 @@ public class InputFieldStatus : Object {
public string? message;
public MessageType message_type;
public InputState input_state;
public bool contains_markup;
public InputFieldStatus(string? message, MessageType message_type, InputState input_state) {
public InputFieldStatus(string? message, MessageType message_type, InputState input_state, bool contains_markup = false) {
this.message = message;
this.message_type = message_type;
this.input_state = input_state;
this.contains_markup = contains_markup;
}
}

View file

@ -14,6 +14,8 @@ public class MucManager : StreamInteractionModule, Object {
public signal void room_info_updated(Account account, Jid muc_jid);
public signal void private_room_occupant_updated(Account account, Jid room, Jid occupant);
public signal void invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason);
public signal void voice_request_received(Account account, Jid room_jid, Jid from_jid, string? nick, string? role, string? label);
public signal void received_occupant_role(Account account, Jid jid, Xep.Muc.Role? role);
public signal void bookmarks_updated(Account account, Set<Conference> conferences);
public signal void conference_added(Account account, Conference conference);
public signal void conference_removed(Account account, Jid jid);
@ -118,6 +120,16 @@ public class MucManager : StreamInteractionModule, Object {
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, jid.bare_jid, nick, role);
}
public void change_role(Account account, Jid jid, string nick, string role) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_role(stream, jid.bare_jid, nick, role);
}
public void request_voice(Account account, Jid jid) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).request_voice(stream, jid.bare_jid);
}
public bool kick_possible(Account account, Jid occupant) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) return stream.get_module(Xep.Muc.Module.IDENTITY).kick_possible(stream, occupant);
@ -137,6 +149,18 @@ public class MucManager : StreamInteractionModule, Object {
return flag.has_room_feature(jid, Xep.Muc.Feature.NON_ANONYMOUS) && flag.has_room_feature(jid, Xep.Muc.Feature.MEMBERS_ONLY);
}
public bool is_moderated_room(Account account, Jid jid) {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream == null) {
return false;
}
Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY);
if (flag == null) {
return false;
}
return flag.has_room_feature(jid, Xep.Muc.Feature.MODERATED);
}
public bool is_public_room(Account account, Jid jid) {
return is_groupchat(jid, account) && !is_private_room(account, jid);
}
@ -285,6 +309,12 @@ public class MucManager : StreamInteractionModule, Object {
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).invite_received.connect( (stream, room_jid, from_jid, password, reason) => {
invite_received(account, room_jid, from_jid, password, reason);
});
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).voice_request_received.connect( (stream, room_jid, from_jid, nick, role, label) => {
voice_request_received(account, room_jid, from_jid, nick, role, label);
});
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_role.connect( (stream, from_jid, role) => {
received_occupant_role(account, from_jid, role);
});
stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_info_updated.connect( (stream, muc_jid) => {
room_info_updated(account, muc_jid);
});

View file

@ -13,6 +13,7 @@ public class NotificationEvents : StreamInteractionModule, Object {
public signal void notify_subscription_request(Conversation conversation);
public signal void notify_connection_error(Account account, ConnectionManager.ConnectionError error);
public signal void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string? password, string? reason);
public signal void notify_voice_request(Account account, Jid room_jid, Jid from_jid, string? nick, string? role, string? label);
private StreamInteractor stream_interactor;
@ -27,6 +28,7 @@ public class NotificationEvents : StreamInteractionModule, Object {
stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received);
stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect(on_received_subscription_request);
stream_interactor.get_module(MucManager.IDENTITY).invite_received.connect((account, room_jid, from_jid, password, reason) => notify_muc_invite(account, room_jid, from_jid, password, reason));
stream_interactor.get_module(MucManager.IDENTITY).voice_request_received.connect((account, room_jid, from_jid, nick, role, label) => notify_voice_request(account, room_jid, from_jid, nick, role, label));
stream_interactor.connection_manager.connection_error.connect((account, error) => notify_connection_error(account, error));
}

View file

@ -132,6 +132,7 @@ SOURCES
src/ui/contact_details/blocking_provider.vala
src/ui/contact_details/settings_provider.vala
src/ui/contact_details/permissions_provider.vala
src/ui/contact_details/dialog.vala
src/ui/contact_details/muc_config_form_provider.vala

View file

@ -138,6 +138,14 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
});
add_action(accept_muc_invite_action);
SimpleAction accept_voice_request_action = new SimpleAction("accept-voice-request", VariantType.INT32);
accept_voice_request_action.activate.connect((variant) => {
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32());
if (conversation == null) return;
stream_interactor.get_module(MucManager.IDENTITY).change_role(conversation.account, conversation.counterpart, conversation.nickname, "participant");
});
add_action(accept_voice_request_action);
SimpleAction loop_conversations_action = new SimpleAction("loop_conversations", null);
loop_conversations_action.activate.connect(() => { window.loop_conversations(false); });
add_action(loop_conversations_action);

View file

@ -5,6 +5,7 @@ using Gtk;
using Dino.Entities;
namespace Dino.Ui {
private const string OPEN_CONVERSATION_DETAILS_URI = "x-dino:open-conversation-details";
public class ChatInputController : Object {
@ -38,8 +39,19 @@ public class ChatInputController : Object {
chat_text_view_controller.send_text.connect(send_text);
chat_input.encryption_widget.encryption_changed.connect(on_encryption_changed);
chat_input.file_button.clicked.connect(() => file_picker_selected());
stream_interactor.get_module(MucManager.IDENTITY).received_occupant_role.connect(update_moderated_input_status);
stream_interactor.get_module(MucManager.IDENTITY).room_info_updated.connect(update_moderated_input_status);
status_description_label.activate_link.connect((uri) => {
if (uri == OPEN_CONVERSATION_DETAILS_URI){
ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation);
contact_details_dialog.present();
}
return true;
});
}
public void set_conversation(Conversation conversation) {
@ -51,6 +63,10 @@ public class ChatInputController : Object {
chat_input.initialize_for_conversation(conversation);
chat_text_view_controller.initialize_for_conversation(conversation);
Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
update_moderated_input_status(conversation.account, own_jid);
}
public void set_file_upload_active(bool active) {
@ -69,6 +85,10 @@ public class ChatInputController : Object {
input_field_status = status;
chat_input.set_input_state(status.message_type);
if (status.contains_markup) status_description_label.use_markup = true;
else status_description_label.use_markup = false;
status_description_label.label = status.message;
chat_input.file_button.sensitive = status.input_state == Plugins.InputFieldStatus.InputState.NORMAL;
@ -147,6 +167,19 @@ public class ChatInputController : Object {
stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_cleared(conversation);
}
}
private void update_moderated_input_status(Account account, Xmpp.Jid jid) {
Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
if (conversation.type_ == conversation.Type.GROUPCHAT){
if (stream_interactor.get_module(MucManager.IDENTITY).is_moderated_room(conversation.account, conversation.counterpart) &&
stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account)==Xmpp.Xep.Muc.Role.VISITOR) {
set_input_field_status(new Plugins.InputFieldStatus(_("This conference does not allow you to send messages. %s").printf("<a href=\"" + OPEN_CONVERSATION_DETAILS_URI + "\">" + _("Request permission") + "</a>"),
Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND, true));
} else {
reset_input_field_status();
}
}
}
private bool on_text_input_key_press(EventKey event) {
if (event.keyval == Gdk.Key.Up && chat_input.chat_text_view.text_view.buffer.text == "") {

View file

@ -46,6 +46,7 @@ public class Dialog : Gtk.Dialog {
app.plugin_registry.register_contact_details_entry(new SettingsProvider(stream_interactor));
app.plugin_registry.register_contact_details_entry(new BlockingProvider(stream_interactor));
app.plugin_registry.register_contact_details_entry(new MucConfigFormProvider(stream_interactor));
app.plugin_registry.register_contact_details_entry(new PermissionsProvider(stream_interactor));
foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) {
provider.populate(conversation, contact_details, Plugins.WidgetType.GTK);

View file

@ -0,0 +1,29 @@
using Gtk;
using Dino.Entities;
namespace Dino.Ui.ContactDetails {
public class PermissionsProvider : Plugins.ContactDetailsProvider, Object {
public string id { get { return "permissions"; } }
private StreamInteractor stream_interactor;
public PermissionsProvider(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor;
}
public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) {
if (type != Plugins.WidgetType.GTK) return;
Xmpp.Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
if (stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account)==Xmpp.Xep.Muc.Role.VISITOR){
Button voice_request = new Button() {visible=true, label=_("Request")};
voice_request.clicked.connect(()=>stream_interactor.get_module(MucManager.IDENTITY).request_voice(conversation.account, conversation.counterpart));
contact_details.add(_("Permissions"), _("Request permission to send messages"), "", voice_request);
}
}
}
}

View file

@ -45,6 +45,7 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object {
combobox.append("on", get_notify_setting_string(Conversation.NotifySetting.ON));
combobox.append("off", get_notify_setting_string(Conversation.NotifySetting.OFF));
contact_details.add(DETAILS_HEADLINE_ROOM, _("Notifications"), "", combobox);
combobox.active_id = get_notify_setting_id(conversation.notify_setting);
combobox.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox.active_id); } );
}

View file

@ -43,6 +43,7 @@ public class Notifications : Object {
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_subscription_request.connect(notify_subscription_request);
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_connection_error.connect(notify_connection_error);
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_muc_invite.connect(on_invite_received);
stream_interactor.get_module(NotificationEvents.IDENTITY).notify_voice_request.connect(on_voice_request_received);
}
private async void notify_content_item(ContentItem content_item, Conversation conversation) {
@ -140,6 +141,28 @@ public class Notifications : Object {
notification.add_button_with_target_value(_("Accept"), "app.open-muc-join", group_conversation.id);
GLib.Application.get_default().send_notification(null, notification);
}
private async void on_voice_request_received(Account account, Jid room_jid, Jid from_jid, string? nick, string? role, string? label) {
Conversation? direct_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(room_jid, account, Conversation.Type.GROUPCHAT);
if (direct_conversation == null) return;
string display_name = Util.get_participant_display_name(stream_interactor, direct_conversation, from_jid);
string display_room = room_jid.bare_jid.to_string();
Notification notification = new Notification(_("Permission request"));
string body = _("%s requests the permission to write in %s").printf(display_name, display_room);
notification.set_body(body);
try {
Cairo.ImageSurface jid_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, direct_conversation)).size(40, 40).draw_image_surface();
notification.set_icon(get_pixbuf_icon(jid_avatar));
} catch (Error e) { }
Conversation group_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT);
group_conversation.nickname = nick;
notification.set_default_action_and_target_value("app.accept-voice-request", new Variant.int32(group_conversation.id));
notification.add_button_with_target_value(_("Deny"), "app.deny-voice-request", group_conversation.id);
notification.add_button_with_target_value(_("Accept"), "app.accept-voice-request", group_conversation.id);
GLib.Application.get_default().send_notification(null, notification);
}
private Icon get_pixbuf_icon(Cairo.ImageSurface surface) throws Error {
Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height());

View file

@ -97,6 +97,21 @@ public class View : Popover {
outer_box.add(kick_button);
kick_button.clicked.connect(kick_button_clicked);
}
if (stream_interactor.get_module(MucManager.IDENTITY).is_moderated_room(conversation.account, conversation.counterpart) && role == Xmpp.Xep.Muc.Role.MODERATOR){
if (stream_interactor.get_module(MucManager.IDENTITY).get_role(selected_jid, conversation.account) == Xmpp.Xep.Muc.Role.VISITOR) {
ModelButton voice_button = new ModelButton() { active=true, text=_("Grant write permission"), visible=true };
outer_box.add(voice_button);
voice_button.clicked.connect(() =>
voice_button_clicked("participant"));
}
else if (stream_interactor.get_module(MucManager.IDENTITY).get_role(selected_jid, conversation.account) == Xmpp.Xep.Muc.Role.PARTICIPANT){
ModelButton voice_button = new ModelButton() { active=true, text=_("Revoke write permission"), visible=true };
outer_box.add(voice_button);
voice_button.clicked.connect(() =>
voice_button_clicked("visitor"));
}
}
if (jid_menu != null) jid_menu.destroy();
stack.add_named(outer_box, "menu");
@ -119,6 +134,12 @@ public class View : Popover {
stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, selected_jid.resourcepart);
}
private void voice_button_clicked(string role) {
if (selected_jid == null) return;
stream_interactor.get_module(MucManager.IDENTITY).change_role(conversation.account, conversation.counterpart, selected_jid.resourcepart, role);
}
}
}

View file

@ -148,6 +148,7 @@ public class DataForm {
public ListSingleField(StanzaNode node) {
base.from_node(node);
type_ = Type.LIST_SINGLE;
node.set_attribute("type", "list-single");
}
}

View file

@ -6,6 +6,7 @@ private const string NS_URI = "http://jabber.org/protocol/muc";
private const string NS_URI_ADMIN = NS_URI + "#admin";
private const string NS_URI_OWNER = NS_URI + "#owner";
private const string NS_URI_USER = NS_URI + "#user";
private const string NS_URI_REQUEST = NS_URI + "#request";
public enum MucEnterError {
NONE,
@ -68,6 +69,7 @@ public class Module : XmppStreamModule {
public signal void received_occupant_role(XmppStream stream, Jid jid, Role? role);
public signal void subject_set(XmppStream stream, string? subject, Jid jid);
public signal void invite_received(XmppStream stream, Jid room_jid, Jid from_jid, string? password, string? reason);
public signal void voice_request_received(XmppStream stream, Jid room_jid, Jid from_jid, string? nick, string? role, string? label);
public signal void room_info_updated(XmppStream stream, Jid muc_jid);
public signal void self_removed_from_room(XmppStream stream, Jid jid, StatusCode code);
@ -154,6 +156,25 @@ public class Module : XmppStreamModule {
stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, message);
}
public void request_voice(XmppStream stream, Jid to_muc) {
MessageStanza message = new MessageStanza() { to=to_muc };
DataForms.DataForm submit_node = new DataForms.DataForm();
submit_node.get_submit_node();
DataForms.DataForm.Field field_node = new DataForms.DataForm.Field() { var="FORM_TYPE" };
field_node.set_value_string(NS_URI_REQUEST);
DataForms.DataForm.ListSingleField single_field = new DataForms.DataForm.ListSingleField(new StanzaNode.build("field", DataForms.NS_URI)) { var="muc#role", label="Requested role", value="participant" };
submit_node.add_field(field_node);
submit_node.add_field(single_field);
message.stanza.put_node(submit_node.stanza_node);
stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, message);
}
public void kick(XmppStream stream, Jid jid, string nick) {
change_role(stream, jid, nick, "none");
}
@ -530,6 +551,38 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
}
}
}
StanzaNode? x_field_node = message.stanza.get_subnode("x", DataForms.NS_URI);
if (x_field_node != null){
Gee.List<StanzaNode>? fields = x_field_node.get_subnodes("field", DataForms.NS_URI);
Jid? from_jid = null;
string? nick = null;
string? role = null;
string? label = null;
if (fields.size!=0){
foreach (var field_node in fields){
string? var_ = field_node.get_attribute("var");
if (var_ == "muc#jid"){
StanzaNode? value_node = field_node.get_subnode("value", DataForms.NS_URI);
if (value_node != null) from_jid = new Jid(value_node.get_string_content());
}
else if (var_ == "muc#roomnick"){
StanzaNode? value_node = field_node.get_subnode("value", DataForms.NS_URI);
if (value_node != null) nick = value_node.get_string_content();
}
else if (var_ == "muc#role"){
StanzaNode? value_node = field_node.get_subnode("value", DataForms.NS_URI);
if (value_node != null) role = value_node.get_string_content();
}
else if (var_ == "muc#request_allow"){
label = field_node.get_attribute("label");
}
}
outer.voice_request_received(stream, message.from, from_jid, nick, role, label);
return true;
}
}
}
return false;
}