| ... |
... |
@@ -54,7 +54,6 @@ |
|
54
|
54
|
import string
|
|
55
|
55
|
import webbrowser
|
|
56
|
56
|
from string import Template
|
|
57
|
|
-import urllib
|
|
58
|
57
|
from datetime import datetime
|
|
59
|
58
|
from datetime import timedelta
|
|
60
|
59
|
from urllib.parse import unquote
|
| ... |
... |
@@ -129,6 +128,7 @@ def parse_jid(jidstring: str) -> str: |
|
129
|
128
|
except Exception as error:
|
|
130
|
129
|
raise InvalidFormat(error)
|
|
131
|
130
|
|
|
|
131
|
+
|
|
132
|
132
|
def idn_to_ascii(host: str) -> str:
|
|
133
|
133
|
"""
|
|
134
|
134
|
Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible
|
| ... |
... |
@@ -144,6 +144,7 @@ def idn_to_ascii(host: str) -> str: |
|
144
|
144
|
converted_labels.append('')
|
|
145
|
145
|
return ".".join(converted_labels)
|
|
146
|
146
|
|
|
|
147
|
+
|
|
147
|
148
|
def ascii_to_idn(host: str) -> str:
|
|
148
|
149
|
"""
|
|
149
|
150
|
Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain
|
| ... |
... |
@@ -156,17 +157,6 @@ def ascii_to_idn(host: str) -> str: |
|
156
|
157
|
converted_labels.append(idna.ToUnicode(label))
|
|
157
|
158
|
return ".".join(converted_labels)
|
|
158
|
159
|
|
|
159
|
|
-def puny_encode_url(url: str) -> Optional[str]:
|
|
160
|
|
- _url = url
|
|
161
|
|
- if '//' not in _url:
|
|
162
|
|
- _url = '//' + _url
|
|
163
|
|
- try:
|
|
164
|
|
- o = urllib.parse.urlparse(_url)
|
|
165
|
|
- p_loc = idn_to_ascii(o.hostname)
|
|
166
|
|
- except Exception:
|
|
167
|
|
- log.debug('urlparse failed: %s', url)
|
|
168
|
|
- return None
|
|
169
|
|
- return url.replace(o.hostname, p_loc)
|
|
170
|
160
|
|
|
171
|
161
|
def parse_resource(resource: str) -> Optional[str]:
|
|
172
|
162
|
"""
|
| ... |
... |
@@ -180,16 +170,13 @@ def parse_resource(resource: str) -> Optional[str]: |
|
180
|
170
|
except UnicodeError:
|
|
181
|
171
|
raise InvalidFormat('Invalid character in resource.')
|
|
182
|
172
|
|
|
183
|
|
-def windowsify(word: str) -> str:
|
|
184
|
|
- if os.name == 'nt':
|
|
185
|
|
- return word.capitalize()
|
|
186
|
|
- return word
|
|
187
|
173
|
|
|
188
|
174
|
def get_uf_show(show: str, use_mnemonic: bool = False) -> str:
|
|
189
|
175
|
if use_mnemonic:
|
|
190
|
176
|
return SHOW_STRING_MNEMONIC[show]
|
|
191
|
177
|
return SHOW_STRING[show]
|
|
192
|
178
|
|
|
|
179
|
+
|
|
193
|
180
|
def get_uf_sub(sub: str) -> str:
|
|
194
|
181
|
if sub == 'none':
|
|
195
|
182
|
uf_sub = Q_('?Subscription we already have:None')
|
| ... |
... |
@@ -204,6 +191,7 @@ def get_uf_sub(sub: str) -> str: |
|
204
|
191
|
|
|
205
|
192
|
return uf_sub
|
|
206
|
193
|
|
|
|
194
|
+
|
|
207
|
195
|
def get_uf_ask(ask: Union[str, None]) -> str:
|
|
208
|
196
|
if ask is None:
|
|
209
|
197
|
uf_ask = Q_('?Ask (for Subscription):None')
|
| ... |
... |
@@ -214,6 +202,7 @@ def get_uf_ask(ask: Union[str, None]) -> str: |
|
214
|
202
|
|
|
215
|
203
|
return uf_ask
|
|
216
|
204
|
|
|
|
205
|
+
|
|
217
|
206
|
def get_uf_role(role: Union[Role, str], plural: bool = False) -> str:
|
|
218
|
207
|
''' plural determines if you get Moderators or Moderator'''
|
|
219
|
208
|
if not isinstance(role, str):
|
| ... |
... |
@@ -235,6 +224,7 @@ def get_uf_role(role: Union[Role, str], plural: bool = False) -> str: |
|
235
|
224
|
return _('Visitor')
|
|
236
|
225
|
return ''
|
|
237
|
226
|
|
|
|
227
|
+
|
|
238
|
228
|
def get_uf_affiliation(affiliation: Union[Affiliation, str],
|
|
239
|
229
|
plural: bool = False
|
|
240
|
230
|
) -> str:
|
| ... |
... |
@@ -258,6 +248,7 @@ def get_uf_affiliation(affiliation: Union[Affiliation, str], |
|
258
|
248
|
return _('Member')
|
|
259
|
249
|
return ''
|
|
260
|
250
|
|
|
|
251
|
+
|
|
261
|
252
|
def get_uf_relative_time(timestamp: float) -> str:
|
|
262
|
253
|
date_time = datetime.fromtimestamp(timestamp)
|
|
263
|
254
|
now = datetime.now()
|
| ... |
... |
@@ -282,9 +273,6 @@ def get_uf_relative_time(timestamp: float) -> str: |
|
282
|
273
|
minutes)
|
|
283
|
274
|
return _('Just now')
|
|
284
|
275
|
|
|
285
|
|
-def get_sorted_keys(adict):
|
|
286
|
|
- keys = sorted(adict.keys())
|
|
287
|
|
- return keys
|
|
288
|
276
|
|
|
289
|
277
|
def to_one_line(msg: str) -> str:
|
|
290
|
278
|
msg = msg.replace('\\', '\\\\')
|
| ... |
... |
@@ -296,6 +284,7 @@ def to_one_line(msg: str) -> str: |
|
296
|
284
|
# 'test\\ntest\\\\ntest'
|
|
297
|
285
|
return msg
|
|
298
|
286
|
|
|
|
287
|
+
|
|
299
|
288
|
def from_one_line(msg: str) -> str:
|
|
300
|
289
|
# (?<!\\) is a lookbehind assertion which asks anything but '\'
|
|
301
|
290
|
# to match the regexp that follows it
|
| ... |
... |
@@ -311,6 +300,7 @@ def from_one_line(msg: str) -> str: |
|
311
|
300
|
# 'test\ntest\\ntest'
|
|
312
|
301
|
return msg
|
|
313
|
302
|
|
|
|
303
|
+
|
|
314
|
304
|
def get_uf_chatstate(chatstate: str) -> str:
|
|
315
|
305
|
"""
|
|
316
|
306
|
Remove chatstate jargon and returns user friendly messages
|
| ... |
... |
@@ -322,12 +312,13 @@ def get_uf_chatstate(chatstate: str) -> str: |
|
322
|
312
|
if chatstate == 'composing':
|
|
323
|
313
|
return _('is composing a message…')
|
|
324
|
314
|
if chatstate == 'paused':
|
|
325
|
|
- #paused means he or she was composing but has stopped for a while
|
|
|
315
|
+ # paused means he or she was composing but has stopped for a while
|
|
326
|
316
|
return _('paused composing a message')
|
|
327
|
317
|
if chatstate == 'gone':
|
|
328
|
318
|
return _('has closed the chat window or tab')
|
|
329
|
319
|
return ''
|
|
330
|
320
|
|
|
|
321
|
+
|
|
331
|
322
|
def exec_command(command: str,
|
|
332
|
323
|
use_shell: bool = False,
|
|
333
|
324
|
posix: bool = True
|
| ... |
... |
@@ -344,6 +335,7 @@ def exec_command(command: str, |
|
344
|
335
|
process = subprocess.Popen(args)
|
|
345
|
336
|
app.thread_interface(process.wait)
|
|
346
|
337
|
|
|
|
338
|
+
|
|
347
|
339
|
def build_command(executable: str, parameter: str) -> str:
|
|
348
|
340
|
# we add to the parameter (can hold path with spaces)
|
|
349
|
341
|
# "" so we have good parsing from shell
|
| ... |
... |
@@ -351,8 +343,9 @@ def build_command(executable: str, parameter: str) -> str: |
|
351
|
343
|
command = f'{executable} "{parameter}"'
|
|
352
|
344
|
return command
|
|
353
|
345
|
|
|
|
346
|
+
|
|
354
|
347
|
def get_file_path_from_dnd_dropped_uri(uri: str) -> str:
|
|
355
|
|
- path = urllib.parse.unquote(uri) # escape special chars
|
|
|
348
|
+ path = unquote(uri) # escape special chars
|
|
356
|
349
|
path = path.strip('\r\n\x00') # remove \r\n and NULL
|
|
357
|
350
|
# get the path to file
|
|
358
|
351
|
if re.match('^file:///[a-zA-Z]:/', path): # windows
|
| ... |
... |
@@ -363,6 +356,7 @@ def get_file_path_from_dnd_dropped_uri(uri: str) -> str: |
|
363
|
356
|
path = path[5:] # 5 is len('file:')
|
|
364
|
357
|
return path
|
|
365
|
358
|
|
|
|
359
|
+
|
|
366
|
360
|
def sanitize_filename(filename: str) -> str:
|
|
367
|
361
|
"""
|
|
368
|
362
|
Make sure the filename we will write does contain only acceptable and latin
|
| ... |
... |
@@ -383,6 +377,7 @@ def sanitize_filename(filename: str) -> str: |
|
383
|
377
|
|
|
384
|
378
|
return filename
|
|
385
|
379
|
|
|
|
380
|
+
|
|
386
|
381
|
def reduce_chars_newlines(text: str, max_chars: int = 0,
|
|
387
|
382
|
max_lines: int = 0) -> str:
|
|
388
|
383
|
"""
|
| ... |
... |
@@ -464,7 +459,10 @@ def check_soundfile_path(file_: str, |
|
464
|
459
|
return dir_
|
|
465
|
460
|
return None
|
|
466
|
461
|
|
|
467
|
|
-def strip_soundfile_path(file_, dirs=None, abs_=True):
|
|
|
462
|
+
|
|
|
463
|
+def strip_soundfile_path(file_: Union[Path, str],
|
|
|
464
|
+ dirs: Optional[list[Path]] = None,
|
|
|
465
|
+ abs_: bool = True):
|
|
468
|
466
|
"""
|
|
469
|
467
|
Remove knowns paths from a sound file
|
|
470
|
468
|
|
| ... |
... |
@@ -494,6 +492,7 @@ def strip_soundfile_path(file_, dirs=None, abs_=True): |
|
494
|
492
|
return name
|
|
495
|
493
|
return file_
|
|
496
|
494
|
|
|
|
495
|
+
|
|
497
|
496
|
def play_sound_file(str_path_to_soundfile: str, loop: bool = False) -> None:
|
|
498
|
497
|
path_to_soundfile = check_soundfile_path(str_path_to_soundfile)
|
|
499
|
498
|
if path_to_soundfile is None:
|
| ... |
... |
@@ -502,6 +501,7 @@ def play_sound_file(str_path_to_soundfile: str, loop: bool = False) -> None: |
|
502
|
501
|
from gajim.common import sound
|
|
503
|
502
|
sound.play(path_to_soundfile, loop)
|
|
504
|
503
|
|
|
|
504
|
+
|
|
505
|
505
|
def get_client_status(account: str) -> str:
|
|
506
|
506
|
client = app.get_client(account)
|
|
507
|
507
|
if client.state.is_disconnected:
|
| ... |
... |
@@ -514,6 +514,7 @@ def get_client_status(account: str) -> str: |
|
514
|
514
|
|
|
515
|
515
|
return client.status
|
|
516
|
516
|
|
|
|
517
|
+
|
|
517
|
518
|
def get_global_show() -> str:
|
|
518
|
519
|
maxi = 0
|
|
519
|
520
|
for account in app.connections:
|
| ... |
... |
@@ -526,6 +527,7 @@ def get_global_show() -> str: |
|
526
|
527
|
maxi = index
|
|
527
|
528
|
return SHOW_LIST[maxi]
|
|
528
|
529
|
|
|
|
530
|
+
|
|
529
|
531
|
def get_global_status_message() -> str:
|
|
530
|
532
|
maxi = 0
|
|
531
|
533
|
status_message = ''
|
| ... |
... |
@@ -539,6 +541,7 @@ def get_global_status_message() -> str: |
|
539
|
541
|
status_message = con.status_message
|
|
540
|
542
|
return status_message
|
|
541
|
543
|
|
|
|
544
|
+
|
|
542
|
545
|
def statuses_unified() -> bool:
|
|
543
|
546
|
"""
|
|
544
|
547
|
Test if all statuses are the same
|
| ... |
... |
@@ -566,6 +569,7 @@ def get_full_jid_from_iq(iq_obj: Iq) -> Optional[str]: |
|
566
|
569
|
return None
|
|
567
|
570
|
return parse_jid(str(iq_obj.getFrom()))
|
|
568
|
571
|
|
|
|
572
|
+
|
|
569
|
573
|
def get_jid_from_iq(iq_obj: Iq) -> Optional[str]:
|
|
570
|
574
|
"""
|
|
571
|
575
|
Return the jid (without resource) from an iq
|
| ... |
... |
@@ -575,6 +579,7 @@ def get_jid_from_iq(iq_obj: Iq) -> Optional[str]: |
|
575
|
579
|
return None
|
|
576
|
580
|
return app.get_jid_without_resource(jid)
|
|
577
|
581
|
|
|
|
582
|
+
|
|
578
|
583
|
def get_auth_sha(sid: str, initiator: str, target: str) -> str:
|
|
579
|
584
|
"""
|
|
580
|
585
|
Return sha of sid + initiator + target used for proxy auth
|
| ... |
... |
@@ -582,11 +587,13 @@ def get_auth_sha(sid: str, initiator: str, target: str) -> str: |
|
582
|
587
|
return hashlib.sha1(
|
|
583
|
588
|
(f'{sid}{initiator}{target}').encode('utf-8')).hexdigest()
|
|
584
|
589
|
|
|
|
590
|
+
|
|
585
|
591
|
def remove_invalid_xml_chars(string_: str) -> str:
|
|
586
|
592
|
if string_:
|
|
587
|
593
|
string_ = re.sub(INVALID_XML_CHARS_REGEX, '', string_)
|
|
588
|
594
|
return string_
|
|
589
|
595
|
|
|
|
596
|
+
|
|
590
|
597
|
def get_random_string(count: int = 16) -> str:
|
|
591
|
598
|
"""
|
|
592
|
599
|
Create random string of count length
|
| ... |
... |
@@ -661,7 +668,7 @@ def allow_sound_notification(account: str, sound_event: str) -> bool: |
|
661
|
668
|
return False
|
|
662
|
669
|
|
|
663
|
670
|
|
|
664
|
|
-def get_optional_features(account: str) -> list[Namespace]:
|
|
|
671
|
+def get_optional_features(account: str) -> list[str]:
|
|
665
|
672
|
features: list[str] = []
|
|
666
|
673
|
|
|
667
|
674
|
if app.settings.get_account_setting(account, 'request_user_data'):
|
| ... |
... |
@@ -684,10 +691,12 @@ def get_optional_features(account: str) -> list[Namespace]: |
|
684
|
691
|
app.plugin_manager.extension_point('update_caps', account, features)
|
|
685
|
692
|
return features
|
|
686
|
693
|
|
|
|
694
|
+
|
|
687
|
695
|
def jid_is_blocked(account: str, jid: str) -> bool:
|
|
688
|
696
|
con = app.connections[account]
|
|
689
|
697
|
return jid in con.get_module('Blocking').blocked
|
|
690
|
698
|
|
|
|
699
|
+
|
|
691
|
700
|
def get_subscription_request_msg(account: Optional[str] = None) -> str:
|
|
692
|
701
|
message = _('I would like to add you to my contact list.')
|
|
693
|
702
|
if account is None:
|
| ... |
... |
@@ -701,8 +710,11 @@ def get_subscription_request_msg(account: Optional[str] = None) -> str: |
|
701
|
710
|
message = _('Hello, I am $name. %s') % message
|
|
702
|
711
|
return Template(message).safe_substitute({'name': app.nicks[account]})
|
|
703
|
712
|
|
|
704
|
|
-def get_retraction_text(account: str, moderator_jid: str,
|
|
|
713
|
+
|
|
|
714
|
+def get_retraction_text(account: str,
|
|
|
715
|
+ moderator_jid: str,
|
|
705
|
716
|
reason: Optional[str]) -> str:
|
|
|
717
|
+
|
|
706
|
718
|
client = app.get_client(account)
|
|
707
|
719
|
contact = client.get_module('Contacts').get_contact(
|
|
708
|
720
|
moderator_jid, groupchat=True)
|
| ... |
... |
@@ -711,12 +723,14 @@ def get_retraction_text(account: str, moderator_jid: str, |
|
711
|
723
|
text += ' ' + _('Reason: %s') % reason
|
|
712
|
724
|
return text
|
|
713
|
725
|
|
|
|
726
|
+
|
|
714
|
727
|
def get_global_proxy() -> Optional[ProxyData]:
|
|
715
|
728
|
proxy_name = app.settings.get('global_proxy')
|
|
716
|
729
|
if not proxy_name:
|
|
717
|
730
|
return None
|
|
718
|
731
|
return get_proxy(proxy_name)
|
|
719
|
732
|
|
|
|
733
|
+
|
|
720
|
734
|
def get_account_proxy(account: str, fallback=True) -> Optional[ProxyData]:
|
|
721
|
735
|
proxy_name = app.settings.get_account_setting(account, 'proxy')
|
|
722
|
736
|
if proxy_name:
|
| ... |
... |
@@ -726,6 +740,7 @@ def get_account_proxy(account: str, fallback=True) -> Optional[ProxyData]: |
|
726
|
740
|
return get_global_proxy()
|
|
727
|
741
|
return None
|
|
728
|
742
|
|
|
|
743
|
+
|
|
729
|
744
|
def get_proxy(proxy_name: str) -> Optional[ProxyData]:
|
|
730
|
745
|
try:
|
|
731
|
746
|
settings = app.settings.get_proxy_settings(proxy_name)
|
| ... |
... |
@@ -741,11 +756,13 @@ def get_proxy(proxy_name: str) -> Optional[ProxyData]: |
|
741
|
756
|
username=username,
|
|
742
|
757
|
password=password)
|
|
743
|
758
|
|
|
|
759
|
+
|
|
744
|
760
|
def version_condition(current_version: str, required_version: str) -> bool:
|
|
745
|
761
|
if V(current_version) < V(required_version):
|
|
746
|
762
|
return False
|
|
747
|
763
|
return True
|
|
748
|
764
|
|
|
|
765
|
+
|
|
749
|
766
|
def get_available_emoticon_themes() -> list[str]:
|
|
750
|
767
|
files: list[Path] = []
|
|
751
|
768
|
for folder in configpaths.get('EMOTICONS').iterdir():
|
| ... |
... |
@@ -761,6 +778,7 @@ def get_available_emoticon_themes() -> list[str]: |
|
761
|
778
|
emoticons_themes += [file.stem for file in files if file.suffix == '.png']
|
|
762
|
779
|
return sorted(emoticons_themes)
|
|
763
|
780
|
|
|
|
781
|
+
|
|
764
|
782
|
def call_counter(func):
|
|
765
|
783
|
def helper(self, restart=False):
|
|
766
|
784
|
if restart:
|
| ... |
... |
@@ -769,6 +787,7 @@ def helper(self, restart=False): |
|
769
|
787
|
return func(self)
|
|
770
|
788
|
return helper
|
|
771
|
789
|
|
|
|
790
|
+
|
|
772
|
791
|
def load_json(path: Path,
|
|
773
|
792
|
key: Optional[str] = None,
|
|
774
|
793
|
default: Optional[Any] = None) -> Any:
|