aboutsummaryrefslogtreecommitdiff
path: root/libdino/src/service/notification_events.vala
blob: 2408aadcd41aa8468cbff8fb85c5861b8927da7b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using Gee;

using Dino.Entities;
using Xmpp;

namespace Dino {

public class NotificationEvents : StreamInteractionModule, Object {
    public static ModuleIdentity<NotificationEvents> IDENTITY = new ModuleIdentity<NotificationEvents>("notification_events");
    public string id { get { return IDENTITY.id; } }

    public signal void notify_content_item(ContentItem content_item, Conversation conversation);

    private StreamInteractor stream_interactor;
    private Future<NotificationProvider> notifier;
    private Promise<NotificationProvider> notifier_promise;
    private bool notifier_outstanding = true;

    public static void start(StreamInteractor stream_interactor) {
        NotificationEvents m = new NotificationEvents(stream_interactor);
        stream_interactor.add_module(m);
    }

    public NotificationEvents(StreamInteractor stream_interactor) {
        this.stream_interactor = stream_interactor;

        stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect((item, conversation) => on_content_item_received.begin(item, conversation));
        stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect((jid, account) => on_received_subscription_request.begin(jid, account));

        stream_interactor.get_module(MucManager.IDENTITY).invite_received.connect((account, room_jid, from_jid, password, reason) => on_invite_received.begin(account, room_jid, from_jid, password, reason));
        stream_interactor.get_module(MucManager.IDENTITY).voice_request_received.connect((account, room_jid, from_jid, nick) => on_voice_request_received.begin(account, room_jid, from_jid, nick));

        stream_interactor.get_module(Calls.IDENTITY).call_incoming.connect((call, state, conversation, video) => on_call_incoming.begin(call, state, conversation, video));
        stream_interactor.connection_manager.connection_error.connect((account, error) => on_connection_error(account, error));
        stream_interactor.get_module(ChatInteraction.IDENTITY).focused_in.connect((conversation) => on_focused_in.begin(conversation));

        notifier_promise = new Promise<NotificationProvider>();
        notifier = notifier_promise.future;
    }

    public async void register_notification_provider(NotificationProvider notification_provider) {
        if (notifier_outstanding || (yield notifier.wait_async()).get_priority() < notification_provider.get_priority()) {
            notifier_outstanding = false;
            notifier_promise.set_value(notification_provider);
        }
    }

    private async void on_content_item_received(ContentItem item, Conversation conversation) {
        ContentItem last_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation);

        if (item.id != last_item.id) return;
        if (item.id == conversation.read_up_to_item) return;
        if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus()) return;

        Conversation.NotifySetting notify = conversation.get_notification_setting(stream_interactor);
        if (notify == Conversation.NotifySetting.OFF) return;

        string conversation_display_name = get_conversation_display_name(stream_interactor, conversation, null);
        string? participant_display_name = null;
        if (conversation.type_ == Conversation.Type.GROUPCHAT) {
            participant_display_name = get_participant_display_name(stream_interactor, conversation, item.jid);
        }

        switch (item.type_) {
            case MessageItem.TYPE:
                Message message = ((MessageItem) item).message;

                if (message.direction == Message.DIRECTION_SENT) return;

                if (notify == Conversation.NotifySetting.HIGHLIGHT) {
                    Jid? nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
                    if (nick == null) return;

                    bool highlight = Regex.match_simple("\\b" + Regex.escape_string(nick.resourcepart) + "\\b", message.body, RegexCompileFlags.CASELESS);
                    if (!highlight) return;
                }

                notify_content_item(item, conversation);
                if (notify != Conversation.NotifySetting.OFF) {
                    NotificationProvider notifier = yield notifier.wait_async();
                    yield notifier.notify_message(message, conversation, conversation_display_name, participant_display_name);
                }
                break;
            case FileItem.TYPE:
                FileTransfer file_transfer = ((FileItem) item).file_transfer;
                bool is_image = file_transfer.mime_type != null && file_transfer.mime_type.has_prefix("image");

                // Don't notify on file transfers in a groupchat set to "mention only"
                if (notify == Conversation.NotifySetting.HIGHLIGHT) return;
                if (file_transfer.direction == FileTransfer.DIRECTION_SENT) return;

                notify_content_item(item, conversation);
                if (notify != Conversation.NotifySetting.OFF) {
                    NotificationProvider notifier = yield notifier.wait_async();
                    yield notifier.notify_file(file_transfer, conversation, is_image, conversation_display_name, participant_display_name);
                }
                break;
            case CallItem.TYPE:
                // handled in `on_call_incoming`
                break;
        }
    }

    private async void on_voice_request_received(Account account, Jid room_jid, Jid from_jid, string nick) {
        Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(room_jid, account, Conversation.Type.GROUPCHAT);
        if (conversation == null) return;

        NotificationProvider notifier = yield notifier.wait_async();
        yield notifier.notify_voice_request(conversation, from_jid);
    }

    private async void on_received_subscription_request(Jid jid, Account account) {
        Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT);
        if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus(conversation)) return;

        NotificationProvider notifier = yield notifier.wait_async();
        yield notifier.notify_subscription_request(conversation);
    }

    private async void on_call_incoming(Call call, CallState call_state, Conversation conversation, bool video) {
        string conversation_display_name = get_conversation_display_name(stream_interactor, conversation, null);

        NotificationProvider notifier = yield notifier.wait_async();
        yield notifier.notify_call(call, conversation, video, conversation_display_name);
        call.notify["state"].connect(() => {
            if (call.state != Call.State.RINGING) {
                notifier.retract_call_notification.begin(call, conversation);
            }
        });
    }

    private async void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) {
        string inviter_display_name;
        if (room_jid.equals_bare(from_jid)) {
            Conversation conversation = new Conversation(room_jid, account, Conversation.Type.GROUPCHAT);
            inviter_display_name = get_participant_display_name(stream_interactor, conversation, from_jid);
        } else {
            Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT);
            inviter_display_name = get_participant_display_name(stream_interactor, direct_conversation, from_jid);
        }
        NotificationProvider notifier = yield notifier.wait_async();
        yield notifier.notify_muc_invite(account, room_jid, from_jid, inviter_display_name);
    }

    private async void on_connection_error(Account account, ConnectionManager.ConnectionError error) {
        NotificationProvider notifier = yield notifier.wait_async();
        yield notifier.notify_connection_error(account, error);
    }

    private async void on_focused_in(Conversation conversation) {
        NotificationProvider notifier = yield notifier.wait_async();
        yield notifier.retract_content_item_notifications();
        yield notifier.retract_conversation_notifications(conversation);
    }
}

public interface NotificationProvider : Object {
    public abstract double get_priority();

    public abstract async void notify_message(Message message, Conversation conversation, string conversation_display_name, string? participant_display_name);
    public abstract async void notify_file(FileTransfer file_transfer, Conversation conversation, bool is_image, string conversation_display_name, string? participant_display_name);
    public abstract async void notify_call(Call call, Conversation conversation, bool video, string conversation_display_name);
    public abstract async void retract_call_notification(Call call, Conversation conversation);
    public abstract async void notify_subscription_request(Conversation conversation);
    public abstract async void notify_connection_error(Account account, ConnectionManager.ConnectionError error);
    public abstract async void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string inviter_display_name);
    public abstract async void notify_voice_request(Conversation conversation, Jid from_jid);

    public abstract async void retract_content_item_notifications();
    public abstract async void retract_conversation_notifications(Conversation conversation);
}

}