diff options
author | fiaxh <git@mx.ax.lt> | 2017-08-16 11:44:42 +0200 |
---|---|---|
committer | fiaxh <git@mx.ax.lt> | 2017-08-17 01:26:03 +0200 |
commit | fb36ea055301b6db513a31acde30f315e2c0fd68 (patch) | |
tree | e9bfeecc12a3824e3b9a86abbed44856ef6dd1e0 | |
parent | 30818b3965aef36f161147881967006d6e9376a6 (diff) | |
download | dino-fb36ea055301b6db513a31acde30f315e2c0fd68.tar.gz dino-fb36ea055301b6db513a31acde30f315e2c0fd68.zip |
Message Archive Management
-rw-r--r-- | libdino/src/entity/account.vala | 7 | ||||
-rw-r--r-- | libdino/src/plugin/interfaces.vala | 2 | ||||
-rw-r--r-- | libdino/src/service/chat_interaction.vala | 4 | ||||
-rw-r--r-- | libdino/src/service/database.vala | 5 | ||||
-rw-r--r-- | libdino/src/service/message_processor.vala | 21 | ||||
-rw-r--r-- | libdino/src/service/module_manager.vala | 1 | ||||
-rw-r--r-- | main/src/ui/contact_details/muc_config_form_provider.vala | 78 | ||||
-rw-r--r-- | main/src/ui/notifications.vala | 5 | ||||
-rw-r--r-- | main/src/ui/occupant_menu/list.vala | 8 | ||||
-rw-r--r-- | xmpp-vala/CMakeLists.txt | 1 | ||||
-rw-r--r-- | xmpp-vala/src/module/xep/0004_data_forms.vala | 93 | ||||
-rw-r--r-- | xmpp-vala/src/module/xep/0045_muc/module.vala | 2 | ||||
-rw-r--r-- | xmpp-vala/src/module/xep/0082_date_time_profiles.vala | 54 | ||||
-rw-r--r-- | xmpp-vala/src/module/xep/0203_delayed_delivery.vala | 18 | ||||
-rw-r--r-- | xmpp-vala/src/module/xep/0313_message_archive_management.vala | 126 |
15 files changed, 309 insertions, 116 deletions
diff --git a/libdino/src/entity/account.vala b/libdino/src/entity/account.vala index 23544b46..f0628ca4 100644 --- a/libdino/src/entity/account.vala +++ b/libdino/src/entity/account.vala @@ -16,6 +16,7 @@ public class Account : Object { public string? alias { get; set; } public bool enabled { get; set; default = false; } public string? roster_version { get; set; } + public DateTime mam_earliest_synced { get; set; default=new DateTime.from_unix_utc(0); } private Database? db; @@ -36,6 +37,7 @@ public class Account : Object { alias = row[db.account.alias]; enabled = row[db.account.enabled]; roster_version = row[db.account.roster_version]; + mam_earliest_synced = new DateTime.from_unix_utc(row[db.account.mam_earliest_synced]); notify.connect(on_update); } @@ -49,6 +51,7 @@ public class Account : Object { .value(db.account.alias, alias) .value(db.account.enabled, enabled) .value(db.account.roster_version, roster_version) + .value(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()) .perform(); notify.connect(on_update); @@ -88,9 +91,11 @@ public class Account : Object { update.set(db.account.enabled, enabled); break; case "roster-version": update.set(db.account.roster_version, roster_version); break; + case "mam-earliest-synced": + update.set(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()); break; } update.perform(); } } -}
\ No newline at end of file +} diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala index 178ca1ab..dbed6a00 100644 --- a/libdino/src/plugin/interfaces.vala +++ b/libdino/src/plugin/interfaces.vala @@ -50,7 +50,7 @@ public abstract class ContactDetailsProvider : Object { public class ContactDetails : Object { public signal void save(); - public signal void add(string category, string label, string desc, Widget widget); + public signal void add(string category, string label, string? desc, Widget widget); } public abstract class ConversationTitlebarEntry : Object { diff --git a/libdino/src/service/chat_interaction.vala b/libdino/src/service/chat_interaction.vala index d7b8f839..3e8f4b24 100644 --- a/libdino/src/service/chat_interaction.vala +++ b/libdino/src/service/chat_interaction.vala @@ -124,6 +124,8 @@ public class ChatInteraction : StreamInteractionModule, Object { } private void on_message_received(Entities.Message message, Conversation conversation) { + if (Xep.MessageArchiveManagement.MessageFlag.get_flag(message.stanza) != null) return; + send_delivery_receipt(conversation, message); if (is_active_focus(conversation)) { check_send_read(); @@ -160,4 +162,4 @@ public class ChatInteraction : StreamInteractionModule, Object { } } -}
\ No newline at end of file +} diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index b414b943..5a7eb46d 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -6,7 +6,7 @@ using Dino.Entities; namespace Dino { public class Database : Qlite.Database { - private const int VERSION = 3; + private const int VERSION = 4; public class AccountTable : Table { public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -16,10 +16,11 @@ public class Database : Qlite.Database { public Column<string> alias = new Column.Text("alias"); public Column<bool> enabled = new Column.BoolInt("enabled"); public Column<string> roster_version = new Column.Text("roster_version") { min_version=2 }; + public Column<long> mam_earliest_synced = new Column.Long("mam_earliest_synced") { min_version=4 }; internal AccountTable(Database db) { base(db, "account"); - init({id, bare_jid, resourcepart, password, alias, enabled, roster_version}); + init({id, bare_jid, resourcepart, password, alias, enabled, roster_version, mam_earliest_synced}); } } diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 390199d5..85b62a42 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -44,6 +44,9 @@ public class MessageProcessor : StreamInteractionModule, Object { stream_interactor.module_manager.get_module(account, Xmpp.Message.Module.IDENTITY).received_message.connect( (stream, message) => { on_message_received(account, message); }); + stream_interactor.module_manager.get_module(account, Xmpp.Xep.MessageArchiveManagement.Module.IDENTITY).feature_available.connect( (stream) => { + stream.get_module(Xep.MessageArchiveManagement.Module.IDENTITY).query_archive(stream, null, account.mam_earliest_synced.add_minutes(-1), null); + }); } private void send_unsent_messages(Account account) { @@ -80,9 +83,15 @@ public class MessageProcessor : StreamInteractionModule, Object { new_message.counterpart = new_message.direction == Entities.Message.DIRECTION_SENT ? new Jid(message.to) : new Jid(message.from); new_message.ourpart = new_message.direction == Entities.Message.DIRECTION_SENT ? new Jid(message.from) : new Jid(message.to); new_message.stanza = message; - Xep.DelayedDelivery.MessageFlag? deleyed_delivery_flag = Xep.DelayedDelivery.MessageFlag.get_flag(message); - new_message.time = deleyed_delivery_flag != null ? deleyed_delivery_flag.datetime : new DateTime.now_local(); - new_message.local_time = new DateTime.now_local(); + + Xep.MessageArchiveManagement.MessageFlag? mam_message_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(message); + if (mam_message_flag != null) new_message.local_time = mam_message_flag.server_time; + if (new_message.local_time == null || new_message.local_time.compare(new DateTime.now_local()) > 0) new_message.local_time = new DateTime.now_local(); + + Xep.DelayedDelivery.MessageFlag? delayed_message_flag = Xep.DelayedDelivery.MessageFlag.get_flag(message); + if (delayed_message_flag != null) new_message.time = delayed_message_flag.datetime; + if (new_message.time == null || new_message.time.compare(new_message.local_time) > 0) new_message.time = new_message.local_time; + return new_message; } @@ -100,6 +109,12 @@ public class MessageProcessor : StreamInteractionModule, Object { } else { message_received(new_message, conversation); } + + Core.XmppStream? stream = stream_interactor.get_stream(conversation.account); + Xep.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xep.MessageArchiveManagement.Flag.IDENTITY) : null; + if (Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null || (mam_flag != null && mam_flag.cought_up == true)) { + conversation.account.mam_earliest_synced = new_message.local_time; + } } } } diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala index 437ecaf0..c2acf9ed 100644 --- a/libdino/src/service/module_manager.vala +++ b/libdino/src/service/module_manager.vala @@ -64,6 +64,7 @@ public class ModuleManager { module_map[account].add(new Xep.Bookmarks.Module()); module_map[account].add(new Presence.Module()); module_map[account].add(new Xmpp.Message.Module()); + module_map[account].add(new Xep.MessageArchiveManagement.Module()); module_map[account].add(new Xep.MessageCarbons.Module()); module_map[account].add(new Xep.Muc.Module()); module_map[account].add(new Xep.Pubsub.Module()); diff --git a/main/src/ui/contact_details/muc_config_form_provider.vala b/main/src/ui/contact_details/muc_config_form_provider.vala index 41df4465..438f41c4 100644 --- a/main/src/ui/contact_details/muc_config_form_provider.vala +++ b/main/src/ui/contact_details/muc_config_form_provider.vala @@ -34,43 +34,46 @@ public class MucConfigFormProvider : Plugins.ContactDetailsProvider { public static void add_field(DataForms.DataForm.Field field, Plugins.ContactDetails contact_details) { string label = field.label ?? ""; string? desc = null; - switch (field.var) { - case "muc#roomconfig_roomname": - label = _("Name of the room"); - break; - case "muc#roomconfig_roomdesc": - label = _("Description of the room"); - break; - case "muc#roomconfig_persistentroom": - label = _("Persistent"); - desc = _("The room will persist after the last occupant exits"); - break; - case "muc#roomconfig_publicroom": - label = _("Publicly searchable"); - break; - case "muc#roomconfig_changesubject": - label = _("Occupants may change subject"); - break; - case "muc#roomconfig_whois": - label = _("Discover real JIDs"); - desc = _("Who may discover real JIDs?"); - break; - case "muc#roomconfig_roomsecret": - label = _("Password"); - desc = _("Password required to enter the room. Leave empty for none"); - break; - case "muc#roomconfig_moderatedroom": - label = _("Moderated"); - desc = _("Only occupants with voice may send messages"); - break; - case "muc#roomconfig_membersonly": - label = _("Members only"); - desc = _("Only members may enter the room"); - break; - case "muc#roomconfig_historylength": - label = _("Message history"); - desc = _("Maximum number of history messages returned by the room"); - break; + + if (field.var != null) { + switch (field.var) { + case "muc#roomconfig_roomname": + label = _("Name of the room"); + break; + case "muc#roomconfig_roomdesc": + label = _("Description of the room"); + break; + case "muc#roomconfig_persistentroom": + label = _("Persistent"); + desc = _("The room will persist after the last occupant exits"); + break; + case "muc#roomconfig_publicroom": + label = _("Publicly searchable"); + break; + case "muc#roomconfig_changesubject": + label = _("Occupants may change subject"); + break; + case "muc#roomconfig_whois": + label = _("Discover real JIDs"); + desc = _("Who may discover real JIDs?"); + break; + case "muc#roomconfig_roomsecret": + label = _("Password"); + desc = _("Password required to enter the room. Leave empty for none"); + break; + case "muc#roomconfig_moderatedroom": + label = _("Moderated"); + desc = _("Only occupants with voice may send messages"); + break; + case "muc#roomconfig_membersonly": + label = _("Members only"); + desc = _("Only members may enter the room"); + break; + case "muc#roomconfig_historylength": + label = _("Message history"); + desc = _("Maximum number of history messages returned by the room"); + break; + } } Widget? widget = get_widget(field); @@ -78,6 +81,7 @@ public class MucConfigFormProvider : Plugins.ContactDetailsProvider { } private static Widget? get_widget(DataForms.DataForm.Field field) { + if (field.type_ == null) return null; switch (field.type_) { case DataForms.DataForm.Type.BOOLEAN: DataForms.DataForm.BooleanField boolean_field = field as DataForms.DataForm.BooleanField; diff --git a/main/src/ui/notifications.vala b/main/src/ui/notifications.vala index 725a3e26..02f0b1de 100644 --- a/main/src/ui/notifications.vala +++ b/main/src/ui/notifications.vala @@ -35,7 +35,7 @@ public class Notifications : Object { if (!notifications.has_key(conversation)) { notifications[conversation] = new Notify.Notification("", null, null); - notifications[conversation].set_hint("transient", true); + notifications[conversation].set_hint("persistent", true); notifications[conversation].add_action("default", "Open", () => { conversation_selected(conversation); #if GDK3_WITH_X11 @@ -69,6 +69,7 @@ public class Notifications : Object { private void on_received_subscription_request(Jid jid, Account account) { Notify.Notification notification = new Notify.Notification(_("Subscription request"), jid.bare_jid.to_string(), null); notification.set_image_from_pixbuf((new AvatarGenerator(40, 40)).draw_jid(stream_interactor, jid, account)); + notification.set_hint("persistent", true); notification.add_action("accept", _("Accept"), () => { stream_interactor.get_module(PresenceManager.IDENTITY).approve_subscription(account, jid); @@ -102,4 +103,4 @@ public class Notifications : Object { } } -}
\ No newline at end of file +} diff --git a/main/src/ui/occupant_menu/list.vala b/main/src/ui/occupant_menu/list.vala index 42ff3da6..aeea7c26 100644 --- a/main/src/ui/occupant_menu/list.vala +++ b/main/src/ui/occupant_menu/list.vala @@ -84,10 +84,12 @@ public class List : Box { private void header(ListBoxRow row, ListBoxRow? before_row) { ListRow c1 = row as ListRow; - Xmpp.Xep.Muc.Affiliation a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.account); + Xmpp.Xep.Muc.Affiliation? a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.account); + if (a1 == null) return; + if (before_row != null) { - ListRow c2 = before_row as ListRow; - Xmpp.Xep.Muc.Affiliation a2 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.account); + ListRow c2 = (ListRow) before_row; + Xmpp.Xep.Muc.Affiliation? a2 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.account); if (a1 != a2) { row.set_header(generate_header_widget(a1, false)); } else if (row.get_header() != null){ diff --git a/xmpp-vala/CMakeLists.txt b/xmpp-vala/CMakeLists.txt index 4c4f34d2..3306cf7e 100644 --- a/xmpp-vala/CMakeLists.txt +++ b/xmpp-vala/CMakeLists.txt @@ -60,6 +60,7 @@ SOURCES "src/module/xep/0184_message_delivery_receipts.vala" "src/module/xep/0203_delayed_delivery.vala" "src/module/xep/0280_message_carbons.vala" + "src/module/xep/0313_message_archive_management.vala" "src/module/xep/0333_chat_markers.vala" "src/module/xep/0368_srv_records_tls.vala" "src/module/xep/pixbuf_storage.vala" diff --git a/xmpp-vala/src/module/xep/0004_data_forms.vala b/xmpp-vala/src/module/xep/0004_data_forms.vala index a0e8cd43..57ff834a 100644 --- a/xmpp-vala/src/module/xep/0004_data_forms.vala +++ b/xmpp-vala/src/module/xep/0004_data_forms.vala @@ -21,8 +21,12 @@ public class DataForm { } public void submit() { + on_result(stream, get_submit_node()); + } + + public StanzaNode get_submit_node() { stanza_node.set_attribute("type", "submit"); - on_result(stream, stanza_node); + return stanza_node; } public enum Type { @@ -46,19 +50,24 @@ public class DataForm { } } - public abstract class Field { - public string label { + public class Field { + public StanzaNode node { get; set; } + public string? label { get { return node.get_attribute("label", NS_URI); } set { node.set_attribute("label", value); } + default = null; } - public StanzaNode node { get; set; } - public abstract Type type_ { get; internal set; } - public string var { + public virtual Type? type_ { get; internal set; default=null; } + public string? var { get { return node.get_attribute("var", NS_URI); } set { node.set_attribute("var", value); } } - public Field(StanzaNode node) { + public Field() { + this.node = new StanzaNode.build("field", NS_URI); + } + + public Field.from_node(StanzaNode node) { this.node = node; } @@ -103,73 +112,94 @@ public class DataForm { } public class BooleanField : Field { - public override Type type_ { get; internal set; default=Type.BOOLEAN; } public bool value { get { return get_value_string() == "1"; } set { set_value_string(value ? "1" : "0"); } } - public BooleanField(StanzaNode node) { base(node); } + public BooleanField(StanzaNode node) { + base.from_node(node); + type_ = Type.BOOLEAN; + } } public class FixedField : Field { - public override Type type_ { get; internal set; default=Type.FIXED; } public string value { owned get { return get_value_string(); } set { set_value_string(value); } } - public FixedField(StanzaNode node) { base(node); } + public FixedField(StanzaNode node) { + base.from_node(node); + type_ = Type.FIXED; + } } public class HiddenField : Field { - public override Type type_ { get; internal set; default=Type.HIDDEN; } - public HiddenField(StanzaNode node) { base(node); } + public HiddenField() { + base(); + type_ = Type.HIDDEN;; + node.put_attribute("type", "hidden"); + } + public HiddenField.from_node(StanzaNode node) { + base.from_node(node); + type_ = Type.HIDDEN;; + } } public class JidMultiField : Field { public Gee.List<Option> options { owned get { return get_options(); } } - public override Type type_ { get; internal set; default=Type.JID_MULTI; } public Gee.List<string> value { get; set; } - public JidMultiField(StanzaNode node) { base(node); } + public JidMultiField(StanzaNode node) { + base.from_node(node); + type_ = Type.JID_MULTI; + } } public class ListSingleField : Field { public Gee.List<Option> options { owned get { return get_options(); } } - public override Type type_ { get; internal set; default=Type.LIST_SINGLE; } public string value { owned get { return get_value_string(); } set { set_value_string(value); } } - public ListSingleField(StanzaNode node) { base(node); } + public ListSingleField(StanzaNode node) { + base.from_node(node); + type_ = Type.LIST_SINGLE;; + } } public class ListMultiField : Field { public Gee.List<Option> options { owned get { return get_options(); } } - public override Type type_ { get; internal set; default=Type.LIST_MULTI; } public Gee.List<string> value { get; set; } - public ListMultiField(StanzaNode node) { base(node); } + public ListMultiField(StanzaNode node) { + base.from_node(node); + type_ = Type.LIST_MULTI; + } } public class TextPrivateField : Field { - public override Type type_ { get; internal set; default=Type.TEXT_PRIVATE; } public string value { owned get { return get_value_string(); } set { set_value_string(value); } } - public TextPrivateField(StanzaNode node) { base(node); } + public TextPrivateField(StanzaNode node) { + base.from_node(node); + type_ = Type.TEXT_PRIVATE; + } } public class TextSingleField : Field { - public override Type type_ { get; internal set; default=Type.TEXT_SINGLE; } public string value { owned get { return get_value_string(); } set { set_value_string(value); } } - public TextSingleField(StanzaNode node) { base(node); } + public TextSingleField(StanzaNode node) { + base.from_node(node); + type_ = Type.TEXT_SINGLE; + } } // TODO text-multi - internal DataForm(StanzaNode node, XmppStream stream, owned OnResult listener) { + internal DataForm.from_node(StanzaNode node, XmppStream stream, owned OnResult listener) { this.stanza_node = node; this.stream = stream; this.on_result = (owned)listener; @@ -183,7 +213,7 @@ public class DataForm { case "fixed": fields.add(new FixedField(field_node)); break; case "hidden": - fields.add(new HiddenField(field_node)); break; + fields.add(new HiddenField.from_node(field_node)); break; case "jid-multi": fields.add(new JidMultiField(field_node)); break; case "list-single": @@ -198,9 +228,18 @@ public class DataForm { } } + internal DataForm() { + this.stanza_node = new StanzaNode.build("x", NS_URI).add_self_xmlns(); + } + public delegate void OnResult(XmppStream stream, StanzaNode node); - public static DataForm? create(XmppStream stream, StanzaNode node, owned OnResult listener) { - return new DataForm(node, stream, (owned)listener); + public static DataForm? create_from_node(XmppStream stream, StanzaNode node, owned OnResult listener) { + return new DataForm.from_node(node, stream, (owned)listener); + } + + public void add_field(Field field) { + fields.add(field); + stanza_node.put_node(field.node); } } diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index a5d99b2c..fd4e8ba5 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -148,7 +148,7 @@ public class Module : XmppStreamModule { stream.get_module(Iq.Module.IDENTITY).send_iq(stream, get_iq, (stream, form_iq) => { StanzaNode? x_node = form_iq.stanza.get_deep_subnode(NS_URI_OWNER + ":query", DataForms.NS_URI + ":x"); if (x_node != null) { - DataForms.DataForm data_form = DataForms.DataForm.create(stream, x_node, (stream, node) => { + DataForms.DataForm data_form = DataForms.DataForm.create_from_node(stream, x_node, (stream, node) => { StanzaNode stanza_node = new StanzaNode.build("query", NS_URI_OWNER); stanza_node.add_self_xmlns().put_node(node); Iq.Stanza set_iq = new Iq.Stanza.set(stanza_node); diff --git a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala index 57c7ec4d..5c09e4a6 100644 --- a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala +++ b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala @@ -1,42 +1,34 @@ namespace Xmpp.Xep.DateTimeProfiles { -public class Module { - public Regex DATETIME_REGEX; - - public Module() { - DATETIME_REGEX = new Regex("""^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.(\d{3}))?(Z|((\+|\-)(\d{2})(:(\d{2}))?))$"""); - } - - public DateTime? parse_string(string time_string) { - MatchInfo match_info; - if (DATETIME_REGEX.match(time_string, RegexMatchFlags.ANCHORED, out match_info)) { - int year = int.parse(match_info.fetch(1)); - int month = int.parse(match_info.fetch(2)); - int day = int.parse(match_info.fetch(3)); - int hour = int.parse(match_info.fetch(4)); - int minute = int.parse(match_info.fetch(5)); - int second = int.parse(match_info.fetch(6)); - DateTime datetime = new DateTime.utc(year, month, day, hour, minute, second); - if (match_info.fetch(9) != "Z") { - char plusminus = match_info.fetch(11)[0]; - int tz_hour = int.parse(match_info.fetch(12)); - int tz_minute = int.parse(match_info.fetch(13)); - if (plusminus == '-') { - tz_hour *= -1; - tz_minute *= -1; - } - datetime.add_hours(tz_hour); - datetime.add_minutes(tz_minute); +public DateTime? parse_string(string time_string) { + Regex DATETIME_REGEX = new Regex("""^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.(\d{3}))?(Z|((\+|\-)(\d{2})(:(\d{2}))?))$"""); + MatchInfo match_info; + if (DATETIME_REGEX.match(time_string, RegexMatchFlags.ANCHORED, out match_info)) { + int year = int.parse(match_info.fetch(1)); + int month = int.parse(match_info.fetch(2)); + int day = int.parse(match_info.fetch(3)); + int hour = int.parse(match_info.fetch(4)); + int minute = int.parse(match_info.fetch(5)); + int second = int.parse(match_info.fetch(6)); + DateTime datetime = new DateTime.utc(year, month, day, hour, minute, second); + if (match_info.fetch(9) != "Z") { + char plusminus = match_info.fetch(11)[0]; + int tz_hour = int.parse(match_info.fetch(12)); + int tz_minute = int.parse(match_info.fetch(13)); + if (plusminus == '-') { + tz_hour *= -1; + tz_minute *= -1; } - return datetime; + datetime.add_hours(tz_hour); + datetime.add_minutes(tz_minute); } - return null; + return datetime; } + return null; +} public string to_datetime(DateTime time) { return time.to_utc().format("%Y-%m-%dT%H:%M:%SZ"); } } - -} diff --git a/xmpp-vala/src/module/xep/0203_delayed_delivery.vala b/xmpp-vala/src/module/xep/0203_delayed_delivery.vala index d2d94367..8ca300c9 100644 --- a/xmpp-vala/src/module/xep/0203_delayed_delivery.vala +++ b/xmpp-vala/src/module/xep/0203_delayed_delivery.vala @@ -8,18 +8,22 @@ namespace Xmpp.Xep.DelayedDelivery { public static void set_message_delay(Message.Stanza message, DateTime datetime) { StanzaNode delay_node = (new StanzaNode.build("delay", NS_URI)).add_self_xmlns(); - delay_node.put_attribute("stamp", (new DateTimeProfiles.Module()).to_datetime(datetime)); + delay_node.put_attribute("stamp", DateTimeProfiles.to_datetime(datetime)); message.stanza.put_node(delay_node); } - public static DateTime? get_send_time(Message.Stanza message) { + public static DateTime? get_time_for_message(Message.Stanza message) { StanzaNode? delay_node = message.stanza.get_subnode("delay", NS_URI); if (delay_node != null) { - string time = delay_node.get_attribute("stamp"); - return (new DateTimeProfiles.Module()).parse_string(time); - } else { - return null; + return get_time_for_node(delay_node); } + return null; + } + + public static DateTime? get_time_for_node(StanzaNode node) { + string? time = node.get_attribute("stamp"); + if (time != null) return DateTimeProfiles.parse_string(time); + return null; } public override void attach(XmppStream stream) { @@ -32,7 +36,7 @@ namespace Xmpp.Xep.DelayedDelivery { public override string get_id() { return IDENTITY.id; } private void on_pre_received_message(XmppStream stream, Message.Stanza message) { - DateTime? datetime = get_send_time(message); + DateTime? datetime = get_time_for_message(message); if (datetime != null) message.add_flag(new MessageFlag(datetime)); } } diff --git a/xmpp-vala/src/module/xep/0313_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_message_archive_management.vala new file mode 100644 index 00000000..522f6dca --- /dev/null +++ b/xmpp-vala/src/module/xep/0313_message_archive_management.vala @@ -0,0 +1,126 @@ +using Xmpp.Core; + +namespace Xmpp.Xep.MessageArchiveManagement { + +public const string NS_URI = "urn:xmpp:mam:2"; +public const string NS_URI_1 = "urn:xmpp:mam:1"; + +public class Module : XmppStreamModule { + public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0313_message_archive_management"); + + public signal void feature_available(XmppStream stream); + + public void query_archive(XmppStream stream, string? jid, DateTime? start, DateTime? end) { + if (stream.get_flag(Flag.IDENTITY) == null) return; + + DataForms.DataForm data_form = new DataForms.DataForm(); + DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" }; + form_type_field.set_value_string(NS_VER(stream)); + data_form.add_field(form_type_field); + if (jid != null) { + DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="with" }; + field.set_value_string(jid); + data_form.add_field(field); + } + if (start != null) { + DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="start" }; + field.set_value_string(DateTimeProfiles.to_datetime(start)); + data_form.add_field(field); + } + if (end != null) { + DataForms.DataForm.Field field = new DataForms.DataForm.Field() { var="end" }; + field.set_value_string(DateTimeProfiles.to_datetime(end)); + data_form.add_field(field); + } + StanzaNode query_node = new StanzaNode.build("query", NS_VER(stream)).add_self_xmlns().put_node(data_form.get_submit_node()); + Iq.Stanza iq = new Iq.Stanza.set(query_node); + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, page_through_results); + } + + public override void attach(XmppStream stream) { + stream.get_module(Message.Module.IDENTITY).pre_received_message.connect(on_pre_received_message); + stream.stream_negotiated.connect(query_availability); + } + + public override void detach(XmppStream stream) { } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } + + private void on_pre_received_message(XmppStream stream, Message.Stanza message) { +// if (message.from != stream.remote_name) return; + if (stream.get_flag(Flag.IDENTITY) == null) return; + + StanzaNode? message_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", "urn:xmpp:forward:0:forwarded", Message.NS_URI + ":message"); + if (message_node != null) { + StanzaNode? forward_node = message.stanza.get_deep_subnode(NS_VER(stream) + ":result", "urn:xmpp:forward:0:forwarded", DelayedDelivery.NS_URI + ":delay"); + DateTime? datetime = DelayedDelivery.Module.get_time_for_node(forward_node); + message.add_flag(new MessageFlag(datetime)); + + message.stanza = message_node; + message.rerun_parsing = true; + } + } + + private static void page_through_results(XmppStream stream, Iq.Stanza iq) { + string? last = iq.stanza.get_deep_string_content(NS_VER(stream) + ":fin", "http://jabber.org/protocol/rsm" + ":set", "last"); + if (last == null) { + stream.get_flag(Flag.IDENTITY).cought_up = true; + return; + } + + Iq.Stanza paging_iq = new Iq.Stanza.set( + new StanzaNode.build("query", NS_VER(stream)).add_self_xmlns().put_node( + new StanzaNode.build("set", "http://jabber.org/protocol/rsm").add_self_xmlns().put_node( + new StanzaNode.build("after", "http://jabber.org/protocol/rsm").put_node(new StanzaNode.text(last)) + ) + ) + ); + stream.get_module(Iq.Module.IDENTITY).send_iq(stream, paging_iq, page_through_results); + } + + private void query_availability(XmppStream stream) { + stream.get_module(Xep.ServiceDiscovery.Module.IDENTITY).request_info(stream, get_bare_jid(stream.get_flag(Bind.Flag.IDENTITY).my_jid), (stream, info_result) => { + if (info_result.features.contains(NS_URI)) { + stream.add_flag(new Flag(NS_URI)); + } else if (info_result.features.contains(NS_URI_1)) { + stream.add_flag(new Flag(NS_URI_1)); + } + if (stream.get_flag(Flag.IDENTITY) != null) feature_available(stream); + }); + } + + private static string NS_VER(XmppStream stream) { + return stream.get_flag(Flag.IDENTITY).ns_ver; + } +} + +public class Flag : XmppStreamFlag { + public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "message_archive_management"); + public bool cought_up { get; set; default=false; } + public string ns_ver; + + public Flag(string ns_ver) { + this.ns_ver = ns_ver; + } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } +} + +public class MessageFlag : Message.MessageFlag { + public const string ID = "message_archive_management"; + + public DateTime? server_time { get; private set; } + + public MessageFlag(DateTime? server_time) { + this.server_time = server_time; + } + + public static MessageFlag? get_flag(Message.Stanza message) { return (MessageFlag) message.get_flag(NS_URI, ID); } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return ID; } +} + +} |