Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
851a7ee9
by Philipp Hörist at 2019-03-11T22:13:00Z
-
f5fe4fd8
by Philipp Hörist at 2019-03-11T22:13:11Z
-
da46bdda
by Philipp Hörist at 2019-03-11T22:13:11Z
-
348a8551
by Philipp Hörist at 2019-03-11T22:13:11Z
9 changed files:
- gajim/common/logger.py
- gajim/common/modules/chatstates.py
- − gajim/common/modules/date_and_time.py
- gajim/common/modules/entity_time.py
- gajim/common/modules/mam.py
- gajim/common/modules/message.py
- gajim/common/modules/misc.py
- − test/no_gui/unit/test_date_time.py
- − test/no_gui/unit/test_helpers.py
Changes:
| ... | ... | @@ -1234,6 +1234,53 @@ class Logger: |
| 1234 | 1234 |
self._con.executescript(sql)
|
| 1235 | 1235 |
self._timeout_commit()
|
| 1236 | 1236 |
|
| 1237 |
+ def deduplicate_muc_message(self, account, jid, resource,
|
|
| 1238 |
+ timestamp, message_id):
|
|
| 1239 |
+ """
|
|
| 1240 |
+ Check if a message is already in the `logs` table
|
|
| 1241 |
+ |
|
| 1242 |
+ :param account: The account
|
|
| 1243 |
+ |
|
| 1244 |
+ :param jid: The muc jid as string
|
|
| 1245 |
+ |
|
| 1246 |
+ :param resource: The resource
|
|
| 1247 |
+ |
|
| 1248 |
+ :param timestamp: The timestamp in UTC epoch
|
|
| 1249 |
+ |
|
| 1250 |
+ :param message_id: The message-id
|
|
| 1251 |
+ """
|
|
| 1252 |
+ |
|
| 1253 |
+ # Add 60 seconds around the timestamp
|
|
| 1254 |
+ start_time = timestamp - 60
|
|
| 1255 |
+ end_time = timestamp + 60
|
|
| 1256 |
+ |
|
| 1257 |
+ account_id = self.get_account_id(account)
|
|
| 1258 |
+ log.debug('Search for MUC duplicate')
|
|
| 1259 |
+ log.debug('start: %s, end: %s, jid: %s, resource: %s, message-id: %s',
|
|
| 1260 |
+ start_time, end_time, jid, resource, message_id)
|
|
| 1261 |
+ |
|
| 1262 |
+ sql = '''
|
|
| 1263 |
+ SELECT * FROM logs
|
|
| 1264 |
+ NATURAL JOIN jids WHERE
|
|
| 1265 |
+ jid = ? AND
|
|
| 1266 |
+ contact_name = ? AND
|
|
| 1267 |
+ message_id = ? AND
|
|
| 1268 |
+ account_id = ? AND
|
|
| 1269 |
+ time BETWEEN ? AND ?
|
|
| 1270 |
+ '''
|
|
| 1271 |
+ |
|
| 1272 |
+ result = self._con.execute(sql, (jid,
|
|
| 1273 |
+ resource,
|
|
| 1274 |
+ message_id,
|
|
| 1275 |
+ account_id,
|
|
| 1276 |
+ start_time,
|
|
| 1277 |
+ end_time)).fetchone()
|
|
| 1278 |
+ |
|
| 1279 |
+ if result is not None:
|
|
| 1280 |
+ log.debug('Found duplicate')
|
|
| 1281 |
+ return True
|
|
| 1282 |
+ return False
|
|
| 1283 |
+ |
|
| 1237 | 1284 |
def search_for_duplicate(self, account, jid, timestamp, msg):
|
| 1238 | 1285 |
"""
|
| 1239 | 1286 |
Check if a message is already in the `logs` table
|
| ... | ... | @@ -30,7 +30,6 @@ from gi.repository import GLib |
| 30 | 30 |
from gajim.common import app
|
| 31 | 31 |
from gajim.common.nec import NetworkEvent
|
| 32 | 32 |
from gajim.common.const import Chatstate as State
|
| 33 |
-from gajim.common.modules.misc import parse_delay
|
|
| 34 | 33 |
from gajim.common.modules.base import BaseModule
|
| 35 | 34 |
from gajim.common.connection_handlers_events import MessageOutgoingEvent
|
| 36 | 35 |
from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
|
| ... | ... | @@ -53,7 +52,7 @@ def ensure_enabled(func): |
| 53 | 52 |
|
| 54 | 53 |
|
| 55 | 54 |
def parse_chatstate(stanza: nbxmpp.Message) -> Optional[str]:
|
| 56 |
- if parse_delay(stanza) is not None:
|
|
| 55 |
+ if stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2) is not None:
|
|
| 57 | 56 |
return None
|
| 58 | 57 |
|
| 59 | 58 |
children = stanza.getChildren()
|
| 1 |
-# This file is part of Gajim.
|
|
| 2 |
-#
|
|
| 3 |
-# Gajim is free software; you can redistribute it and/or modify
|
|
| 4 |
-# it under the terms of the GNU General Public License as published
|
|
| 5 |
-# by the Free Software Foundation; version 3 only.
|
|
| 6 |
-#
|
|
| 7 |
-# Gajim is distributed in the hope that it will be useful,
|
|
| 8 |
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 9 |
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 10 |
-# GNU General Public License for more details.
|
|
| 11 |
-#
|
|
| 12 |
-# You should have received a copy of the GNU General Public License
|
|
| 13 |
-# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 14 |
- |
|
| 15 |
-# XEP-0082: XMPP Date and Time Profiles
|
|
| 16 |
- |
|
| 17 |
-import re
|
|
| 18 |
-import time
|
|
| 19 |
-import logging
|
|
| 20 |
-from datetime import datetime
|
|
| 21 |
-from datetime import timedelta
|
|
| 22 |
-from datetime import timezone
|
|
| 23 |
-from datetime import tzinfo
|
|
| 24 |
- |
|
| 25 |
-log = logging.getLogger('gajim.c.m.date_and_time')
|
|
| 26 |
- |
|
| 27 |
-PATTERN_DATETIME = re.compile(
|
|
| 28 |
- r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
|
|
| 29 |
- r'T'
|
|
| 30 |
- r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
|
|
| 31 |
- r'(\.[0-9]{0,6})?'
|
|
| 32 |
- r'(?:[0-9]+)?'
|
|
| 33 |
- r'(?:(Z)|(?:([-+][0-9]{2}):([0-9]{2})))$'
|
|
| 34 |
-)
|
|
| 35 |
- |
|
| 36 |
-PATTERN_DELAY = re.compile(
|
|
| 37 |
- r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
|
|
| 38 |
- r'T'
|
|
| 39 |
- r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
|
|
| 40 |
- r'(\.[0-9]{0,6})?'
|
|
| 41 |
- r'(?:[0-9]+)?'
|
|
| 42 |
- r'(?:(Z)|(?:([-+][0]{2}):([0]{2})))$'
|
|
| 43 |
-)
|
|
| 44 |
- |
|
| 45 |
- |
|
| 46 |
-ZERO = timedelta(0)
|
|
| 47 |
-HOUR = timedelta(hours=1)
|
|
| 48 |
-SECOND = timedelta(seconds=1)
|
|
| 49 |
- |
|
| 50 |
-STDOFFSET = timedelta(seconds=-time.timezone)
|
|
| 51 |
-if time.daylight:
|
|
| 52 |
- DSTOFFSET = timedelta(seconds=-time.altzone)
|
|
| 53 |
-else:
|
|
| 54 |
- DSTOFFSET = STDOFFSET
|
|
| 55 |
- |
|
| 56 |
-DSTDIFF = DSTOFFSET - STDOFFSET
|
|
| 57 |
- |
|
| 58 |
- |
|
| 59 |
-class LocalTimezone(tzinfo):
|
|
| 60 |
- '''
|
|
| 61 |
- A class capturing the platform's idea of local time.
|
|
| 62 |
- May result in wrong values on historical times in
|
|
| 63 |
- timezones where UTC offset and/or the DST rules had
|
|
| 64 |
- changed in the past.
|
|
| 65 |
- '''
|
|
| 66 |
- def fromutc(self, dt):
|
|
| 67 |
- assert dt.tzinfo is self
|
|
| 68 |
- stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND
|
|
| 69 |
- args = time.localtime(stamp)[:6]
|
|
| 70 |
- dst_diff = DSTDIFF // SECOND
|
|
| 71 |
- # Detect fold
|
|
| 72 |
- fold = (args == time.localtime(stamp - dst_diff))
|
|
| 73 |
- return datetime(*args, microsecond=dt.microsecond,
|
|
| 74 |
- tzinfo=self, fold=fold)
|
|
| 75 |
- |
|
| 76 |
- def utcoffset(self, dt):
|
|
| 77 |
- if self._isdst(dt):
|
|
| 78 |
- return DSTOFFSET
|
|
| 79 |
- return STDOFFSET
|
|
| 80 |
- |
|
| 81 |
- def dst(self, dt):
|
|
| 82 |
- if self._isdst(dt):
|
|
| 83 |
- return DSTDIFF
|
|
| 84 |
- return ZERO
|
|
| 85 |
- |
|
| 86 |
- def tzname(self, dt):
|
|
| 87 |
- return 'local'
|
|
| 88 |
- |
|
| 89 |
- @staticmethod
|
|
| 90 |
- def _isdst(dt):
|
|
| 91 |
- tt = (dt.year, dt.month, dt.day,
|
|
| 92 |
- dt.hour, dt.minute, dt.second,
|
|
| 93 |
- dt.weekday(), 0, 0)
|
|
| 94 |
- stamp = time.mktime(tt)
|
|
| 95 |
- tt = time.localtime(stamp)
|
|
| 96 |
- return tt.tm_isdst > 0
|
|
| 97 |
- |
|
| 98 |
- |
|
| 99 |
-def create_tzinfo(hours=0, minutes=0, tz_string=None):
|
|
| 100 |
- if tz_string is None:
|
|
| 101 |
- return timezone(timedelta(hours=hours, minutes=minutes))
|
|
| 102 |
- |
|
| 103 |
- if tz_string.lower() == 'z':
|
|
| 104 |
- return timezone.utc
|
|
| 105 |
- |
|
| 106 |
- try:
|
|
| 107 |
- hours, minutes = map(int, tz_string.split(':'))
|
|
| 108 |
- except Exception:
|
|
| 109 |
- log.warning('Wrong tz string: %s', tz_string)
|
|
| 110 |
- return
|
|
| 111 |
- |
|
| 112 |
- if hours not in range(-24, 24):
|
|
| 113 |
- log.warning('Wrong tz string: %s', tz_string)
|
|
| 114 |
- return
|
|
| 115 |
- |
|
| 116 |
- if minutes not in range(0, 59):
|
|
| 117 |
- log.warning('Wrong tz string: %s', tz_string)
|
|
| 118 |
- return
|
|
| 119 |
- |
|
| 120 |
- if hours in (24, -24) and minutes != 0:
|
|
| 121 |
- log.warning('Wrong tz string: %s', tz_string)
|
|
| 122 |
- return
|
|
| 123 |
- return timezone(timedelta(hours=hours, minutes=minutes))
|
|
| 124 |
- |
|
| 125 |
- |
|
| 126 |
-def parse_datetime(timestring, check_utc=False,
|
|
| 127 |
- convert='utc', epoch=False):
|
|
| 128 |
- '''
|
|
| 129 |
- Parse a XEP-0082 DateTime Profile String
|
|
| 130 |
- |
|
| 131 |
- :param timestring: a XEP-0082 DateTime profile formated string
|
|
| 132 |
- |
|
| 133 |
- :param check_utc: if True, returns None if timestring is not
|
|
| 134 |
- a timestring expressing UTC
|
|
| 135 |
- |
|
| 136 |
- :param convert: convert the given timestring to utc or local time
|
|
| 137 |
- |
|
| 138 |
- :param epoch: if True, returns the time in epoch
|
|
| 139 |
- |
|
| 140 |
- Examples:
|
|
| 141 |
- '2017-11-05T01:41:20Z'
|
|
| 142 |
- '2017-11-05T01:41:20.123Z'
|
|
| 143 |
- '2017-11-05T01:41:20.123+05:00'
|
|
| 144 |
- |
|
| 145 |
- return a datetime or epoch
|
|
| 146 |
- '''
|
|
| 147 |
- if convert not in (None, 'utc', 'local'):
|
|
| 148 |
- raise TypeError('"%s" is not a valid value for convert')
|
|
| 149 |
- if check_utc:
|
|
| 150 |
- match = PATTERN_DELAY.match(timestring)
|
|
| 151 |
- else:
|
|
| 152 |
- match = PATTERN_DATETIME.match(timestring)
|
|
| 153 |
- |
|
| 154 |
- if match:
|
|
| 155 |
- timestring = ''.join(match.groups(''))
|
|
| 156 |
- strformat = '%Y-%m-%d%H:%M:%S%z'
|
|
| 157 |
- if match.group(3):
|
|
| 158 |
- # Fractional second addendum to Time
|
|
| 159 |
- strformat = '%Y-%m-%d%H:%M:%S.%f%z'
|
|
| 160 |
- if match.group(4):
|
|
| 161 |
- # UTC string denoted by addition of the character 'Z'
|
|
| 162 |
- timestring = timestring[:-1] + '+0000'
|
|
| 163 |
- try:
|
|
| 164 |
- date_time = datetime.strptime(timestring, strformat)
|
|
| 165 |
- except ValueError:
|
|
| 166 |
- pass
|
|
| 167 |
- else:
|
|
| 168 |
- if check_utc:
|
|
| 169 |
- if convert != 'utc':
|
|
| 170 |
- raise ValueError(
|
|
| 171 |
- 'check_utc can only be used with convert="utc"')
|
|
| 172 |
- date_time.replace(tzinfo=timezone.utc)
|
|
| 173 |
- if epoch:
|
|
| 174 |
- return date_time.timestamp()
|
|
| 175 |
- return date_time
|
|
| 176 |
- |
|
| 177 |
- if convert == 'utc':
|
|
| 178 |
- date_time = date_time.astimezone(timezone.utc)
|
|
| 179 |
- if epoch:
|
|
| 180 |
- return date_time.timestamp()
|
|
| 181 |
- return date_time
|
|
| 182 |
- |
|
| 183 |
- if epoch:
|
|
| 184 |
- # epoch is always UTC, use convert='utc' or check_utc=True
|
|
| 185 |
- raise ValueError(
|
|
| 186 |
- 'epoch not available while converting to local')
|
|
| 187 |
- |
|
| 188 |
- if convert == 'local':
|
|
| 189 |
- date_time = date_time.astimezone(LocalTimezone())
|
|
| 190 |
- return date_time
|
|
| 191 |
- |
|
| 192 |
- # convert=None
|
|
| 193 |
- return date_time
|
|
| 194 |
- return None
|
| ... | ... | @@ -18,12 +18,12 @@ import time |
| 18 | 18 |
|
| 19 | 19 |
import nbxmpp
|
| 20 | 20 |
from nbxmpp.structs import StanzaHandler
|
| 21 |
+from nbxmpp.modules.date_and_time import parse_datetime
|
|
| 22 |
+from nbxmpp.modules.date_and_time import create_tzinfo
|
|
| 21 | 23 |
|
| 22 | 24 |
from gajim.common import app
|
| 23 | 25 |
from gajim.common.nec import NetworkEvent
|
| 24 | 26 |
from gajim.common.modules.base import BaseModule
|
| 25 |
-from gajim.common.modules.date_and_time import parse_datetime
|
|
| 26 |
-from gajim.common.modules.date_and_time import create_tzinfo
|
|
| 27 | 27 |
|
| 28 | 28 |
|
| 29 | 29 |
class EntityTime(BaseModule):
|
| ... | ... | @@ -29,7 +29,6 @@ from gajim.common.const import SyncThreshold |
| 29 | 29 |
from gajim.common.caps_cache import muc_caps_cache
|
| 30 | 30 |
from gajim.common.helpers import get_sync_threshold
|
| 31 | 31 |
from gajim.common.helpers import AdditionalDataDict
|
| 32 |
-from gajim.common.modules.misc import parse_delay
|
|
| 33 | 32 |
from gajim.common.modules.misc import parse_oob
|
| 34 | 33 |
from gajim.common.modules.misc import parse_correction
|
| 35 | 34 |
from gajim.common.modules.util import get_eme_message
|
| ... | ... | @@ -138,9 +137,15 @@ class MAM(BaseModule): |
| 138 | 137 |
'message-id: %s', stanza_id, message_id)
|
| 139 | 138 |
raise nbxmpp.NodeProcessed
|
| 140 | 139 |
|
| 140 |
+ additional_data = AdditionalDataDict()
|
|
| 141 |
+ if properties.has_user_delay:
|
|
| 142 |
+ # Record it as a user timestamp
|
|
| 143 |
+ additional_data.set_value(
|
|
| 144 |
+ 'gajim', 'user_timestamp', properties.user_timestamp)
|
|
| 145 |
+ |
|
| 141 | 146 |
event_attrs.update(
|
| 142 | 147 |
{'conn': self._con,
|
| 143 |
- 'additional_data': AdditionalDataDict(),
|
|
| 148 |
+ 'additional_data': additional_data,
|
|
| 144 | 149 |
'encrypted': False,
|
| 145 | 150 |
'timestamp': properties.mam.timestamp,
|
| 146 | 151 |
'self_message': properties.is_self_message,
|
| ... | ... | @@ -205,12 +210,6 @@ class MAM(BaseModule): |
| 205 | 210 |
self._log.debug(event.message.getProperties())
|
| 206 | 211 |
return
|
| 207 | 212 |
|
| 208 |
- user_timestamp = parse_delay(event.stanza)
|
|
| 209 |
- if user_timestamp is not None:
|
|
| 210 |
- # Record it as a user timestamp
|
|
| 211 |
- event.additional_data.set_value(
|
|
| 212 |
- 'gajim', 'user_timestamp', user_timestamp)
|
|
| 213 |
- |
|
| 214 | 213 |
event.correct_id = parse_correction(event.message)
|
| 215 | 214 |
parse_oob(event)
|
| 216 | 215 |
|
| ... | ... | @@ -31,7 +31,6 @@ from gajim.common.modules.base import BaseModule |
| 31 | 31 |
from gajim.common.modules.util import get_eme_message
|
| 32 | 32 |
from gajim.common.modules.security_labels import parse_securitylabel
|
| 33 | 33 |
from gajim.common.modules.user_nickname import parse_nickname
|
| 34 |
-from gajim.common.modules.misc import parse_delay
|
|
| 35 | 34 |
from gajim.common.modules.misc import parse_correction
|
| 36 | 35 |
from gajim.common.modules.misc import parse_attention
|
| 37 | 36 |
from gajim.common.modules.misc import parse_form
|
| ... | ... | @@ -86,18 +85,20 @@ class Message(BaseModule): |
| 86 | 85 |
|
| 87 | 86 |
type_ = properties.type
|
| 88 | 87 |
|
| 89 |
- # Check for duplicates
|
|
| 90 | 88 |
stanza_id, message_id = self._get_unique_id(properties)
|
| 91 | 89 |
|
| 92 |
- # Check groupchat messages for duplicates,
|
|
| 93 |
- # We do this because of MUC History messages
|
|
| 94 |
- if (properties.type.is_groupchat or
|
|
| 95 |
- properties.is_self_message or
|
|
| 96 |
- properties.is_muc_pm):
|
|
| 97 |
- if properties.type.is_groupchat:
|
|
| 98 |
- archive_jid = stanza.getFrom().getStripped()
|
|
| 99 |
- else:
|
|
| 100 |
- archive_jid = self._con.get_own_jid().getStripped()
|
|
| 90 |
+ if properties.type.is_groupchat and properties.has_server_delay:
|
|
| 91 |
+ # Only for XEP-0045 MUC History
|
|
| 92 |
+ # Dont check for message text because the message could be encrypted
|
|
| 93 |
+ if app.logger.deduplicate_muc_message(self._account,
|
|
| 94 |
+ properties.jid.getBare(),
|
|
| 95 |
+ properties.jid.getResource(),
|
|
| 96 |
+ properties.timestamp,
|
|
| 97 |
+ properties.id):
|
|
| 98 |
+ raise nbxmpp.NodeProcessed
|
|
| 99 |
+ |
|
| 100 |
+ if (properties.is_self_message or properties.is_muc_pm):
|
|
| 101 |
+ archive_jid = self._con.get_own_jid().getStripped()
|
|
| 101 | 102 |
if app.logger.find_stanza_id(self._account,
|
| 102 | 103 |
archive_jid,
|
| 103 | 104 |
stanza_id,
|
| ... | ... | @@ -144,13 +145,19 @@ class Message(BaseModule): |
| 144 | 145 |
|
| 145 | 146 |
session.last_receive = time.time()
|
| 146 | 147 |
|
| 148 |
+ additional_data = AdditionalDataDict()
|
|
| 149 |
+ |
|
| 150 |
+ if properties.has_user_delay:
|
|
| 151 |
+ additional_data.set_value(
|
|
| 152 |
+ 'gajim', 'user_timestamp', properties.user_timestamp)
|
|
| 153 |
+ |
|
| 147 | 154 |
event_attr = {
|
| 148 | 155 |
'conn': self._con,
|
| 149 | 156 |
'stanza': stanza,
|
| 150 | 157 |
'account': self._account,
|
| 151 | 158 |
'id_': properties.id,
|
| 152 | 159 |
'encrypted': False,
|
| 153 |
- 'additional_data': AdditionalDataDict(),
|
|
| 160 |
+ 'additional_data': additional_data,
|
|
| 154 | 161 |
'forwarded': forwarded,
|
| 155 | 162 |
'sent': sent,
|
| 156 | 163 |
'fjid': fjid,
|
| ... | ... | @@ -164,6 +171,8 @@ class Message(BaseModule): |
| 164 | 171 |
'thread_id': thread_id,
|
| 165 | 172 |
'session': session,
|
| 166 | 173 |
'self_message': properties.is_self_message,
|
| 174 |
+ 'timestamp': properties.timestamp,
|
|
| 175 |
+ 'delayed': properties.user_timestamp is not None,
|
|
| 167 | 176 |
'muc_pm': properties.is_muc_pm,
|
| 168 | 177 |
'gc_control': gc_control
|
| 169 | 178 |
}
|
| ... | ... | @@ -197,22 +206,6 @@ class Message(BaseModule): |
| 197 | 206 |
subject = event.stanza.getSubject()
|
| 198 | 207 |
groupchat = event.mtype == 'groupchat'
|
| 199 | 208 |
|
| 200 |
- # Determine timestamps
|
|
| 201 |
- if groupchat:
|
|
| 202 |
- delay_jid = event.jid
|
|
| 203 |
- else:
|
|
| 204 |
- delay_jid = self._con.get_own_jid().getDomain()
|
|
| 205 |
- timestamp = parse_delay(event.stanza, from_=delay_jid)
|
|
| 206 |
- if timestamp is None:
|
|
| 207 |
- timestamp = time.time()
|
|
| 208 |
- |
|
| 209 |
- user_timestamp = parse_delay(event.stanza,
|
|
| 210 |
- not_from=[delay_jid])
|
|
| 211 |
- |
|
| 212 |
- if user_timestamp is not None:
|
|
| 213 |
- event.additional_data.set_value(
|
|
| 214 |
- 'gajim', 'user_timestamp', user_timestamp)
|
|
| 215 |
- |
|
| 216 | 209 |
event_attr = {
|
| 217 | 210 |
'popup': False,
|
| 218 | 211 |
'msg_log_id': None,
|
| ... | ... | @@ -223,8 +216,6 @@ class Message(BaseModule): |
| 223 | 216 |
'user_nick': '' if event.sent else parse_nickname(event.stanza),
|
| 224 | 217 |
'form_node': parse_form(event.stanza),
|
| 225 | 218 |
'xhtml': parse_xhtml(event.stanza),
|
| 226 |
- 'timestamp': timestamp,
|
|
| 227 |
- 'delayed': user_timestamp is not None,
|
|
| 228 | 219 |
}
|
| 229 | 220 |
parse_oob(event)
|
| 230 | 221 |
|
| ... | ... | @@ -19,54 +19,10 @@ import logging |
| 19 | 19 |
import nbxmpp
|
| 20 | 20 |
|
| 21 | 21 |
from gajim.common import app
|
| 22 |
-from gajim.common.modules.date_and_time import parse_datetime
|
|
| 23 | 22 |
|
| 24 | 23 |
log = logging.getLogger('gajim.c.m.misc')
|
| 25 | 24 |
|
| 26 | 25 |
|
| 27 |
-# XEP-0203: Delayed Delivery
|
|
| 28 |
- |
|
| 29 |
-def parse_delay(stanza, epoch=True, convert='utc', from_=None, not_from=None):
|
|
| 30 |
- '''
|
|
| 31 |
- Returns the first valid delay timestamp that matches
|
|
| 32 |
- |
|
| 33 |
- :param epoch: Returns the timestamp as epoch
|
|
| 34 |
- |
|
| 35 |
- :param convert: Converts the timestamp to either utc or local
|
|
| 36 |
- |
|
| 37 |
- :param from_: Matches only delays that have the according
|
|
| 38 |
- from attr set
|
|
| 39 |
- |
|
| 40 |
- :param not_from: Matches only delays that have the according
|
|
| 41 |
- from attr not set
|
|
| 42 |
- '''
|
|
| 43 |
- delays = stanza.getTags('delay', namespace=nbxmpp.NS_DELAY2)
|
|
| 44 |
- |
|
| 45 |
- for delay in delays:
|
|
| 46 |
- stamp = delay.getAttr('stamp')
|
|
| 47 |
- if stamp is None:
|
|
| 48 |
- log.warning('Invalid timestamp received: %s', stamp)
|
|
| 49 |
- log.warning(stanza)
|
|
| 50 |
- continue
|
|
| 51 |
- |
|
| 52 |
- delay_from = delay.getAttr('from')
|
|
| 53 |
- if from_ is not None:
|
|
| 54 |
- if delay_from != from_:
|
|
| 55 |
- continue
|
|
| 56 |
- if not_from is not None:
|
|
| 57 |
- if delay_from in not_from:
|
|
| 58 |
- continue
|
|
| 59 |
- |
|
| 60 |
- timestamp = parse_datetime(stamp, check_utc=True,
|
|
| 61 |
- epoch=epoch, convert=convert)
|
|
| 62 |
- if timestamp is None:
|
|
| 63 |
- log.warning('Invalid timestamp received: %s', stamp)
|
|
| 64 |
- log.warning(stanza)
|
|
| 65 |
- continue
|
|
| 66 |
- |
|
| 67 |
- return timestamp
|
|
| 68 |
- |
|
| 69 |
- |
|
| 70 | 26 |
# XEP-0066: Out of Band Data
|
| 71 | 27 |
|
| 72 | 28 |
def parse_oob(event):
|
| 1 |
-import unittest
|
|
| 2 |
-from datetime import datetime
|
|
| 3 |
-from datetime import timezone
|
|
| 4 |
-from datetime import timedelta
|
|
| 5 |
- |
|
| 6 |
-from gajim.common.modules.date_and_time import parse_datetime
|
|
| 7 |
-from gajim.common.modules.date_and_time import LocalTimezone
|
|
| 8 |
-from gajim.common.modules.date_and_time import create_tzinfo
|
|
| 9 |
- |
|
| 10 |
- |
|
| 11 |
-class TestDateTime(unittest.TestCase):
|
|
| 12 |
- |
|
| 13 |
- def test_convert_to_utc(self):
|
|
| 14 |
- |
|
| 15 |
- strings = {
|
|
| 16 |
- # Valid UTC strings and fractions
|
|
| 17 |
- '2017-11-05T01:41:20Z': 1509846080.0,
|
|
| 18 |
- '2017-11-05T01:41:20.123Z': 1509846080.123,
|
|
| 19 |
- '2017-11-05T01:41:20.123123123+00:00': 1509846080.123123,
|
|
| 20 |
- '2017-11-05T01:41:20.123123123123123-00:00': 1509846080.123123,
|
|
| 21 |
- |
|
| 22 |
- # Invalid strings
|
|
| 23 |
- '2017-11-05T01:41:20Z+05:00': None,
|
|
| 24 |
- '2017-11-05T01:41:20+0000': None,
|
|
| 25 |
- '2017-11-05T01:41:20-0000': None,
|
|
| 26 |
- |
|
| 27 |
- # Valid strings with offset
|
|
| 28 |
- '2017-11-05T01:41:20-05:00': 1509864080.0,
|
|
| 29 |
- '2017-11-05T01:41:20+05:00': 1509828080.0,
|
|
| 30 |
- }
|
|
| 31 |
- |
|
| 32 |
- strings2 = {
|
|
| 33 |
- # Valid strings with offset
|
|
| 34 |
- '2017-11-05T01:41:20-05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=-5)),
|
|
| 35 |
- '2017-11-05T01:41:20+05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=5)),
|
|
| 36 |
- }
|
|
| 37 |
- |
|
| 38 |
- for time_string, expected_value in strings.items():
|
|
| 39 |
- result = parse_datetime(time_string, convert='utc', epoch=True)
|
|
| 40 |
- self.assertEqual(result, expected_value)
|
|
| 41 |
- |
|
| 42 |
- for time_string, expected_value in strings2.items():
|
|
| 43 |
- result = parse_datetime(time_string, convert='utc')
|
|
| 44 |
- self.assertEqual(result, expected_value.astimezone(timezone.utc))
|
|
| 45 |
- |
|
| 46 |
- def test_convert_to_local(self):
|
|
| 47 |
- |
|
| 48 |
- strings = {
|
|
| 49 |
- # Valid UTC strings and fractions
|
|
| 50 |
- '2017-11-05T01:41:20Z': datetime(2017, 11, 5, 1, 41, 20, 0, timezone.utc),
|
|
| 51 |
- '2017-11-05T01:41:20.123Z': datetime(2017, 11, 5, 1, 41, 20, 123000, timezone.utc),
|
|
| 52 |
- '2017-11-05T01:41:20.123123123+00:00': datetime(2017, 11, 5, 1, 41, 20, 123123, timezone.utc),
|
|
| 53 |
- '2017-11-05T01:41:20.123123123123123-00:00': datetime(2017, 11, 5, 1, 41, 20, 123123, timezone.utc),
|
|
| 54 |
- |
|
| 55 |
- # Valid strings with offset
|
|
| 56 |
- '2017-11-05T01:41:20-05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=-5)),
|
|
| 57 |
- '2017-11-05T01:41:20+05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=5)),
|
|
| 58 |
- }
|
|
| 59 |
- |
|
| 60 |
- for time_string, expected_value in strings.items():
|
|
| 61 |
- result = parse_datetime(time_string, convert='local')
|
|
| 62 |
- self.assertEqual(result, expected_value.astimezone(LocalTimezone()))
|
|
| 63 |
- |
|
| 64 |
- def test_no_convert(self):
|
|
| 65 |
- |
|
| 66 |
- strings = {
|
|
| 67 |
- # Valid UTC strings and fractions
|
|
| 68 |
- '2017-11-05T01:41:20Z': timedelta(0),
|
|
| 69 |
- '2017-11-05T01:41:20.123Z': timedelta(0),
|
|
| 70 |
- '2017-11-05T01:41:20.123123123+00:00': timedelta(0),
|
|
| 71 |
- '2017-11-05T01:41:20.123123123123123-00:00': timedelta(0),
|
|
| 72 |
- |
|
| 73 |
- # Valid strings with offset
|
|
| 74 |
- '2017-11-05T01:41:20-05:00': timedelta(hours=-5),
|
|
| 75 |
- '2017-11-05T01:41:20+05:00': timedelta(hours=5),
|
|
| 76 |
- }
|
|
| 77 |
- |
|
| 78 |
- for time_string, expected_value in strings.items():
|
|
| 79 |
- result = parse_datetime(time_string, convert=None)
|
|
| 80 |
- self.assertEqual(result.utcoffset(), expected_value)
|
|
| 81 |
- |
|
| 82 |
- def test_check_utc(self):
|
|
| 83 |
- |
|
| 84 |
- strings = {
|
|
| 85 |
- # Valid UTC strings and fractions
|
|
| 86 |
- '2017-11-05T01:41:20Z': 1509846080.0,
|
|
| 87 |
- '2017-11-05T01:41:20.123Z': 1509846080.123,
|
|
| 88 |
- '2017-11-05T01:41:20.123123123+00:00': 1509846080.123123,
|
|
| 89 |
- '2017-11-05T01:41:20.123123123123123-00:00': 1509846080.123123,
|
|
| 90 |
- |
|
| 91 |
- # Valid strings with offset
|
|
| 92 |
- '2017-11-05T01:41:20-05:00': None,
|
|
| 93 |
- '2017-11-05T01:41:20+05:00': None,
|
|
| 94 |
- }
|
|
| 95 |
- |
|
| 96 |
- for time_string, expected_value in strings.items():
|
|
| 97 |
- result = parse_datetime(
|
|
| 98 |
- time_string, check_utc=True, epoch=True)
|
|
| 99 |
- self.assertEqual(result, expected_value)
|
| 1 |
-import unittest
|
|
| 2 |
- |
|
| 3 |
-import nbxmpp
|
|
| 4 |
- |
|
| 5 |
-from gajim.common.modules.misc import parse_delay
|
|
| 6 |
- |
|
| 7 |
-class TestHelpers(unittest.TestCase):
|
|
| 8 |
- |
|
| 9 |
- def test_parse_delay(self):
|
|
| 10 |
- |
|
| 11 |
- node = """
|
|
| 12 |
- <message>
|
|
| 13 |
- <delay xmlns='urn:xmpp:delay' from='capulet.com' stamp='2002-09-10T23:08:25Z' />
|
|
| 14 |
- <delay xmlns='urn:xmpp:delay' from='romeo.com' stamp='2010-09-10T23:08:25Z' />
|
|
| 15 |
- <delay xmlns='urn:xmpp:delay' stamp='2015-09-10T23:08:25Z' />
|
|
| 16 |
- </message>
|
|
| 17 |
- """
|
|
| 18 |
- message = nbxmpp.Node(node=node)
|
|
| 19 |
- |
|
| 20 |
- timestamp = parse_delay(message)
|
|
| 21 |
- self.assertEqual(timestamp, 1031699305.0)
|
|
| 22 |
- |
|
| 23 |
- timestamp = parse_delay(message, from_='capulet.com')
|
|
| 24 |
- self.assertEqual(timestamp, 1031699305.0)
|
|
| 25 |
- |
|
| 26 |
- timestamp = parse_delay(message, from_='romeo.com')
|
|
| 27 |
- self.assertEqual(timestamp, 1284160105.0)
|
|
| 28 |
- |
|
| 29 |
- timestamp = parse_delay(message, not_from=['romeo.com'])
|
|
| 30 |
- self.assertEqual(timestamp, 1031699305.0)
|
|
| \ No newline at end of file |