simple updates
This commit is contained in:
parent
dcde8e3d1e
commit
4e77ddc2de
97 changed files with 16121 additions and 282 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -25,4 +25,5 @@ Toxygen.egg-info
|
|||
*.tox
|
||||
.cache
|
||||
*.db
|
||||
|
||||
*~
|
||||
Makefile
|
||||
|
|
0
.rsync
Normal file
0
.rsync
Normal file
130
_Bugs/segv.err
Normal file
130
_Bugs/segv.err
Normal file
|
@ -0,0 +1,130 @@
|
|||
0
|
||||
TRAC> network.c#1748:net_connect connecting socket 58 to 127.0.0.1:9050
|
||||
TRAC> Messenger.c#2709:do_messenger Friend num in DHT 2 != friend num in msger 14
|
||||
TRAC> Messenger.c#2723:do_messenger F[--: 0] D3385007C28852C5398393E3338E6AABE5F86EF249BF724E7404233207D4D927
|
||||
TRAC> Messenger.c#2723:do_messenger F[--: 1] 98984E104B8A97CC43AF03A27BE159AC1F4CF35FADCC03D6CD5F8D67B5942A56
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 185.87.49.189:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 185.87.49.189:3389 (0: OK) | 010001b95731bd0d...3d
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.221.66.161:443 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.221.66.161:443 (0: OK) | 01000125dd42a101...bb
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 172.93.52.70:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 139.162.110.188:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 37.59.63.150:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 37.97.185.116:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 85.143.221.42:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 104.244.74.69:38445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 168.119.209.10:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 81.169.136.229:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 91.219.59.156:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 46.101.197.175:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 198.199.98.108:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 188.225.9.167:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 5.19.249.240:38296 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 94.156.35.247:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 172.93.52.70:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 172.93.52.70:33445 (0: OK) | 010001ac5d344682...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 139.162.110.188:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 139.162.110.188:33445 (0: OK) | 0100018ba26ebc82...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.59.63.150:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.59.63.150:33445 (0: OK) | 010001253b3f9682...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 130.133.110.14:33445 (0: OK) | 01000182856e0e82...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.97.185.116:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.97.185.116:33445 (0: OK) | 0100012561b97482...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 85.143.221.42:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 85.143.221.42:33445 (0: OK) | 010001558fdd2a82...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 104.244.74.69:38445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 104.244.74.69:38445 (0: OK) | 01000168f44a4596...2d
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 49.12.229.145:3389 (0: OK) | 010001310ce5910d...3d
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 168.119.209.10:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 168.119.209.10:33445 (0: OK) | 010001a877d10a82...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 81.169.136.229:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 81.169.136.229:33445 (0: OK) | 01000151a988e582...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 91.219.59.156:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 91.219.59.156:33445 (0: OK) | 0100015bdb3b9c82...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 46.101.197.175:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 46.101.197.175:3389 (0: OK) | 0100012e65c5af0d...3d
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 198.199.98.108:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 198.199.98.108:33445 (0: OK) | 010001c6c7626c82...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 130.133.110.14:33445 (0: OK) | 01000182856e0e82...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 49.12.229.145:3389 (0: OK) | 010001310ce5910d...3d
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 188.225.9.167:33445 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 188.225.9.167:33445 (0: OK) | 010001bce109a782...a5
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 5.19.249.240:38296 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 5.19.249.240:38296 (0: OK) | 0100010513f9f095...98
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 94.156.35.247:3389 (0: OK) | 0000000000000000...00
|
||||
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 94.156.35.247:3389 (0: OK) | 0100015e9c23f70d...3d
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
|
||||
app.contacts.contacts_manager INFO update_groups_numbers len(groups)={len(groups)}
|
||||
|
||||
Thread 76 "ToxIterateThrea" received signal SIGSEGV, Segmentation fault.
|
||||
[Switching to Thread 0x7ffedcb6b640 (LWP 2950427)]
|
11
_Bugs/tox.abilinski.com.ping
Normal file
11
_Bugs/tox.abilinski.com.ping
Normal file
|
@ -0,0 +1,11 @@
|
|||
ping tox.abilinski.com
|
||||
ping: socket: Address family not supported by protocol
|
||||
PING tox.abilinski.com (172.103.226.229) 56(84) bytes of data.
|
||||
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=1 ttl=48 time=86.6 ms
|
||||
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=2 ttl=48 time=83.1 ms
|
||||
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=3 ttl=48 time=82.9 ms
|
||||
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=4 ttl=48 time=83.4 ms
|
||||
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=5 ttl=48 time=102 ms
|
||||
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=6 ttl=48 time=87.4 ms
|
||||
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=7 ttl=48 time=84.9 ms
|
||||
^C
|
5
toxygen/.pylint.sh
Executable file
5
toxygen/.pylint.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
ROLE=logging
|
||||
/var/local/bin/pydev_pylint.bash -E -f text *py [a-nr-z]*/*py >.pylint.err
|
||||
/var/local/bin/pydev_pylint.bash *py [a-nr-z]*/*py >.pylint.out
|
|
@ -179,9 +179,7 @@ class App:
|
|||
self._uri = uri[4:]
|
||||
self._history = None
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Public methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_trace(self):
|
||||
"""unused"""
|
||||
|
@ -255,9 +253,7 @@ class App:
|
|||
|
||||
return retval
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# App executing
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _execute_app(self):
|
||||
LOG.debug("_execute_app")
|
||||
|
@ -321,9 +317,7 @@ class App:
|
|||
oArgs.log_oFd.close()
|
||||
delattr(oArgs, 'log_oFd')
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# App loading
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _load_base_style(self):
|
||||
if self._args.theme in ['', 'default']: return
|
||||
|
@ -470,9 +464,7 @@ class App:
|
|||
|
||||
return True
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _start_threads(self, initial_start=True):
|
||||
LOG.debug(f"_start_threads before: {threading.enumerate()!r}")
|
||||
|
@ -513,9 +505,7 @@ class App:
|
|||
self._tox.iterate()
|
||||
gevent.sleep(interval / 1000.0)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Profiles
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _select_profile(self):
|
||||
LOG.debug("_select_profile")
|
||||
|
@ -600,9 +590,7 @@ class App:
|
|||
data = data or self._tox.get_savedata()
|
||||
self._profile_manager.save_profile(data)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Other private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _enter_password(self, data):
|
||||
"""
|
||||
|
@ -967,17 +955,18 @@ class App:
|
|||
reply = util_ui.question(text, title)
|
||||
if not reply: return
|
||||
|
||||
if self._args.proxy_type == 0:
|
||||
sProt = "udp4"
|
||||
else:
|
||||
sProt = "tcp4"
|
||||
if lElts is None:
|
||||
if self._args.proxy_type == 0:
|
||||
sProt = "udp4"
|
||||
lElts = self._settings['current_nodes_tcp']
|
||||
lElts = self._settings['current_nodes_udp']
|
||||
else:
|
||||
sProt = "tcp4"
|
||||
lElts = self._settings['current_nodes_tcp']
|
||||
shuffle(lElts)
|
||||
try:
|
||||
ts.bootstrap_iNmapInfo(lElts, self._args, sProt)
|
||||
self._ms.log_console()
|
||||
except Exception as e:
|
||||
LOG.error(f"test_nmap ' +' : {e}")
|
||||
LOG.error('_test_nmap(): ' \
|
||||
|
@ -990,7 +979,7 @@ class App:
|
|||
self._ms.log_console()
|
||||
|
||||
def _test_main(self):
|
||||
from tests.tests_socks import main as tests_main
|
||||
from toxygen_wrapper.tests_wrapper import main as tests_main
|
||||
LOG.debug("_test_main")
|
||||
if not self._tox: return
|
||||
title = 'Extended Test Suite'
|
||||
|
|
|
@ -17,9 +17,7 @@ class Call:
|
|||
|
||||
is_active = property(get_is_active, set_is_active)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Audio
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_in_audio(self):
|
||||
return self._in_audio
|
||||
|
@ -37,9 +35,7 @@ class Call:
|
|||
|
||||
out_audio = property(get_out_audio, set_out_audio)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Video
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_in_video(self):
|
||||
return self._in_video
|
||||
|
|
|
@ -91,9 +91,7 @@ class AV(common.tox_save.ToxAvSave):
|
|||
def __contains__(self, friend_number):
|
||||
return friend_number in self._calls
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Calls
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def __call__(self, friend_number, audio, video):
|
||||
"""Call friend with specified number"""
|
||||
|
@ -189,9 +187,7 @@ class AV(common.tox_save.ToxAvSave):
|
|||
def is_video_call(self, number):
|
||||
return number in self and self._calls[number].in_video
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def start_audio_thread(self):
|
||||
"""
|
||||
|
@ -350,9 +346,7 @@ class AV(common.tox_save.ToxAvSave):
|
|||
self._video_thread = None
|
||||
self._video = None
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Incoming chunks
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def audio_chunk(self, samples, channels_count, rate):
|
||||
"""
|
||||
|
@ -388,9 +382,7 @@ class AV(common.tox_save.ToxAvSave):
|
|||
LOG.debug(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}")
|
||||
self._out_stream.write(samples)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# AV sending
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_audio_data(self, data, count, *largs, **kwargs):
|
||||
pcm = data
|
||||
|
|
|
@ -30,9 +30,7 @@ class CallsManager:
|
|||
def set_toxav(self, toxav):
|
||||
self._callav.set_toxav(toxav)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Events
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_call_started_event(self):
|
||||
return self._call_started_event
|
||||
|
@ -44,9 +42,7 @@ class CallsManager:
|
|||
|
||||
call_finished_event = property(get_call_finished_event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# AV support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def call_click(self, audio=True, video=False):
|
||||
"""User clicked audio button in main window"""
|
||||
|
@ -159,9 +155,7 @@ class CallsManager:
|
|||
if friend_number in self._callav:
|
||||
self._callav.finish_call(friend_number, True)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _get_incoming_call_widget(self, friend_number, text, friend_name):
|
||||
return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name)
|
||||
|
|
|
@ -43,6 +43,6 @@ def download_nodes_list(settings, oArgs):
|
|||
def _save_nodes(nodes, app):
|
||||
if not nodes:
|
||||
return
|
||||
with open(_get_nodes_path(oArgs=app._args), 'wb') as fl:
|
||||
LOG.info("Saving nodes to " +_get_nodes_path())
|
||||
with open(_get_nodes_path(app._args), 'wb') as fl:
|
||||
LOG.info("Saving nodes to " +_get_nodes_path(app._args))
|
||||
fl.write(nodes)
|
||||
|
|
|
@ -35,9 +35,7 @@ class BaseContact:
|
|||
self._avatar_changed_event = event.Event()
|
||||
self.init_widget()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Name - current name or alias of user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
@ -57,9 +55,7 @@ class BaseContact:
|
|||
|
||||
name_changed_event = property(get_name_changed_event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
@ -79,9 +75,7 @@ class BaseContact:
|
|||
|
||||
status_message_changed_event = property(get_status_message_changed_event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
@ -100,31 +94,30 @@ class BaseContact:
|
|||
|
||||
status_changed_event = property(get_status_changed_event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
"""
|
||||
avatar_path = self.get_avatar_path()
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(avatar_path)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self._widget.avatar_label.repaint()
|
||||
self._avatar_changed_event(avatar_path)
|
||||
|
||||
try:
|
||||
avatar_path = self.get_avatar_path()
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(avatar_path)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self._widget.avatar_label.repaint()
|
||||
self._avatar_changed_event(avatar_path)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
def reset_avatar(self, generate_new):
|
||||
avatar_path = self.get_avatar_path()
|
||||
if os.path.isfile(avatar_path) and not avatar_path == self._get_default_avatar_path():
|
||||
|
@ -165,9 +158,7 @@ class BaseContact:
|
|||
|
||||
avatar_changed_event = property(get_avatar_changed_event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Widgets
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def init_widget(self):
|
||||
self._widget.name.setText(self._name)
|
||||
|
@ -177,9 +168,7 @@ class BaseContact:
|
|||
self._widget.connection_status.update(self._status)
|
||||
self.load_avatar()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _get_default_avatar_path():
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
from pydenticon import Generator
|
||||
import hashlib
|
||||
|
||||
from pydenticon import Generator
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Typing notifications
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class BaseTypingNotificationHandler:
|
||||
|
||||
|
@ -30,9 +28,7 @@ class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
|
|||
BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Identicons support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def generate_avatar(public_key):
|
||||
|
|
|
@ -42,9 +42,7 @@ class Contact(basecontact.BaseContact):
|
|||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
|
@ -121,9 +119,7 @@ class Contact(basecontact.BaseContact):
|
|||
|
||||
return TextMessage(message, author, unix_time, message_type, unique_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unsent messages
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_unsent_messages(self):
|
||||
"""
|
||||
|
@ -151,9 +147,7 @@ class Contact(basecontact.BaseContact):
|
|||
# wrapped C/C++ object of type QLabel has been deleted
|
||||
LOG.error(f"Mark as sent: {ex!s}")
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Message deletion
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def delete_message(self, message_id):
|
||||
elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0]
|
||||
|
@ -199,9 +193,7 @@ class Contact(basecontact.BaseContact):
|
|||
self._corr))
|
||||
self._unsaved_messages = len(self.get_unsent_messages())
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Chat history search
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def search_string(self, search_string):
|
||||
self._search_string, self._search_index = search_string, 0
|
||||
|
@ -234,9 +226,7 @@ class Contact(basecontact.BaseContact):
|
|||
return i
|
||||
return None # not found
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Current text - text from message area
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_curr_text(self):
|
||||
return self._curr_text
|
||||
|
@ -246,9 +236,7 @@ class Contact(basecontact.BaseContact):
|
|||
|
||||
curr_text = property(get_curr_text, set_curr_text)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
"""
|
||||
|
@ -264,9 +252,7 @@ class Contact(basecontact.BaseContact):
|
|||
def has_alias(self):
|
||||
return self._alias
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
@ -276,9 +262,7 @@ class Contact(basecontact.BaseContact):
|
|||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages and other actions from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_actions(self):
|
||||
return self._new_actions
|
||||
|
@ -306,9 +290,7 @@ class Contact(basecontact.BaseContact):
|
|||
|
||||
messages = property(get_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's or group's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
@ -318,25 +300,19 @@ class Contact(basecontact.BaseContact):
|
|||
|
||||
number = property(get_number, set_number)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Typing notifications
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_typing_notification_handler(self):
|
||||
return common.BaseTypingNotificationHandler.DEFAULT_HANDLER
|
||||
|
||||
typing_notification_handler = property(get_typing_notification_handler)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Context menu support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_context_menu_generator(self):
|
||||
return BaseContactMenuGenerator(self)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Filtration support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_widget(self, widget):
|
||||
self._widget = widget
|
||||
|
|
|
@ -8,9 +8,7 @@ global LOG
|
|||
import logging
|
||||
LOG = logging.getLogger('app')
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Builder
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _create_menu(menu_name, parent):
|
||||
menu_name = menu_name or ''
|
||||
|
@ -83,9 +81,7 @@ class ContactMenuBuilder:
|
|||
self._actions[self._index] = (text, handler)
|
||||
self._index += 1
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Generators
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class BaseContactMenuGenerator:
|
||||
|
@ -96,9 +92,7 @@ class BaseContactMenuGenerator:
|
|||
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
|
||||
return ContactMenuBuilder().build()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _generate_copy_menu_builder(self, main_screen):
|
||||
copy_menu_builder = ContactMenuBuilder()
|
||||
|
@ -150,9 +144,7 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
|
|||
|
||||
return menu
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _generate_plugins_menu_builder(plugin_loader, number):
|
||||
|
|
|
@ -28,9 +28,7 @@ class ContactProvider(tox_save.ToxSave):
|
|||
self._group_peer_factory = group_peer_factory
|
||||
self._cache = {} # key - contact's public key, value - contact instance
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friends
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_friend_by_number(self, friend_number):
|
||||
try:
|
||||
|
@ -60,9 +58,7 @@ class ContactProvider(tox_save.ToxSave):
|
|||
|
||||
return list(friends)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Groups
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_all_groups(self):
|
||||
"""from callbacks"""
|
||||
|
@ -131,9 +127,7 @@ class ContactProvider(tox_save.ToxSave):
|
|||
|
||||
return group
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Group peers
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_all_group_peers(self):
|
||||
return list()
|
||||
|
@ -148,16 +142,12 @@ class ContactProvider(tox_save.ToxSave):
|
|||
|
||||
return self._get_group_peer(group, peer)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# All contacts
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_all(self):
|
||||
return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Caching
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def clear_cache(self):
|
||||
self._cache.clear()
|
||||
|
@ -166,9 +156,7 @@ class ContactProvider(tox_save.ToxSave):
|
|||
if contact_public_key in self._cache:
|
||||
del self._cache[contact_public_key]
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _get_contact_from_cache(self, public_key):
|
||||
return self._cache[public_key] if public_key in self._cache else None
|
||||
|
|
|
@ -105,17 +105,13 @@ class ContactsManager(ToxSave):
|
|||
|
||||
return self._contacts[self._active_contact].tox_id == contact.tox_id
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Reconnection support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def reset_contacts_statuses(self):
|
||||
for contact in self._contacts:
|
||||
contact.status = None
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Work with active friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_active(self):
|
||||
return self._active_contact
|
||||
|
@ -204,9 +200,7 @@ class ContactsManager(ToxSave):
|
|||
def is_active_a_group_chat_peer(self):
|
||||
return type(self.get_curr_contact()) is GroupPeerContact
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Filtration
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def filtration_and_sorting(self, sorting=0, filter_str=''):
|
||||
"""
|
||||
|
@ -286,9 +280,7 @@ class ContactsManager(ToxSave):
|
|||
"""
|
||||
self.filtration_and_sorting(self._sorting, self._filter_string)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Contact getters
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_friend_by_number(self, number):
|
||||
return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0]
|
||||
|
@ -324,9 +316,7 @@ class ContactsManager(ToxSave):
|
|||
def is_active_online(self):
|
||||
return self._active_contact + 1 and self.get_curr_contact().status is not None
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Work with friends (remove, block, set alias, get public key)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_alias(self, num):
|
||||
"""
|
||||
|
@ -411,9 +401,7 @@ class ContactsManager(ToxSave):
|
|||
self.add_friend(tox_id)
|
||||
self.save_profile()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Groups support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_group_chats(self):
|
||||
return list(filter(lambda c: type(c) is GroupChat, self._contacts))
|
||||
|
@ -423,7 +411,7 @@ class ContactsManager(ToxSave):
|
|||
group = self._contact_provider.get_group_by_number(group_number)
|
||||
if group is None:
|
||||
LOG.warn(f"CM.add_group: NULL group from group_number={group_number}")
|
||||
elif group < 0:
|
||||
elif type(group) == int and group < 0:
|
||||
LOG.warn(f"CM.add_group: NO group from group={group} group_number={group_number}")
|
||||
else:
|
||||
LOG.info(f"CM.add_group: Adding group {group._name}")
|
||||
|
@ -441,9 +429,7 @@ class ContactsManager(ToxSave):
|
|||
num = self._contacts.index(group)
|
||||
self._delete_contact(num)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Groups private messaging
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def add_group_peer(self, group, peer):
|
||||
contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
|
||||
|
@ -485,9 +471,7 @@ class ContactsManager(ToxSave):
|
|||
|
||||
return suggested_names[0]
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend requests
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_friend_request(self, sToxPkOrId, message):
|
||||
"""
|
||||
|
@ -551,9 +535,7 @@ class ContactsManager(ToxSave):
|
|||
def can_send_typing_notification(self):
|
||||
return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Contacts numbers update
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def update_friends_numbers(self):
|
||||
for friend in self._contact_provider.get_all_friends():
|
||||
|
@ -581,9 +563,7 @@ class ContactsManager(ToxSave):
|
|||
for group in groups:
|
||||
group.remove_all_peers_except_self()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _load_contacts(self):
|
||||
self._load_friends()
|
||||
|
@ -608,9 +588,7 @@ class ContactsManager(ToxSave):
|
|||
def _load_groups(self):
|
||||
self._contacts.extend(self._contact_provider.get_all_groups())
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Current contact subscriptions
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _subscribe_to_events(self, contact):
|
||||
contact.name_changed_event.add_callback(self._current_contact_name_changed)
|
||||
|
|
|
@ -14,9 +14,7 @@ class Friend(contact.Contact):
|
|||
self._receipts = 0
|
||||
self._typing_notification_handler = common.FriendTypingNotificationHandler(number)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def insert_inline(self, before_message_id, inline):
|
||||
"""
|
||||
|
@ -52,23 +50,17 @@ class Friend(contact.Contact):
|
|||
self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id),
|
||||
self._corr))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Full status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_full_status(self):
|
||||
return self._status_message
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Typing notifications
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_typing_notification_handler(self):
|
||||
return self._typing_notification_handler
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Context menu support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_context_menu_generator(self):
|
||||
return FriendMenuGenerator(self)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from common.tox_save import ToxSave
|
||||
from contacts.friend import Friend
|
||||
from common.tox_save import ToxSave
|
||||
|
||||
|
||||
class FriendFactory(ToxSave):
|
||||
|
@ -32,9 +32,7 @@ class FriendFactory(ToxSave):
|
|||
|
||||
return friend
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _create_friend_item(self):
|
||||
"""
|
||||
|
|
|
@ -35,9 +35,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||
def get_context_menu_generator(self):
|
||||
return GroupMenuGenerator(self)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Properties
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_is_private(self):
|
||||
return self._is_private
|
||||
|
@ -63,9 +61,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||
|
||||
peers_limit = property(get_peers_limit, set_peers_limit)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Peers methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_self_peer(self):
|
||||
return self._peers[0]
|
||||
|
@ -156,9 +152,7 @@ class GroupChat(contact.Contact, ToxSave):
|
|||
#
|
||||
bans = property(get_bans)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _get_default_avatar_path():
|
||||
|
|
|
@ -43,9 +43,7 @@ class GroupFactory(ToxSave):
|
|||
|
||||
return group
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _create_group_item(self):
|
||||
"""
|
||||
|
@ -55,7 +53,7 @@ class GroupFactory(ToxSave):
|
|||
return self._items_factory.create_contact_item()
|
||||
|
||||
def _get_group_number_by_chat_id(self, chat_id):
|
||||
for i in range(self._tox.group_get_number_groups()):
|
||||
for i in range(self._tox.group_get_number_groups()+100):
|
||||
if self._tox.group_get_chat_id(i) == chat_id:
|
||||
return i
|
||||
return -1
|
||||
|
|
|
@ -35,9 +35,7 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
|||
self._waiting_for_reconnection = False
|
||||
self._timer = None
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Edit current user's data
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def change_status(self):
|
||||
"""
|
||||
|
@ -72,9 +70,7 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
|||
self._sToxId = self._tox.self_get_address()
|
||||
return self._sToxId
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Reset
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def restart(self):
|
||||
"""
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from os import chdir, remove, rename
|
||||
from os.path import basename, dirname, exists, getsize
|
||||
from os.path import basename, getsize, exists, dirname
|
||||
from time import time
|
||||
|
||||
from common.event import Event
|
||||
from middleware.threads import invoke_in_main_thread
|
||||
from wrapper.tox import Tox
|
||||
from wrapper.toxcore_enums_and_consts import TOX_FILE_CONTROL, TOX_FILE_KIND
|
||||
from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
||||
|
||||
FILE_TRANSFER_STATE = {
|
||||
'RUNNING': 0,
|
||||
|
@ -126,9 +126,7 @@ class FileTransfer:
|
|||
def _finished(self):
|
||||
self._finished_event(self._friend_number, self._file_number)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Send file
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class SendTransfer(FileTransfer):
|
||||
|
@ -223,9 +221,7 @@ class SendFromFileBuffer(SendTransfer):
|
|||
chdir(dirname(self._path))
|
||||
remove(self._path)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Receive file
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ReceiveTransfer(FileTransfer):
|
||||
|
|
|
@ -33,9 +33,7 @@ class FileTransfersHandler(ToxSave):
|
|||
self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
|
||||
self._settings.save()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def incoming_file_transfer(self, friend_number, file_number, size, file_name):
|
||||
"""
|
||||
|
@ -255,9 +253,7 @@ class FileTransfersHandler(ToxSave):
|
|||
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()]
|
||||
self.cancel_transfer(friend_num, file_num, True)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_avatar(self, friend_number, avatar_path=None):
|
||||
"""
|
||||
|
@ -297,9 +293,7 @@ class FileTransfersHandler(ToxSave):
|
|||
for friend in filter(self._is_friend_online, friends):
|
||||
self.send_avatar(friend.number)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _is_friend_online(self, friend_number):
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
|
|
|
@ -75,9 +75,7 @@ class FileTransfersMessagesService:
|
|||
|
||||
return tm
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _is_friend_active(self, friend_number):
|
||||
if not self._contacts_manager.is_active_a_friend():
|
||||
|
|
|
@ -15,9 +15,7 @@ class GroupChatPeer:
|
|||
self._is_muted = is_muted
|
||||
# unused?
|
||||
self._kind = 'grouppeer'
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Readonly properties
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_id(self):
|
||||
return self._peer_id
|
||||
|
@ -34,9 +32,7 @@ class GroupChatPeer:
|
|||
|
||||
is_current_user = property(get_is_current_user)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Read-write properties
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
|
|
@ -31,9 +31,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
for group in self._get_all_groups():
|
||||
group.set_tox(tox)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Groups creation
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def create_new_gc(self, name, privacy_state, nick, status):
|
||||
try:
|
||||
|
@ -74,9 +72,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
group.status = constants.TOX_USER_STATUS['NONE']
|
||||
self._contacts_manager.update_filtration()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Groups reconnect and leaving
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def leave_group(self, group_number):
|
||||
if type(group_number) == int:
|
||||
|
@ -95,9 +91,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
group.status = constants.TOX_USER_STATUS['NONE']
|
||||
self._clear_peers_list(group)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Group invites
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def invite_friend(self, friend_number, group_number):
|
||||
if self._tox.friend_get_connection_status(friend_number) == TOX_CONNECTION['NONE']:
|
||||
|
@ -142,9 +136,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
|
||||
group_invites_count = property(get_group_invites_count)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Group info methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def update_group_info(self, group):
|
||||
group.name = self._tox.group_get_name(group.number)
|
||||
|
@ -190,9 +182,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
self._tox.group_founder_set_privacy_state(group.number, privacy_state)
|
||||
group.is_private = is_private
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Peers list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def generate_peers_list(self):
|
||||
if not self._contacts_manager.is_active_a_group():
|
||||
|
@ -210,9 +200,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
self._screen = widgets_factory.create_self_peer_screen_window(group)
|
||||
self._screen.show()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Peers actions
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_new_peer_role(self, group, peer, role):
|
||||
self._tox.group_mod_set_role(group.number, peer.id, role)
|
||||
|
@ -231,9 +219,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
self_peer.status = status
|
||||
self.generate_peers_list()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Bans support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def show_bans_list(self, group):
|
||||
return
|
||||
|
@ -250,9 +236,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
def cancel_ban(self, group_number, ban_id):
|
||||
self._tox.group_mod_remove_ban(group_number, ban_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _add_new_group_by_number(self, group_number):
|
||||
LOG.debug(f"_add_new_group_by_number group_number={group_number}")
|
||||
|
|
|
@ -3,9 +3,7 @@ from wrapper.toxcore_enums_and_consts import *
|
|||
from ui.widgets import *
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Builder
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class PeerListBuilder:
|
||||
|
@ -63,9 +61,7 @@ class PeerListBuilder:
|
|||
self._peers[self._index] = peer
|
||||
self._index += 1
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Generators
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class PeersListGenerator:
|
||||
|
|
|
@ -51,9 +51,7 @@ class Database:
|
|||
os.remove(path)
|
||||
LOG.info('Db opened: ' +path)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Public methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def save(self):
|
||||
if self._toxes.has_password():
|
||||
|
@ -178,9 +176,7 @@ class Database:
|
|||
|
||||
return Database.MessageGetter(self._path, tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Messages loading
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class MessageGetter:
|
||||
|
||||
|
@ -225,9 +221,7 @@ class Database:
|
|||
def _disconnect(self):
|
||||
self._db.close()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _connect(self):
|
||||
return connect(self._path, timeout=TIMEOUT)
|
||||
|
|
|
@ -22,9 +22,7 @@ class History:
|
|||
def set_contacts_manager(self, contacts_manager):
|
||||
self._contacts_manager = contacts_manager
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def save_history(self):
|
||||
"""
|
||||
|
@ -128,9 +126,7 @@ class History:
|
|||
|
||||
return generator.generate()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Items creation
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _create_message_item(self, message):
|
||||
return self._messages_items_factory.create_message_item(message, False)
|
||||
|
|
|
@ -192,8 +192,8 @@ def main_parser(_=None, iMode=2):
|
|||
parser.add_argument('--auto_accept_path', '--auto-accept-path', type=str,
|
||||
default=os.path.join(os.environ['HOME'], 'Downloads'),
|
||||
help="auto_accept_path")
|
||||
parser.add_argument('--mode', type=int, default=iMode,
|
||||
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
|
||||
# parser.add_argument('--mode', type=int, default=iMode,
|
||||
# help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
|
||||
parser.add_argument('--font', type=str, default="Courier",
|
||||
help='Message font')
|
||||
parser.add_argument('--message_font_size', type=int, default=15,
|
||||
|
|
|
@ -38,9 +38,7 @@ class Messenger(tox_save.ToxSave):
|
|||
|
||||
return contact.get_last_message_text()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Messaging - friends
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def new_message(self, friend_number, message_type, message):
|
||||
"""
|
||||
|
@ -140,9 +138,7 @@ class Messenger(tox_save.ToxSave):
|
|||
except Exception as ex:
|
||||
LOG.warn('Sending pending messages failed with ' + str(ex))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Messaging - groups
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_message_to_group(self, text, message_type, group_number=None):
|
||||
if group_number is None:
|
||||
|
@ -183,9 +179,7 @@ class Messenger(tox_save.ToxSave):
|
|||
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
|
||||
self._add_message(text_message, group)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Messaging - group peers
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None):
|
||||
if group_number is None or peer_id is None:
|
||||
|
@ -238,17 +232,13 @@ class Messenger(tox_save.ToxSave):
|
|||
return
|
||||
self._add_message(text_message, group_peer_contact)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Message receipts
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def receipt(self, friend_number, message_id):
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
friend.mark_as_sent(message_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Typing notifications
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_typing(self, typing):
|
||||
"""
|
||||
|
@ -266,9 +256,7 @@ class Messenger(tox_save.ToxSave):
|
|||
if self._contacts_manager.is_friend_active(friend_number):
|
||||
self._screen.typing.setVisible(typing)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Contact info updated
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def new_friend_name(self, friend, old_name, new_name):
|
||||
if old_name == new_name or friend.has_alias():
|
||||
|
@ -279,9 +267,7 @@ class Messenger(tox_save.ToxSave):
|
|||
friend.actions = True
|
||||
self._add_info_message(friend.number, message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _split_message(message):
|
||||
|
|
|
@ -46,9 +46,7 @@ def bTooSoon(key, sSlot, fSec=10.0):
|
|||
|
||||
# TODO: refactoring. Use contact provider instead of manager
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - current user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
global iBYTES
|
||||
iBYTES=0
|
||||
|
@ -95,9 +93,7 @@ def self_connection_status(tox, profile):
|
|||
return wrapped
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - friends
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def friend_status(contacts_manager, file_transfer_handler, profile, settings):
|
||||
|
@ -235,9 +231,7 @@ def friend_read_receipt(messenger):
|
|||
return wrapped
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - file transfers
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings):
|
||||
|
@ -312,9 +306,7 @@ def file_recv_control(file_transfer_handler):
|
|||
|
||||
return wrapped
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - custom packets
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def lossless_packet(plugin_loader):
|
||||
|
@ -339,9 +331,7 @@ def lossy_packet(plugin_loader):
|
|||
return wrapped
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - audio
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def call_state(calls_manager):
|
||||
def wrapped(iToxav, friend_number, mask, user_data):
|
||||
|
@ -384,9 +374,7 @@ def callback_audio(calls_manager):
|
|||
|
||||
return wrapped
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - video
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
|
||||
|
@ -444,9 +432,7 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
|
|||
LOG_ERROR(f"video_receive_frame {ex} #{friend_number}")
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - groups
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def group_message(window, tray, tox, messenger, settings, profile):
|
||||
|
@ -714,9 +700,7 @@ def group_privacy_state(contacts_provider):
|
|||
|
||||
return wrapped
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - initialization
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
|
||||
|
|
|
@ -28,9 +28,7 @@ def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
|||
iLAST_CONN = 0
|
||||
iLAST_DELTA = 60
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Base threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class BaseThread(threading.Thread):
|
||||
|
||||
|
@ -74,9 +72,7 @@ class BaseQThread(QtCore.QThread):
|
|||
else:
|
||||
LOG_WARN(f"BaseQThread {self.name} BLOCKED")
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Toxcore threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class InitThread(BaseThread):
|
||||
|
||||
|
@ -163,9 +159,7 @@ class ToxAVIterateThread(BaseQThread):
|
|||
sleep(self._toxav.iteration_interval() / 1000)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers thread
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class FileTransfersThread(BaseQThread):
|
||||
|
||||
|
@ -203,9 +197,7 @@ def execute(func, *args, **kwargs):
|
|||
_thread.execute(func, *args, **kwargs)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Invoking in main thread
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||
|
|
27
toxygen/plugins/README.md
Normal file
27
toxygen/plugins/README.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Plugins
|
||||
|
||||
Repo with plugins for [Toxygen](https://macaw.me/emdee/toxygen/)
|
||||
|
||||
For more info visit [plugins.md](https://macaw.me/emdee/toxygen/blob/master/docs/plugins.md) and [plugin_api.md](https://github.com/toxygen-project[/toxygen/blob/master/docs/plugin-api.md)
|
||||
|
||||
# Plugins list:
|
||||
|
||||
- ToxId - share your Tox ID and copy friend's Tox ID easily.
|
||||
- MarqueeStatus - create ticker from your status message.
|
||||
- BirthDay - get notifications on your friends' birthdays.
|
||||
- Bot - bot which can communicate with your friends when you are away.
|
||||
- SearchPlugin - select text in message and find it in search engine.
|
||||
- AutoAwayStatusLinux - sets "Away" status when user is inactive (Linux only).
|
||||
- AutoAwayStatusWindows - sets "Away" status when user is inactive (Windows only).
|
||||
- Chess - play chess with your friends using Tox.
|
||||
- Garland - changes your status like it's garland.
|
||||
- AutoAnswer - calls auto answering.
|
||||
- uToxInlineSending - send inlines with the same name as uTox does.
|
||||
- AvatarEncryption - encrypt all avatars using profile password
|
||||
|
||||
## Hard fork
|
||||
|
||||
Not all of these are working...
|
||||
|
||||
Work on this project is suspended until the
|
||||
[MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me!
|
83
toxygen/plugins/ae.py
Normal file
83
toxygen/plugins/ae.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import plugin_super_class
|
||||
import json
|
||||
from user_data import settings
|
||||
import os
|
||||
from bootstrap.bootstrap import get_user_config_path
|
||||
|
||||
class AvatarEncryption(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(AvatarEncryption, self).__init__('AvatarEncryption', 'ae', *args)
|
||||
self._path = os.path.join(get_user_config_path(), 'avatars')
|
||||
self._app = args[0]
|
||||
self._profile = self._app._ms._profile
|
||||
self._window = None
|
||||
#was self._contacts = self._profile._contacts[:]
|
||||
self._contacts = self._profile._contacts_provider.get_all_friends()
|
||||
|
||||
def get_description(self):
|
||||
return QApplication.translate("AvatarEncryption", 'Encrypt all avatars using profile password.')
|
||||
|
||||
def close(self):
|
||||
if not self._encrypt_save.has_password():
|
||||
return
|
||||
i, data = 1, {}
|
||||
|
||||
self.save_contact_avatar(data, self._profile, 0)
|
||||
for friend in self._contacts:
|
||||
self.save_contact_avatar(data, friend, i)
|
||||
i += 1
|
||||
self.save_settings(json.dumps(data))
|
||||
|
||||
def start(self):
|
||||
if not self._encrypt_save.has_password():
|
||||
return
|
||||
data = json.loads(self.load_settings())
|
||||
|
||||
self.load_contact_avatar(data, self._profile)
|
||||
for friend in self._contacts:
|
||||
self.load_contact_avatar(data, friend)
|
||||
self._profile.update()
|
||||
|
||||
def save_contact_avatar(self, data, contact, i):
|
||||
tox_id = contact.tox_id[:64]
|
||||
data[str(tox_id)] = str(i)
|
||||
path = os.path.join(self._path, tox_id + '.png')
|
||||
if os.path.isfile(path):
|
||||
with open(path, 'rb') as fl:
|
||||
avatar = fl.read()
|
||||
encr_avatar = self._encrypt_save.pass_encrypt(avatar)
|
||||
with open(os.path.join(self._path, self._settings.name + '_' + str(i) + '.png'), 'wb') as fl:
|
||||
fl.write(encr_avatar)
|
||||
os.remove(path)
|
||||
|
||||
def load_contact_avatar(self, data, contact):
|
||||
tox_id = str(contact.tox_id[:64])
|
||||
if tox_id not in data:
|
||||
return
|
||||
path = os.path.join(self._path, self._settings.name + '_' + data[tox_id] + '.png')
|
||||
if os.path.isfile(path):
|
||||
with open(path, 'rb') as fl:
|
||||
avatar = fl.read()
|
||||
decr_avatar = self._encrypt_save.pass_decrypt(avatar)
|
||||
with open(os.path.join(self._path, str(tox_id) + '.png'), 'wb') as fl:
|
||||
fl.write(decr_avatar)
|
||||
os.remove(path)
|
||||
contact.load_avatar()
|
||||
|
||||
def load_settings(self):
|
||||
try:
|
||||
with open(plugin_super_class.path_to_data(self._short_name) + self._settings.name + '.json', 'rb') as fl:
|
||||
data = fl.read()
|
||||
return str(self._encrypt_save.pass_decrypt(data), 'utf-8') if data else '{}'
|
||||
except:
|
||||
return '{}'
|
||||
|
||||
def save_settings(self, data):
|
||||
try:
|
||||
data = self._encrypt_save.pass_encrypt(bytes(data, 'utf-8'))
|
||||
with open(plugin_super_class.path_to_data(self._short_name) + self._settings.name + '.json', 'wb') as fl:
|
||||
fl.write(data)
|
||||
except:
|
||||
pass
|
111
toxygen/plugins/awayl.py
Normal file
111
toxygen/plugins/awayl.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import plugin_super_class
|
||||
import threading
|
||||
import time
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from subprocess import check_output
|
||||
import json
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||
|
||||
def __init__(self, fn, *args, **kwargs):
|
||||
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
class Invoker(QtCore.QObject):
|
||||
|
||||
def event(self, event):
|
||||
event.fn(*event.args, **event.kwargs)
|
||||
return True
|
||||
|
||||
_invoker = Invoker()
|
||||
|
||||
|
||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||
|
||||
|
||||
class AutoAwayStatusLinux(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__('AutoAwayStatusLinux', 'awayl', *args)
|
||||
self._thread = None
|
||||
self._exec = None
|
||||
self._active = False
|
||||
self._time = json.loads(self.load_settings())['time']
|
||||
self._prev_status = 0
|
||||
self._app = args[0]
|
||||
self._profile=self._app._ms._profile
|
||||
self._window = None
|
||||
|
||||
def get_description(self):
|
||||
return QApplication.translate("AutoAwayStatusLinux", 'sets "Away" status when user is inactive (Linux only).')
|
||||
|
||||
def close(self):
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
self._exec = False
|
||||
if self._active:
|
||||
self._thread.join()
|
||||
|
||||
def start(self):
|
||||
self._exec = True
|
||||
self._thread = threading.Thread(target=self.loop)
|
||||
self._thread.start()
|
||||
|
||||
def save(self):
|
||||
self.save_settings('{"time": ' + str(self._time) + '}')
|
||||
|
||||
def change_status(self, status=1):
|
||||
if self._profile.status in (0, 2):
|
||||
self._prev_status = self._profile.status
|
||||
if status is not None:
|
||||
invoke_in_main_thread(self._profile.set_status, status)
|
||||
|
||||
def get_window(self):
|
||||
inst = self
|
||||
|
||||
class Window(QtWidgets.QWidget):
|
||||
def __init__(self):
|
||||
super(Window, self).__init__()
|
||||
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
|
||||
self.label = QtWidgets.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(20, 0, 310, 35))
|
||||
self.label.setText(QtWidgets.QApplication.translate("AutoAwayStatusLinux", "Auto away time in minutes\n(0 - to disable)"))
|
||||
self.time = QtWidgets.QLineEdit(self)
|
||||
self.time.setGeometry(QtCore.QRect(20, 40, 310, 25))
|
||||
self.time.setText(str(inst._time))
|
||||
self.setWindowTitle("AutoAwayStatusLinux")
|
||||
self.ok = QtWidgets.QPushButton(self)
|
||||
self.ok.setGeometry(QtCore.QRect(20, 70, 310, 25))
|
||||
self.ok.setText(
|
||||
QtWidgets.QApplication.translate("AutoAwayStatusLinux", "Save"))
|
||||
self.ok.clicked.connect(self.update)
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
t = int(self.time.text())
|
||||
except:
|
||||
t = 0
|
||||
inst._time = t
|
||||
inst.save()
|
||||
self.close()
|
||||
|
||||
return Window()
|
||||
|
||||
def loop(self):
|
||||
self._active = True
|
||||
while self._exec:
|
||||
time.sleep(5)
|
||||
d = check_output(['xprintidle'])
|
||||
d = int(d) // 1000
|
||||
if self._time:
|
||||
if d > 60 * self._time:
|
||||
self.change_status()
|
||||
elif self._profile.status == 1:
|
||||
self.change_status(self._prev_status)
|
115
toxygen/plugins/awayw.py.windows
Normal file
115
toxygen/plugins/awayw.py.windows
Normal file
|
@ -0,0 +1,115 @@
|
|||
import plugin_super_class
|
||||
import threading
|
||||
import time
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from ctypes import Structure, windll, c_uint, sizeof, byref
|
||||
import json
|
||||
|
||||
|
||||
class LASTINPUTINFO(Structure):
|
||||
_fields_ = [('cbSize', c_uint), ('dwTime', c_uint)]
|
||||
|
||||
|
||||
def get_idle_duration():
|
||||
lastInputInfo = LASTINPUTINFO()
|
||||
lastInputInfo.cbSize = sizeof(lastInputInfo)
|
||||
windll.user32.GetLastInputInfo(byref(lastInputInfo))
|
||||
millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
|
||||
return millis / 1000.0
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||
|
||||
def __init__(self, fn, *args, **kwargs):
|
||||
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
class Invoker(QtCore.QObject):
|
||||
|
||||
def event(self, event):
|
||||
event.fn(*event.args, **event.kwargs)
|
||||
return True
|
||||
|
||||
_invoker = Invoker()
|
||||
|
||||
|
||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||
|
||||
|
||||
class AutoAwayStatusWindows(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__('AutoAwayStatusWindows', 'awayw', *args)
|
||||
self._thread = None
|
||||
self._exec = None
|
||||
self._active = False
|
||||
self._time = json.loads(self.load_settings())['time']
|
||||
self._prev_status = 0
|
||||
|
||||
def close(self):
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
self._exec = False
|
||||
if self._active:
|
||||
self._thread.join()
|
||||
|
||||
def start(self):
|
||||
self._exec = True
|
||||
self._thread = threading.Thread(target=self.loop)
|
||||
self._thread.start()
|
||||
|
||||
def save(self):
|
||||
self.save_settings('{"time": ' + str(self._time) + '}')
|
||||
|
||||
def change_status(self, status=1):
|
||||
if self._profile.status != 1:
|
||||
self._prev_status = self._profile.status
|
||||
invoke_in_main_thread(self._profile.set_status, status)
|
||||
|
||||
def get_window(self):
|
||||
inst = self
|
||||
|
||||
class Window(QtWidgets.QWidget):
|
||||
def __init__(self):
|
||||
super(Window, self).__init__()
|
||||
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
|
||||
self.label = QtWidgets.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(20, 0, 310, 35))
|
||||
self.label.setText(QtWidgets.QApplication.translate("AutoAwayStatusWindows", "Auto away time in minutes\n(0 - to disable)"))
|
||||
self.time = QtWidgets.QLineEdit(self)
|
||||
self.time.setGeometry(QtCore.QRect(20, 40, 310, 25))
|
||||
self.time.setText(str(inst._time))
|
||||
self.setWindowTitle("AutoAwayStatusWindows")
|
||||
self.ok = QtWidgets.QPushButton(self)
|
||||
self.ok.setGeometry(QtCore.QRect(20, 70, 310, 25))
|
||||
self.ok.setText(
|
||||
QtWidgets.QApplication.translate("AutoAwayStatusWindows", "Save"))
|
||||
self.ok.clicked.connect(self.update)
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
t = int(self.time.text())
|
||||
except:
|
||||
t = 0
|
||||
inst._time = t
|
||||
inst.save()
|
||||
self.close()
|
||||
|
||||
return Window()
|
||||
|
||||
def loop(self):
|
||||
self._active = True
|
||||
while self._exec:
|
||||
time.sleep(5)
|
||||
d = get_idle_duration()
|
||||
if self._time:
|
||||
if d > 60 * self._time:
|
||||
self.change_status()
|
||||
elif self._profile.status == 1:
|
||||
self.change_status(self._prev_status)
|
2
toxygen/plugins/bday.pro
Normal file
2
toxygen/plugins/bday.pro
Normal file
|
@ -0,0 +1,2 @@
|
|||
SOURCES = bday.py
|
||||
TRANSLATIONS = bday/en_GB.ts bday/en_US.ts bday/ru_RU.ts
|
95
toxygen/plugins/bday.py
Normal file
95
toxygen/plugins/bday.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
import plugin_super_class
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
import json
|
||||
import importlib
|
||||
|
||||
|
||||
class BirthDay(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
# Constructor. In plugin __init__ should take only 1 last argument
|
||||
super(BirthDay, self).__init__('BirthDay', 'bday', *args)
|
||||
self._data = json.loads(self.load_settings())
|
||||
self._datetime = importlib.import_module('datetime')
|
||||
self._timers = []
|
||||
self._app = args[0]
|
||||
self._profile=self._app._ms._profile
|
||||
self._window = None
|
||||
|
||||
def start(self):
|
||||
now = self._datetime.datetime.now()
|
||||
today = {}
|
||||
x = self._profile.tox_id[:64]
|
||||
for key in self._data:
|
||||
if key != x and key != 'send_date':
|
||||
arr = self._data[key].split('.')
|
||||
if int(arr[0]) == now.day and int(arr[1]) == now.month:
|
||||
today[key] = now.year - int(arr[2])
|
||||
if len(today):
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
title = QtWidgets.QApplication.translate('BirthDay', "Birthday!")
|
||||
msgbox.setWindowTitle(title)
|
||||
text = ', '.join(self._profile.get_friend_by_number(self._tox.friend_by_public_key(x)).name + ' ({})'.format(today[x]) for x in today)
|
||||
msgbox.setText('Birthdays: ' + text)
|
||||
msgbox.exec_()
|
||||
|
||||
def get_description(self):
|
||||
return QApplication.translate("BirthDay", "Send and get notifications on your friends' birthdays.")
|
||||
|
||||
def get_window(self):
|
||||
inst = self
|
||||
x = self._profile.tox_id[:64]
|
||||
|
||||
class Window(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super(Window, self).__init__()
|
||||
self.setGeometry(QtCore.QRect(450, 300, 350, 150))
|
||||
self.send = QtWidgets.QCheckBox(self)
|
||||
self.send.setGeometry(QtCore.QRect(20, 10, 310, 25))
|
||||
self.send.setText(QtWidgets.QApplication.translate('BirthDay', "Send my birthday date to contacts"))
|
||||
self.setWindowTitle(QtWidgets.QApplication.translate('BirthDay', "Birthday"))
|
||||
self.send.clicked.connect(self.update)
|
||||
self.send.setChecked(inst._data['send_date'])
|
||||
self.date = QtWidgets.QLineEdit(self)
|
||||
self.date.setGeometry(QtCore.QRect(20, 50, 310, 25))
|
||||
self.date.setPlaceholderText(QtWidgets.QApplication.translate('BirthDay', "Date in format dd.mm.yyyy"))
|
||||
self.set_date = QtWidgets.QPushButton(self)
|
||||
self.set_date.setGeometry(QtCore.QRect(20, 90, 310, 25))
|
||||
self.set_date.setText(QtWidgets.QApplication.translate('BirthDay', "Save date"))
|
||||
self.set_date.clicked.connect(self.save_curr_date)
|
||||
self.date.setText(inst._data[x] if x in inst._data else '')
|
||||
|
||||
def save_curr_date(self):
|
||||
inst._data[x] = self.date.text()
|
||||
inst.save_settings(json.dumps(inst._data))
|
||||
self.close()
|
||||
|
||||
def update(self):
|
||||
inst._data['send_date'] = self.send.isChecked()
|
||||
inst.save_settings(json.dumps(inst._data))
|
||||
|
||||
if not hasattr(self, '_window') or not self._window:
|
||||
self._window = Window()
|
||||
return self._window
|
||||
|
||||
def lossless_packet(self, data, friend_number):
|
||||
if len(data):
|
||||
friend = self._profile.get_friend_by_number(friend_number)
|
||||
self._data[friend.tox_id] = data
|
||||
self.save_settings(json.dumps(self._data))
|
||||
elif self._data['send_date'] and self._profile.tox_id[:64] in self._data:
|
||||
self.send_lossless(self._data[self._profile.tox_id[:64]], friend_number)
|
||||
|
||||
def friend_connected(self, friend_number):
|
||||
timer = QtCore.QTimer()
|
||||
timer.timeout.connect(lambda: self.timer(friend_number))
|
||||
timer.start(10000)
|
||||
self._timers.append(timer)
|
||||
|
||||
def timer(self, friend_number):
|
||||
timer = self._timers.pop()
|
||||
timer.stop()
|
||||
if self._profile.get_friend_by_number(friend_number).tox_id not in self._data:
|
||||
self.send_lossless('', friend_number)
|
||||
|
81
toxygen/plugins/bot.py
Normal file
81
toxygen/plugins/bot.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
import plugin_super_class
|
||||
from PyQt5 import QtCore
|
||||
import time
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||
|
||||
def __init__(self, fn, *args, **kwargs):
|
||||
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
class Invoker(QtCore.QObject):
|
||||
|
||||
def event(self, event):
|
||||
event.fn(*event.args, **event.kwargs)
|
||||
return True
|
||||
|
||||
_invoker = Invoker()
|
||||
|
||||
|
||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||
|
||||
|
||||
class Bot(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(Bot, self).__init__('Bot', 'bot', *args)
|
||||
self._callback = None
|
||||
self._mode = 0
|
||||
self._message = "I'm away, will back soon"
|
||||
self._timer = QtCore.QTimer()
|
||||
self._timer.timeout.connect(self.initialize)
|
||||
self._app = args[0]
|
||||
self._profile=self._app._ms._profile
|
||||
self._window = None
|
||||
|
||||
def get_description(self):
|
||||
return QApplication.translate("Bot", 'Plugin to answer bot to your friends.')
|
||||
|
||||
def start(self):
|
||||
self._timer.start(10000)
|
||||
|
||||
def command(self, command):
|
||||
if command.startswith('mode '):
|
||||
self._mode = int(command.split(' ')[-1])
|
||||
elif command.startswith('message '):
|
||||
self._message = command[8:]
|
||||
else:
|
||||
super().command(command)
|
||||
|
||||
def initialize(self):
|
||||
self._timer.stop()
|
||||
self._callback = self._tox.friend_message_cb
|
||||
|
||||
def incoming_message(tox, friend_number, message_type, message, size, user_data):
|
||||
self._callback(tox, friend_number, message_type, message, size, user_data)
|
||||
if self._profile.status == 1: # TOX_USER_STATUS['AWAY']
|
||||
self.answer(friend_number, str(message, 'utf-8'))
|
||||
|
||||
self._tox.callback_friend_message(incoming_message) # , None
|
||||
|
||||
def stop(self):
|
||||
if not self._callback: return
|
||||
try:
|
||||
# TypeError: argument must be callable or integer function address
|
||||
self._tox.callback_friend_message(self._callback) # , None
|
||||
except: pass
|
||||
|
||||
def close(self):
|
||||
self.stop()
|
||||
|
||||
def answer(self, friend_number, message):
|
||||
if not self._mode:
|
||||
message = self._message
|
||||
invoke_in_main_thread(self._profile.send_message, message, friend_number)
|
||||
|
1695
toxygen/plugins/chess.py
Normal file
1695
toxygen/plugins/chess.py
Normal file
File diff suppressed because it is too large
Load diff
31
toxygen/plugins/en_GB.ts
Normal file
31
toxygen/plugins/en_GB.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="1.1">
|
||||
<context>
|
||||
<name>BirthDay</name>
|
||||
<message>
|
||||
<location filename="bday.py" line="28"/>
|
||||
<source>Birthday!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="44"/>
|
||||
<source>Send my birthday date to contacts</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="45"/>
|
||||
<source>Birthday</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="50"/>
|
||||
<source>Date in format dd.mm.yyyy</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="53"/>
|
||||
<source>Save date</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
31
toxygen/plugins/en_US.ts
Normal file
31
toxygen/plugins/en_US.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="1.1">
|
||||
<context>
|
||||
<name>BirthDay</name>
|
||||
<message>
|
||||
<location filename="bday.py" line="28"/>
|
||||
<source>Birthday!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="44"/>
|
||||
<source>Send my birthday date to contacts</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="45"/>
|
||||
<source>Birthday</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="50"/>
|
||||
<source>Date in format dd.mm.yyyy</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="53"/>
|
||||
<source>Save date</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
75
toxygen/plugins/garland.py
Normal file
75
toxygen/plugins/garland.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
import plugin_super_class
|
||||
import threading
|
||||
import time
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||
|
||||
def __init__(self, fn, *args, **kwargs):
|
||||
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
class Invoker(QtCore.QObject):
|
||||
|
||||
def event(self, event):
|
||||
event.fn(*event.args, **event.kwargs)
|
||||
return True
|
||||
|
||||
_invoker = Invoker()
|
||||
|
||||
|
||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||
|
||||
|
||||
class Garland(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(Garland, self).__init__('Garland', 'grlnd', *args)
|
||||
self._thread = None
|
||||
self._exec = None
|
||||
self._time = 3
|
||||
self._app = args[0]
|
||||
self._profile=self._app._ms._profile
|
||||
self._window = None
|
||||
|
||||
def get_description(self):
|
||||
return QApplication.translate("Garland", "Changes your status like it's garland.")
|
||||
|
||||
def close(self):
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
self._exec = False
|
||||
self._thread.join()
|
||||
|
||||
def start(self):
|
||||
self._exec = True
|
||||
self._thread = threading.Thread(target=self.change_status)
|
||||
self._thread.start()
|
||||
|
||||
def command(self, command):
|
||||
if command.startswith('time'):
|
||||
self._time = max(int(command.split(' ')[1]), 300) / 1000
|
||||
else:
|
||||
super().command(command)
|
||||
|
||||
def update(self):
|
||||
if hasattr(self, '_profile'):
|
||||
if not hasattr(self._profile, 'status') or not self._profile.status:
|
||||
retval = 0
|
||||
else:
|
||||
retval = (self._profile.status + 1) % 3
|
||||
self._profile.set_status(retval)
|
||||
|
||||
def change_status(self):
|
||||
time.sleep(5)
|
||||
while self._exec:
|
||||
invoke_in_main_thread(self.update)
|
||||
time.sleep(self._time)
|
||||
|
86
toxygen/plugins/mrq.py
Normal file
86
toxygen/plugins/mrq.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import plugin_super_class
|
||||
import threading
|
||||
import time
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
|
||||
|
||||
def __init__(self, fn, *args, **kwargs):
|
||||
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
|
||||
self.fn = fn
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
class Invoker(QtCore.QObject):
|
||||
|
||||
def event(self, event):
|
||||
event.fn(*event.args, **event.kwargs)
|
||||
return True
|
||||
|
||||
_invoker = Invoker()
|
||||
|
||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||
|
||||
|
||||
class MarqueeStatus(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(MarqueeStatus, self).__init__('MarqueeStatus', 'mrq', *args)
|
||||
self._thread = None
|
||||
self._exec = None
|
||||
self.active = False
|
||||
self.left = True
|
||||
self._app = args[0]
|
||||
self._profile=self._app._ms._profile
|
||||
self._window = None
|
||||
|
||||
def get_description(self):
|
||||
return QApplication.translate("MarqueeStatus", 'Create ticker from your status message.')
|
||||
|
||||
def close(self):
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
self._exec = False
|
||||
if self.active:
|
||||
self._thread.join()
|
||||
|
||||
def start(self):
|
||||
self._exec = True
|
||||
self._thread = threading.Thread(target=self.change_status)
|
||||
self._thread.start()
|
||||
|
||||
def command(self, command):
|
||||
if command == 'rev':
|
||||
self.left = not self.left
|
||||
else:
|
||||
super(MarqueeStatus, self).command(command)
|
||||
|
||||
def set_status_message(self):
|
||||
message = str(self._profile.status_message)
|
||||
if self.left:
|
||||
self._profile.set_status_message(bytes(message[1:] + message[0], 'utf-8'))
|
||||
else:
|
||||
self._profile.set_status_message(bytes(message[-1] + message[:-1], 'utf-8'))
|
||||
|
||||
def init_status(self):
|
||||
self._profile.status_message = bytes(self._profile.status_message.strip() + ' ', 'utf-8')
|
||||
|
||||
def change_status(self):
|
||||
self.active = True
|
||||
if hasattr(self, '_profile'):
|
||||
tmp = self._profile.status_message
|
||||
time.sleep(10)
|
||||
invoke_in_main_thread(self.init_status)
|
||||
while self._exec:
|
||||
time.sleep(1)
|
||||
if self._profile.status is not None:
|
||||
invoke_in_main_thread(self.set_status_message)
|
||||
invoke_in_main_thread(self._profile.set_status_message, bytes(tmp, 'utf-8'))
|
||||
self.active = False
|
||||
|
|
@ -53,9 +53,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
|
||||
self._translator = None # translator for plugin's GUI
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Get methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
|
@ -98,9 +96,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||
"""
|
||||
return None
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Plugin was stopped, started or new command received
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
|
@ -130,9 +126,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||
title = util_ui.tr('List of commands for plugin {}').format(self._name)
|
||||
util_ui.message_box(text, title)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Translations support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_translator(self):
|
||||
"""
|
||||
|
@ -149,9 +143,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||
self._translator.load(path_to_data(self._short_name) + lang_path)
|
||||
app.installTranslator(self._translator)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Settings loading and saving
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_settings(self):
|
||||
"""
|
||||
|
@ -170,9 +162,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
|
||||
fl.write(bytes(data, 'utf-8'))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def lossless_packet(self, data, friend_number):
|
||||
"""
|
||||
|
@ -196,9 +186,7 @@ class PluginSuperClass(tox_save.ToxSave):
|
|||
"""
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Custom packets sending
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_lossless(self, data, friend_number):
|
||||
"""
|
||||
|
|
BIN
toxygen/plugins/ru_RU.qm
Normal file
BIN
toxygen/plugins/ru_RU.qm
Normal file
Binary file not shown.
32
toxygen/plugins/ru_RU.ts
Normal file
32
toxygen/plugins/ru_RU.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.0" language="ru_RU">
|
||||
<context>
|
||||
<name>BirthDay</name>
|
||||
<message>
|
||||
<location filename="bday.py" line="28"/>
|
||||
<source>Birthday!</source>
|
||||
<translation>День рождения!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="44"/>
|
||||
<source>Send my birthday date to contacts</source>
|
||||
<translation>Отправлять дату моего рождения контактам</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="45"/>
|
||||
<source>Birthday</source>
|
||||
<translation>День рождения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="50"/>
|
||||
<source>Date in format dd.mm.yyyy</source>
|
||||
<translation>Дата в формате дд.мм.гггг</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="bday.py" line="53"/>
|
||||
<source>Save date</source>
|
||||
<translation>Сохранить дату</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
2
toxygen/plugins/srch.pro
Normal file
2
toxygen/plugins/srch.pro
Normal file
|
@ -0,0 +1,2 @@
|
|||
SOURCES = srch.py
|
||||
TRANSLATIONS = srch/en_GB.ts srch/en_US.ts srch/ru_RU.ts
|
54
toxygen/plugins/srch.py
Normal file
54
toxygen/plugins/srch.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import plugin_super_class
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
|
||||
|
||||
class SearchPlugin(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(SearchPlugin, self).__init__('SearchPlugin', 'srch', *args)
|
||||
|
||||
def get_description(self):
|
||||
return QApplication.translate("SearchPlugin", 'Plugin search with search engines.')
|
||||
|
||||
def get_message_menu(self, menu, text):
|
||||
google = QtWidgets.QAction(
|
||||
QtWidgets.QApplication.translate("srch", "Find in Google"),
|
||||
menu)
|
||||
google.triggered.connect(lambda: self.google(text))
|
||||
|
||||
duck = QtWidgets.QAction(
|
||||
QtWidgets.QApplication.translate("srch", "Find in DuckDuckGo"),
|
||||
menu)
|
||||
duck.triggered.connect(lambda: self.duck(text))
|
||||
|
||||
yandex = QtWidgets.QAction(
|
||||
QtWidgets.QApplication.translate("srch", "Find in Yandex"),
|
||||
menu)
|
||||
yandex.triggered.connect(lambda: self.yandex(text))
|
||||
|
||||
bing = QtWidgets.QAction(
|
||||
QtWidgets.QApplication.translate("srch", "Find in Bing"),
|
||||
menu)
|
||||
bing.triggered.connect(lambda: self.bing(text))
|
||||
|
||||
return [duck, google, yandex, bing]
|
||||
|
||||
def google(self, text):
|
||||
url = QtCore.QUrl('https://www.google.com/search?q=' + text)
|
||||
self.open_url(url)
|
||||
|
||||
def duck(self, text):
|
||||
url = QtCore.QUrl('https://duckduckgo.com/?q=' + text)
|
||||
self.open_url(url)
|
||||
|
||||
def yandex(self, text):
|
||||
url = QtCore.QUrl('https://yandex.com/search/?text=' + text)
|
||||
self.open_url(url)
|
||||
|
||||
def bing(self, text):
|
||||
url = QtCore.QUrl('https://www.bing.com/search?q=' + text)
|
||||
self.open_url(url)
|
||||
|
||||
def open_url(self, url):
|
||||
QtGui.QDesktopServices.openUrl(url)
|
||||
|
2
toxygen/plugins/toxid.pro
Normal file
2
toxygen/plugins/toxid.pro
Normal file
|
@ -0,0 +1,2 @@
|
|||
SOURCES = toxid.py
|
||||
TRANSLATIONS = toxid/en_GB.ts toxid/en_US.ts toxid/ru_RU.ts
|
136
toxygen/plugins/toxid.py
Normal file
136
toxygen/plugins/toxid.py
Normal file
|
@ -0,0 +1,136 @@
|
|||
import plugin_super_class
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
import json
|
||||
|
||||
|
||||
class CopyableToxId(plugin_super_class.PluginSuperClass):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(CopyableToxId, self).__init__('CopyableToxId', 'toxid', *args)
|
||||
self._data = json.loads(self.load_settings())
|
||||
self._copy = False
|
||||
self._curr = -1
|
||||
self._timer = QtCore.QTimer()
|
||||
self._timer.timeout.connect(lambda: self.timer())
|
||||
self.load_translator()
|
||||
self._app = args[0]
|
||||
self._profile=self._app._ms._profile
|
||||
self._window = None
|
||||
|
||||
def get_description(self):
|
||||
return QtWidgets.QApplication.translate("TOXID", 'Plugin which allows you to copy TOX ID of your friends easily.')
|
||||
|
||||
def get_window(self):
|
||||
inst = self
|
||||
|
||||
class Window(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self):
|
||||
super(Window, self).__init__()
|
||||
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
|
||||
self.send = QtWidgets.QCheckBox(self)
|
||||
self.send.setGeometry(QtCore.QRect(20, 10, 310, 25))
|
||||
self.send.setText(QtWidgets.QApplication.translate("TOXID", "Send my TOX ID to contacts"))
|
||||
self.setWindowTitle(QtWidgets.QApplication.translate("TOXID", "CopyableToxID"))
|
||||
self.send.clicked.connect(self.update)
|
||||
self.send.setChecked(inst._data['send_id'])
|
||||
self.help = QtWidgets.QPushButton(self)
|
||||
self.help.setGeometry(QtCore.QRect(20, 40, 200, 25))
|
||||
self.help.setText(QtWidgets.QApplication.translate("TOXID", "List of commands"))
|
||||
self.help.clicked.connect(lambda: inst.command('help'))
|
||||
|
||||
def update(self):
|
||||
inst._data['send_id'] = self.send.isChecked()
|
||||
inst.save_settings(json.dumps(inst._data))
|
||||
|
||||
if not hasattr(self, '_window') or not self._window:
|
||||
self._window = Window()
|
||||
return self._window
|
||||
|
||||
def lossless_packet(self, data, friend_number):
|
||||
if len(data):
|
||||
self._data['id'] = list(filter(lambda x: not x.startswith(data[:64]), self._data['id']))
|
||||
self._data['id'].append(data)
|
||||
if self._copy:
|
||||
self._timer.stop()
|
||||
self._copy = False
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
clipboard.setText(data)
|
||||
self.save_settings(json.dumps(self._data))
|
||||
elif self._data['send_id']:
|
||||
self.send_lossless(self._tox.self_get_address(), friend_number)
|
||||
|
||||
def error(self):
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
title = QtWidgets.QApplication.translate("TOXID", "Error")
|
||||
msgbox.setWindowTitle(title.format(self._name))
|
||||
text = QtWidgets.QApplication.translate("TOXID", "Tox ID cannot be copied")
|
||||
msgbox.setText(text)
|
||||
msgbox.exec_()
|
||||
|
||||
def timer(self):
|
||||
self._copy = False
|
||||
if self._curr + 1:
|
||||
public_key = self._tox.friend_get_public_key(self._curr)
|
||||
self._curr = -1
|
||||
arr = list(filter(lambda x: x.startswith(public_key), self._data['id']))
|
||||
if len(arr):
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
clipboard.setText(arr[0])
|
||||
else:
|
||||
self.error()
|
||||
else:
|
||||
self.error()
|
||||
self._timer.stop()
|
||||
|
||||
def friend_connected(self, friend_number):
|
||||
self.send_lossless('', friend_number)
|
||||
|
||||
def command(self, text):
|
||||
if text == 'copy':
|
||||
num = self._profile.get_active_number()
|
||||
if num == -1:
|
||||
return
|
||||
elif text.startswith('copy '):
|
||||
num = int(text[5:])
|
||||
if num < 0:
|
||||
return
|
||||
elif text == 'enable':
|
||||
self._copy = True
|
||||
return
|
||||
elif text == 'disable':
|
||||
self._copy = False
|
||||
return
|
||||
elif text == 'help':
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
title = QtWidgets.QApplication.translate("TOXID", "List of commands for plugin CopyableToxID")
|
||||
msgbox.setWindowTitle(title)
|
||||
text = QtWidgets.QApplication.translate("TOXID", """Commands:
|
||||
copy: copy TOX ID of current friend
|
||||
copy <friend_number>: copy TOX ID of friend with specified number
|
||||
enable: allow send your TOX ID to friends
|
||||
disable: disallow send your TOX ID to friends
|
||||
help: show this help""")
|
||||
msgbox.setText(text)
|
||||
msgbox.exec_()
|
||||
return
|
||||
else:
|
||||
return
|
||||
public_key = self._tox.friend_get_public_key(num)
|
||||
arr = list(filter(lambda x: x.startswith(public_key), self._data['id']))
|
||||
if self._profile.get_friend_by_number(num).status is None and len(arr):
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
clipboard.setText(arr[0])
|
||||
elif self._profile.get_friend_by_number(num).status is not None:
|
||||
self._copy = True
|
||||
self._curr = num
|
||||
self.send_lossless('', num)
|
||||
self._timer.start(2000)
|
||||
else:
|
||||
self.error()
|
||||
|
||||
def get_menu(self, menu, num):
|
||||
act = QtWidgets.QAction(QtWidgets.QApplication.translate("TOXID", "Copy TOX ID"), menu)
|
||||
friend = self._profile.get_friend(num)
|
||||
act.connect(act, QtCore.SIGNAL("triggered()"), lambda: self.command('copy ' + str(friend.number)))
|
||||
return [act]
|
87
toxygen/tests/README.md
Normal file
87
toxygen/tests/README.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
# toxygen_wrapper
|
||||
|
||||
[ctypes](https://docs.python.org/3/library/ctypes.html)
|
||||
wrapping of [Tox](https://tox.chat/)
|
||||
[```libtoxcore```](https://github.com/TokTok/c-toxcore) into Python.
|
||||
Taken from the ```wrapper``` directory of the now abandoned
|
||||
<https://github.com/toxygen-project/toxygen> ```next_gen``` branch
|
||||
by Ingvar.
|
||||
|
||||
The basics of NGC groups are supported, as well as AV and toxencryptsave.
|
||||
There is no coverage of conferences as they are not used in ```toxygen```
|
||||
and the list of still unwrapped calls as of Sept. 2022 can be found in
|
||||
```tox.c-toxcore.missing```. The code still needs double-checking
|
||||
that every call in ```tox.py``` has the right signature, but it runs
|
||||
```toxygen``` with no apparent issues.
|
||||
|
||||
It has been tested with UDP and TCP proxy (Tor). It has ***not*** been
|
||||
tested on Windows, and there may be some minor breakage, which should be
|
||||
easy to fix. There is a good coverage integration testsuite in ```wrapper_tests```.
|
||||
Change to that directory and run ```tests_wrapper.py --help```; the test
|
||||
suite gives a good set of examples of usage.
|
||||
|
||||
## Install
|
||||
|
||||
Put the parent of the wrapper directory on your PYTHONPATH and
|
||||
touch a file called `__init__.py` in its parent directory.
|
||||
|
||||
Then you need a ```libs``` directory beside the `wrapper` directory
|
||||
and you need to link your ```libtoxcore.so``` and ```libtoxav.so```
|
||||
and ```libtoxencryptsave.so``` into it. Link all 3 filenames
|
||||
to ```libtoxcore.so``` if you have only ```libtoxcore.so```
|
||||
(which is usually the case if you built ```c-toxcore``` with ```cmake```
|
||||
rather than ```autogen/configure```). If you want to be different,
|
||||
the environment variable TOXCORE_LIBS overrides the location of ```libs```.
|
||||
|
||||
As is, the code in ```tox.py``` is very verbose. Edit the file to change
|
||||
```
|
||||
def LOG_ERROR(a): print('EROR> '+a)
|
||||
def LOG_WARN(a): print('WARN> '+a)
|
||||
def LOG_INFO(a): print('INFO> '+a)
|
||||
def LOG_DEBUG(a): print('DBUG> '+a)
|
||||
def LOG_TRACE(a): pass # print('TRAC> '+a)
|
||||
```
|
||||
to all ```pass #``` or use ```logging.logger``` to suite your tastes.
|
||||
```logging.logger``` can be dangerous in callbacks in ```Qt``` applications,
|
||||
so we use simple print statements as default. The same applies to
|
||||
```wrapper/tests_wrapper.py```.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
No prerequisites in Python3.
|
||||
|
||||
## Other wrappers
|
||||
|
||||
There are a number of other wrappings into Python of Tox core.
|
||||
This one uses [ctypes](https://docs.python.org/3/library/ctypes.html)
|
||||
which has its merits - there is no need to recompile anything as with
|
||||
Cython - change the Python file and it's done. And you can follow things
|
||||
in a Python debugger, or with the utterly stupendous Python feature of
|
||||
```gdb``` (```gdb -ex r --args /usr/bin/python3.9 <pyfile>```).
|
||||
|
||||
CTYPES code can be brittle, segfaulting if you've got things wrong,
|
||||
but if your wrapping is right, it is very efficient and easy to work on.
|
||||
The [faulthandler](https://docs.python.org/3/library/faulthandler.html)
|
||||
module can be helpful in debugging crashes
|
||||
(e.g. from segmentation faults produced by erroneous C library wrapping).
|
||||
|
||||
Others include:
|
||||
|
||||
* <https://github.com/TokTok/py-toxcore-c> Cython bindings.
|
||||
Incomplete and not really actively supported. Maybe it will get
|
||||
worked on in the future, but TokTok seems to be working on
|
||||
java, rust, scalla, go, etc. bindings instead.
|
||||
No support for NGC groups or toxencryptsave.
|
||||
|
||||
* <https://github.com/oxij/PyTox>
|
||||
forked from https://github.com/aitjcize/PyTox
|
||||
by Wei-Ning Huang <aitjcize@gmail.com>.
|
||||
Hardcore C wrapping which is not easy to keep up to date.
|
||||
No support for NGC or toxencryptsave. Abandonned.
|
||||
This was the basis for the TokTok/py-toxcore-c code until recently.
|
||||
|
||||
To our point of view, the ability of CTYPEs to follow code in the
|
||||
debugger is a crucial advantage.
|
||||
|
||||
Work on this project is suspended until the
|
||||
[MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me!
|
0
toxygen/tests/__init__.py
Normal file
0
toxygen/tests/__init__.py
Normal file
151
toxygen/tests/conference_tests.py
Normal file
151
toxygen/tests/conference_tests.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
if False:
|
||||
@unittest.skip # to yet
|
||||
def test_conference(self):
|
||||
"""
|
||||
t:group_new
|
||||
t:conference_delete
|
||||
t:conference_get_chatlist_size
|
||||
t:conference_get_chatlist
|
||||
t:conference_send_message
|
||||
"""
|
||||
bob_addr = self.bob.self_get_address()
|
||||
alice_addr = self.alice.self_get_address()
|
||||
|
||||
self.abid = self.alice.friend_by_public_key(bob_addr)
|
||||
self.baid = self.bob.friend_by_public_key(alice_addr)
|
||||
|
||||
assert self.bob_just_add_alice_as_friend()
|
||||
|
||||
#: Test group add
|
||||
privacy_state = enums.TOX_GROUP_PRIVACY_STATE['PUBLIC']
|
||||
group_name = 'test_group'
|
||||
nick = 'test_nick'
|
||||
status = None # dunno
|
||||
self.group_id = self.bob.group_new(privacy_state, group_name, nick, status)
|
||||
# :return group number on success, UINT32_MAX on failure.
|
||||
assert self.group_id >= 0
|
||||
|
||||
self.loop(50)
|
||||
|
||||
BID = self.abid
|
||||
|
||||
def alices_on_conference_invite(self, fid, type_, data):
|
||||
assert fid == BID
|
||||
assert type_ == 0
|
||||
gn = self.conference_join(fid, data)
|
||||
assert type_ == self.conference_get_type(gn)
|
||||
self.gi = True
|
||||
|
||||
def alices_on_conference_peer_list_changed(self, gid):
|
||||
logging.debug("alices_on_conference_peer_list_changed")
|
||||
assert gid == self.group_id
|
||||
self.gn = True
|
||||
|
||||
try:
|
||||
AliceTox.on_conference_invite = alices_on_conference_invite
|
||||
AliceTox.on_conference_peer_list_changed = alices_on_conference_peer_list_changed
|
||||
|
||||
self.alice.gi = False
|
||||
self.alice.gn = False
|
||||
|
||||
self.wait_ensure_exec(self.bob.conference_invite, (self.aid, self.group_id))
|
||||
|
||||
assert self.wait_callback_trues(self.alice, ['gi', 'gn'])
|
||||
except AssertionError as e:
|
||||
raise
|
||||
finally:
|
||||
AliceTox.on_conference_invite = Tox.on_conference_invite
|
||||
AliceTox.on_conference_peer_list_change = Tox.on_conference_peer_list_changed
|
||||
|
||||
#: Test group number of peers
|
||||
self.loop(50)
|
||||
assert self.bob.conference_peer_count(self.group_id) == 2
|
||||
|
||||
#: Test group peername
|
||||
self.alice.self_set_name('Alice')
|
||||
self.bob.self_set_name('Bob')
|
||||
|
||||
def alices_on_conference_peer_list_changed(self, gid):
|
||||
logging.debug("alices_on_conference_peer_list_changed")
|
||||
self.gn = True
|
||||
try:
|
||||
AliceTox.on_conference_peer_list_changed = alices_on_conference_peer_list_changed
|
||||
self.alice.gn = False
|
||||
|
||||
assert self.wait_callback_true(self.alice, 'gn')
|
||||
except AssertionError as e:
|
||||
raise
|
||||
finally:
|
||||
AliceTox.on_conference_peer_list_changed = Tox.on_conference_peer_list_changed
|
||||
|
||||
peernames = [self.bob.conference_peer_get_name(self.group_id, i) for i in
|
||||
range(self.bob.conference_peer_count(self.group_id))]
|
||||
assert 'Alice' in peernames
|
||||
assert 'Bob' in peernames
|
||||
|
||||
#: Test title change
|
||||
self.bob.conference_set_title(self.group_id, 'My special title')
|
||||
assert self.bob.conference_get_title(self.group_id) == 'My special title'
|
||||
|
||||
#: Test group message
|
||||
AID = self.aid
|
||||
BID = self.bid
|
||||
MSG = 'Group message test'
|
||||
|
||||
def alices_on_conference_message(self, gid, fgid, msg_type, message):
|
||||
logging.debug("alices_on_conference_message" +repr(message))
|
||||
if fgid == AID:
|
||||
assert gid == self.group_id
|
||||
assert str(message, 'UTF-8') == MSG
|
||||
self.alice.gm = True
|
||||
|
||||
try:
|
||||
AliceTox.on_conference_message = alices_on_conference_message
|
||||
self.alice.gm = False
|
||||
|
||||
self.wait_ensure_exec(self.bob.conference_send_message, (
|
||||
self.group_id, TOX_MESSAGE_TYPE['NORMAL'], MSG))
|
||||
assert self.wait_callback_true(self.alice, 'gm')
|
||||
except AssertionError as e:
|
||||
raise
|
||||
finally:
|
||||
AliceTox.on_conference_message = Tox.on_conference_message
|
||||
|
||||
#: Test group action
|
||||
AID = self.aid
|
||||
BID = self.bid
|
||||
MSG = 'Group action test'
|
||||
|
||||
def on_conference_action(self, gid, fgid, msg_type, action):
|
||||
if fgid == AID:
|
||||
assert gid == self.group_id
|
||||
assert msg_type == TOX_MESSAGE_TYPE['ACTION']
|
||||
assert str(action, 'UTF-8') == MSG
|
||||
self.ga = True
|
||||
|
||||
try:
|
||||
AliceTox.on_conference_message = on_conference_action
|
||||
self.alice.ga = False
|
||||
|
||||
self.wait_ensure_exec(self.bob.conference_send_message,
|
||||
(self.group_id, TOX_MESSAGE_TYPE['ACTION'], MSG))
|
||||
|
||||
assert self.wait_callback_true(self.alice, 'ga')
|
||||
|
||||
#: Test chatlist
|
||||
assert len(self.bob.conference_get_chatlist()) == self.bob.conference_get_chatlist_size(), \
|
||||
print(len(self.bob.conference_get_chatlist()), '!=', self.bob.conference_get_chatlist_size())
|
||||
assert len(self.alice.conference_get_chatlist()) == self.bob.conference_get_chatlist_size(), \
|
||||
print(len(self.alice.conference_get_chatlist()), '!=', self.bob.conference_get_chatlist_size())
|
||||
assert self.bob.conference_get_chatlist_size() == 1, \
|
||||
self.bob.conference_get_chatlist_size()
|
||||
self.bob.conference_delete(self.group_id)
|
||||
assert self.bob.conference_get_chatlist_size() == 0, \
|
||||
self.bob.conference_get_chatlist_size()
|
||||
|
||||
except AssertionError as e:
|
||||
raise
|
||||
finally:
|
||||
AliceTox.on_conference_message = Tox.on_conference_message
|
||||
|
||||
|
439
toxygen/tests/logging_toxygen_echo.py
Normal file
439
toxygen/tests/logging_toxygen_echo.py
Normal file
|
@ -0,0 +1,439 @@
|
|||
#!/var/local/bin/python3.bash
|
||||
#
|
||||
""" echo.py features
|
||||
- accept friend request
|
||||
- echo back friend message
|
||||
- accept and answer friend call request
|
||||
- send back friend audio/video data
|
||||
- send back files friend sent
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import random
|
||||
from ctypes import *
|
||||
import argparse
|
||||
|
||||
import time
|
||||
from os.path import exists
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
# log = lambda x: LOG.info(x)
|
||||
LOG = logging.getLogger('app')
|
||||
def LOG_error(a): print('EROR_ '+a)
|
||||
def LOG_warn(a): print('WARN_ '+a)
|
||||
def LOG_info(a): print('INFO_ '+a)
|
||||
def LOG_debug(a): print('DBUG_ '+a)
|
||||
def LOG_trace(a): pass # print('TRAC_ '+a)
|
||||
|
||||
from middleware.tox_factory import tox_factory
|
||||
import wrapper
|
||||
import wrapper.toxcore_enums_and_consts as enums
|
||||
from wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS, \
|
||||
TOX_MESSAGE_TYPE, TOX_PUBLIC_KEY_SIZE, TOX_FILE_CONTROL
|
||||
import user_data
|
||||
from wrapper.libtox import LibToxCore
|
||||
import wrapper_tests.support_testing as ts
|
||||
from wrapper_tests.support_testing import oMainArgparser
|
||||
from wrapper_tests.support_testing import logging_toxygen_echo
|
||||
|
||||
def sleep(fSec):
|
||||
if 'QtCore' in globals():
|
||||
if fSec > .000001: globals['QtCore'].QThread.msleep(fSec)
|
||||
globals['QtCore'].QCoreApplication.processEvents()
|
||||
else:
|
||||
time.sleep(fSec)
|
||||
|
||||
try:
|
||||
import coloredlogs
|
||||
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
||||
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
|
||||
except ImportError as e:
|
||||
# logging.log(logging.DEBUG, f"coloredlogs not available: {e}")
|
||||
coloredlogs = None
|
||||
|
||||
import wrapper_tests.support_testing as ts
|
||||
if 'USER' in os.environ:
|
||||
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USER'] +'.tox'
|
||||
elif 'USERNAME' in os.environ:
|
||||
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USERNAME'] +'.tox'
|
||||
else:
|
||||
sDATA_FILE = '/tmp/logging_toxygen_' +'data' +'.tox'
|
||||
|
||||
bHAVE_AV = True
|
||||
iDHT_TRIES = 100
|
||||
iDHT_TRY = 0
|
||||
|
||||
#?SERVER = lLOCAL[-1]
|
||||
|
||||
class AV(wrapper.tox.ToxAV):
|
||||
def __init__(self, core):
|
||||
super(AV, self).__init__(core)
|
||||
self.core = self.get_tox()
|
||||
|
||||
def on_call(self, fid, audio_enabled, video_enabled):
|
||||
LOG.info("Incoming %s call from %d:%s ..." % (
|
||||
"video" if video_enabled else "audio", fid,
|
||||
self.core.friend_get_name(fid)))
|
||||
bret = self.answer(fid, 48, 64)
|
||||
LOG.info(f"Answered, in call... {bret!s}")
|
||||
|
||||
def on_call_state(self, fid, state):
|
||||
LOG.info('call state:fn=%d, state=%d' % (fid, state))
|
||||
|
||||
def on_audio_bit_rate(self, fid, audio_bit_rate):
|
||||
LOG.info('audio bit rate status: fn=%d, abr=%d' %
|
||||
(fid, audio_bit_rate))
|
||||
|
||||
def on_video_bit_rate(self, fid, video_bit_rate):
|
||||
LOG.info('video bit rate status: fn=%d, vbr=%d' %
|
||||
(fid, video_bit_rate))
|
||||
|
||||
def on_audio_receive_frame(self, fid, pcm, sample_count,
|
||||
channels, sampling_rate):
|
||||
# LOG.info('audio frame: %d, %d, %d, %d' %
|
||||
# (fid, sample_count, channels, sampling_rate))
|
||||
# LOG.info('pcm len:%d, %s' % (len(pcm), str(type(pcm))))
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
bret = self.audio_send_frame(fid, pcm, sample_count,
|
||||
channels, sampling_rate)
|
||||
if bret is False:
|
||||
LOG.error('on_audio_receive_frame error.')
|
||||
|
||||
def on_video_receive_frame(self, fid, width, height, frame, u, v):
|
||||
LOG.info('video frame: %d, %d, %d, ' % (fid, width, height))
|
||||
sys.stdout.write('*')
|
||||
sys.stdout.flush()
|
||||
bret = self.video_send_frame(fid, width, height, frame, u, v)
|
||||
if bret is False:
|
||||
LOG.error('on_video_receive_frame error.')
|
||||
|
||||
def witerate(self):
|
||||
self.iterate()
|
||||
|
||||
|
||||
def save_to_file(tox, fname):
|
||||
data = tox.get_savedata()
|
||||
with open(fname, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
def load_from_file(fname):
|
||||
assert os.path.exists(fname)
|
||||
return open(fname, 'rb').read()
|
||||
|
||||
class EchoBot():
|
||||
def __init__(self, oTox):
|
||||
self._tox = oTox
|
||||
self._tox.self_set_name("EchoBot")
|
||||
LOG.info('ID: %s' % self._tox.self_get_address())
|
||||
|
||||
self.files = {}
|
||||
self.av = None
|
||||
self.on_connection_status = None
|
||||
|
||||
def start(self):
|
||||
self.connect()
|
||||
if bHAVE_AV:
|
||||
# RuntimeError: Attempted to create a second session for the same Tox instance.
|
||||
|
||||
self.av = True # AV(self._tox_pointer)
|
||||
def bobs_on_friend_request(iTox,
|
||||
public_key,
|
||||
message_data,
|
||||
message_data_size,
|
||||
*largs):
|
||||
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
||||
sPk = wrapper.tox.bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
||||
sMd = str(message_data, 'UTF-8')
|
||||
LOG.debug('on_friend_request ' +sPk +' ' +sMd)
|
||||
self.on_friend_request(sPk, sMd)
|
||||
LOG.info('setting bobs_on_friend_request')
|
||||
self._tox.callback_friend_request(bobs_on_friend_request)
|
||||
|
||||
def bobs_on_friend_message(iTox,
|
||||
iFriendNum,
|
||||
iMessageType,
|
||||
message_data,
|
||||
message_data_size,
|
||||
*largs):
|
||||
sMd = str(message_data, 'UTF-8')
|
||||
LOG_debug(f"on_friend_message {iFriendNum}" +' ' +sMd)
|
||||
self.on_friend_message(iFriendNum, iMessageType, sMd)
|
||||
LOG.info('setting bobs_on_friend_message')
|
||||
self._tox.callback_friend_message(bobs_on_friend_message)
|
||||
|
||||
def bobs_on_file_chunk_request(iTox, fid, filenumber, position, length, *largs):
|
||||
if length == 0:
|
||||
return
|
||||
|
||||
data = self.files[(fid, filenumber)]['f'][position:(position + length)]
|
||||
self._tox.file_send_chunk(fid, filenumber, position, data)
|
||||
self._tox.callback_file_chunk_request(bobs_on_file_chunk_request)
|
||||
|
||||
def bobs_on_file_recv(iTox, fid, filenumber, kind, size, filename, *largs):
|
||||
LOG_info(f"on_file_recv {fid!s} {filenumber!s} {kind!s} {size!s} {filename}")
|
||||
if size == 0:
|
||||
return
|
||||
self.files[(fid, filenumber)] = {
|
||||
'f': bytes(),
|
||||
'filename': filename,
|
||||
'size': size
|
||||
}
|
||||
self._tox.file_control(fid, filenumber, TOX_FILE_CONTROL['RESUME'])
|
||||
|
||||
|
||||
def connect(self):
|
||||
if not self.on_connection_status:
|
||||
def on_connection_status(iTox, iCon, *largs):
|
||||
LOG_info('ON_CONNECTION_STATUS - CONNECTED ' + repr(iCon))
|
||||
self._tox.callback_self_connection_status(on_connection_status)
|
||||
LOG.info('setting on_connection_status callback ')
|
||||
self.on_connection_status = on_connection_status
|
||||
if self._oargs.network in ['newlocal', 'local']:
|
||||
LOG.info('connecting on the new network ')
|
||||
sNet = 'newlocal'
|
||||
elif self._oargs.network == 'new':
|
||||
LOG.info('connecting on the new network ')
|
||||
sNet = 'new'
|
||||
else: # main old
|
||||
LOG.info('connecting on the old network ')
|
||||
sNet = 'old'
|
||||
sFile = self._oargs.nodes_json
|
||||
lNodes = generate_nodes_from_file(sFile)
|
||||
lElts = lNodes
|
||||
random.shuffle(lElts)
|
||||
for lElt in lElts[:10]:
|
||||
status = self._tox.self_get_connection_status()
|
||||
try:
|
||||
if self._tox.bootstrap(*lElt):
|
||||
LOG.info('connected to ' + lElt[0]+' '+repr(status))
|
||||
else:
|
||||
LOG.warn('failed connecting to ' + lElt[0])
|
||||
except Exception as e:
|
||||
LOG.warn('error connecting to ' + lElt[0])
|
||||
|
||||
if self._oargs.proxy_type > 0:
|
||||
random.shuffle(ts.lRELAYS)
|
||||
for lElt in ts.lRELAYS[:10]:
|
||||
status = self._tox.self_get_connection_status()
|
||||
try:
|
||||
if self._tox.add_tcp_relay(*lElt):
|
||||
LOG.info('relayed to ' + lElt[0] +' '+repr(status))
|
||||
else:
|
||||
LOG.warn('failed relay to ' + lElt[0])
|
||||
except Exception as e:
|
||||
LOG.warn('error relay to ' + lElt[0])
|
||||
|
||||
def loop(self):
|
||||
if not self.av:
|
||||
self.start()
|
||||
checked = False
|
||||
save_to_file(self._tox, sDATA_FILE)
|
||||
|
||||
LOG.info('Starting loop.')
|
||||
while True:
|
||||
|
||||
status = self._tox.self_get_connection_status()
|
||||
if not checked and status:
|
||||
LOG.info('Connected to DHT.')
|
||||
checked = True
|
||||
if not checked and not status:
|
||||
global iDHT_TRY
|
||||
iDHT_TRY += 10
|
||||
self.connect()
|
||||
self.iterate(100)
|
||||
if iDHT_TRY >= iDHT_TRIES:
|
||||
raise RuntimeError("Failed to connect to the DHT.")
|
||||
LOG.warn(f"NOT Connected to DHT. {iDHT_TRY}")
|
||||
checked = True
|
||||
if checked and not status:
|
||||
LOG.info('Disconnected from DHT.')
|
||||
self.connect()
|
||||
checked = False
|
||||
|
||||
if bHAVE_AV:
|
||||
True # self.av.witerate()
|
||||
self.iterate(100)
|
||||
|
||||
LOG.info('Ending loop.')
|
||||
|
||||
def iterate(self, n=100):
|
||||
interval = self._tox.iteration_interval()
|
||||
for i in range(n):
|
||||
self._tox.iterate()
|
||||
sleep(interval / 1000.0)
|
||||
self._tox.iterate()
|
||||
|
||||
def on_friend_request(self, pk, message):
|
||||
LOG.debug('Friend request from %s: %s' % (pk, message))
|
||||
self._tox.friend_add_norequest(pk)
|
||||
LOG.info('on_friend_request Accepted.')
|
||||
save_to_file(self._tox, sDATA_FILE)
|
||||
|
||||
def on_friend_message(self, friendId, type, message):
|
||||
name = self._tox.friend_get_name(friendId)
|
||||
LOG.debug('%s: %s' % (name, message))
|
||||
yMessage = bytes(message, 'UTF-8')
|
||||
self._tox.friend_send_message(friendId, TOX_MESSAGE_TYPE['NORMAL'], yMessage)
|
||||
LOG.info('EchoBot sent: %s' % message)
|
||||
|
||||
def on_file_recv_chunk(self, fid, filenumber, position, data):
|
||||
filename = self.files[(fid, filenumber)]['filename']
|
||||
size = self.files[(fid, filenumber)]['size']
|
||||
LOG.debug(f"on_file_recv_chunk {fid!s} {filenumber!s} {filename} {position/float(size)*100!s}")
|
||||
|
||||
if data is None:
|
||||
msg = "I got '{}', sending it back right away!".format(filename)
|
||||
self._tox.friend_send_message(fid, TOX_MESSAGE_TYPE['NORMAL'], msg)
|
||||
|
||||
self.files[(fid, 0)] = self.files[(fid, filenumber)]
|
||||
|
||||
length = self.files[(fid, filenumber)]['size']
|
||||
self.file_send(fid, 0, length, filename, filename)
|
||||
|
||||
del self.files[(fid, filenumber)]
|
||||
return
|
||||
|
||||
self.files[(fid, filenumber)]['f'] += data
|
||||
|
||||
def iMain(oArgs):
|
||||
global sDATA_FILE
|
||||
# oTOX_OPTIONS = ToxOptions()
|
||||
global oTOX_OPTIONS
|
||||
oTOX_OPTIONS = oToxygenToxOptions(oArgs)
|
||||
opts = oTOX_OPTIONS
|
||||
if coloredlogs:
|
||||
coloredlogs.install(
|
||||
level=oArgs.loglevel,
|
||||
logger=LOG,
|
||||
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
|
||||
fmt='%(name)s %(levelname)s %(message)s'
|
||||
)
|
||||
else:
|
||||
if 'logfile' in oArgs:
|
||||
logging.basicConfig(filename=oArgs.logfile,
|
||||
level=oArgs.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
else:
|
||||
logging.basicConfig(level=oArgs.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
iRet = 0
|
||||
if hasattr(oArgs,'profile') and oArgs.profile and os.path.isfile(oArgs.profile):
|
||||
sDATA_FILE = oArgs.profile
|
||||
LOG.info(f"loading from {sDATA_FILE}")
|
||||
opts.savedata_data = load_from_file(sDATA_FILE)
|
||||
opts.savedata_length = len(opts.savedata_data)
|
||||
opts.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
|
||||
else:
|
||||
opts.savedata_data = None
|
||||
|
||||
try:
|
||||
if False:
|
||||
oTox = tox_factory(data=opts.savedata_data,
|
||||
settings=opts, args=oArgs, app=None)
|
||||
else:
|
||||
oTox = wrapper.tox.Tox(opts)
|
||||
t = EchoBot(oTox)
|
||||
t._oargs = oArgs
|
||||
t.start()
|
||||
t.loop()
|
||||
save_to_file(t._tox, sDATA_FILE)
|
||||
except KeyboardInterrupt:
|
||||
save_to_file(t._tox, sDATA_FILE)
|
||||
except RuntimeError as e:
|
||||
LOG.error(f"exiting with {e}")
|
||||
iRet = 1
|
||||
except Exception as e:
|
||||
LOG.error(f"exiting with {e}")
|
||||
LOG.warn(' iMain(): ' \
|
||||
+'\n' + traceback.format_exc())
|
||||
iRet = 1
|
||||
return iRet
|
||||
|
||||
def oToxygenToxOptions(oArgs, data=None):
|
||||
tox_options = wrapper.tox.Tox.options_new()
|
||||
|
||||
tox_options.contents.local_discovery_enabled = False
|
||||
tox_options.contents.dht_announcements_enabled = False
|
||||
tox_options.contents.hole_punching_enabled = False
|
||||
tox_options.contents.experimental_thread_safety = False
|
||||
tox_options.contents.ipv6_enabled = False
|
||||
tox_options.contents.tcp_port = 3390
|
||||
|
||||
if oArgs.proxy_type > 0:
|
||||
tox_options.contents.proxy_type = int(oArgs.proxy_type)
|
||||
tox_options.contents.proxy_host = bytes(oArgs.proxy_host, 'UTF-8')
|
||||
tox_options.contents.proxy_port = int(oArgs.proxy_port)
|
||||
tox_options.contents.udp_enabled = False
|
||||
LOG.debug('setting oArgs.proxy_host = ' +oArgs.proxy_host)
|
||||
else:
|
||||
tox_options.contents.udp_enabled = True
|
||||
|
||||
if data: # load existing profile
|
||||
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
|
||||
tox_options.contents.savedata_data = c_char_p(data)
|
||||
tox_options.contents.savedata_length = len(data)
|
||||
else: # create new profile
|
||||
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
|
||||
tox_options.contents.savedata_data = None
|
||||
tox_options.contents.savedata_length = 0
|
||||
|
||||
if tox_options._options_pointer:
|
||||
ts.vAddLoggerCallback(tox_options, ts.on_log)
|
||||
else:
|
||||
logging.warn("No tox_options._options_pointer " +repr(tox_options._options_pointer))
|
||||
|
||||
return tox_options
|
||||
|
||||
def oArgparse(lArgv):
|
||||
parser = ts.oMainArgparser()
|
||||
parser.add_argument('profile', type=str, nargs='?',
|
||||
default=sDATA_FILE,
|
||||
help='Path to Tox profile to save')
|
||||
oArgs = parser.parse_args(lArgv)
|
||||
if hasattr(oArgs, 'sleep') and oArgs.sleep == 'qt':
|
||||
pass # broken
|
||||
else:
|
||||
oArgs.sleep = 'time'
|
||||
for key in ts.lBOOLEANS:
|
||||
if key not in oArgs: continue
|
||||
val = getattr(oArgs, key)
|
||||
if val in ['False', 'false', 0]:
|
||||
setattr(oArgs, key, False)
|
||||
else:
|
||||
setattr(oArgs, key, True)
|
||||
if not os.path.exists('/proc/sys/net/ipv6') and oArgs.ipv6_enabled:
|
||||
LOG.warn('setting oArgs.ipv6_enabled = False')
|
||||
oArgs.ipv6_enabled = False
|
||||
return oArgs
|
||||
|
||||
def main(largs=None):
|
||||
if largs is None: largs = []
|
||||
oArgs = oArgparse(largs)
|
||||
global oTOX_OARGS
|
||||
oTOX_OARGS = oArgs
|
||||
print(oArgs)
|
||||
|
||||
if coloredlogs:
|
||||
logger = logging.getLogger()
|
||||
# https://pypi.org/project/coloredlogs/
|
||||
coloredlogs.install(level=oArgs.loglevel,
|
||||
logger=logger,
|
||||
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
|
||||
fmt='%(name)s %(levelname)s %(message)s'
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(level=oArgs.loglevel) # logging.INFO
|
||||
|
||||
return iMain(oArgs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
391
toxygen/tests/socks.py
Normal file
391
toxygen/tests/socks.py
Normal file
|
@ -0,0 +1,391 @@
|
|||
"""SocksiPy - Python SOCKS module.
|
||||
Version 1.00
|
||||
|
||||
Copyright 2006 Dan-Haim. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||
|
||||
|
||||
This module provides a standard socket-like interface for Python
|
||||
for tunneling connections through SOCKS proxies.
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
|
||||
for use in PyLoris (http://pyloris.sourceforge.net/)
|
||||
|
||||
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
|
||||
mainly to merge bug fixes found in Sourceforge
|
||||
|
||||
Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/)
|
||||
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
PROXY_TYPE_SOCKS4 = 1
|
||||
PROXY_TYPE_SOCKS5 = 2
|
||||
PROXY_TYPE_HTTP = 3
|
||||
|
||||
_defaultproxy = None
|
||||
_orgsocket = socket.socket
|
||||
|
||||
class ProxyError(Exception): pass
|
||||
class GeneralProxyError(ProxyError): pass
|
||||
class Socks5AuthError(ProxyError): pass
|
||||
class Socks5Error(ProxyError): pass
|
||||
class Socks4Error(ProxyError): pass
|
||||
class HTTPError(ProxyError): pass
|
||||
|
||||
_generalerrors = ("success",
|
||||
"invalid data",
|
||||
"not connected",
|
||||
"not available",
|
||||
"bad proxy type",
|
||||
"bad input")
|
||||
|
||||
_socks5errors = ("succeeded",
|
||||
"general SOCKS server failure",
|
||||
"connection not allowed by ruleset",
|
||||
"Network unreachable",
|
||||
"Host unreachable",
|
||||
"Connection refused",
|
||||
"TTL expired",
|
||||
"Command not supported",
|
||||
"Address type not supported",
|
||||
"Unknown error")
|
||||
|
||||
_socks5autherrors = ("succeeded",
|
||||
"authentication is required",
|
||||
"all offered authentication methods were rejected",
|
||||
"unknown username or invalid password",
|
||||
"unknown error")
|
||||
|
||||
_socks4errors = ("request granted",
|
||||
"request rejected or failed",
|
||||
"request rejected because SOCKS server cannot connect to identd on the client",
|
||||
"request rejected because the client program and identd report different user-ids",
|
||||
"unknown error")
|
||||
|
||||
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets a default proxy which all further socksocket objects will use,
|
||||
unless explicitly changed.
|
||||
"""
|
||||
global _defaultproxy
|
||||
_defaultproxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def wrapmodule(module):
|
||||
"""wrapmodule(module)
|
||||
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||
a default proxy using setdefaultproxy(...) first.
|
||||
This will only work on modules that import socket directly into the namespace;
|
||||
most of the Python Standard Library falls into this category.
|
||||
"""
|
||||
if _defaultproxy != None:
|
||||
module.socket.socket = socksocket
|
||||
else:
|
||||
raise GeneralProxyError((4, "no proxy specified"))
|
||||
|
||||
class socksocket(socket.socket):
|
||||
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||
Open a SOCKS enabled socket. The parameters are the same as
|
||||
those of the standard socket init. In order for SOCKS to work,
|
||||
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
|
||||
"""
|
||||
|
||||
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
|
||||
_orgsocket.__init__(self, family, type, proto, _sock)
|
||||
if _defaultproxy != None:
|
||||
self.__proxy = _defaultproxy
|
||||
else:
|
||||
self.__proxy = (None, None, None, None, None, None)
|
||||
self.__proxysockname = None
|
||||
self.__proxypeername = None
|
||||
|
||||
def __recvall(self, count):
|
||||
"""__recvall(count) -> data
|
||||
Receive EXACTLY the number of bytes requested from the socket.
|
||||
Blocks until the required number of bytes have been received.
|
||||
"""
|
||||
data = self.recv(count)
|
||||
while len(data) < count:
|
||||
d = self.recv(count-len(data))
|
||||
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
|
||||
data = data + d
|
||||
return data
|
||||
|
||||
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets the proxy to be used.
|
||||
proxytype - The type of the proxy to be used. Three types
|
||||
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
|
||||
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
|
||||
addr - The address of the server (IP or DNS).
|
||||
port - The port of the server. Defaults to 1080 for SOCKS
|
||||
servers and 8080 for HTTP proxy servers.
|
||||
rdns - Should DNS queries be preformed on the remote side
|
||||
(rather than the local side). The default is True.
|
||||
Note: This has no effect with SOCKS4 servers.
|
||||
username - Username to authenticate with to the server.
|
||||
The default is no authentication.
|
||||
password - Password to authenticate with to the server.
|
||||
Only relevant when username is also provided.
|
||||
"""
|
||||
self.__proxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def __negotiatesocks5(self, destaddr, destport):
|
||||
"""__negotiatesocks5(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS5 server.
|
||||
"""
|
||||
# First we'll send the authentication packages we support.
|
||||
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
|
||||
# The username/password details were supplied to the
|
||||
# setproxy method so we support the USERNAME/PASSWORD
|
||||
# authentication (in addition to the standard none).
|
||||
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
|
||||
else:
|
||||
# No username/password were entered, therefore we
|
||||
# only support connections with no authentication.
|
||||
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
|
||||
# We'll receive the server's response to determine which
|
||||
# method was selected
|
||||
chosenauth = self.__recvall(2)
|
||||
if chosenauth[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Check the chosen authentication method
|
||||
if chosenauth[1:2] == chr(0x00).encode():
|
||||
# No authentication is required
|
||||
pass
|
||||
elif chosenauth[1:2] == chr(0x02).encode():
|
||||
# Okay, we need to perform a basic username/password
|
||||
# authentication.
|
||||
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
|
||||
authstat = self.__recvall(2)
|
||||
if authstat[0:1] != chr(0x01).encode():
|
||||
# Bad response
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if authstat[1:2] != chr(0x00).encode():
|
||||
# Authentication failed
|
||||
self.close()
|
||||
raise Socks5AuthError((3, _socks5autherrors[3]))
|
||||
# Authentication succeeded
|
||||
else:
|
||||
# Reaching here is always bad
|
||||
self.close()
|
||||
if chosenauth[1] == chr(0xFF).encode():
|
||||
raise Socks5AuthError((2, _socks5autherrors[2]))
|
||||
else:
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Now we can request the actual connection
|
||||
req = struct.pack('BBB', 0x05, 0x01, 0x00)
|
||||
# If the given destination address is an IP address, we'll
|
||||
# use the IPv4 address request even if remote resolving was specified.
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
except socket.error:
|
||||
# Well it's not an IP number, so it's probably a DNS name.
|
||||
if self.__proxy[3]:
|
||||
# Resolve remotely
|
||||
ipaddr = None
|
||||
if type(destaddr) != type(b''): # python3
|
||||
destaddr_bytes = destaddr.encode(encoding='idna')
|
||||
else:
|
||||
destaddr_bytes = destaddr
|
||||
req = req + chr(0x03).encode() + chr(len(destaddr_bytes)).encode() + destaddr_bytes
|
||||
else:
|
||||
# Resolve locally
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
req = req + struct.pack(">H", destport)
|
||||
self.sendall(req)
|
||||
# Get the response
|
||||
resp = self.__recvall(4)
|
||||
if resp[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
elif resp[1:2] != chr(0x00).encode():
|
||||
# Connection failed
|
||||
self.close()
|
||||
if ord(resp[1:2])<=8:
|
||||
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
|
||||
else:
|
||||
raise Socks5Error((9, _socks5errors[9]))
|
||||
# Get the bound address/port
|
||||
elif resp[3:4] == chr(0x01).encode():
|
||||
boundaddr = self.__recvall(4)
|
||||
elif resp[3:4] == chr(0x03).encode():
|
||||
resp = resp + self.recv(1)
|
||||
boundaddr = self.__recvall(ord(resp[4:5]))
|
||||
else:
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
boundport = struct.unpack(">H", self.__recvall(2))[0]
|
||||
self.__proxysockname = (boundaddr, boundport)
|
||||
if ipaddr != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def getproxysockname(self):
|
||||
"""getsockname() -> address info
|
||||
Returns the bound IP address and port number at the proxy.
|
||||
"""
|
||||
return self.__proxysockname
|
||||
|
||||
def getproxypeername(self):
|
||||
"""getproxypeername() -> address info
|
||||
Returns the IP and port number of the proxy.
|
||||
"""
|
||||
return _orgsocket.getpeername(self)
|
||||
|
||||
def getpeername(self):
|
||||
"""getpeername() -> address info
|
||||
Returns the IP address and port number of the destination
|
||||
machine (note: getproxypeername returns the proxy)
|
||||
"""
|
||||
return self.__proxypeername
|
||||
|
||||
def __negotiatesocks4(self,destaddr,destport):
|
||||
"""__negotiatesocks4(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS4 server.
|
||||
"""
|
||||
# Check if the destination address provided is an IP address
|
||||
rmtrslv = False
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
except socket.error:
|
||||
# It's a DNS name. Check where it should be resolved.
|
||||
if self.__proxy[3]:
|
||||
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
|
||||
rmtrslv = True
|
||||
else:
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
# Construct the request packet
|
||||
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
|
||||
# The username parameter is considered userid for SOCKS4
|
||||
if self.__proxy[4] != None:
|
||||
req = req + self.__proxy[4]
|
||||
req = req + chr(0x00).encode()
|
||||
# DNS name if remote resolving is required
|
||||
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||
# called SOCKS4A and may not be supported in all cases.
|
||||
if rmtrslv:
|
||||
req = req + destaddr + chr(0x00).encode()
|
||||
self.sendall(req)
|
||||
# Get the response from the server
|
||||
resp = self.__recvall(8)
|
||||
if resp[0:1] != chr(0x00).encode():
|
||||
# Bad data
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
if resp[1:2] != chr(0x5A).encode():
|
||||
# Server returned an error
|
||||
self.close()
|
||||
if ord(resp[1:2]) in (91, 92, 93):
|
||||
self.close()
|
||||
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
|
||||
else:
|
||||
raise Socks4Error((94, _socks4errors[4]))
|
||||
# Get the bound address/port
|
||||
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
|
||||
if rmtrslv != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def __negotiatehttp(self, destaddr, destport):
|
||||
"""__negotiatehttp(self,destaddr,destport)
|
||||
Negotiates a connection through an HTTP server.
|
||||
"""
|
||||
# If we need to resolve locally, we do this now
|
||||
if not self.__proxy[3]:
|
||||
addr = socket.gethostbyname(destaddr)
|
||||
else:
|
||||
addr = destaddr
|
||||
self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
|
||||
# We read the response until we get the string "\r\n\r\n"
|
||||
resp = self.recv(1)
|
||||
while resp.find("\r\n\r\n".encode()) == -1:
|
||||
recv = self.recv(1)
|
||||
if not recv:
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
resp = resp + recv
|
||||
# We just need the first line to check if the connection
|
||||
# was successful
|
||||
statusline = resp.splitlines()[0].split(" ".encode(), 2)
|
||||
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
try:
|
||||
statuscode = int(statusline[1])
|
||||
except ValueError:
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if statuscode != 200:
|
||||
self.close()
|
||||
raise HTTPError((statuscode, statusline[2]))
|
||||
self.__proxysockname = ("0.0.0.0", 0)
|
||||
self.__proxypeername = (addr, destport)
|
||||
|
||||
def connect(self, destpair):
|
||||
"""connect(self, despair)
|
||||
Connects to the specified destination through a proxy.
|
||||
destpar - A tuple of the IP/DNS address and the port number.
|
||||
(identical to socket's connect).
|
||||
To select the proxy server use setproxy().
|
||||
"""
|
||||
# Do a minimal input check first
|
||||
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
|
||||
raise GeneralProxyError((5, _generalerrors[5]))
|
||||
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = int(self.__proxy[2])
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
self.__negotiatesocks5(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatesocks4(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_HTTP:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 8080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatehttp(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == None:
|
||||
_orgsocket.connect(self, (destpair[0], destpair[1]))
|
||||
else:
|
||||
raise GeneralProxyError((4, _generalerrors[4]))
|
914
toxygen/tests/support_testing.py
Normal file
914
toxygen/tests/support_testing.py
Normal file
|
@ -0,0 +1,914 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
from ctypes import *
|
||||
from random import Random
|
||||
import functools
|
||||
|
||||
random = Random()
|
||||
|
||||
try:
|
||||
import coloredlogs
|
||||
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
||||
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
|
||||
# https://pypi.org/project/coloredlogs/
|
||||
except ImportError as e:
|
||||
coloredlogs = False
|
||||
try:
|
||||
import stem
|
||||
except ImportError as e:
|
||||
stem = False
|
||||
try:
|
||||
import nmap
|
||||
except ImportError as e:
|
||||
nmap = False
|
||||
|
||||
import wrapper
|
||||
from wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS
|
||||
|
||||
from wrapper_tests.support_http import bAreWeConnected
|
||||
from wrapper_tests.support_onions import (is_valid_fingerprint,
|
||||
lIntroductionPoints,
|
||||
oGetStemController,
|
||||
sMapaddressResolv, sTorResolve)
|
||||
|
||||
try:
|
||||
from user_data.settings import get_user_config_path
|
||||
except ImportError:
|
||||
get_user_config_path = None
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
LOG = logging.getLogger()
|
||||
|
||||
def LOG_ERROR(l): print('ERRORc: '+l)
|
||||
def LOG_WARN(l): print('WARNc: ' +l)
|
||||
def LOG_INFO(l): print('INFOc: ' +l)
|
||||
def LOG_DEBUG(l): print('DEBUGc: '+l)
|
||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
||||
|
||||
try:
|
||||
from trepan.api import debug
|
||||
from trepan.interfaces import server as Mserver
|
||||
except:
|
||||
# print('trepan3 TCP server NOT available.')
|
||||
pass
|
||||
else:
|
||||
# print('trepan3 TCP server available.')
|
||||
def trepan_handler(num=None, f=None):
|
||||
connection_opts={'IO': 'TCP', 'PORT': 6666}
|
||||
intf = Mserver.ServerInterface(connection_opts=connection_opts)
|
||||
dbg_opts = { 'interface': intf }
|
||||
print(f'Starting TCP server listening on port 6666.')
|
||||
debug(dbg_opts=dbg_opts)
|
||||
return
|
||||
|
||||
# self._audio_thread.isAlive
|
||||
iTHREAD_TIMEOUT = 1
|
||||
iTHREAD_SLEEP = 1
|
||||
iTHREAD_JOINS = 8
|
||||
iNODES = 6
|
||||
|
||||
lToxSamplerates = [8000, 12000, 16000, 24000, 48000]
|
||||
lToxSampleratesK = [8, 12, 16, 24, 48]
|
||||
lBOOLEANS = [
|
||||
'local_discovery_enabled',
|
||||
'udp_enabled',
|
||||
'ipv6_enabled',
|
||||
'trace_enabled',
|
||||
'compact_mode',
|
||||
'allow_inline',
|
||||
'notifications',
|
||||
'sound_notifications',
|
||||
'calls_sound',
|
||||
'hole_punching_enabled',
|
||||
'dht_announcements_enabled',
|
||||
'save_history',
|
||||
'download_nodes_list'
|
||||
'core_logging',
|
||||
]
|
||||
|
||||
sDIR = os.environ.get('TMPDIR', '/tmp')
|
||||
sTOX_VERSION = "1000002018"
|
||||
bHAVE_NMAP = shutil.which('nmap')
|
||||
bHAVE_JQ = shutil.which('jq')
|
||||
bHAVE_BASH = shutil.which('bash')
|
||||
bHAVE_TORR = shutil.which('tor-resolve')
|
||||
|
||||
lDEAD_BS = [
|
||||
# Failed to resolve "tox3.plastiras.org"
|
||||
"tox3.plastiras.org",
|
||||
'tox.kolka.tech',
|
||||
# IPs that do not reverse resolve
|
||||
'49.12.229.145',
|
||||
"46.101.197.175",
|
||||
'114.35.245.150',
|
||||
'172.93.52.70',
|
||||
'195.123.208.139',
|
||||
'205.185.115.131',
|
||||
# IPs that do not rreverse resolve
|
||||
'yggnode.cf', '188.225.9.167',
|
||||
'85-143-221-42.simplecloud.ru', '85.143.221.42',
|
||||
# IPs that do not ping
|
||||
'104.244.74.69', 'tox.plastiras.org',
|
||||
'195.123.208.139',
|
||||
'gt.sot-te.ch', '32.226.5.82',
|
||||
# suspicious IPs
|
||||
'tox.abilinski.com', '172.103.164.250', '172.103.164.250.tpia.cipherkey.com',
|
||||
]
|
||||
|
||||
|
||||
def assert_main_thread():
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
# this "instance" method is very useful!
|
||||
app_thread = QtWidgets.QApplication.instance().thread()
|
||||
curr_thread = QtCore.QThread.currentThread()
|
||||
if app_thread != curr_thread:
|
||||
raise RuntimeError('attempt to call MainWindow.append_message from non-app thread')
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignoreStdout():
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
old_stdout = os.dup(1)
|
||||
sys.stdout.flush()
|
||||
os.dup2(devnull, 1)
|
||||
os.close(devnull)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.dup2(old_stdout, 1)
|
||||
os.close(old_stdout)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignoreStderr():
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
old_stderr = os.dup(2)
|
||||
sys.stderr.flush()
|
||||
os.dup2(devnull, 2)
|
||||
os.close(devnull)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.dup2(old_stderr, 2)
|
||||
os.close(old_stderr)
|
||||
|
||||
def clean_booleans(oArgs):
|
||||
for key in lBOOLEANS:
|
||||
if not hasattr(oArgs, key): continue
|
||||
val = getattr(oArgs, key)
|
||||
if type(val) == bool: continue
|
||||
if val in ['False', 'false', '0']:
|
||||
setattr(oArgs, key, False)
|
||||
else:
|
||||
setattr(oArgs, key, True)
|
||||
|
||||
def on_log(iTox, level, filename, line, func, message, *data):
|
||||
# LOG.debug(repr((level, filename, line, func, message,)))
|
||||
tox_log_cb(level, filename, line, func, message)
|
||||
|
||||
def tox_log_cb(level, filename, line, func, message, *args):
|
||||
"""
|
||||
* @param level The severity of the log message.
|
||||
* @param filename The source file from which the message originated.
|
||||
* @param line The source line from which the message originated.
|
||||
* @param func The function from which the message originated.
|
||||
* @param message The log message.
|
||||
* @param user_data The user data pointer passed to tox_new in options.
|
||||
"""
|
||||
if type(func) == bytes:
|
||||
func = str(func, 'utf-8')
|
||||
message = str(message, 'UTF-8')
|
||||
filename = str(filename, 'UTF-8')
|
||||
|
||||
if filename == 'network.c':
|
||||
if line == 660: return
|
||||
# root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket
|
||||
if line == 944: return
|
||||
i = message.find('07 = GET_NODES')
|
||||
if i > 0:
|
||||
return
|
||||
if filename == 'TCP_common.c': return
|
||||
|
||||
i = message.find(' | ')
|
||||
if i > 0:
|
||||
message = message[:i]
|
||||
# message = filename +'#' +str(line) +':'+func +' '+message
|
||||
|
||||
name = 'core'
|
||||
# old level is meaningless
|
||||
level = 10 # LOG.level
|
||||
|
||||
# LOG._log(LOG.level, f"{level}: {message}", list())
|
||||
|
||||
i = message.find('(0: OK)')
|
||||
if i > 0:
|
||||
level = 10 # LOG.debug
|
||||
else:
|
||||
i = message.find('(1: ')
|
||||
if i > 0:
|
||||
level = 30 # LOG.warn
|
||||
else:
|
||||
level = 20 # LOG.info
|
||||
|
||||
o = LOG.makeRecord(filename, level, func, line, message, list(), None)
|
||||
# LOG.handle(o)
|
||||
LOG_TRACE(f"{level}: {func}{line} {message}")
|
||||
return
|
||||
|
||||
elif level == 1:
|
||||
LOG.critical(f"{level}: {message}")
|
||||
elif level == 2:
|
||||
LOG.error(f"{level}: {message}")
|
||||
elif level == 3:
|
||||
LOG.warn(f"{level}: {message}")
|
||||
elif level == 4:
|
||||
LOG.info(f"{level}: {message}")
|
||||
elif level == 5:
|
||||
LOG.debug(f"{level}: {message}")
|
||||
else:
|
||||
LOG_TRACE(f"{level}: {message}")
|
||||
|
||||
def vAddLoggerCallback(tox_options, callback=None):
|
||||
if callback is None:
|
||||
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
|
||||
tox_options._options_pointer,
|
||||
POINTER(None)())
|
||||
tox_options.self_logger_cb = None
|
||||
return
|
||||
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p)
|
||||
tox_options.self_logger_cb = c_callback(callback)
|
||||
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
|
||||
tox_options._options_pointer,
|
||||
tox_options.self_logger_cb)
|
||||
|
||||
def get_video_indexes():
|
||||
# Linux
|
||||
return [str(l[5:]) for l in os.listdir('/dev/') if l.startswith('video')]
|
||||
|
||||
def get_audio():
|
||||
with ignoreStderr():
|
||||
import pyaudio
|
||||
oPyA = pyaudio.PyAudio()
|
||||
|
||||
input_devices = output_devices = 0
|
||||
for i in range(oPyA.get_device_count()):
|
||||
device = oPyA.get_device_info_by_index(i)
|
||||
if device["maxInputChannels"]:
|
||||
input_devices += 1
|
||||
if device["maxOutputChannels"]:
|
||||
output_devices += 1
|
||||
# {'index': 21, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 64, 'maxOutputChannels': 64, 'defaultLowInputLatency': 0.008707482993197279, 'defaultLowOutputLatency': 0.008707482993197279, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
|
||||
audio = {'input': oPyA.get_default_input_device_info()['index'] if input_devices else -1,
|
||||
'output': oPyA.get_default_output_device_info()['index'] if output_devices else -1,
|
||||
'enabled': input_devices and output_devices}
|
||||
return audio
|
||||
|
||||
def oMainArgparser(_=None, iMode=0):
|
||||
# 'Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0'
|
||||
if not os.path.exists('/proc/sys/net/ipv6'):
|
||||
bIpV6 = 'False'
|
||||
else:
|
||||
bIpV6 = 'True'
|
||||
lIpV6Choices=[bIpV6, 'False']
|
||||
|
||||
sNodesJson = os.path.join(os.environ['HOME'], '.config', 'tox', 'DHTnodes.json')
|
||||
if not os.path.exists(sNodesJson): sNodesJson = ''
|
||||
|
||||
logfile = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'toxygen.log')
|
||||
if not os.path.exists(sNodesJson): logfile = ''
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=True)
|
||||
parser.add_argument('--proxy_host', '--proxy-host', type=str,
|
||||
# oddball - we want to use '' as a setting
|
||||
default='0.0.0.0',
|
||||
help='proxy host')
|
||||
parser.add_argument('--proxy_port', '--proxy-port', default=0, type=int,
|
||||
help='proxy port')
|
||||
parser.add_argument('--proxy_type', '--proxy-type', default=0, type=int,
|
||||
choices=[0,1,2],
|
||||
help='proxy type 1=http, 2=socks')
|
||||
parser.add_argument('--tcp_port', '--tcp-port', default=0, type=int,
|
||||
help='tcp port')
|
||||
parser.add_argument('--udp_enabled', type=str, default='True',
|
||||
choices=['True', 'False'],
|
||||
help='En/Disable udp')
|
||||
parser.add_argument('--ipv6_enabled', type=str, default=bIpV6,
|
||||
choices=lIpV6Choices,
|
||||
help=f"En/Disable ipv6 - default {bIpV6}")
|
||||
parser.add_argument('--trace_enabled',type=str,
|
||||
default='True' if os.environ.get('DEBUG') else 'False',
|
||||
choices=['True','False'],
|
||||
help='Debugging from toxcore logger_trace or env DEBUG=1')
|
||||
parser.add_argument('--download_nodes_list', type=str, default='False',
|
||||
choices=['True', 'False'],
|
||||
help='Download nodes list')
|
||||
parser.add_argument('--nodes_json', type=str,
|
||||
default=sNodesJson)
|
||||
parser.add_argument('--network', type=str,
|
||||
choices=['main', 'local'],
|
||||
default='main')
|
||||
parser.add_argument('--download_nodes_url', type=str,
|
||||
default='https://nodes.tox.chat/json')
|
||||
parser.add_argument('--logfile', default=logfile,
|
||||
help='Filename for logging - start with + for stdout too')
|
||||
parser.add_argument('--loglevel', default=logging.INFO, type=int,
|
||||
# choices=[logging.info,logging.trace,logging.debug,logging.error]
|
||||
help='Threshold for logging (lower is more) default: 20')
|
||||
parser.add_argument('--mode', type=int, default=iMode,
|
||||
choices=[0,1,2],
|
||||
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
|
||||
parser.add_argument('--hole_punching_enabled',type=str,
|
||||
default='False', choices=['True','False'],
|
||||
help='En/Enable hole punching')
|
||||
parser.add_argument('--dht_announcements_enabled',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='En/Disable DHT announcements')
|
||||
return parser
|
||||
|
||||
def vSetupLogging(oArgs):
|
||||
global LOG
|
||||
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
|
||||
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
|
||||
logging._defaultFormatter.default_msec_format = ''
|
||||
|
||||
add = None
|
||||
kwargs = dict(level=oArgs.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
if oArgs.logfile:
|
||||
add = oArgs.logfile.startswith('+')
|
||||
sub = oArgs.logfile.startswith('-')
|
||||
if add or sub:
|
||||
oArgs.logfile = oArgs.logfile[1:]
|
||||
kwargs['filename'] = oArgs.logfile
|
||||
|
||||
if coloredlogs:
|
||||
# https://pypi.org/project/coloredlogs/
|
||||
aKw = dict(level=oArgs.loglevel,
|
||||
logger=LOG,
|
||||
stream=sys.stdout,
|
||||
fmt='%(name)s %(levelname)s %(message)s'
|
||||
)
|
||||
coloredlogs.install(**aKw)
|
||||
if oArgs.logfile:
|
||||
oHandler = logging.FileHandler(oArgs.logfile)
|
||||
LOG.addHandler(oHandler)
|
||||
else:
|
||||
logging.basicConfig(**kwargs)
|
||||
if add:
|
||||
oHandler = logging.StreamHandler(sys.stdout)
|
||||
LOG.addHandler(oHandler)
|
||||
|
||||
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
|
||||
|
||||
|
||||
def setup_logging(oArgs):
|
||||
global LOG
|
||||
if coloredlogs:
|
||||
aKw = dict(level=oArgs.loglevel,
|
||||
logger=LOG,
|
||||
fmt='%(name)s %(levelname)s %(message)s')
|
||||
if oArgs.logfile:
|
||||
oFd = open(oArgs.logfile, 'wt')
|
||||
setattr(oArgs, 'log_oFd', oFd)
|
||||
aKw['stream'] = oFd
|
||||
coloredlogs.install(**aKw)
|
||||
if oArgs.logfile:
|
||||
oHandler = logging.StreamHandler(stream=sys.stdout)
|
||||
LOG.addHandler(oHandler)
|
||||
else:
|
||||
aKw = dict(level=oArgs.loglevel,
|
||||
format='%(name)s %(levelname)-4s %(message)s')
|
||||
if oArgs.logfile:
|
||||
aKw['filename'] = oArgs.logfile
|
||||
logging.basicConfig(**aKw)
|
||||
|
||||
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
|
||||
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
|
||||
logging._defaultFormatter.default_msec_format = ''
|
||||
|
||||
LOG.setLevel(oArgs.loglevel)
|
||||
# LOG.trace = lambda l: LOG.log(0, repr(l))
|
||||
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
|
||||
|
||||
def signal_handler(num, f):
|
||||
from trepan.api import debug
|
||||
from trepan.interfaces import server as Mserver
|
||||
connection_opts={'IO': 'TCP', 'PORT': 6666}
|
||||
intf = Mserver.ServerInterface(connection_opts=connection_opts)
|
||||
dbg_opts = {'interface': intf}
|
||||
LOG.info('Starting TCP server listening on port 6666.')
|
||||
debug(dbg_opts=dbg_opts)
|
||||
return
|
||||
|
||||
def merge_args_into_settings(args, settings):
|
||||
if args:
|
||||
if not hasattr(args, 'audio'):
|
||||
LOG.warn('No audio ' +repr(args))
|
||||
settings['audio'] = getattr(args, 'audio')
|
||||
if not hasattr(args, 'video'):
|
||||
LOG.warn('No video ' +repr(args))
|
||||
settings['video'] = getattr(args, 'video')
|
||||
for key in settings.keys():
|
||||
# proxy_type proxy_port proxy_host
|
||||
not_key = 'not_' +key
|
||||
if hasattr(args, key):
|
||||
val = getattr(args, key)
|
||||
if type(val) == bytes:
|
||||
# proxy_host - ascii?
|
||||
# filenames - ascii?
|
||||
val = str(val, 'UTF-8')
|
||||
settings[key] = val
|
||||
elif hasattr(args, not_key):
|
||||
val = not getattr(args, not_key)
|
||||
settings[key] = val
|
||||
clean_settings(settings)
|
||||
return
|
||||
|
||||
def clean_settings(self):
|
||||
# failsafe to ensure C tox is bytes and Py settings is str
|
||||
|
||||
# overrides
|
||||
self['mirror_mode'] = False
|
||||
# REQUIRED!!
|
||||
if not os.path.exists('/proc/sys/net/ipv6'):
|
||||
LOG.warn('Disabling IPV6 because /proc/sys/net/ipv6 does not exist')
|
||||
self['ipv6_enabled'] = False
|
||||
|
||||
if 'proxy_type' in self and self['proxy_type'] == 0:
|
||||
self['proxy_host'] = ''
|
||||
self['proxy_port'] = 0
|
||||
|
||||
if 'proxy_type' in self and self['proxy_type'] != 0 and \
|
||||
'proxy_host' in self and self['proxy_host'] != '' and \
|
||||
'proxy_port' in self and self['proxy_port'] != 0:
|
||||
if 'udp_enabled' in self and self['udp_enabled']:
|
||||
# We don't currently support UDP over proxy.
|
||||
LOG.info("UDP enabled and proxy set: disabling UDP")
|
||||
self['udp_enabled'] = False
|
||||
if 'local_discovery_enabled' in self and self['local_discovery_enabled']:
|
||||
LOG.info("local_discovery_enabled enabled and proxy set: disabling local_discovery_enabled")
|
||||
self['local_discovery_enabled'] = False
|
||||
if 'dht_announcements_enabled' in self and self['dht_announcements_enabled']:
|
||||
LOG.info("dht_announcements_enabled enabled and proxy set: disabling dht_announcements_enabled")
|
||||
self['dht_announcements_enabled'] = False
|
||||
|
||||
if 'auto_accept_path' in self and \
|
||||
type(self['auto_accept_path']) == bytes:
|
||||
self['auto_accept_path'] = str(self['auto_accept_path'], 'UTF-8')
|
||||
|
||||
LOG.debug("Cleaned settings")
|
||||
|
||||
def lSdSamplerates(iDev):
|
||||
try:
|
||||
import sounddevice as sd
|
||||
except ImportError:
|
||||
return []
|
||||
samplerates = (32000, 44100, 48000, 96000, )
|
||||
device = iDev
|
||||
supported_samplerates = []
|
||||
for fs in samplerates:
|
||||
try:
|
||||
sd.check_output_settings(device=device, samplerate=fs)
|
||||
except Exception as e:
|
||||
# LOG.debug(f"Sample rate not supported {fs}" +' '+str(e))
|
||||
pass
|
||||
else:
|
||||
supported_samplerates.append(fs)
|
||||
return supported_samplerates
|
||||
|
||||
def _get_nodes_path(oArgs=None):
|
||||
if oArgs and oArgs.nodes_json and os.path.isfile(oArgs.nodes_json):
|
||||
LOG.debug("_get_nodes_path: " +oArgs.nodes_json)
|
||||
default = oArgs.nodes_json
|
||||
elif get_user_config_path:
|
||||
default = os.path.join(get_user_config_path(), 'toxygen_nodes.json')
|
||||
else:
|
||||
# Windwoes
|
||||
default = os.path.join(os.getenv('HOME'), '.config', 'tox', 'toxygen_nodes.json')
|
||||
LOG.debug("_get_nodes_path: " +default)
|
||||
return default
|
||||
|
||||
DEFAULT_NODES_COUNT = 8
|
||||
|
||||
global aNODES
|
||||
aNODES = {}
|
||||
|
||||
|
||||
# @functools.lru_cache(maxsize=12) TypeError: unhashable type: 'Namespace'
|
||||
def generate_nodes(oArgs=None,
|
||||
nodes_count=DEFAULT_NODES_COUNT,
|
||||
ipv='ipv4',
|
||||
udp_not_tcp=True):
|
||||
global aNODES
|
||||
sKey = ipv
|
||||
sKey += ',0' if udp_not_tcp else ',1'
|
||||
if sKey in aNODES and aNODES[sKey]:
|
||||
return aNODES[sKey]
|
||||
sFile = _get_nodes_path(oArgs=oArgs)
|
||||
assert os.path.exists(sFile), sFile
|
||||
lNodes = generate_nodes_from_file(sFile,
|
||||
nodes_count=nodes_count,
|
||||
ipv=ipv, udp_not_tcp=udp_not_tcp)
|
||||
assert lNodes
|
||||
aNODES[sKey] = lNodes
|
||||
return aNODES[sKey]
|
||||
|
||||
aNODES_CACHE = {}
|
||||
def generate_nodes_from_file(sFile,
|
||||
nodes_count=DEFAULT_NODES_COUNT,
|
||||
ipv='ipv4',
|
||||
udp_not_tcp=True,
|
||||
):
|
||||
"""https://github.com/TokTok/c-toxcore/issues/469
|
||||
I had a conversation with @irungentoo on IRC about whether we really need to call tox_bootstrap() when having UDP disabled and why. The answer is yes, because in addition to TCP relays (tox_add_tcp_relay()), toxcore also needs to know addresses of UDP onion nodes in order to work correctly. The DHT, however, is not used when UDP is disabled. tox_bootstrap() function resolves the address passed to it as argument and calls onion_add_bs_node_path() and DHT_bootstrap() functions. Although calling DHT_bootstrap() is not really necessary as DHT is not used, we still need to resolve the address of the DHT node in order to populate the onion routes with onion_add_bs_node_path() call.
|
||||
"""
|
||||
global aNODES_CACHE
|
||||
|
||||
key = ipv
|
||||
key += ',0' if udp_not_tcp else ',1'
|
||||
if key in aNODES_CACHE:
|
||||
sorted_nodes = aNODES_CACHE[key]
|
||||
else:
|
||||
if not os.path.exists(sFile):
|
||||
LOG.error("generate_nodes_from_file file not found " +sFile)
|
||||
return []
|
||||
try:
|
||||
with open(sFile, 'rt') as fl:
|
||||
json_nodes = json.loads(fl.read())['nodes']
|
||||
except Exception as e:
|
||||
LOG.error(f"generate_nodes_from_file error {sFile}\n{e}")
|
||||
return []
|
||||
else:
|
||||
LOG.debug("generate_nodes_from_file " +sFile)
|
||||
|
||||
if udp_not_tcp:
|
||||
nodes = [(node[ipv], node['port'], node['public_key'],) for
|
||||
node in json_nodes if node[ipv] != 'NONE' \
|
||||
and node["status_udp"] in [True, "true"]
|
||||
]
|
||||
else:
|
||||
nodes = []
|
||||
elts = [(node[ipv], node['tcp_ports'], node['public_key'],) \
|
||||
for node in json_nodes if node[ipv] != 'NONE' \
|
||||
and node["status_tcp"] in [True, "true"]
|
||||
]
|
||||
for (ipv, ports, public_key,) in elts:
|
||||
for port in ports:
|
||||
nodes += [(ipv, port, public_key)]
|
||||
if not nodes:
|
||||
LOG.warn(f'empty generate_nodes from {sFile} {json_nodes!r}')
|
||||
return []
|
||||
sorted_nodes = nodes
|
||||
aNODES_CACHE[key] = sorted_nodes
|
||||
|
||||
random.shuffle(sorted_nodes)
|
||||
if nodes_count is not None and len(sorted_nodes) > nodes_count:
|
||||
sorted_nodes = sorted_nodes[-nodes_count:]
|
||||
LOG.debug(f"generate_nodes_from_file {sFile} len={len(sorted_nodes)}")
|
||||
return sorted_nodes
|
||||
|
||||
def tox_bootstrapd_port():
|
||||
port = 33446
|
||||
sFile = '/etc/tox-bootstrapd.conf'
|
||||
if os.path.exists(sFile):
|
||||
with open(sFile, 'rt') as oFd:
|
||||
for line in oFd.readlines():
|
||||
if line.startswith('port = '):
|
||||
port = int(line[7:])
|
||||
return port
|
||||
|
||||
def bootstrap_local(elts, lToxes, oArgs=None):
|
||||
if os.path.exists('/run/tox-bootstrapd/tox-bootstrapd.pid'):
|
||||
LOG.debug('/run/tox-bootstrapd/tox-bootstrapd.pid')
|
||||
iRet = True
|
||||
else:
|
||||
iRet = os.system("netstat -nle4|grep -q :33")
|
||||
if iRet > 0:
|
||||
LOG.warn(f'bootstraping local No local DHT running')
|
||||
LOG.info(f'bootstraping local')
|
||||
return bootstrap_udp(elts, lToxes, oArgs)
|
||||
|
||||
def lDNSClean(l):
|
||||
global lDEAD_BS
|
||||
# list(set(l).difference(set(lDEAD_BS)))
|
||||
return [elt for elt in l if elt not in lDEAD_BS]
|
||||
|
||||
def lExitExcluder(oArgs, iPort=9051):
|
||||
"""
|
||||
https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py
|
||||
"""
|
||||
if not stem:
|
||||
LOG.warn('please install the stem Python package')
|
||||
return ''
|
||||
LOG.debug('lExcludeExitNodes')
|
||||
|
||||
try:
|
||||
controller = oGetStemController(log_level=10)
|
||||
# generator
|
||||
relays = controller.get_server_descriptors()
|
||||
except Exception as e:
|
||||
LOG.error(f'Failed to get relay descriptors {e}')
|
||||
return None
|
||||
|
||||
if controller.is_set('ExcludeExitNodes'):
|
||||
LOG.info('ExcludeExitNodes is in use already.')
|
||||
return None
|
||||
|
||||
exit_excludelist=[]
|
||||
LOG.debug("Excluded exit relays:")
|
||||
for relay in relays:
|
||||
if relay.exit_policy.is_exiting_allowed() and not relay.contact:
|
||||
if is_valid_fingerprint(relay.fingerprint):
|
||||
exit_excludelist.append(relay.fingerprint)
|
||||
LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint)
|
||||
else:
|
||||
LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint)
|
||||
|
||||
try:
|
||||
controller.set_conf('ExcludeExitNodes', exit_excludelist)
|
||||
LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist))
|
||||
except Exception as e:
|
||||
LOG.exception('ExcludeExitNodes ' +str(e))
|
||||
return exit_excludelist
|
||||
|
||||
aHOSTS = {}
|
||||
@functools.lru_cache(maxsize=20)
|
||||
def sDNSLookup(host):
|
||||
global aHOSTS
|
||||
ipv = 0
|
||||
if host in lDEAD_BS:
|
||||
# LOG.warn(f"address skipped because in lDEAD_BS {host}")
|
||||
return ''
|
||||
if host in aHOSTS:
|
||||
return aHOSTS[host]
|
||||
|
||||
try:
|
||||
s = host.replace('.','')
|
||||
int(s)
|
||||
ipv = 4
|
||||
except:
|
||||
try:
|
||||
s = host.replace(':','')
|
||||
int(s)
|
||||
ipv = 6
|
||||
except: pass
|
||||
|
||||
if ipv > 0:
|
||||
# LOG.debug(f"v={ipv} IP address {host}")
|
||||
return host
|
||||
|
||||
LOG.debug(f"sDNSLookup {host}")
|
||||
ip = ''
|
||||
if host.endswith('.tox') or host.endswith('.onion'):
|
||||
if False and stem:
|
||||
ip = sMapaddressResolv(host)
|
||||
if ip: return ip
|
||||
|
||||
ip = sTorResolve(host)
|
||||
if ip: return ip
|
||||
|
||||
if not bHAVE_TORR:
|
||||
LOG.warn(f"onion address skipped because no tor-resolve {host}")
|
||||
return ''
|
||||
try:
|
||||
sout = f"/tmp/TR{os.getpid()}.log"
|
||||
i = os.system(f"tor-resolve -4 {host} > {sout}")
|
||||
if not i:
|
||||
LOG.warn(f"onion address skipped because tor-resolve on {host}")
|
||||
return ''
|
||||
ip = open(sout, 'rt').read()
|
||||
if ip.endswith('failed.'):
|
||||
LOG.warn(f"onion address skipped because tor-resolve failed on {host}")
|
||||
return ''
|
||||
LOG.debug(f"onion address tor-resolve {ip} on {host}")
|
||||
return ip
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
ip = socket.gethostbyname(host)
|
||||
LOG.debug(f"host={host} gethostbyname IP address {ip}")
|
||||
if ip:
|
||||
aHOSTS[host] = ip
|
||||
return ip
|
||||
# drop through
|
||||
except:
|
||||
# drop through
|
||||
pass
|
||||
|
||||
if ip == '':
|
||||
try:
|
||||
sout = f"/tmp/TR{os.getpid()}.log"
|
||||
i = os.system(f"dig {host} +timeout=15|grep ^{host}|sed -e 's/.* //'> {sout}")
|
||||
if not i:
|
||||
LOG.warn(f"address skipped because dig failed on {host}")
|
||||
return ''
|
||||
ip = open(sout, 'rt').read().strip()
|
||||
LOG.debug(f"address dig {ip} on {host}")
|
||||
aHOSTS[host] = ip
|
||||
return ip
|
||||
except:
|
||||
ip = host
|
||||
LOG.debug(f'sDNSLookup {host} -> {ip}')
|
||||
if ip and ip != host:
|
||||
aHOSTS[host] = ip
|
||||
return ip
|
||||
|
||||
def bootstrap_udp(lelts, lToxes, oArgs=None):
|
||||
lelts = lDNSClean(lelts)
|
||||
socket.setdefaulttimeout(15.0)
|
||||
for oTox in lToxes:
|
||||
random.shuffle(lelts)
|
||||
if hasattr(oTox, 'oArgs'):
|
||||
oArgs = oTox.oArgs
|
||||
if hasattr(oArgs, 'contents') and oArgs.contents.proxy_type != 0:
|
||||
lelts = lelts[:1]
|
||||
|
||||
# LOG.debug(f'bootstrap_udp DHT bootstraping {oTox.name} {len(lelts)}')
|
||||
for largs in lelts:
|
||||
assert len(largs) == 3
|
||||
host, port, key = largs
|
||||
assert host; assert port; assert key
|
||||
if host in lDEAD_BS: continue
|
||||
ip = sDNSLookup(host)
|
||||
if not ip:
|
||||
LOG.warn(f'bootstrap_udp to host={host} port={port} did not resolve ip={ip}')
|
||||
continue
|
||||
|
||||
if type(port) == str:
|
||||
port = int(port)
|
||||
try:
|
||||
assert len(key) == 64, key
|
||||
# NOT ip
|
||||
oRet = oTox.bootstrap(host,
|
||||
port,
|
||||
key)
|
||||
except Exception as e:
|
||||
if oArgs is None or (
|
||||
hasattr(oArgs, 'contents') and oArgs.contents.proxy_type == 0):
|
||||
pass
|
||||
# LOG.error(f'bootstrap_udp failed to host={host} port={port} {e}')
|
||||
continue
|
||||
if not oRet:
|
||||
LOG.warn(f'bootstrap_udp failed to {host} : {oRet}')
|
||||
elif oTox.self_get_connection_status() != TOX_CONNECTION['NONE']:
|
||||
LOG.info(f'bootstrap_udp to {host} connected')
|
||||
break
|
||||
else:
|
||||
# LOG.debug(f'bootstrap_udp to {host} not connected')
|
||||
pass
|
||||
|
||||
def bootstrap_tcp(lelts, lToxes, oArgs=None):
|
||||
lelts = lDNSClean(lelts)
|
||||
for oTox in lToxes:
|
||||
if hasattr(oTox, 'oArgs'): oArgs = oTox.oArgs
|
||||
random.shuffle(lelts)
|
||||
# LOG.debug(f'bootstrap_tcp bootstapping {oTox.name} {len(lelts)}')
|
||||
for (host, port, key,) in lelts:
|
||||
assert host; assert port;assert key
|
||||
if host in lDEAD_BS: continue
|
||||
ip = sDNSLookup(host)
|
||||
if not ip:
|
||||
LOG.warn(f'bootstrap_tcp to {host} did not resolve ip={ip}')
|
||||
# continue
|
||||
ip = host
|
||||
if host.endswith('.onion') and stem:
|
||||
l = lIntroductionPoints(host)
|
||||
if not l:
|
||||
LOG.warn(f'bootstrap_tcp to {host} has no introduction points')
|
||||
continue
|
||||
if type(port) == str:
|
||||
port = int(port)
|
||||
try:
|
||||
assert len(key) == 64, key
|
||||
oRet = oTox.add_tcp_relay(ip,
|
||||
port,
|
||||
key)
|
||||
except Exception as e:
|
||||
LOG.error(f'bootstrap_tcp to {host} : ' +str(e))
|
||||
continue
|
||||
if not oRet:
|
||||
LOG.warn(f'bootstrap_tcp failed to {host} : {oRet}')
|
||||
elif oTox.mycon_time == 1:
|
||||
LOG.info(f'bootstrap_tcp to {host} not yet connected last=1')
|
||||
elif oTox.mycon_status is False:
|
||||
LOG.info(f'bootstrap_tcp to {host} not True' \
|
||||
+f" last={int(oTox.mycon_time)}" )
|
||||
elif oTox.self_get_connection_status() != TOX_CONNECTION['NONE']:
|
||||
LOG.info(f'bootstrap_tcp to {host} connected' \
|
||||
+f" last={int(oTox.mycon_time)}" )
|
||||
break
|
||||
else:
|
||||
LOG.debug(f'bootstrap_tcp to {host} but not connected' \
|
||||
+f" last={int(oTox.mycon_time)}" )
|
||||
pass
|
||||
|
||||
def iNmapInfoNmap(sProt, sHost, sPort, key=None, environ=None, cmd=''):
|
||||
if sHost in ['-', 'NONE']: return 0
|
||||
if not nmap: return 0
|
||||
nmps = nmap.PortScanner
|
||||
if sProt in ['socks', 'socks5', 'tcp4']:
|
||||
prot = 'tcp'
|
||||
cmd = f" -Pn -n -sT -p T:{sPort}"
|
||||
else:
|
||||
prot = 'udp'
|
||||
cmd = f" -Pn -n -sU -p U:{sPort}"
|
||||
LOG.debug(f"iNmapInfoNmap cmd={cmd}")
|
||||
sys.stdout.flush()
|
||||
o = nmps().scan(hosts=sHost, arguments=cmd)
|
||||
aScan = o['scan']
|
||||
ip = list(aScan.keys())[0]
|
||||
state = aScan[ip][prot][sPort]['state']
|
||||
LOG.info(f"iNmapInfoNmap: to {sHost} {state}")
|
||||
return 0
|
||||
|
||||
def iNmapInfo(sProt, sHost, sPort, key=None, environ=None, cmd='nmap'):
|
||||
if sHost in ['-', 'NONE']: return 0
|
||||
sFile = os.path.join("/tmp", f"{sHost}.{os.getpid()}.nmap")
|
||||
if sProt in ['socks', 'socks5', 'tcp4']:
|
||||
cmd += f" -Pn -n -sT -p T:{sPort} {sHost} | grep /tcp "
|
||||
else:
|
||||
cmd += f" -Pn -n -sU -p U:{sPort} {sHost} | grep /udp "
|
||||
LOG.debug(f"iNmapInfo cmd={cmd}")
|
||||
sys.stdout.flush()
|
||||
iRet = os.system('sudo ' +cmd +f" >{sFile} 2>&1 ")
|
||||
LOG.debug(f"iNmapInfo cmd={cmd} iRet={iRet}")
|
||||
if iRet != 0:
|
||||
return iRet
|
||||
assert os.path.exists(sFile), sFile
|
||||
with open(sFile, 'rt') as oFd:
|
||||
l = oFd.readlines()
|
||||
assert len(l)
|
||||
l = [line for line in l if line and not line.startswith('WARNING:')]
|
||||
s = '\n'.join([s.strip() for s in l])
|
||||
LOG.info(f"iNmapInfo: to {sHost}\n{s}")
|
||||
return 0
|
||||
|
||||
def bootstrap_iNmapInfo(lElts, oArgs, protocol="tcp4", bIS_LOCAL=False, iNODES=iNODES, cmd='nmap'):
|
||||
if not bIS_LOCAL and not bAreWeConnected():
|
||||
LOG.warn(f"bootstrap_iNmapInfo not local and NOT CONNECTED")
|
||||
return True
|
||||
if os.environ['USER'] != 'root':
|
||||
LOG.warn(f"bootstrap_iNmapInfo not ROOT")
|
||||
return True
|
||||
|
||||
lRetval = []
|
||||
for elts in lElts[:iNODES]:
|
||||
host, port, key = elts
|
||||
ip = sDNSLookup(host)
|
||||
if not ip:
|
||||
LOG.info('bootstrap_iNmapInfo to {host} did not resolve ip={ip}')
|
||||
continue
|
||||
if type(port) == str:
|
||||
port = int(port)
|
||||
iRet = -1
|
||||
try:
|
||||
if not nmap:
|
||||
iRet = iNmapInfo(protocol, ip, port, key, cmd=cmd)
|
||||
else:
|
||||
iRet = iNmapInfoNmap(protocol, ip, port, key)
|
||||
if iRet != 0:
|
||||
LOG.warn('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
|
||||
lRetval += [False]
|
||||
else:
|
||||
LOG.debug('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
|
||||
lRetval += [True]
|
||||
except Exception as e:
|
||||
LOG.exception('iNmapInfo to {host} : ' +str(e)
|
||||
)
|
||||
lRetval += [False]
|
||||
return any(lRetval)
|
||||
|
||||
def caseFactory(cases):
|
||||
"""We want the tests run in order."""
|
||||
if len(cases) > 1:
|
||||
ordered_cases = sorted(cases, key=lambda f: inspect.findsource(f)[1])
|
||||
else:
|
||||
ordered_cases = cases
|
||||
return ordered_cases
|
||||
|
||||
def suiteFactory(*testcases):
|
||||
"""We want the tests run in order."""
|
||||
linen = lambda f: getattr(tc, f).__code__.co_firstlineno
|
||||
lncmp = lambda a, b: linen(a) - linen(b)
|
||||
|
||||
test_suite = unittest.TestSuite()
|
||||
for tc in testcases:
|
||||
test_suite.addTest(unittest.makeSuite(tc, sortUsing=lncmp))
|
||||
return test_suite
|
936
toxygen/tests/test_gdb.py
Normal file
936
toxygen/tests/test_gdb.py
Normal file
|
@ -0,0 +1,936 @@
|
|||
# Verify that gdb can pretty-print the various PyObject* types
|
||||
#
|
||||
# The code for testing gdb was adapted from similar work in Unladen Swallow's
|
||||
# Lib/test/test_jit_gdb.py
|
||||
|
||||
import locale
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import textwrap
|
||||
import unittest
|
||||
|
||||
# Is this Python configured to support threads?
|
||||
try:
|
||||
import _thread
|
||||
except ImportError:
|
||||
_thread = None
|
||||
|
||||
from test import support
|
||||
from test.support import run_unittest, findfile, python_is_optimized
|
||||
|
||||
def get_gdb_version():
|
||||
try:
|
||||
proc = subprocess.Popen(["gdb", "-nx", "--version"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
with proc:
|
||||
version = proc.communicate()[0]
|
||||
except OSError:
|
||||
# This is what "no gdb" looks like. There may, however, be other
|
||||
# errors that manifest this way too.
|
||||
raise unittest.SkipTest("Couldn't find gdb on the path")
|
||||
|
||||
# Regex to parse:
|
||||
# 'GNU gdb (GDB; SUSE Linux Enterprise 12) 7.7\n' -> 7.7
|
||||
# 'GNU gdb (GDB) Fedora 7.9.1-17.fc22\n' -> 7.9
|
||||
# 'GNU gdb 6.1.1 [FreeBSD]\n' -> 6.1
|
||||
# 'GNU gdb (GDB) Fedora (7.5.1-37.fc18)\n' -> 7.5
|
||||
match = re.search(r"^GNU gdb.*?\b(\d+)\.(\d+)", version)
|
||||
if match is None:
|
||||
raise Exception("unable to parse GDB version: %r" % version)
|
||||
return (version, int(match.group(1)), int(match.group(2)))
|
||||
|
||||
gdb_version, gdb_major_version, gdb_minor_version = get_gdb_version()
|
||||
if gdb_major_version < 7:
|
||||
raise unittest.SkipTest("gdb versions before 7.0 didn't support python "
|
||||
"embedding. Saw %s.%s:\n%s"
|
||||
% (gdb_major_version, gdb_minor_version,
|
||||
gdb_version))
|
||||
|
||||
if not sysconfig.is_python_build():
|
||||
raise unittest.SkipTest("test_gdb only works on source builds at the moment.")
|
||||
|
||||
# Location of custom hooks file in a repository checkout.
|
||||
checkout_hook_path = os.path.join(os.path.dirname(sys.executable),
|
||||
'python-gdb.py')
|
||||
|
||||
PYTHONHASHSEED = '123'
|
||||
|
||||
def run_gdb(*args, **env_vars):
|
||||
"""Runs gdb in --batch mode with the additional arguments given by *args.
|
||||
|
||||
Returns its (stdout, stderr) decoded from utf-8 using the replace handler.
|
||||
"""
|
||||
if env_vars:
|
||||
env = os.environ.copy()
|
||||
env.update(env_vars)
|
||||
else:
|
||||
env = None
|
||||
# -nx: Do not execute commands from any .gdbinit initialization files
|
||||
# (issue #22188)
|
||||
base_cmd = ('gdb', '--batch', '-nx')
|
||||
if (gdb_major_version, gdb_minor_version) >= (7, 4):
|
||||
base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path)
|
||||
proc = subprocess.Popen(base_cmd + args,
|
||||
# Redirect stdin to prevent GDB from messing with
|
||||
# the terminal settings
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=env)
|
||||
with proc:
|
||||
out, err = proc.communicate()
|
||||
return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace')
|
||||
|
||||
# Verify that "gdb" was built with the embedded python support enabled:
|
||||
gdbpy_version, _ = run_gdb("--eval-command=python import sys; print(sys.version_info)")
|
||||
if not gdbpy_version:
|
||||
raise unittest.SkipTest("gdb not built with embedded python support")
|
||||
|
||||
# Verify that "gdb" can load our custom hooks, as OS security settings may
|
||||
# disallow this without a customized .gdbinit.
|
||||
_, gdbpy_errors = run_gdb('--args', sys.executable)
|
||||
if "auto-loading has been declined" in gdbpy_errors:
|
||||
msg = "gdb security settings prevent use of custom hooks: "
|
||||
raise unittest.SkipTest(msg + gdbpy_errors.rstrip())
|
||||
|
||||
def gdb_has_frame_select():
|
||||
# Does this build of gdb have gdb.Frame.select ?
|
||||
stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))")
|
||||
m = re.match(r'.*\[(.*)\].*', stdout)
|
||||
if not m:
|
||||
raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test")
|
||||
gdb_frame_dir = m.group(1).split(', ')
|
||||
return "'select'" in gdb_frame_dir
|
||||
|
||||
HAS_PYUP_PYDOWN = gdb_has_frame_select()
|
||||
|
||||
BREAKPOINT_FN='builtin_id'
|
||||
|
||||
@unittest.skipIf(support.PGO, "not useful for PGO")
|
||||
class DebuggerTests(unittest.TestCase):
|
||||
|
||||
"""Test that the debugger can debug Python."""
|
||||
|
||||
def get_stack_trace(self, source=None, script=None,
|
||||
breakpoint=BREAKPOINT_FN,
|
||||
cmds_after_breakpoint=None,
|
||||
import_site=False):
|
||||
'''
|
||||
Run 'python -c SOURCE' under gdb with a breakpoint.
|
||||
|
||||
Support injecting commands after the breakpoint is reached
|
||||
|
||||
Returns the stdout from gdb
|
||||
|
||||
cmds_after_breakpoint: if provided, a list of strings: gdb commands
|
||||
'''
|
||||
# We use "set breakpoint pending yes" to avoid blocking with a:
|
||||
# Function "foo" not defined.
|
||||
# Make breakpoint pending on future shared library load? (y or [n])
|
||||
# error, which typically happens python is dynamically linked (the
|
||||
# breakpoints of interest are to be found in the shared library)
|
||||
# When this happens, we still get:
|
||||
# Function "textiowrapper_write" not defined.
|
||||
# emitted to stderr each time, alas.
|
||||
|
||||
# Initially I had "--eval-command=continue" here, but removed it to
|
||||
# avoid repeated print breakpoints when traversing hierarchical data
|
||||
# structures
|
||||
|
||||
# Generate a list of commands in gdb's language:
|
||||
commands = ['set breakpoint pending yes',
|
||||
'break %s' % breakpoint,
|
||||
|
||||
# The tests assume that the first frame of printed
|
||||
# backtrace will not contain program counter,
|
||||
# that is however not guaranteed by gdb
|
||||
# therefore we need to use 'set print address off' to
|
||||
# make sure the counter is not there. For example:
|
||||
# #0 in PyObject_Print ...
|
||||
# is assumed, but sometimes this can be e.g.
|
||||
# #0 0x00003fffb7dd1798 in PyObject_Print ...
|
||||
'set print address off',
|
||||
|
||||
'run']
|
||||
|
||||
# GDB as of 7.4 onwards can distinguish between the
|
||||
# value of a variable at entry vs current value:
|
||||
# http://sourceware.org/gdb/onlinedocs/gdb/Variables.html
|
||||
# which leads to the selftests failing with errors like this:
|
||||
# AssertionError: 'v@entry=()' != '()'
|
||||
# Disable this:
|
||||
if (gdb_major_version, gdb_minor_version) >= (7, 4):
|
||||
commands += ['set print entry-values no']
|
||||
|
||||
if cmds_after_breakpoint:
|
||||
commands += cmds_after_breakpoint
|
||||
else:
|
||||
commands += ['backtrace']
|
||||
|
||||
# print commands
|
||||
|
||||
# Use "commands" to generate the arguments with which to invoke "gdb":
|
||||
args = ['--eval-command=%s' % cmd for cmd in commands]
|
||||
args += ["--args",
|
||||
sys.executable]
|
||||
args.extend(subprocess._args_from_interpreter_flags())
|
||||
|
||||
if not import_site:
|
||||
# -S suppresses the default 'import site'
|
||||
args += ["-S"]
|
||||
|
||||
if source:
|
||||
args += ["-c", source]
|
||||
elif script:
|
||||
args += [script]
|
||||
|
||||
# print args
|
||||
# print (' '.join(args))
|
||||
|
||||
# Use "args" to invoke gdb, capturing stdout, stderr:
|
||||
out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
|
||||
|
||||
errlines = err.splitlines()
|
||||
unexpected_errlines = []
|
||||
|
||||
# Ignore some benign messages on stderr.
|
||||
ignore_patterns = (
|
||||
'Function "%s" not defined.' % breakpoint,
|
||||
'Do you need "set solib-search-path" or '
|
||||
'"set sysroot"?',
|
||||
# BFD: /usr/lib/debug/(...): unable to initialize decompress
|
||||
# status for section .debug_aranges
|
||||
'BFD: ',
|
||||
# ignore all warnings
|
||||
'warning: ',
|
||||
)
|
||||
for line in errlines:
|
||||
if not line:
|
||||
continue
|
||||
if not line.startswith(ignore_patterns):
|
||||
unexpected_errlines.append(line)
|
||||
|
||||
# Ensure no unexpected error messages:
|
||||
self.assertEqual(unexpected_errlines, [])
|
||||
return out
|
||||
|
||||
def get_gdb_repr(self, source,
|
||||
cmds_after_breakpoint=None,
|
||||
import_site=False):
|
||||
# Given an input python source representation of data,
|
||||
# run "python -c'id(DATA)'" under gdb with a breakpoint on
|
||||
# builtin_id and scrape out gdb's representation of the "op"
|
||||
# parameter, and verify that the gdb displays the same string
|
||||
#
|
||||
# Verify that the gdb displays the expected string
|
||||
#
|
||||
# For a nested structure, the first time we hit the breakpoint will
|
||||
# give us the top-level structure
|
||||
|
||||
# NOTE: avoid decoding too much of the traceback as some
|
||||
# undecodable characters may lurk there in optimized mode
|
||||
# (issue #19743).
|
||||
cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
|
||||
gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
|
||||
cmds_after_breakpoint=cmds_after_breakpoint,
|
||||
import_site=import_site)
|
||||
# gdb can insert additional '\n' and space characters in various places
|
||||
# in its output, depending on the width of the terminal it's connected
|
||||
# to (using its "wrap_here" function)
|
||||
m = re.match(r'.*#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)\)\s+at\s+\S*Python/bltinmodule.c.*',
|
||||
gdb_output, re.DOTALL)
|
||||
if not m:
|
||||
self.fail('Unexpected gdb output: %r\n%s' % (gdb_output, gdb_output))
|
||||
return m.group(1), gdb_output
|
||||
|
||||
def assertEndsWith(self, actual, exp_end):
|
||||
'''Ensure that the given "actual" string ends with "exp_end"'''
|
||||
self.assertTrue(actual.endswith(exp_end),
|
||||
msg='%r did not end with %r' % (actual, exp_end))
|
||||
|
||||
def assertMultilineMatches(self, actual, pattern):
|
||||
m = re.match(pattern, actual, re.DOTALL)
|
||||
if not m:
|
||||
self.fail(msg='%r did not match %r' % (actual, pattern))
|
||||
|
||||
def get_sample_script(self):
|
||||
return findfile('gdb_sample.py')
|
||||
|
||||
class PrettyPrintTests(DebuggerTests):
|
||||
def test_getting_backtrace(self):
|
||||
gdb_output = self.get_stack_trace('id(42)')
|
||||
self.assertTrue(BREAKPOINT_FN in gdb_output)
|
||||
|
||||
def assertGdbRepr(self, val, exp_repr=None):
|
||||
# Ensure that gdb's rendering of the value in a debugged process
|
||||
# matches repr(value) in this process:
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
|
||||
if not exp_repr:
|
||||
exp_repr = repr(val)
|
||||
self.assertEqual(gdb_repr, exp_repr,
|
||||
('%r did not equal expected %r; full output was:\n%s'
|
||||
% (gdb_repr, exp_repr, gdb_output)))
|
||||
|
||||
def test_int(self):
|
||||
'Verify the pretty-printing of various int values'
|
||||
self.assertGdbRepr(42)
|
||||
self.assertGdbRepr(0)
|
||||
self.assertGdbRepr(-7)
|
||||
self.assertGdbRepr(1000000000000)
|
||||
self.assertGdbRepr(-1000000000000000)
|
||||
|
||||
def test_singletons(self):
|
||||
'Verify the pretty-printing of True, False and None'
|
||||
self.assertGdbRepr(True)
|
||||
self.assertGdbRepr(False)
|
||||
self.assertGdbRepr(None)
|
||||
|
||||
def test_dicts(self):
|
||||
'Verify the pretty-printing of dictionaries'
|
||||
self.assertGdbRepr({})
|
||||
self.assertGdbRepr({'foo': 'bar'}, "{'foo': 'bar'}")
|
||||
# Python preserves insertion order since 3.6
|
||||
self.assertGdbRepr({'foo': 'bar', 'douglas': 42}, "{'foo': 'bar', 'douglas': 42}")
|
||||
|
||||
def test_lists(self):
|
||||
'Verify the pretty-printing of lists'
|
||||
self.assertGdbRepr([])
|
||||
self.assertGdbRepr(list(range(5)))
|
||||
|
||||
def test_bytes(self):
|
||||
'Verify the pretty-printing of bytes'
|
||||
self.assertGdbRepr(b'')
|
||||
self.assertGdbRepr(b'And now for something hopefully the same')
|
||||
self.assertGdbRepr(b'string with embedded NUL here \0 and then some more text')
|
||||
self.assertGdbRepr(b'this is a tab:\t'
|
||||
b' this is a slash-N:\n'
|
||||
b' this is a slash-R:\r'
|
||||
)
|
||||
|
||||
self.assertGdbRepr(b'this is byte 255:\xff and byte 128:\x80')
|
||||
|
||||
self.assertGdbRepr(bytes([b for b in range(255)]))
|
||||
|
||||
def test_strings(self):
|
||||
'Verify the pretty-printing of unicode strings'
|
||||
encoding = locale.getpreferredencoding()
|
||||
def check_repr(text):
|
||||
try:
|
||||
text.encode(encoding)
|
||||
printable = True
|
||||
except UnicodeEncodeError:
|
||||
self.assertGdbRepr(text, ascii(text))
|
||||
else:
|
||||
self.assertGdbRepr(text)
|
||||
|
||||
self.assertGdbRepr('')
|
||||
self.assertGdbRepr('And now for something hopefully the same')
|
||||
self.assertGdbRepr('string with embedded NUL here \0 and then some more text')
|
||||
|
||||
# Test printing a single character:
|
||||
# U+2620 SKULL AND CROSSBONES
|
||||
check_repr('\u2620')
|
||||
|
||||
# Test printing a Japanese unicode string
|
||||
# (I believe this reads "mojibake", using 3 characters from the CJK
|
||||
# Unified Ideographs area, followed by U+3051 HIRAGANA LETTER KE)
|
||||
check_repr('\u6587\u5b57\u5316\u3051')
|
||||
|
||||
# Test a character outside the BMP:
|
||||
# U+1D121 MUSICAL SYMBOL C CLEF
|
||||
# This is:
|
||||
# UTF-8: 0xF0 0x9D 0x84 0xA1
|
||||
# UTF-16: 0xD834 0xDD21
|
||||
check_repr(chr(0x1D121))
|
||||
|
||||
def test_tuples(self):
|
||||
'Verify the pretty-printing of tuples'
|
||||
self.assertGdbRepr(tuple(), '()')
|
||||
self.assertGdbRepr((1,), '(1,)')
|
||||
self.assertGdbRepr(('foo', 'bar', 'baz'))
|
||||
|
||||
def test_sets(self):
|
||||
'Verify the pretty-printing of sets'
|
||||
if (gdb_major_version, gdb_minor_version) < (7, 3):
|
||||
self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
|
||||
self.assertGdbRepr(set(), "set()")
|
||||
self.assertGdbRepr(set(['a']), "{'a'}")
|
||||
# PYTHONHASHSEED is need to get the exact frozenset item order
|
||||
if not sys.flags.ignore_environment:
|
||||
self.assertGdbRepr(set(['a', 'b']), "{'a', 'b'}")
|
||||
self.assertGdbRepr(set([4, 5, 6]), "{4, 5, 6}")
|
||||
|
||||
# Ensure that we handle sets containing the "dummy" key value,
|
||||
# which happens on deletion:
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
|
||||
s.remove('a')
|
||||
id(s)''')
|
||||
self.assertEqual(gdb_repr, "{'b'}")
|
||||
|
||||
def test_frozensets(self):
|
||||
'Verify the pretty-printing of frozensets'
|
||||
if (gdb_major_version, gdb_minor_version) < (7, 3):
|
||||
self.skipTest("pretty-printing of frozensets needs gdb 7.3 or later")
|
||||
self.assertGdbRepr(frozenset(), "frozenset()")
|
||||
self.assertGdbRepr(frozenset(['a']), "frozenset({'a'})")
|
||||
# PYTHONHASHSEED is need to get the exact frozenset item order
|
||||
if not sys.flags.ignore_environment:
|
||||
self.assertGdbRepr(frozenset(['a', 'b']), "frozenset({'a', 'b'})")
|
||||
self.assertGdbRepr(frozenset([4, 5, 6]), "frozenset({4, 5, 6})")
|
||||
|
||||
def test_exceptions(self):
|
||||
# Test a RuntimeError
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('''
|
||||
try:
|
||||
raise RuntimeError("I am an error")
|
||||
except RuntimeError as e:
|
||||
id(e)
|
||||
''')
|
||||
self.assertEqual(gdb_repr,
|
||||
"RuntimeError('I am an error',)")
|
||||
|
||||
|
||||
# Test division by zero:
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('''
|
||||
try:
|
||||
a = 1 / 0
|
||||
except ZeroDivisionError as e:
|
||||
id(e)
|
||||
''')
|
||||
self.assertEqual(gdb_repr,
|
||||
"ZeroDivisionError('division by zero',)")
|
||||
|
||||
def test_modern_class(self):
|
||||
'Verify the pretty-printing of new-style class instances'
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('''
|
||||
class Foo:
|
||||
pass
|
||||
foo = Foo()
|
||||
foo.an_int = 42
|
||||
id(foo)''')
|
||||
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||
self.assertTrue(m,
|
||||
msg='Unexpected new-style class rendering %r' % gdb_repr)
|
||||
|
||||
def test_subclassing_list(self):
|
||||
'Verify the pretty-printing of an instance of a list subclass'
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('''
|
||||
class Foo(list):
|
||||
pass
|
||||
foo = Foo()
|
||||
foo += [1, 2, 3]
|
||||
foo.an_int = 42
|
||||
id(foo)''')
|
||||
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||
|
||||
self.assertTrue(m,
|
||||
msg='Unexpected new-style class rendering %r' % gdb_repr)
|
||||
|
||||
def test_subclassing_tuple(self):
|
||||
'Verify the pretty-printing of an instance of a tuple subclass'
|
||||
# This should exercise the negative tp_dictoffset code in the
|
||||
# new-style class support
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('''
|
||||
class Foo(tuple):
|
||||
pass
|
||||
foo = Foo((1, 2, 3))
|
||||
foo.an_int = 42
|
||||
id(foo)''')
|
||||
m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||
|
||||
self.assertTrue(m,
|
||||
msg='Unexpected new-style class rendering %r' % gdb_repr)
|
||||
|
||||
def assertSane(self, source, corruption, exprepr=None):
|
||||
'''Run Python under gdb, corrupting variables in the inferior process
|
||||
immediately before taking a backtrace.
|
||||
|
||||
Verify that the variable's representation is the expected failsafe
|
||||
representation'''
|
||||
if corruption:
|
||||
cmds_after_breakpoint=[corruption, 'backtrace']
|
||||
else:
|
||||
cmds_after_breakpoint=['backtrace']
|
||||
|
||||
gdb_repr, gdb_output = \
|
||||
self.get_gdb_repr(source,
|
||||
cmds_after_breakpoint=cmds_after_breakpoint)
|
||||
if exprepr:
|
||||
if gdb_repr == exprepr:
|
||||
# gdb managed to print the value in spite of the corruption;
|
||||
# this is good (see http://bugs.python.org/issue8330)
|
||||
return
|
||||
|
||||
# Match anything for the type name; 0xDEADBEEF could point to
|
||||
# something arbitrary (see http://bugs.python.org/issue8330)
|
||||
pattern = '<.* at remote 0x-?[0-9a-f]+>'
|
||||
|
||||
m = re.match(pattern, gdb_repr)
|
||||
if not m:
|
||||
self.fail('Unexpected gdb representation: %r\n%s' % \
|
||||
(gdb_repr, gdb_output))
|
||||
|
||||
def test_NULL_ptr(self):
|
||||
'Ensure that a NULL PyObject* is handled gracefully'
|
||||
gdb_repr, gdb_output = (
|
||||
self.get_gdb_repr('id(42)',
|
||||
cmds_after_breakpoint=['set variable v=0',
|
||||
'backtrace'])
|
||||
)
|
||||
|
||||
self.assertEqual(gdb_repr, '0x0')
|
||||
|
||||
def test_NULL_ob_type(self):
|
||||
'Ensure that a PyObject* with NULL ob_type is handled gracefully'
|
||||
self.assertSane('id(42)',
|
||||
'set v->ob_type=0')
|
||||
|
||||
def test_corrupt_ob_type(self):
|
||||
'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
|
||||
self.assertSane('id(42)',
|
||||
'set v->ob_type=0xDEADBEEF',
|
||||
exprepr='42')
|
||||
|
||||
def test_corrupt_tp_flags(self):
|
||||
'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
|
||||
self.assertSane('id(42)',
|
||||
'set v->ob_type->tp_flags=0x0',
|
||||
exprepr='42')
|
||||
|
||||
def test_corrupt_tp_name(self):
|
||||
'Ensure that a PyObject* with a type with corrupt tp_name is handled'
|
||||
self.assertSane('id(42)',
|
||||
'set v->ob_type->tp_name=0xDEADBEEF',
|
||||
exprepr='42')
|
||||
|
||||
def test_builtins_help(self):
|
||||
'Ensure that the new-style class _Helper in site.py can be handled'
|
||||
|
||||
if sys.flags.no_site:
|
||||
self.skipTest("need site module, but -S option was used")
|
||||
|
||||
# (this was the issue causing tracebacks in
|
||||
# http://bugs.python.org/issue8032#msg100537 )
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True)
|
||||
|
||||
m = re.match(r'<_Helper at remote 0x-?[0-9a-f]+>', gdb_repr)
|
||||
self.assertTrue(m,
|
||||
msg='Unexpected rendering %r' % gdb_repr)
|
||||
|
||||
def test_selfreferential_list(self):
|
||||
'''Ensure that a reference loop involving a list doesn't lead proxyval
|
||||
into an infinite loop:'''
|
||||
gdb_repr, gdb_output = \
|
||||
self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)")
|
||||
self.assertEqual(gdb_repr, '[3, 4, 5, [...]]')
|
||||
|
||||
gdb_repr, gdb_output = \
|
||||
self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)")
|
||||
self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]')
|
||||
|
||||
def test_selfreferential_dict(self):
|
||||
'''Ensure that a reference loop involving a dict doesn't lead proxyval
|
||||
into an infinite loop:'''
|
||||
gdb_repr, gdb_output = \
|
||||
self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)")
|
||||
|
||||
self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
|
||||
|
||||
def test_selfreferential_old_style_instance(self):
|
||||
gdb_repr, gdb_output = \
|
||||
self.get_gdb_repr('''
|
||||
class Foo:
|
||||
pass
|
||||
foo = Foo()
|
||||
foo.an_attr = foo
|
||||
id(foo)''')
|
||||
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
|
||||
gdb_repr),
|
||||
'Unexpected gdb representation: %r\n%s' % \
|
||||
(gdb_repr, gdb_output))
|
||||
|
||||
def test_selfreferential_new_style_instance(self):
|
||||
gdb_repr, gdb_output = \
|
||||
self.get_gdb_repr('''
|
||||
class Foo(object):
|
||||
pass
|
||||
foo = Foo()
|
||||
foo.an_attr = foo
|
||||
id(foo)''')
|
||||
self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
|
||||
gdb_repr),
|
||||
'Unexpected gdb representation: %r\n%s' % \
|
||||
(gdb_repr, gdb_output))
|
||||
|
||||
gdb_repr, gdb_output = \
|
||||
self.get_gdb_repr('''
|
||||
class Foo(object):
|
||||
pass
|
||||
a = Foo()
|
||||
b = Foo()
|
||||
a.an_attr = b
|
||||
b.an_attr = a
|
||||
id(a)''')
|
||||
self.assertTrue(re.match(r'<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>',
|
||||
gdb_repr),
|
||||
'Unexpected gdb representation: %r\n%s' % \
|
||||
(gdb_repr, gdb_output))
|
||||
|
||||
def test_truncation(self):
|
||||
'Verify that very long output is truncated'
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))')
|
||||
self.assertEqual(gdb_repr,
|
||||
"[0, 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, "
|
||||
"174, 175, 176, 177, 178, 179, 180, 181, 182, 183, "
|
||||
"184, 185, 186, 187, 188, 189, 190, 191, 192, 193, "
|
||||
"194, 195, 196, 197, 198, 199, 200, 201, 202, 203, "
|
||||
"204, 205, 206, 207, 208, 209, 210, 211, 212, 213, "
|
||||
"214, 215, 216, 217, 218, 219, 220, 221, 222, 223, "
|
||||
"224, 225, 226...(truncated)")
|
||||
self.assertEqual(len(gdb_repr),
|
||||
1024 + len('...(truncated)'))
|
||||
|
||||
def test_builtin_method(self):
|
||||
gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
|
||||
self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
|
||||
gdb_repr),
|
||||
'Unexpected gdb representation: %r\n%s' % \
|
||||
(gdb_repr, gdb_output))
|
||||
|
||||
def test_frames(self):
|
||||
gdb_output = self.get_stack_trace('''
|
||||
def foo(a, b, c):
|
||||
pass
|
||||
|
||||
foo(3, 4, 5)
|
||||
id(foo.__code__)''',
|
||||
breakpoint='builtin_id',
|
||||
cmds_after_breakpoint=['print (PyFrameObject*)(((PyCodeObject*)v)->co_zombieframe)']
|
||||
)
|
||||
self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 3, in foo \(\)\s+.*',
|
||||
gdb_output,
|
||||
re.DOTALL),
|
||||
'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
class PyListTests(DebuggerTests):
|
||||
def assertListing(self, expected, actual):
|
||||
self.assertEndsWith(actual, expected)
|
||||
|
||||
def test_basic_command(self):
|
||||
'Verify that the "py-list" command works'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-list'])
|
||||
|
||||
self.assertListing(' 5 \n'
|
||||
' 6 def bar(a, b, c):\n'
|
||||
' 7 baz(a, b, c)\n'
|
||||
' 8 \n'
|
||||
' 9 def baz(*args):\n'
|
||||
' >10 id(42)\n'
|
||||
' 11 \n'
|
||||
' 12 foo(1, 2, 3)\n',
|
||||
bt)
|
||||
|
||||
def test_one_abs_arg(self):
|
||||
'Verify the "py-list" command with one absolute argument'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-list 9'])
|
||||
|
||||
self.assertListing(' 9 def baz(*args):\n'
|
||||
' >10 id(42)\n'
|
||||
' 11 \n'
|
||||
' 12 foo(1, 2, 3)\n',
|
||||
bt)
|
||||
|
||||
def test_two_abs_args(self):
|
||||
'Verify the "py-list" command with two absolute arguments'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-list 1,3'])
|
||||
|
||||
self.assertListing(' 1 # Sample script for use by test_gdb.py\n'
|
||||
' 2 \n'
|
||||
' 3 def foo(a, b, c):\n',
|
||||
bt)
|
||||
|
||||
class StackNavigationTests(DebuggerTests):
|
||||
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_pyup_command(self):
|
||||
'Verify that the "py-up" command works'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-up'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r'''^.*
|
||||
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
|
||||
baz\(a, b, c\)
|
||||
$''')
|
||||
|
||||
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
|
||||
def test_down_at_bottom(self):
|
||||
'Verify handling of "py-down" at the bottom of the stack'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-down'])
|
||||
self.assertEndsWith(bt,
|
||||
'Unable to find a newer python frame\n')
|
||||
|
||||
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
|
||||
def test_up_at_top(self):
|
||||
'Verify handling of "py-up" at the top of the stack'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up'] * 5)
|
||||
self.assertEndsWith(bt,
|
||||
'Unable to find an older python frame\n')
|
||||
|
||||
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_up_then_down(self):
|
||||
'Verify "py-up" followed by "py-down"'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r'''^.*
|
||||
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
|
||||
baz\(a, b, c\)
|
||||
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
|
||||
id\(42\)
|
||||
$''')
|
||||
|
||||
class PyBtTests(DebuggerTests):
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_bt(self):
|
||||
'Verify that the "py-bt" command works'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-bt'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r'''^.*
|
||||
Traceback \(most recent call first\):
|
||||
<built-in method id of module object .*>
|
||||
File ".*gdb_sample.py", line 10, in baz
|
||||
id\(42\)
|
||||
File ".*gdb_sample.py", line 7, in bar
|
||||
baz\(a, b, c\)
|
||||
File ".*gdb_sample.py", line 4, in foo
|
||||
bar\(a, b, c\)
|
||||
File ".*gdb_sample.py", line 12, in <module>
|
||||
foo\(1, 2, 3\)
|
||||
''')
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_bt_full(self):
|
||||
'Verify that the "py-bt-full" command works'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-bt-full'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r'''^.*
|
||||
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
|
||||
baz\(a, b, c\)
|
||||
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
|
||||
bar\(a, b, c\)
|
||||
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
|
||||
foo\(1, 2, 3\)
|
||||
''')
|
||||
|
||||
@unittest.skipUnless(_thread,
|
||||
"Python was compiled without thread support")
|
||||
def test_threads(self):
|
||||
'Verify that "py-bt" indicates threads that are waiting for the GIL'
|
||||
cmd = '''
|
||||
from threading import Thread
|
||||
|
||||
class TestThread(Thread):
|
||||
# These threads would run forever, but we'll interrupt things with the
|
||||
# debugger
|
||||
def run(self):
|
||||
i = 0
|
||||
while 1:
|
||||
i += 1
|
||||
|
||||
t = {}
|
||||
for i in range(4):
|
||||
t[i] = TestThread()
|
||||
t[i].start()
|
||||
|
||||
# Trigger a breakpoint on the main thread
|
||||
id(42)
|
||||
|
||||
'''
|
||||
# Verify with "py-bt":
|
||||
gdb_output = self.get_stack_trace(cmd,
|
||||
cmds_after_breakpoint=['thread apply all py-bt'])
|
||||
self.assertIn('Waiting for the GIL', gdb_output)
|
||||
|
||||
# Verify with "py-bt-full":
|
||||
gdb_output = self.get_stack_trace(cmd,
|
||||
cmds_after_breakpoint=['thread apply all py-bt-full'])
|
||||
self.assertIn('Waiting for the GIL', gdb_output)
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
# Some older versions of gdb will fail with
|
||||
# "Cannot find new threads: generic error"
|
||||
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
|
||||
@unittest.skipUnless(_thread,
|
||||
"Python was compiled without thread support")
|
||||
def test_gc(self):
|
||||
'Verify that "py-bt" indicates if a thread is garbage-collecting'
|
||||
cmd = ('from gc import collect\n'
|
||||
'id(42)\n'
|
||||
'def foo():\n'
|
||||
' collect()\n'
|
||||
'def bar():\n'
|
||||
' foo()\n'
|
||||
'bar()\n')
|
||||
# Verify with "py-bt":
|
||||
gdb_output = self.get_stack_trace(cmd,
|
||||
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
|
||||
)
|
||||
self.assertIn('Garbage-collecting', gdb_output)
|
||||
|
||||
# Verify with "py-bt-full":
|
||||
gdb_output = self.get_stack_trace(cmd,
|
||||
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
|
||||
)
|
||||
self.assertIn('Garbage-collecting', gdb_output)
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
# Some older versions of gdb will fail with
|
||||
# "Cannot find new threads: generic error"
|
||||
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
|
||||
@unittest.skipUnless(_thread,
|
||||
"Python was compiled without thread support")
|
||||
def test_pycfunction(self):
|
||||
'Verify that "py-bt" displays invocations of PyCFunction instances'
|
||||
# Tested function must not be defined with METH_NOARGS or METH_O,
|
||||
# otherwise call_function() doesn't call PyCFunction_Call()
|
||||
cmd = ('from time import gmtime\n'
|
||||
'def foo():\n'
|
||||
' gmtime(1)\n'
|
||||
'def bar():\n'
|
||||
' foo()\n'
|
||||
'bar()\n')
|
||||
# Verify with "py-bt":
|
||||
gdb_output = self.get_stack_trace(cmd,
|
||||
breakpoint='time_gmtime',
|
||||
cmds_after_breakpoint=['bt', 'py-bt'],
|
||||
)
|
||||
self.assertIn('<built-in method gmtime', gdb_output)
|
||||
|
||||
# Verify with "py-bt-full":
|
||||
gdb_output = self.get_stack_trace(cmd,
|
||||
breakpoint='time_gmtime',
|
||||
cmds_after_breakpoint=['py-bt-full'],
|
||||
)
|
||||
self.assertIn('#2 <built-in method gmtime', gdb_output)
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_wrapper_call(self):
|
||||
cmd = textwrap.dedent('''
|
||||
class MyList(list):
|
||||
def __init__(self):
|
||||
super().__init__() # wrapper_call()
|
||||
|
||||
id("first break point")
|
||||
l = MyList()
|
||||
''')
|
||||
# Verify with "py-bt":
|
||||
gdb_output = self.get_stack_trace(cmd,
|
||||
cmds_after_breakpoint=['break wrapper_call', 'continue', 'py-bt'])
|
||||
self.assertRegex(gdb_output,
|
||||
r"<method-wrapper u?'__init__' of MyList object at ")
|
||||
|
||||
|
||||
class PyPrintTests(DebuggerTests):
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_basic_command(self):
|
||||
'Verify that the "py-print" command works'
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-print args'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r".*\nlocal 'args' = \(1, 2, 3\)\n.*")
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
|
||||
def test_print_after_up(self):
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-up', 'py-print c', 'py-print b', 'py-print a'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r".*\nlocal 'c' = 3\nlocal 'b' = 2\nlocal 'a' = 1\n.*")
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_printing_global(self):
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-print __name__'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r".*\nglobal '__name__' = '__main__'\n.*")
|
||||
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_printing_builtin(self):
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-print len'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")
|
||||
|
||||
class PyLocalsTests(DebuggerTests):
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_basic_command(self):
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-locals'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r".*\nargs = \(1, 2, 3\)\n.*")
|
||||
|
||||
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
|
||||
@unittest.skipIf(python_is_optimized(),
|
||||
"Python was compiled with optimizations")
|
||||
def test_locals_after_up(self):
|
||||
bt = self.get_stack_trace(script=self.get_sample_script(),
|
||||
cmds_after_breakpoint=['py-up', 'py-up', 'py-locals'])
|
||||
self.assertMultilineMatches(bt,
|
||||
r".*\na = 1\nb = 2\nc = 3\n.*")
|
||||
|
||||
def test_main():
|
||||
if support.verbose:
|
||||
print("GDB version %s.%s:" % (gdb_major_version, gdb_minor_version))
|
||||
for line in gdb_version.splitlines():
|
||||
print(" " * 4 + line)
|
||||
run_unittest(PrettyPrintTests,
|
||||
PyListTests,
|
||||
StackNavigationTests,
|
||||
PyBtTests,
|
||||
PyPrintTests,
|
||||
PyLocalsTests
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_main()
|
1
toxygen/tests/test_gdb.urls
Normal file
1
toxygen/tests/test_gdb.urls
Normal file
|
@ -0,0 +1 @@
|
|||
https://github.com/akheron/cpython/raw/master/Lib/test/test_gdb.py
|
1885
toxygen/tests/tests_socks.py
Normal file
1885
toxygen/tests/tests_socks.py
Normal file
File diff suppressed because it is too large
Load diff
1885
toxygen/tests/tests_wrapper.py
Normal file
1885
toxygen/tests/tests_wrapper.py
Normal file
File diff suppressed because it is too large
Load diff
17
toxygen/tests/toxygen_tests.py
Normal file
17
toxygen/tests/toxygen_tests.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
from notifications import sound
|
||||
from notifications.sound import SOUND_NOTIFICATION
|
||||
from time import sleep
|
||||
|
||||
if True:
|
||||
def test_sound_notification(self):
|
||||
"""
|
||||
Plays sound notification
|
||||
:param type of notification
|
||||
"""
|
||||
sound.sound_notification( SOUND_NOTIFICATION['MESSAGE'] )
|
||||
sleep(10)
|
||||
sound.sound_notification( SOUND_NOTIFICATION['FILE_TRANSFER'] )
|
||||
sleep(10)
|
||||
sound.sound_notification( None )
|
4
toxygen/third_party/qweechat/qweechat.py
vendored
4
toxygen/third_party/qweechat/qweechat.py
vendored
|
@ -90,7 +90,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
|
||||
QtWidgets.QSizePolicy.Preferred)
|
||||
self.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||
QtWidgets.QSizePolicy.Expanding)
|
||||
QtWidgets.QSizePolicy.Expanding)
|
||||
# MainWindow
|
||||
self.setCentralWidget(splitter)
|
||||
|
||||
|
@ -191,7 +191,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.actions['preferences'],
|
||||
self.actions['about'],
|
||||
self.actions['quit']])
|
||||
self.toolbar = toolbar
|
||||
self.toolbar = toolbar
|
||||
self.buffers[0].widget.input.setFocus()
|
||||
|
||||
# open debug dialog
|
||||
|
|
|
@ -98,9 +98,7 @@ class MessagesItemsFactory:
|
|||
|
||||
return item
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _create_message_browser(self, text, width, message_type, parent=None):
|
||||
return MessageBrowser(self._settings, self._message_edit, self._smiley_loader, self._plugin_loader,
|
||||
|
|
|
@ -652,9 +652,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Functions which called when user click in menu
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def log_console(self):
|
||||
self._me.show()
|
||||
|
@ -759,7 +757,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self._we.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
|
||||
QtWidgets.QSizePolicy.Preferred)
|
||||
self._we.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||
QtWidgets.QSizePolicy.Expanding)
|
||||
QtWidgets.QSizePolicy.Expanding)
|
||||
|
||||
LOG.info("Showing WeechatConsole")
|
||||
self._we.show()
|
||||
|
@ -877,9 +875,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
120))
|
||||
self.menu.show()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Messages, calls and file transfers
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_message(self):
|
||||
self._messenger.send_message()
|
||||
|
@ -942,9 +938,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.videocallButton.setIcon(icon)
|
||||
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Functions which called when user open context menu in friends list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _friend_right_click(self, pos):
|
||||
item = self.friends_list.itemAt(pos)
|
||||
|
@ -1001,9 +995,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
def select_contact_row(self, row_index):
|
||||
self.friends_list.setCurrentRow(row_index)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Functions which called when user click somewhere else
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _selected_contact_changed(self):
|
||||
num = self.friends_list.currentRow()
|
||||
|
|
|
@ -48,7 +48,7 @@ class MessageBrowser(QtWidgets.QTextBrowser):
|
|||
# resize(self, a0: QSize): argument 1 has unexpected type 'int'
|
||||
# resize(self, w: int, h: int): argument 2 has unexpected type 'float'
|
||||
pass
|
||||
|
||||
|
||||
self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
|
||||
self.anchorClicked.connect(self.on_anchor_clicked)
|
||||
|
||||
|
|
99
toxygen/ui/views/add_bootstrap_screen.ui
Normal file
99
toxygen/ui/views/add_bootstrap_screen.ui
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>560</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>560</width>
|
||||
<height>320</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>560</width>
|
||||
<height>320</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="toxIdLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>10</y>
|
||||
<width>150</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="messageLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>70</y>
|
||||
<width>150</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPlainTextEdit" name="messagePlainTextEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>110</y>
|
||||
<width>460</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="addBootstrapPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>270</y>
|
||||
<width>460</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="errorLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>220</x>
|
||||
<y>10</y>
|
||||
<width>321</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::NoContextMenu</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
99
toxygen/ui/views/add_contact_screen.ui
Normal file
99
toxygen/ui/views/add_contact_screen.ui
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>560</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>560</width>
|
||||
<height>320</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>560</width>
|
||||
<height>320</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="toxIdLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>10</y>
|
||||
<width>150</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="messageLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>70</y>
|
||||
<width>150</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPlainTextEdit" name="messagePlainTextEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>110</y>
|
||||
<width>460</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="addContactPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>270</y>
|
||||
<width>460</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="errorLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>220</x>
|
||||
<y>10</y>
|
||||
<width>321</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::NoContextMenu</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
87
toxygen/ui/views/audio_settings_screen.ui
Normal file
87
toxygen/ui/views/audio_settings_screen.ui
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>315</width>
|
||||
<height>218</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>315</width>
|
||||
<height>218</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>315</width>
|
||||
<height>218</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="inputDeviceLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>10</y>
|
||||
<width>261</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="outputDeviceLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>100</y>
|
||||
<width>261</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="inputDeviceComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>50</y>
|
||||
<width>255</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="outputDeviceComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>140</y>
|
||||
<width>255</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
29
toxygen/ui/views/bans_list_screen.ui
Normal file
29
toxygen/ui/views/bans_list_screen.ui
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>375</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QListWidget" name="bansListWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>375</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
127
toxygen/ui/views/create_group_screen.ui
Normal file
127
toxygen/ui/views/create_group_screen.ui
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>640</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QPushButton" name="addGroupButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>250</y>
|
||||
<width>601</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="groupNameLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>150</x>
|
||||
<y>20</y>
|
||||
<width>470</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="groupTypeComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>150</x>
|
||||
<y>80</y>
|
||||
<width>470</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="groupNameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
<width>121</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="groupTypeLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>80</y>
|
||||
<width>121</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>200</y>
|
||||
<width>111</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="nickLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>150</y>
|
||||
<width>111</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="nickLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>150</x>
|
||||
<y>140</y>
|
||||
<width>470</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="statusComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>150</x>
|
||||
<y>190</y>
|
||||
<width>470</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
128
toxygen/ui/views/create_profile_screen.ui
Normal file
128
toxygen/ui/views/create_profile_screen.ui
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>340</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>340</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>340</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QPushButton" name="createProfile">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>270</y>
|
||||
<width>341</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="confirmPassword">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>170</y>
|
||||
<width>341</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>120</y>
|
||||
<width>341</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>80</y>
|
||||
<width>330</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="defaultFolder">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>10</y>
|
||||
<width>330</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="programFolder">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>40</y>
|
||||
<width>330</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="errorLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>220</y>
|
||||
<width>341</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
58
toxygen/ui/views/gc_ban_item.ui
Normal file
58
toxygen/ui/views/gc_ban_item.ui
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>100</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QPushButton" name="cancelPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>330</x>
|
||||
<y>30</y>
|
||||
<width>161</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="banTargetLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>15</x>
|
||||
<y>20</y>
|
||||
<width>305</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="banTimeLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>15</x>
|
||||
<y>50</y>
|
||||
<width>305</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
71
toxygen/ui/views/gc_invite_item.ui
Normal file
71
toxygen/ui/views/gc_invite_item.ui
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="friendNameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>250</x>
|
||||
<y>30</y>
|
||||
<width>300</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="groupNameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>250</x>
|
||||
<y>70</y>
|
||||
<width>300</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="friendAvatarLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>30</y>
|
||||
<width>60</width>
|
||||
<height>60</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="selectCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>50</y>
|
||||
<width>20</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
83
toxygen/ui/views/gc_settings_screen.ui
Normal file
83
toxygen/ui/views/gc_settings_screen.ui
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>220</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>220</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>220</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>20</y>
|
||||
<width>380</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="copyPasswordPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>60</y>
|
||||
<width>380</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="peerLimitLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>120</y>
|
||||
<width>380</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="privacyStateLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>160</y>
|
||||
<width>380</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
113
toxygen/ui/views/group_invites_screen.ui
Normal file
113
toxygen/ui/views/group_invites_screen.ui
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="noInvitesLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>150</y>
|
||||
<width>600</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QListWidget" name="invitesListWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>341</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="nickLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>360</y>
|
||||
<width>350</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="passwordLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>410</y>
|
||||
<width>350</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="statusComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>390</x>
|
||||
<y>390</y>
|
||||
<width>200</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="acceptPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>460</y>
|
||||
<width>201</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="declinePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>360</x>
|
||||
<y>460</y>
|
||||
<width>201</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
123
toxygen/ui/views/group_management_screen.ui
Normal file
123
toxygen/ui/views/group_management_screen.ui
Normal file
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>658</width>
|
||||
<height>283</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLineEdit" name="passwordLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>180</x>
|
||||
<y>20</y>
|
||||
<width>450</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>30</y>
|
||||
<width>145</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="peerLimitLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>80</y>
|
||||
<width>145</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSpinBox" name="peersLimitSpinBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>180</x>
|
||||
<y>70</y>
|
||||
<width>450</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>512</number>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="privacyStateLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>130</y>
|
||||
<width>145</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="privacyStateComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>180</x>
|
||||
<y>120</y>
|
||||
<width>450</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="deletePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>180</y>
|
||||
<width>300</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="savePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>220</y>
|
||||
<width>611</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
255
toxygen/ui/views/interface_settings_screen.ui
Normal file
255
toxygen/ui/views/interface_settings_screen.ui
Normal file
|
@ -0,0 +1,255 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>552</width>
|
||||
<height>847</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>532</width>
|
||||
<height>827</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QLabel" name="themeLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>140</y>
|
||||
<width>67</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="themeComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>180</y>
|
||||
<width>471</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="languageComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>60</y>
|
||||
<width>471</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="languageLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>20</y>
|
||||
<width>67</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<!--
|
||||
<widget class="QCheckBox" name="mirrorModeCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>220</y>
|
||||
<width>461</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
-->
|
||||
<widget class="QGroupBox" name="smileysGroupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>280</y>
|
||||
<width>461</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>GroupBox</string>
|
||||
</property>
|
||||
<widget class="QCheckBox" name="smileysCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>40</y>
|
||||
<width>92</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="smileysPackLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>80</y>
|
||||
<width>411</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="smileysPackComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>120</y>
|
||||
<width>411</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="compactModeCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>250</y>
|
||||
<width>461</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="importStickersPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>750</y>
|
||||
<width>471</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="importSmileysPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>690</y>
|
||||
<width>471</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="showAvatarsCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>520</y>
|
||||
<width>461</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="appClosingGroupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>550</y>
|
||||
<width>471</width>
|
||||
<height>131</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>GroupBox</string>
|
||||
</property>
|
||||
<widget class="QRadioButton" name="closeRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>30</y>
|
||||
<width>421</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="hideRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>60</y>
|
||||
<width>431</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="closeToTrayRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>90</y>
|
||||
<width>421</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
139
toxygen/ui/views/join_group_screen.ui
Normal file
139
toxygen/ui/views/join_group_screen.ui
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>740</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>740</width>
|
||||
<height>320</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>740</width>
|
||||
<height>320</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="chatIdLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>30</y>
|
||||
<width>67</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>90</y>
|
||||
<width>67</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="joinGroupButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>260</y>
|
||||
<width>680</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="chatIdLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>20</y>
|
||||
<width>520</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="passwordLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>80</y>
|
||||
<width>520</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="nickLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>150</y>
|
||||
<width>67</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>210</y>
|
||||
<width>67</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="nickLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>140</y>
|
||||
<width>520</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="statusComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>200</y>
|
||||
<width>520</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
135
toxygen/ui/views/login_screen.ui
Normal file
135
toxygen/ui/views/login_screen.ui
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>loginScreen</class>
|
||||
<widget class="QWidget" name="loginScreen">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="toxygenLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>5</y>
|
||||
<width>401</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>16</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Toxygen</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="newProfileGroupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>40</y>
|
||||
<width>180</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>GroupBox</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<widget class="QPushButton" name="createProfilePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>110</y>
|
||||
<width>160</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="existingProfileGroupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>40</y>
|
||||
<width>180</width>
|
||||
<height>150</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>GroupBox</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<widget class="QComboBox" name="profilesComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>40</y>
|
||||
<width>160</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="defaultProfileCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>75</y>
|
||||
<width>160</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="loadProfilePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>110</y>
|
||||
<width>160</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
94
toxygen/ui/views/ms_left_column.ui
Normal file
94
toxygen/ui/views/ms_left_column.ui
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>270</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="avatarLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>5</x>
|
||||
<y>5</y>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="searchLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>75</y>
|
||||
<width>150</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="contactsFilterComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>150</x>
|
||||
<y>75</y>
|
||||
<width>120</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="searchLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>77</y>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QListWidget" name="friendsListWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>100</y>
|
||||
<width>270</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="groupInvitesPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>100</y>
|
||||
<width>270</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
196
toxygen/ui/views/network_settings_screen.ui
Normal file
196
toxygen/ui/views/network_settings_screen.ui
Normal file
|
@ -0,0 +1,196 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>545</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>545</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>545</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QCheckBox" name="ipv6CheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>20</y>
|
||||
<width>150</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="udpCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>20</y>
|
||||
<width>150</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="proxyCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>140</y>
|
||||
<width>150</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="httpProxyRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>190</y>
|
||||
<width>150</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="socksProxyRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>230</y>
|
||||
<width>150</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="lanCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>100</y>
|
||||
<width>150</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="ipLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>280</y>
|
||||
<width>60</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="portLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>330</y>
|
||||
<width>60</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="urlLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>380</y>
|
||||
<width>60</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Chat Url</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="restartCorePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>430</y>
|
||||
<width>340</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="downloadNodesCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>60</y>
|
||||
<width>340</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="warningLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>480</y>
|
||||
<width>340</width>
|
||||
<height>65</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
71
toxygen/ui/views/notifications_settings_screen.ui
Normal file
71
toxygen/ui/views/notifications_settings_screen.ui
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>320</width>
|
||||
<height>201</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QCheckBox" name="notificationsCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
<width>271</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="soundNotificationsCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>60</y>
|
||||
<width>271</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="groupNotificationsCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>100</y>
|
||||
<width>271</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="callsSoundCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>140</y>
|
||||
<width>271</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
202
toxygen/ui/views/peer_screen.ui
Normal file
202
toxygen/ui/views/peer_screen.ui
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="peerNameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>110</x>
|
||||
<y>10</y>
|
||||
<width>431</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="sendPrivateMessagePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>140</y>
|
||||
<width>500</width>
|
||||
<height>50</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="ignorePeerCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>100</y>
|
||||
<width>500</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="banGroupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>300</y>
|
||||
<width>500</width>
|
||||
<height>161</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>GroupBox</string>
|
||||
</property>
|
||||
<!--
|
||||
<widget class="QPushButton" name="banPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>380</x>
|
||||
<y>50</y>
|
||||
<width>101</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
-->
|
||||
<widget class="QRadioButton" name="ipBanRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>40</y>
|
||||
<width>251</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="nickBanRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>80</y>
|
||||
<width>251</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="pkBanRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>120</y>
|
||||
<width>251</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="kickPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>380</x>
|
||||
<y>100</y>
|
||||
<width>101</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QLabel" name="roleLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>60</y>
|
||||
<width>67</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="roleNameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>130</x>
|
||||
<y>60</y>
|
||||
<width>411</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="copyPublicKeyPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>210</y>
|
||||
<width>500</width>
|
||||
<height>50</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="rolesComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>130</x>
|
||||
<y>55</y>
|
||||
<width>291</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
280
toxygen/ui/views/profile_settings_screen.ui
Normal file
280
toxygen/ui/views/profile_settings_screen.ui
Normal file
|
@ -0,0 +1,280 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>900</width>
|
||||
<height>680</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>10</y>
|
||||
<width>161</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>90</y>
|
||||
<width>161</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="nameLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>50</y>
|
||||
<width>421</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="statusMessageLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>130</y>
|
||||
<width>421</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="statusComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>520</x>
|
||||
<y>30</y>
|
||||
<width>311</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="toxIdTitleLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>180</y>
|
||||
<width>131</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="toxIdLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>210</y>
|
||||
<width>831</width>
|
||||
<height>60</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="copyToxIdPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>280</y>
|
||||
<width>371</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="copyPublicKeyPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>440</x>
|
||||
<y>280</y>
|
||||
<width>371</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="newAvatarPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>520</x>
|
||||
<y>80</y>
|
||||
<width>321</width>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="resetAvatarPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>520</x>
|
||||
<y>130</y>
|
||||
<width>321</width>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="profilePasswordLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>60</x>
|
||||
<y>380</y>
|
||||
<width>161</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="passwordLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>420</y>
|
||||
<width>421</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="confirmPasswordLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>470</y>
|
||||
<width>421</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="emptyPasswordLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>500</x>
|
||||
<y>420</y>
|
||||
<width>381</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="warningLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>60</x>
|
||||
<y>580</y>
|
||||
<width>381</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="defaultProfilePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>630</y>
|
||||
<width>831</width>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="changePasswordPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>520</y>
|
||||
<width>421</width>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="invalidPasswordsLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>500</x>
|
||||
<y>470</y>
|
||||
<width>381</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="exportProfilePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>330</y>
|
||||
<width>371</width>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="newNoSpamPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>440</x>
|
||||
<y>330</y>
|
||||
<width>371</width>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
119
toxygen/ui/views/self_peer_screen.ui
Normal file
119
toxygen/ui/views/self_peer_screen.ui
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>600</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>120</y>
|
||||
<width>67</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="copyPublicKeyPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>250</y>
|
||||
<width>500</width>
|
||||
<height>50</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="statusComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>110</y>
|
||||
<width>400</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>40</y>
|
||||
<width>67</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="roleLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>190</y>
|
||||
<width>67</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="roleNameLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>190</y>
|
||||
<width>411</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="savePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>330</y>
|
||||
<width>500</width>
|
||||
<height>50</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
67
toxygen/ui/views/update_settings_screen.ui
Normal file
67
toxygen/ui/views/update_settings_screen.ui
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>120</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="updateModeLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>25</x>
|
||||
<y>5</y>
|
||||
<width>350</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="updateModeComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>25</x>
|
||||
<y>30</y>
|
||||
<width>350</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="updatePushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>25</x>
|
||||
<y>70</y>
|
||||
<width>350</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
77
toxygen/ui/views/video_settings_screen.ui
Normal file
77
toxygen/ui/views/video_settings_screen.ui
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>120</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="deviceLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>25</x>
|
||||
<y>5</y>
|
||||
<width>350</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="deviceComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>25</x>
|
||||
<y>30</y>
|
||||
<width>350</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="selectRegionPushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>25</x>
|
||||
<y>70</y>
|
||||
<width>350</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="resolutionComboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>25</x>
|
||||
<y>70</y>
|
||||
<width>350</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -33,18 +33,14 @@ class ProfileManager:
|
|||
if not os.path.exists(avatars_directory):
|
||||
os.makedirs(avatars_directory)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Properties
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_profile_saved_event(self):
|
||||
return self._profile_saved_event
|
||||
|
||||
profile_saved_event = property(get_profile_saved_event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Public methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def open_profile(self):
|
||||
with open(self._path, 'rb') as fl:
|
||||
|
|
|
@ -192,18 +192,14 @@ class Settings(dict):
|
|||
self.unlockScreen = False
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Properties
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_settings_saved_event(self):
|
||||
return self._settings_saved_event
|
||||
|
||||
settings_saved_event = property(get_settings_saved_event)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Public methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def save(self):
|
||||
text = json.dumps(self)
|
||||
|
@ -252,9 +248,7 @@ class Settings(dict):
|
|||
self._path = new_path
|
||||
self.save()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Static methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def get_auto_profile():
|
||||
|
@ -387,9 +381,7 @@ class Settings(dict):
|
|||
}
|
||||
return retval
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _upgrade(self):
|
||||
default = Settings.get_default_settings()
|
||||
|
|
0
toxygen/wrapper_tests/__init__.py
Normal file
0
toxygen/wrapper_tests/__init__.py
Normal file
391
toxygen/wrapper_tests/socks.py
Normal file
391
toxygen/wrapper_tests/socks.py
Normal file
|
@ -0,0 +1,391 @@
|
|||
"""SocksiPy - Python SOCKS module.
|
||||
Version 1.00
|
||||
|
||||
Copyright 2006 Dan-Haim. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||
|
||||
|
||||
This module provides a standard socket-like interface for Python
|
||||
for tunneling connections through SOCKS proxies.
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
|
||||
for use in PyLoris (http://pyloris.sourceforge.net/)
|
||||
|
||||
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
|
||||
mainly to merge bug fixes found in Sourceforge
|
||||
|
||||
Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/)
|
||||
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
PROXY_TYPE_SOCKS4 = 1
|
||||
PROXY_TYPE_SOCKS5 = 2
|
||||
PROXY_TYPE_HTTP = 3
|
||||
|
||||
_defaultproxy = None
|
||||
_orgsocket = socket.socket
|
||||
|
||||
class ProxyError(Exception): pass
|
||||
class GeneralProxyError(ProxyError): pass
|
||||
class Socks5AuthError(ProxyError): pass
|
||||
class Socks5Error(ProxyError): pass
|
||||
class Socks4Error(ProxyError): pass
|
||||
class HTTPError(ProxyError): pass
|
||||
|
||||
_generalerrors = ("success",
|
||||
"invalid data",
|
||||
"not connected",
|
||||
"not available",
|
||||
"bad proxy type",
|
||||
"bad input")
|
||||
|
||||
_socks5errors = ("succeeded",
|
||||
"general SOCKS server failure",
|
||||
"connection not allowed by ruleset",
|
||||
"Network unreachable",
|
||||
"Host unreachable",
|
||||
"Connection refused",
|
||||
"TTL expired",
|
||||
"Command not supported",
|
||||
"Address type not supported",
|
||||
"Unknown error")
|
||||
|
||||
_socks5autherrors = ("succeeded",
|
||||
"authentication is required",
|
||||
"all offered authentication methods were rejected",
|
||||
"unknown username or invalid password",
|
||||
"unknown error")
|
||||
|
||||
_socks4errors = ("request granted",
|
||||
"request rejected or failed",
|
||||
"request rejected because SOCKS server cannot connect to identd on the client",
|
||||
"request rejected because the client program and identd report different user-ids",
|
||||
"unknown error")
|
||||
|
||||
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets a default proxy which all further socksocket objects will use,
|
||||
unless explicitly changed.
|
||||
"""
|
||||
global _defaultproxy
|
||||
_defaultproxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def wrapmodule(module):
|
||||
"""wrapmodule(module)
|
||||
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||
a default proxy using setdefaultproxy(...) first.
|
||||
This will only work on modules that import socket directly into the namespace;
|
||||
most of the Python Standard Library falls into this category.
|
||||
"""
|
||||
if _defaultproxy != None:
|
||||
module.socket.socket = socksocket
|
||||
else:
|
||||
raise GeneralProxyError((4, "no proxy specified"))
|
||||
|
||||
class socksocket(socket.socket):
|
||||
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||
Open a SOCKS enabled socket. The parameters are the same as
|
||||
those of the standard socket init. In order for SOCKS to work,
|
||||
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
|
||||
"""
|
||||
|
||||
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
|
||||
_orgsocket.__init__(self, family, type, proto, _sock)
|
||||
if _defaultproxy != None:
|
||||
self.__proxy = _defaultproxy
|
||||
else:
|
||||
self.__proxy = (None, None, None, None, None, None)
|
||||
self.__proxysockname = None
|
||||
self.__proxypeername = None
|
||||
|
||||
def __recvall(self, count):
|
||||
"""__recvall(count) -> data
|
||||
Receive EXACTLY the number of bytes requested from the socket.
|
||||
Blocks until the required number of bytes have been received.
|
||||
"""
|
||||
data = self.recv(count)
|
||||
while len(data) < count:
|
||||
d = self.recv(count-len(data))
|
||||
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
|
||||
data = data + d
|
||||
return data
|
||||
|
||||
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets the proxy to be used.
|
||||
proxytype - The type of the proxy to be used. Three types
|
||||
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
|
||||
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
|
||||
addr - The address of the server (IP or DNS).
|
||||
port - The port of the server. Defaults to 1080 for SOCKS
|
||||
servers and 8080 for HTTP proxy servers.
|
||||
rdns - Should DNS queries be preformed on the remote side
|
||||
(rather than the local side). The default is True.
|
||||
Note: This has no effect with SOCKS4 servers.
|
||||
username - Username to authenticate with to the server.
|
||||
The default is no authentication.
|
||||
password - Password to authenticate with to the server.
|
||||
Only relevant when username is also provided.
|
||||
"""
|
||||
self.__proxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
def __negotiatesocks5(self, destaddr, destport):
|
||||
"""__negotiatesocks5(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS5 server.
|
||||
"""
|
||||
# First we'll send the authentication packages we support.
|
||||
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
|
||||
# The username/password details were supplied to the
|
||||
# setproxy method so we support the USERNAME/PASSWORD
|
||||
# authentication (in addition to the standard none).
|
||||
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
|
||||
else:
|
||||
# No username/password were entered, therefore we
|
||||
# only support connections with no authentication.
|
||||
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
|
||||
# We'll receive the server's response to determine which
|
||||
# method was selected
|
||||
chosenauth = self.__recvall(2)
|
||||
if chosenauth[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Check the chosen authentication method
|
||||
if chosenauth[1:2] == chr(0x00).encode():
|
||||
# No authentication is required
|
||||
pass
|
||||
elif chosenauth[1:2] == chr(0x02).encode():
|
||||
# Okay, we need to perform a basic username/password
|
||||
# authentication.
|
||||
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
|
||||
authstat = self.__recvall(2)
|
||||
if authstat[0:1] != chr(0x01).encode():
|
||||
# Bad response
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if authstat[1:2] != chr(0x00).encode():
|
||||
# Authentication failed
|
||||
self.close()
|
||||
raise Socks5AuthError((3, _socks5autherrors[3]))
|
||||
# Authentication succeeded
|
||||
else:
|
||||
# Reaching here is always bad
|
||||
self.close()
|
||||
if chosenauth[1] == chr(0xFF).encode():
|
||||
raise Socks5AuthError((2, _socks5autherrors[2]))
|
||||
else:
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Now we can request the actual connection
|
||||
req = struct.pack('BBB', 0x05, 0x01, 0x00)
|
||||
# If the given destination address is an IP address, we'll
|
||||
# use the IPv4 address request even if remote resolving was specified.
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
except socket.error:
|
||||
# Well it's not an IP number, so it's probably a DNS name.
|
||||
if self.__proxy[3]:
|
||||
# Resolve remotely
|
||||
ipaddr = None
|
||||
if type(destaddr) != type(b''): # python3
|
||||
destaddr_bytes = destaddr.encode(encoding='idna')
|
||||
else:
|
||||
destaddr_bytes = destaddr
|
||||
req = req + chr(0x03).encode() + chr(len(destaddr_bytes)).encode() + destaddr_bytes
|
||||
else:
|
||||
# Resolve locally
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
req = req + struct.pack(">H", destport)
|
||||
self.sendall(req)
|
||||
# Get the response
|
||||
resp = self.__recvall(4)
|
||||
if resp[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
elif resp[1:2] != chr(0x00).encode():
|
||||
# Connection failed
|
||||
self.close()
|
||||
if ord(resp[1:2])<=8:
|
||||
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
|
||||
else:
|
||||
raise Socks5Error((9, _socks5errors[9]))
|
||||
# Get the bound address/port
|
||||
elif resp[3:4] == chr(0x01).encode():
|
||||
boundaddr = self.__recvall(4)
|
||||
elif resp[3:4] == chr(0x03).encode():
|
||||
resp = resp + self.recv(1)
|
||||
boundaddr = self.__recvall(ord(resp[4:5]))
|
||||
else:
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
boundport = struct.unpack(">H", self.__recvall(2))[0]
|
||||
self.__proxysockname = (boundaddr, boundport)
|
||||
if ipaddr != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def getproxysockname(self):
|
||||
"""getsockname() -> address info
|
||||
Returns the bound IP address and port number at the proxy.
|
||||
"""
|
||||
return self.__proxysockname
|
||||
|
||||
def getproxypeername(self):
|
||||
"""getproxypeername() -> address info
|
||||
Returns the IP and port number of the proxy.
|
||||
"""
|
||||
return _orgsocket.getpeername(self)
|
||||
|
||||
def getpeername(self):
|
||||
"""getpeername() -> address info
|
||||
Returns the IP address and port number of the destination
|
||||
machine (note: getproxypeername returns the proxy)
|
||||
"""
|
||||
return self.__proxypeername
|
||||
|
||||
def __negotiatesocks4(self,destaddr,destport):
|
||||
"""__negotiatesocks4(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS4 server.
|
||||
"""
|
||||
# Check if the destination address provided is an IP address
|
||||
rmtrslv = False
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
except socket.error:
|
||||
# It's a DNS name. Check where it should be resolved.
|
||||
if self.__proxy[3]:
|
||||
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
|
||||
rmtrslv = True
|
||||
else:
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
# Construct the request packet
|
||||
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
|
||||
# The username parameter is considered userid for SOCKS4
|
||||
if self.__proxy[4] != None:
|
||||
req = req + self.__proxy[4]
|
||||
req = req + chr(0x00).encode()
|
||||
# DNS name if remote resolving is required
|
||||
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||
# called SOCKS4A and may not be supported in all cases.
|
||||
if rmtrslv:
|
||||
req = req + destaddr + chr(0x00).encode()
|
||||
self.sendall(req)
|
||||
# Get the response from the server
|
||||
resp = self.__recvall(8)
|
||||
if resp[0:1] != chr(0x00).encode():
|
||||
# Bad data
|
||||
self.close()
|
||||
raise GeneralProxyError((1,_generalerrors[1]))
|
||||
if resp[1:2] != chr(0x5A).encode():
|
||||
# Server returned an error
|
||||
self.close()
|
||||
if ord(resp[1:2]) in (91, 92, 93):
|
||||
self.close()
|
||||
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
|
||||
else:
|
||||
raise Socks4Error((94, _socks4errors[4]))
|
||||
# Get the bound address/port
|
||||
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
|
||||
if rmtrslv != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def __negotiatehttp(self, destaddr, destport):
|
||||
"""__negotiatehttp(self,destaddr,destport)
|
||||
Negotiates a connection through an HTTP server.
|
||||
"""
|
||||
# If we need to resolve locally, we do this now
|
||||
if not self.__proxy[3]:
|
||||
addr = socket.gethostbyname(destaddr)
|
||||
else:
|
||||
addr = destaddr
|
||||
self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
|
||||
# We read the response until we get the string "\r\n\r\n"
|
||||
resp = self.recv(1)
|
||||
while resp.find("\r\n\r\n".encode()) == -1:
|
||||
recv = self.recv(1)
|
||||
if not recv:
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
resp = resp + recv
|
||||
# We just need the first line to check if the connection
|
||||
# was successful
|
||||
statusline = resp.splitlines()[0].split(" ".encode(), 2)
|
||||
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
try:
|
||||
statuscode = int(statusline[1])
|
||||
except ValueError:
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if statuscode != 200:
|
||||
self.close()
|
||||
raise HTTPError((statuscode, statusline[2]))
|
||||
self.__proxysockname = ("0.0.0.0", 0)
|
||||
self.__proxypeername = (addr, destport)
|
||||
|
||||
def connect(self, destpair):
|
||||
"""connect(self, despair)
|
||||
Connects to the specified destination through a proxy.
|
||||
destpar - A tuple of the IP/DNS address and the port number.
|
||||
(identical to socket's connect).
|
||||
To select the proxy server use setproxy().
|
||||
"""
|
||||
# Do a minimal input check first
|
||||
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
|
||||
raise GeneralProxyError((5, _generalerrors[5]))
|
||||
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = int(self.__proxy[2])
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
self.__negotiatesocks5(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatesocks4(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_HTTP:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 8080
|
||||
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||
self.__negotiatehttp(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == None:
|
||||
_orgsocket.connect(self, (destpair[0], destpair[1]))
|
||||
else:
|
||||
raise GeneralProxyError((4, _generalerrors[4]))
|
164
toxygen/wrapper_tests/support_http.py
Normal file
164
toxygen/wrapper_tests/support_http.py
Normal file
|
@ -0,0 +1,164 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from io import BytesIO
|
||||
import urllib
|
||||
import traceback
|
||||
|
||||
global LOG
|
||||
LOG = logging.getLogger('app.'+'ts')
|
||||
|
||||
try:
|
||||
import pycurl
|
||||
except ImportError:
|
||||
pycurl = None
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
requests = None
|
||||
|
||||
lNO_PROXY = ['localhost', '127.0.0.1']
|
||||
CONNECT_TIMEOUT = 20.0
|
||||
|
||||
def bAreWeConnected():
|
||||
# FixMe: Linux only
|
||||
sFile = f"/proc/{os.getpid()}/net/route"
|
||||
if not os.path.isfile(sFile): return None
|
||||
i = 0
|
||||
for elt in open(sFile, "r").readlines():
|
||||
if elt.startswith('Iface'): continue
|
||||
if elt.startswith('lo'): continue
|
||||
i += 1
|
||||
return i > 0
|
||||
|
||||
def pick_up_proxy_from_environ():
|
||||
retval = dict()
|
||||
if os.environ.get('socks_proxy', ''):
|
||||
# socks_proxy takes precedence over https/http
|
||||
proxy = os.environ.get('socks_proxy', '')
|
||||
i = proxy.find('//')
|
||||
if i >= 0: proxy = proxy[i+2:]
|
||||
retval['proxy_host'] = proxy.split(':')[0]
|
||||
retval['proxy_port'] = proxy.split(':')[-1]
|
||||
retval['proxy_type'] = 2
|
||||
retval['udp_enabled'] = False
|
||||
elif os.environ.get('https_proxy', ''):
|
||||
# https takes precedence over http
|
||||
proxy = os.environ.get('https_proxy', '')
|
||||
i = proxy.find('//')
|
||||
if i >= 0: proxy = proxy[i+2:]
|
||||
retval['proxy_host'] = proxy.split(':')[0]
|
||||
retval['proxy_port'] = proxy.split(':')[-1]
|
||||
retval['proxy_type'] = 1
|
||||
retval['udp_enabled'] = False
|
||||
elif os.environ.get('http_proxy', ''):
|
||||
proxy = os.environ.get('http_proxy', '')
|
||||
i = proxy.find('//')
|
||||
if i >= 0: proxy = proxy[i+2:]
|
||||
retval['proxy_host'] = proxy.split(':')[0]
|
||||
retval['proxy_port'] = proxy.split(':')[-1]
|
||||
retval['proxy_type'] = 1
|
||||
retval['udp_enabled'] = False
|
||||
else:
|
||||
retval['proxy_host'] = ''
|
||||
retval['proxy_port'] = ''
|
||||
retval['proxy_type'] = 0
|
||||
retval['udp_enabled'] = True
|
||||
return retval
|
||||
|
||||
def download_url(url, settings=None):
|
||||
if not bAreWeConnected(): return ''
|
||||
|
||||
if settings is None:
|
||||
settings = pick_up_proxy_from_environ()
|
||||
|
||||
if pycurl:
|
||||
LOG.debug('Downloading with pycurl: ' + str(url))
|
||||
buffer = BytesIO()
|
||||
c = pycurl.Curl()
|
||||
c.setopt(c.URL, url)
|
||||
c.setopt(c.WRITEDATA, buffer)
|
||||
# Follow redirect.
|
||||
c.setopt(c.FOLLOWLOCATION, True)
|
||||
|
||||
# cookie jar
|
||||
cjar = os.path.join(os.environ['HOME'], '.local', 'jar.cookie')
|
||||
if os.path.isfile(cjar):
|
||||
c.setopt(c.COOKIEFILE, cjar)
|
||||
# LARGS+=( --cookie-jar --junk-session-cookies )
|
||||
|
||||
#? c.setopt(c.ALTSVC_CTRL, 16)
|
||||
|
||||
c.setopt(c.NOPROXY, ','.join(lNO_PROXY))
|
||||
#? c.setopt(c.CAINFO, certifi.where())
|
||||
if settings['proxy_type'] == 2 and settings['proxy_host']:
|
||||
socks_proxy = 'socks5h://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||
settings['udp_enabled'] = False
|
||||
c.setopt(c.PROXY, socks_proxy)
|
||||
c.setopt(c.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5_HOSTNAME)
|
||||
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||
https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||
c.setopt(c.PROXY, https_proxy)
|
||||
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||
http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||
c.setopt(c.PROXY, http_proxy)
|
||||
c.setopt(c.PROTOCOLS, c.PROTO_HTTPS)
|
||||
try:
|
||||
c.perform()
|
||||
c.close()
|
||||
#? assert c.getinfo(c.RESPONSE_CODE) < 300
|
||||
result = buffer.getvalue()
|
||||
# Body is a byte string.
|
||||
LOG.info('nodes loaded with pycurl: ' + str(url))
|
||||
return result
|
||||
except Exception as ex:
|
||||
LOG.error('TOX Downloading error with pycurl: ' + str(ex))
|
||||
LOG.error('\n' + traceback.format_exc())
|
||||
# drop through
|
||||
|
||||
if requests:
|
||||
LOG.debug('Downloading with requests: ' + str(url))
|
||||
try:
|
||||
headers = dict()
|
||||
headers['Content-Type'] = 'application/json'
|
||||
proxies = dict()
|
||||
if settings['proxy_type'] == 2 and settings['proxy_host']:
|
||||
socks_proxy = 'socks5://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||
settings['udp_enabled'] = False
|
||||
proxies['https'] = socks_proxy
|
||||
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||
https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||
proxies['https'] = https_proxy
|
||||
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||
http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||
proxies['http'] = http_proxy
|
||||
req = requests.get(url,
|
||||
headers=headers,
|
||||
proxies=proxies,
|
||||
timeout=CONNECT_TIMEOUT)
|
||||
# max_retries=3
|
||||
assert req.status_code < 300
|
||||
result = req.content
|
||||
LOG.info('nodes loaded with requests: ' + str(url))
|
||||
return result
|
||||
except Exception as ex:
|
||||
LOG.error('TOX Downloading error with requests: ' + str(ex))
|
||||
# drop through
|
||||
|
||||
if not settings['proxy_type']: # no proxy
|
||||
LOG.debug('Downloading with urllib no proxy: ' + str(url))
|
||||
try:
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
response = urllib.request.urlopen(req)
|
||||
result = response.read()
|
||||
LOG.info('nodes loaded with no proxy: ' + str(url))
|
||||
return result
|
||||
except Exception as ex:
|
||||
LOG.error('TOX Downloading ' + str(ex))
|
||||
return ''
|
||||
|
||||
return ''
|
||||
|
572
toxygen/wrapper_tests/support_onions.py
Normal file
572
toxygen/wrapper_tests/support_onions.py
Normal file
|
@ -0,0 +1,572 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
import getpass
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
if False:
|
||||
import cepa as stem
|
||||
from cepa.connection import MissingPassword
|
||||
from cepa.control import Controller
|
||||
from cepa.util.tor_tools import is_valid_fingerprint
|
||||
else:
|
||||
import stem
|
||||
from stem.connection import MissingPassword
|
||||
from stem.control import Controller
|
||||
from stem.util.tor_tools import is_valid_fingerprint
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
LOG = logging.getLogger()
|
||||
|
||||
bHAVE_TORR = shutil.which('tor-resolve')
|
||||
|
||||
yKNOWN_ONIONS = """
|
||||
- facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd # facebook
|
||||
- duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad # ddg
|
||||
- zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad # hks
|
||||
"""
|
||||
# grep -B 1 '<li><a href="' /tmp/tor.html |sed -e 's/<li><a href="http:../ - /' -e 's/.onion.*//' -e 's/<li id=./ # /' -e 's/".*//' -e '/^--/d' -e '/<li id/d'
|
||||
# This will slow things down 1-2 min each
|
||||
yKNOWN_ONIONS_TOR = """
|
||||
# 2019.www.torproject.org
|
||||
- jqyzxhjk6psc6ul5jnfwloamhtyh7si74b4743k2qgpskwwxrzhsxmad
|
||||
# api.donate.torproject.org
|
||||
- rbi3fpvpz4vlrx67scoqef2zxz7k4xyiludszg655favvkygjmhz6wyd
|
||||
# archive.torproject.org
|
||||
- uy3qxvwzwoeztnellvvhxh7ju7kfvlsauka7avilcjg7domzxptbq7qd
|
||||
# aus1.torproject.org
|
||||
- ot3ivcdxmalbsbponeeq5222hftpf3pqil24q3s5ejwo5t52l65qusid
|
||||
# aus2.torproject.org
|
||||
- b5t7emfr2rn3ixr4lvizpi3stnni4j4p6goxho7lldf4qg4hz5hvpqid
|
||||
# blog.torproject.org
|
||||
- pzhdfe7jraknpj2qgu5cz2u3i4deuyfwmonvzu5i3nyw4t4bmg7o5pad
|
||||
# bridges.torproject.org
|
||||
- yq5jjvr7drkjrelzhut7kgclfuro65jjlivyzfmxiq2kyv5lickrl4qd
|
||||
# cloud.torproject.org
|
||||
- ui3cpcohcoko6aydhuhlkwqqtvadhaflcc5zb7mwandqmcal7sbwzwqd
|
||||
# collector.torproject.org
|
||||
- pgmrispjerzzf2tdzbfp624cg5vpbvdw2q5a3hvtsbsx25vnni767yad
|
||||
# collector2.torproject.org
|
||||
- 3srlmjzbyyzz62jvdfqwn5ldqmh6mwnqxre2zamxveb75uz2qrqkrkyd
|
||||
# community.torproject.org
|
||||
- xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd
|
||||
# consensus-health.torproject.org
|
||||
- tkskz5dkjel4xqyw5d5l3k52kgglotwn6vgb5wrl2oa5yi2szvywiyid
|
||||
# crm.torproject.org
|
||||
- 6ojylpznauimd2fga3m7g24vd7ebkzlemxdprxckevqpzs347ugmynqd
|
||||
# deb.torproject.org
|
||||
- apow7mjfryruh65chtdydfmqfpj5btws7nbocgtaovhvezgccyjazpqd
|
||||
# dev.crm.torproject.org
|
||||
- eewp4iydzyu2a5d6bvqadadkozxdbhsdtmsoczu2joexfrjjsheaecad
|
||||
# dist.torproject.org
|
||||
- scpalcwstkydpa3y7dbpkjs2dtr7zvtvdbyj3dqwkucfrwyixcl5ptqd
|
||||
# donate-api.torproject.org
|
||||
- lkfkuhcx62yfvzuz5o3ju4divuf4bsh2bybwd3oierq47kyp2ig2gvid
|
||||
# donate.torproject.org
|
||||
- yoaenchicimox2qdc47p36zm3cuclq7s7qxx6kvxqaxjodigfifljqqd
|
||||
# exonerator.torproject.org
|
||||
- pm46i5h2lfewyx6l7pnicbxhts2sxzacvsbmqiemqaspredf2gm3dpad
|
||||
# extra.torproject.org
|
||||
- kkr72iohlfix5ipjg776eyhplnl2oiv5tz4h2y2bkhjix3quafvjd5ad
|
||||
# gettor.torproject.org
|
||||
- ueghr2hzndecdntou33mhymbbxj7pir74nwzhqr6drhxpbz3j272p4id
|
||||
# git.torproject.org
|
||||
- xtlfhaspqtkeeqxk6umggfbr3gyfznvf4jhrge2fujz53433i2fcs3id
|
||||
# gitlab.torproject.org
|
||||
- eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad
|
||||
# gitweb.torproject.org
|
||||
- gzgme7ov25seqjbphab4fkcph3jkobfwwpivt5kzbv3kqx2y2qttl4yd
|
||||
# grafana1.torproject.org
|
||||
- 7zjnw5lx2x27rwiocxkqdquo7fawj46mf2wiu2l7e6z6ng6nivmdxnad
|
||||
# grafana2.torproject.org
|
||||
- f3vd6fyiccuppybkxiblgigej3pfvvqzjnhd3wyv7h4ee5asawf2fhqd
|
||||
# ircbouncer.torproject.org
|
||||
- moz5kotsnjony4oxccxfo4lwk3pvoxmdoljibhgoonzgzjs5oemtjmqd
|
||||
# metabase.metrics.torproject.org
|
||||
- gr5pseamigereei4c6654hafzhid5z2c3oqzn6cfnx7yfyelt47znhad
|
||||
# metrics.torproject.org
|
||||
- hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid
|
||||
# moat.torproject.org
|
||||
- z7m7ogzdhu43nosvjtsuplfmuqa3ge5obahixydhmzdox6owwxfoxzid
|
||||
# nagios.torproject.org
|
||||
- w6vizvw4ckesva5fvlkrepynemxdq6pgo5sh4r76ec6msq5notkhqryd
|
||||
# newsletter.torproject.org
|
||||
- a4ygisnerpgtc5ayerl22pll6cls3oyj54qgpm7qrmb66xrxts6y3lyd
|
||||
# nightlies.tbb.torproject.org
|
||||
- umj4zbqdfcyevlkgqgpq6foxk3z75zzxsbgt5jqmfxofrbrjh3crbnad
|
||||
# nyx.torproject.org
|
||||
- 3ewfgrt4gzfccp6bnquhqb266r3zepiqpnsk3falwygkegtluwuyevid
|
||||
- xao2lxsmia2edq2n5zxg6uahx6xox2t7bfjw6b5vdzsxi7ezmqob6qid
|
||||
- dud2sxm6feahhuwj4y4lzktduy7v3qpaqsfkggtj2ojmzathttkegoid
|
||||
# openpgpkey.torproject.org
|
||||
- 2yldcptk56shc7lwieozoglw3t5ghty7m6mf2faysvfnzccqavbu2mad
|
||||
# people.torproject.org
|
||||
- 5ecey6oe4rocdsfoigr4idu42cecm2j7zfogc3xc7kfn4uriehwrs6qd
|
||||
# prometheus1.torproject.org
|
||||
- ydok5jiruh3ak6hcfdlm2g7iuraaxcomeckj2nucjsxif6qmrrda2byd
|
||||
# prometheus2.torproject.org
|
||||
- vyo6yrqhl3by7d6n5t6hjkflaqbarjpqjnvapr5u5rafk4imnfrmcjyd
|
||||
# rbm.torproject.org
|
||||
- nkuz2tpok7ctwd5ueer5bytj3bm42vp7lgjcsnznal3stotg6vyaakyd
|
||||
# research.torproject.org
|
||||
- xhqthou6scpfnwjyzc3ekdgcbvj76ccgyjyxp6cgypxjlcuhnxiktnqd
|
||||
# review.torproject.net
|
||||
- zhkhhhnppc5k6xju7n25rjba3wuip73jnodicxl65qdpchrwvvsilcyd
|
||||
# rpm.torproject.org
|
||||
- 4ayyzfoh5qdrokqaejis3rdredhvf22n3migyxfudpkpunngfc7g4lqd
|
||||
# snowflake.torproject.org
|
||||
- oljlphash3bpqtrvqpr5gwzrhroziw4mddidi5d2qa4qjejcbrmoypqd
|
||||
# spec.torproject.org
|
||||
- i3xi5qxvbrngh3g6o7czwjfxwjzigook7zxzjmgwg5b7xnjcn5hzciad
|
||||
# staging-api.donate.torproject.org
|
||||
- vorwws6g6mx23djlznmlqva4t5olulpnet6fxyiyytcu5dorp3fstdqd
|
||||
# staging.crm.torproject.org
|
||||
- pt34uujusar4arrvsqljndqlt7tck2d5cosaav5xni4nh7bmvshyp2yd
|
||||
# staging.donate-api.torproject.org
|
||||
- 7niqsyixinnhxvh33zh5dqnplxnc2yd6ktvats3zmtbbpzcphpbsa6qd
|
||||
# status.torproject.org
|
||||
- eixoaclv7qvnmu5rolbdwba65xpdiditdoyp6edsre3fitad777jr3ad
|
||||
# stem.torproject.org
|
||||
- mf34jlghauz5pxjcmdymdqbe5pva4v24logeys446tdrgd5lpsrocmqd
|
||||
# styleguide.torproject.org
|
||||
- 7khzpw47s35pwo3lvtctwf2szvnq3kgglvzc22elx7of2awdzpovqmqd
|
||||
# submission.torproject.org
|
||||
- givpjczyrb5jjseful3o5tn3tg7tidbu4gydl4sa5ekpcipivqaqnpad
|
||||
# support.torproject.org
|
||||
- rzuwtpc4wb3xdzrj3yeajsvm3fkq4vbeubm2tdxaqruzzzgs5dwemlad
|
||||
# survey.torproject.org
|
||||
- eh5esdnd6fkbkapfc6nuyvkjgbtnzq2is72lmpwbdbxepd2z7zbgzsqd
|
||||
# svn-archive.torproject.org
|
||||
- b63iq6es4biaawfilwftlfkw6a6putogxh4iakei2ioppb7dsfucekyd
|
||||
# tb-manual.torproject.org
|
||||
- dsbqrprgkqqifztta6h3w7i2htjhnq7d3qkh3c7gvc35e66rrcv66did
|
||||
# test-api.donate.torproject.org
|
||||
- wiofesr5qt2k7qrlljpk53isgedxi6ddw6z3o7iay2l7ne3ziyagxaid
|
||||
# test-data.tbb.torproject.org
|
||||
- umbk3kbgov4ekg264yulvbrpykfye7ohguqbds53qn547mdpt6o4qkad
|
||||
# test.crm.torproject.org
|
||||
- a4d52y2erv4eijii66cpnyqn7rsnnq3gmtrsdxzt2laoutvu4gz7fwid
|
||||
# test.donate-api.torproject.org
|
||||
- i4zhrn4md3ucd5dfgeo5lnqd3jy2z2kzp3lt4tdisvivzoqqtlrymkid
|
||||
# www
|
||||
- tttyx2vwp7ihml3vkhywwcizv6nbwrikpgeciy3qrow7l7muak2pnhad
|
||||
# www.torproject.org
|
||||
- 2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid
|
||||
"""
|
||||
|
||||
# we check these each time but we got them by sorting bad relays
|
||||
# in the wild we'll keep a copy here so we can avoid restesting
|
||||
yKNOWN_NODNS = """
|
||||
- 0x0.is
|
||||
- a9.wtf
|
||||
- apt96.com
|
||||
- axims.net
|
||||
- backup.spekadyon.org
|
||||
- dfri.se
|
||||
- dotsrc.org
|
||||
- dtf.contact
|
||||
- ezyn.de
|
||||
- for-privacy.net
|
||||
- galtland.network
|
||||
- heraldonion.org
|
||||
- interfesse.net
|
||||
- kryptonit.org
|
||||
- linkspartei.org
|
||||
- mkg20001.io
|
||||
- nicdex.com
|
||||
- nx42.de
|
||||
- pineapple.cx
|
||||
- privacylayer.xyz
|
||||
- privacysvcs.net
|
||||
- prsv.ch
|
||||
- sebastian-elisa-pfeifer.eu
|
||||
- thingtohide.nl
|
||||
- tor-exit-2.aa78i2efsewr0neeknk.xyz
|
||||
- tor-exit-3.aa78i2efsewr0neeknk.xyz
|
||||
- tor.dlecan.com
|
||||
- tor.skankhunt42.pw
|
||||
- transliberation.today
|
||||
- tuxli.org
|
||||
- unzane.com
|
||||
- verification-for-nusenu.net
|
||||
- www.defcon.org
|
||||
"""
|
||||
# - aklad5.com
|
||||
# - artikel5ev.de
|
||||
# - arvanode.net
|
||||
# - dodo.pm
|
||||
# - erjan.net
|
||||
# - galtland.network
|
||||
# - lonet.sh
|
||||
# - moneneis.de
|
||||
# - olonet.sh
|
||||
# - or-exit-2.aa78i2efsewr0neeknk.xyz
|
||||
# - or.wowplanet.de
|
||||
# - ormycloud.org
|
||||
# - plied-privacy.net
|
||||
# - rivacysvcs.net
|
||||
# - redacted.org
|
||||
# - rofl.cat
|
||||
# - sv.ch
|
||||
# - tikel10.org
|
||||
# - tor.wowplanet.de
|
||||
# - torix-relays.org
|
||||
# - tse.com
|
||||
# - w.digidow.eu
|
||||
# - w.cccs.de
|
||||
|
||||
def oMakeController(sSock='', port=9051):
|
||||
import getpass
|
||||
if sSock and os.path.exists(sSock):
|
||||
controller = Controller.from_socket_file(path=sSock)
|
||||
else:
|
||||
controller = Controller.from_port(port=port)
|
||||
sys.stdout.flush()
|
||||
p = getpass.unix_getpass(prompt='Controller Password: ', stream=sys.stderr)
|
||||
controller.authenticate(p)
|
||||
return controller
|
||||
|
||||
oSTEM_CONTROLER = None
|
||||
def oGetStemController(log_level=10, sock_or_pair='/run/tor/control'):
|
||||
|
||||
global oSTEM_CONTROLER
|
||||
if oSTEM_CONTROLER: return oSTEM_CONTROLER
|
||||
import stem.util.log
|
||||
# stem.util.log.Runlevel = 'DEBUG' = 20 # log_level
|
||||
|
||||
if os.path.exists(sock_or_pair):
|
||||
LOG.info(f"controller from socket {sock_or_pair}")
|
||||
controller = Controller.from_socket_file(path=sock_or_pair)
|
||||
else:
|
||||
if type(sock_or_pair) == int:
|
||||
port = sock_or_pair
|
||||
elif ':' in sock_or_pair:
|
||||
port = sock_or_pair.split(':')[1]
|
||||
else:
|
||||
port = sock_or_pair
|
||||
try:
|
||||
port = int(port)
|
||||
except: port = 9051
|
||||
LOG.info(f"controller from port {port}")
|
||||
controller = Controller.from_port(port=port)
|
||||
try:
|
||||
controller.authenticate()
|
||||
except (Exception, MissingPassword):
|
||||
sys.stdout.flush()
|
||||
p = getpass.unix_getpass(prompt='Controller Password: ', stream=sys.stderr)
|
||||
controller.authenticate(p)
|
||||
oSTEM_CONTROLER = controller
|
||||
LOG.debug(f"{controller}")
|
||||
return oSTEM_CONTROLER
|
||||
|
||||
def bAreWeConnected():
|
||||
# FixMe: Linux only
|
||||
sFile = f"/proc/{os.getpid()}/net/route"
|
||||
if not os.path.isfile(sFile): return None
|
||||
i = 0
|
||||
for elt in open(sFile, "r").readlines():
|
||||
if elt.startswith('Iface'): continue
|
||||
if elt.startswith('lo'): continue
|
||||
i += 1
|
||||
return i > 0
|
||||
|
||||
def sMapaddressResolv(target, iPort=9051, log_level=10):
|
||||
if not stem:
|
||||
LOG.warn('please install the stem Python package')
|
||||
return ''
|
||||
|
||||
try:
|
||||
controller = oGetStemController(log_level=log_level)
|
||||
|
||||
map_dict = {"0.0.0.0": target}
|
||||
map_ret = controller.map_address(map_dict)
|
||||
|
||||
return map_ret
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
return ''
|
||||
|
||||
def vwait_for_controller(controller, wait_boot=10):
|
||||
if bAreWeConnected() is False:
|
||||
raise SystemExit("we are not connected")
|
||||
percent = i = 0
|
||||
# You can call this while boostrapping
|
||||
while percent < 100 and i < wait_boot:
|
||||
bootstrap_status = controller.get_info("status/bootstrap-phase")
|
||||
progress_percent = re.match('.* PROGRESS=([0-9]+).*', bootstrap_status)
|
||||
percent = int(progress_percent.group(1))
|
||||
LOG.info(f"Bootstrapping {percent}%")
|
||||
time.sleep(5)
|
||||
i += 5
|
||||
|
||||
def bin_to_hex(raw_id, length=None):
|
||||
if length is None: length = len(raw_id)
|
||||
res = ''.join('{:02x}'.format(raw_id[i]) for i in range(length))
|
||||
return res.upper()
|
||||
|
||||
def lIntroductionPoints(controller=None, lOnions=[], itimeout=120, log_level=10):
|
||||
"""now working !!! stem 1.8.x timeout must be huge >120
|
||||
'Provides the descriptor for a hidden service. The **address** is the
|
||||
'.onion' address of the hidden service '
|
||||
What about Services?
|
||||
"""
|
||||
try:
|
||||
from cryptography.utils import int_from_bytes
|
||||
except ImportError:
|
||||
import cryptography.utils
|
||||
|
||||
# guessing - not in the current cryptography but stem expects it
|
||||
def int_from_bytes(**args): return int.to_bytes(*args)
|
||||
cryptography.utils.int_from_bytes = int_from_bytes
|
||||
# this will fai if the trick above didnt work
|
||||
from stem.prereq import is_crypto_available
|
||||
is_crypto_available(ed25519=True)
|
||||
|
||||
from queue import Empty
|
||||
|
||||
from stem import Timeout
|
||||
from stem.client.datatype import LinkByFingerprint
|
||||
from stem.descriptor.hidden_service import HiddenServiceDescriptorV3
|
||||
|
||||
if type(lOnions) not in [set, tuple, list]:
|
||||
lOnions = list(lOnions)
|
||||
if controller is None:
|
||||
controller = oGetStemController(log_level=log_level)
|
||||
l = []
|
||||
for elt in lOnions:
|
||||
LOG.info(f"controller.get_hidden_service_descriptor {elt}")
|
||||
try:
|
||||
desc = controller.get_hidden_service_descriptor(elt,
|
||||
await_result=True,
|
||||
timeout=itimeout)
|
||||
# LOG.log(40, f"{dir(desc)} get_hidden_service_descriptor")
|
||||
# timeouts 20 sec
|
||||
# mistakenly a HSv2 descriptor
|
||||
hs_address = HiddenServiceDescriptorV3.from_str(str(desc)) # reparse as HSv3
|
||||
oInnerLayer = hs_address.decrypt(elt)
|
||||
# LOG.log(40, f"{dir(oInnerLayer)}")
|
||||
|
||||
# IntroductionPointV3
|
||||
n = oInnerLayer.introduction_points
|
||||
if not n:
|
||||
LOG.warn(f"NO introduction points for {elt}")
|
||||
continue
|
||||
LOG.info(f"{elt} {len(n)} introduction points")
|
||||
lp = []
|
||||
for introduction_point in n:
|
||||
for linkspecifier in introduction_point.link_specifiers:
|
||||
if isinstance(linkspecifier, LinkByFingerprint):
|
||||
# LOG.log(40, f"Getting fingerprint for {linkspecifier}")
|
||||
if hasattr(linkspecifier, 'fingerprint'):
|
||||
assert len(linkspecifier.value) == 20
|
||||
lp += [bin_to_hex(linkspecifier.value)]
|
||||
LOG.info(f"{len(lp)} introduction points for {elt}")
|
||||
l += lp
|
||||
except (Empty, Timeout,) as e: # noqa
|
||||
LOG.warn(f"Timed out getting introduction points for {elt}")
|
||||
except stem.DescriptorUnavailable as e:
|
||||
LOG.error(e)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
return l
|
||||
|
||||
def zResolveDomain(domain):
|
||||
try:
|
||||
ip = sTorResolve(domain)
|
||||
except Exception as e: # noqa
|
||||
ip = ''
|
||||
if ip == '':
|
||||
try:
|
||||
lpair = getaddrinfo(domain, 443)
|
||||
except Exception as e:
|
||||
LOG.warn(f"{e}")
|
||||
lpair = None
|
||||
if lpair is None:
|
||||
LOG.warn(f"TorResolv and getaddrinfo failed for {domain}")
|
||||
return ''
|
||||
ip = lpair[0]
|
||||
return ip
|
||||
|
||||
def sTorResolve(target,
|
||||
verbose=False,
|
||||
sHost='127.0.0.1',
|
||||
iPort=9050,
|
||||
SOCK_TIMEOUT_SECONDS=10.0,
|
||||
SOCK_TIMEOUT_TRIES=3,
|
||||
):
|
||||
MAX_INFO_RESPONSE_PACKET_LENGTH = 8
|
||||
if '@' in target:
|
||||
LOG.warn(f"sTorResolve failed invalid hostname {target}")
|
||||
return ''
|
||||
target = target.strip('/')
|
||||
seb = b"\x04\xf0\x00\x00\x00\x00\x00\x01\x00"
|
||||
seb += bytes(target, 'US-ASCII') + b"\x00"
|
||||
assert len(seb) == 10 + len(target), str(len(seb)) + repr(seb)
|
||||
|
||||
# LOG.debug(f"0 Sending {len(seb)} to The TOR proxy {seb}")
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((sHost, iPort))
|
||||
|
||||
sock.settimeout(SOCK_TIMEOUT_SECONDS)
|
||||
oRet = sock.sendall(seb) # noqa
|
||||
|
||||
i = 0
|
||||
data = ''
|
||||
while i < SOCK_TIMEOUT_TRIES:
|
||||
i += 1
|
||||
time.sleep(3)
|
||||
lReady = select.select([sock.fileno()], [], [],
|
||||
SOCK_TIMEOUT_SECONDS)
|
||||
if not lReady[0]: continue
|
||||
try:
|
||||
flags=socket.MSG_WAITALL
|
||||
data = sock.recv(MAX_INFO_RESPONSE_PACKET_LENGTH, flags)
|
||||
except socket.timeout:
|
||||
LOG.warn(f"4 The TOR proxy {(sHost, iPort)}" \
|
||||
+" didnt reply in " + str(SOCK_TIMEOUT_SECONDS) + " sec."
|
||||
+" #" +str(i))
|
||||
except Exception as e:
|
||||
LOG.error("4 The TOR proxy " \
|
||||
+repr((sHost, iPort)) \
|
||||
+" errored with " + str(e)
|
||||
+" #" +str(i))
|
||||
sock.close()
|
||||
return ''
|
||||
else:
|
||||
if len(data) > 0: break
|
||||
|
||||
if len(data) == 0:
|
||||
if i > SOCK_TIMEOUT_TRIES:
|
||||
sLabel = "5 No reply #"
|
||||
else:
|
||||
sLabel = "5 No data #"
|
||||
LOG.warn(f"sTorResolve: {sLabel} {i} on {sHost}:{iPort}")
|
||||
sock.close()
|
||||
return ''
|
||||
|
||||
assert len(data) >= 8
|
||||
packet_sf = data[1]
|
||||
if packet_sf == 90:
|
||||
# , "%d" % packet_sf
|
||||
assert f"{packet_sf}" == "90", f"packet_sf = {packet_sf}"
|
||||
return f"{data[4]}.{data[5]}.{data[6]}.{data[7]}"
|
||||
else:
|
||||
# 91
|
||||
LOG.warn(f"tor-resolve failed for {target} on {sHost}:{iPort}")
|
||||
|
||||
os.system(f"tor-resolve -4 {target} > /tmp/e 2>/dev/null")
|
||||
# os.system("strace tor-resolve -4 "+target+" 2>&1|grep '^sen\|^rec'")
|
||||
|
||||
return ''
|
||||
|
||||
def getaddrinfo(sHost, sPort):
|
||||
# do this the explicit way = Ive seen the compact connect fail
|
||||
# >>> sHost, sPort = 'l27.0.0.1', 33446
|
||||
# >>> sock.connect((sHost, sPort))
|
||||
# socket.gaierror: [Errno -2] Name or service not known
|
||||
try:
|
||||
lElts = socket.getaddrinfo(sHost, int(sPort), socket.AF_INET)
|
||||
lElts = list(filter(lambda elt: elt[1] == socket.SOCK_DGRAM, lElts))
|
||||
assert len(lElts) == 1, repr(lElts)
|
||||
lPair = lElts[0][-1]
|
||||
assert len(lPair) == 2, repr(lPair)
|
||||
assert type(lPair[1]) == int, repr(lPair)
|
||||
except (socket.gaierror, OSError, BaseException) as e:
|
||||
LOG.error(e)
|
||||
return None
|
||||
return lPair
|
||||
|
||||
def icheck_torrc(sFile, oArgs):
|
||||
l = open(sFile, 'rt').readlines()
|
||||
a = {}
|
||||
for elt in l:
|
||||
elt = elt.strip()
|
||||
if not elt or ' ' not in elt: continue
|
||||
(k, v,) = elt.split(' ', 1)
|
||||
a[k] = v
|
||||
keys = a
|
||||
|
||||
if 'HashedControlPassword' not in keys:
|
||||
LOG.info('Add HashedControlPassword for security')
|
||||
print('run: tor --hashcontrolpassword <TopSecretWord>')
|
||||
if 'ExcludeExitNodes' in keys:
|
||||
elt = 'BadNodes.ExcludeExitNodes.BadExit'
|
||||
LOG.warn(f"Remove ExcludeNodes and move then to {oArgs.bad_nodes}")
|
||||
print(f"move to the {elt} section as a list")
|
||||
if 'GuardNodes' in keys:
|
||||
elt = 'GoodNodes.GuardNodes'
|
||||
LOG.warn(f"Remove GuardNodes and move then to {oArgs.good_nodes}")
|
||||
print(f"move to the {elt} section as a list")
|
||||
if 'ExcludeNodes' in keys:
|
||||
elt = 'BadNodes.ExcludeNodes.BadExit'
|
||||
LOG.warn(f"Remove ExcludeNodes and move then to {oArgs.bad_nodes}")
|
||||
print(f"move to the {elt} section as a list")
|
||||
if 'ControlSocket' not in keys and os.path.exists('/run/tor/control'):
|
||||
LOG.info('Add ControlSocket /run/tor/control for us')
|
||||
print('ControlSocket /run/tor/control GroupWritable RelaxDirModeCheck')
|
||||
if 'UseMicrodescriptors' not in keys or keys['UseMicrodescriptors'] != '1':
|
||||
LOG.info('Add UseMicrodescriptors 0 for us')
|
||||
print('UseMicrodescriptors 0')
|
||||
if 'AutomapHostsSuffixes' not in keys:
|
||||
LOG.info('Add AutomapHostsSuffixes for onions')
|
||||
print('AutomapHostsSuffixes .exit,.onion')
|
||||
if 'AutoMapHostsOnResolve' not in keys:
|
||||
LOG.info('Add AutoMapHostsOnResolve for onions')
|
||||
print('AutoMapHostsOnResolve 1')
|
||||
if 'VirtualAddrNetworkIPv4' not in keys:
|
||||
LOG.info('Add VirtualAddrNetworkIPv4 for onions')
|
||||
print('VirtualAddrNetworkIPv4 172.16.0.0/12')
|
||||
return 0
|
||||
|
||||
def lExitExcluder(oArgs, iPort=9051, log_level=10):
|
||||
"""
|
||||
https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py
|
||||
"""
|
||||
if not stem:
|
||||
LOG.warn('please install the stem Python package')
|
||||
return ''
|
||||
LOG.debug('lExcludeExitNodes')
|
||||
|
||||
try:
|
||||
controller = oGetStemController(log_level=log_level)
|
||||
# generator
|
||||
relays = controller.get_server_descriptors()
|
||||
except Exception as e:
|
||||
LOG.error(f'Failed to get relay descriptors {e}')
|
||||
return None
|
||||
|
||||
if controller.is_set('ExcludeExitNodes'):
|
||||
LOG.info('ExcludeExitNodes is in use already.')
|
||||
return None
|
||||
|
||||
exit_excludelist=[]
|
||||
LOG.debug("Excluded exit relays:")
|
||||
for relay in relays:
|
||||
if relay.exit_policy.is_exiting_allowed() and not relay.contact:
|
||||
if is_valid_fingerprint(relay.fingerprint):
|
||||
exit_excludelist.append(relay.fingerprint)
|
||||
LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint)
|
||||
else:
|
||||
LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint)
|
||||
|
||||
try:
|
||||
controller.set_conf('ExcludeExitNodes', exit_excludelist)
|
||||
LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist))
|
||||
except Exception as e:
|
||||
LOG.exception('ExcludeExitNodes ' +str(e))
|
||||
return exit_excludelist
|
||||
|
||||
if __name__ == '__main__':
|
||||
target = 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad'
|
||||
controller = oGetStemController(log_level=10)
|
||||
lIntroductionPoints(controller, [target], itimeout=120)
|
914
toxygen/wrapper_tests/support_testing.py
Normal file
914
toxygen/wrapper_tests/support_testing.py
Normal file
|
@ -0,0 +1,914 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
from ctypes import *
|
||||
from random import Random
|
||||
import functools
|
||||
|
||||
random = Random()
|
||||
|
||||
try:
|
||||
import coloredlogs
|
||||
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
||||
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
|
||||
# https://pypi.org/project/coloredlogs/
|
||||
except ImportError as e:
|
||||
coloredlogs = False
|
||||
try:
|
||||
import stem
|
||||
except ImportError as e:
|
||||
stem = False
|
||||
try:
|
||||
import nmap
|
||||
except ImportError as e:
|
||||
nmap = False
|
||||
|
||||
import wrapper
|
||||
from wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS
|
||||
|
||||
from wrapper_tests.support_http import bAreWeConnected
|
||||
from wrapper_tests.support_onions import (is_valid_fingerprint,
|
||||
lIntroductionPoints,
|
||||
oGetStemController,
|
||||
sMapaddressResolv, sTorResolve)
|
||||
|
||||
try:
|
||||
from user_data.settings import get_user_config_path
|
||||
except ImportError:
|
||||
get_user_config_path = None
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
LOG = logging.getLogger()
|
||||
|
||||
def LOG_ERROR(l): print('ERRORc: '+l)
|
||||
def LOG_WARN(l): print('WARNc: ' +l)
|
||||
def LOG_INFO(l): print('INFOc: ' +l)
|
||||
def LOG_DEBUG(l): print('DEBUGc: '+l)
|
||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
||||
|
||||
try:
|
||||
from trepan.api import debug
|
||||
from trepan.interfaces import server as Mserver
|
||||
except:
|
||||
# print('trepan3 TCP server NOT available.')
|
||||
pass
|
||||
else:
|
||||
# print('trepan3 TCP server available.')
|
||||
def trepan_handler(num=None, f=None):
|
||||
connection_opts={'IO': 'TCP', 'PORT': 6666}
|
||||
intf = Mserver.ServerInterface(connection_opts=connection_opts)
|
||||
dbg_opts = { 'interface': intf }
|
||||
print(f'Starting TCP server listening on port 6666.')
|
||||
debug(dbg_opts=dbg_opts)
|
||||
return
|
||||
|
||||
# self._audio_thread.isAlive
|
||||
iTHREAD_TIMEOUT = 1
|
||||
iTHREAD_SLEEP = 1
|
||||
iTHREAD_JOINS = 8
|
||||
iNODES = 6
|
||||
|
||||
lToxSamplerates = [8000, 12000, 16000, 24000, 48000]
|
||||
lToxSampleratesK = [8, 12, 16, 24, 48]
|
||||
lBOOLEANS = [
|
||||
'local_discovery_enabled',
|
||||
'udp_enabled',
|
||||
'ipv6_enabled',
|
||||
'trace_enabled',
|
||||
'compact_mode',
|
||||
'allow_inline',
|
||||
'notifications',
|
||||
'sound_notifications',
|
||||
'calls_sound',
|
||||
'hole_punching_enabled',
|
||||
'dht_announcements_enabled',
|
||||
'save_history',
|
||||
'download_nodes_list'
|
||||
'core_logging',
|
||||
]
|
||||
|
||||
sDIR = os.environ.get('TMPDIR', '/tmp')
|
||||
sTOX_VERSION = "1000002018"
|
||||
bHAVE_NMAP = shutil.which('nmap')
|
||||
bHAVE_JQ = shutil.which('jq')
|
||||
bHAVE_BASH = shutil.which('bash')
|
||||
bHAVE_TORR = shutil.which('tor-resolve')
|
||||
|
||||
lDEAD_BS = [
|
||||
# Failed to resolve "tox3.plastiras.org"
|
||||
"tox3.plastiras.org",
|
||||
'tox.kolka.tech',
|
||||
# IPs that do not reverse resolve
|
||||
'49.12.229.145',
|
||||
"46.101.197.175",
|
||||
'114.35.245.150',
|
||||
'172.93.52.70',
|
||||
'195.123.208.139',
|
||||
'205.185.115.131',
|
||||
# IPs that do not rreverse resolve
|
||||
'yggnode.cf', '188.225.9.167',
|
||||
'85-143-221-42.simplecloud.ru', '85.143.221.42',
|
||||
# IPs that do not ping
|
||||
'104.244.74.69', 'tox.plastiras.org',
|
||||
'195.123.208.139',
|
||||
'gt.sot-te.ch', '32.226.5.82',
|
||||
# suspicious IPs
|
||||
'tox.abilinski.com', '172.103.164.250', '172.103.164.250.tpia.cipherkey.com',
|
||||
]
|
||||
|
||||
|
||||
def assert_main_thread():
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
# this "instance" method is very useful!
|
||||
app_thread = QtWidgets.QApplication.instance().thread()
|
||||
curr_thread = QtCore.QThread.currentThread()
|
||||
if app_thread != curr_thread:
|
||||
raise RuntimeError('attempt to call MainWindow.append_message from non-app thread')
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignoreStdout():
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
old_stdout = os.dup(1)
|
||||
sys.stdout.flush()
|
||||
os.dup2(devnull, 1)
|
||||
os.close(devnull)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.dup2(old_stdout, 1)
|
||||
os.close(old_stdout)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignoreStderr():
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
old_stderr = os.dup(2)
|
||||
sys.stderr.flush()
|
||||
os.dup2(devnull, 2)
|
||||
os.close(devnull)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
os.dup2(old_stderr, 2)
|
||||
os.close(old_stderr)
|
||||
|
||||
def clean_booleans(oArgs):
|
||||
for key in lBOOLEANS:
|
||||
if not hasattr(oArgs, key): continue
|
||||
val = getattr(oArgs, key)
|
||||
if type(val) == bool: continue
|
||||
if val in ['False', 'false', '0']:
|
||||
setattr(oArgs, key, False)
|
||||
else:
|
||||
setattr(oArgs, key, True)
|
||||
|
||||
def on_log(iTox, level, filename, line, func, message, *data):
|
||||
# LOG.debug(repr((level, filename, line, func, message,)))
|
||||
tox_log_cb(level, filename, line, func, message)
|
||||
|
||||
def tox_log_cb(level, filename, line, func, message, *args):
|
||||
"""
|
||||
* @param level The severity of the log message.
|
||||
* @param filename The source file from which the message originated.
|
||||
* @param line The source line from which the message originated.
|
||||
* @param func The function from which the message originated.
|
||||
* @param message The log message.
|
||||
* @param user_data The user data pointer passed to tox_new in options.
|
||||
"""
|
||||
if type(func) == bytes:
|
||||
func = str(func, 'utf-8')
|
||||
message = str(message, 'UTF-8')
|
||||
filename = str(filename, 'UTF-8')
|
||||
|
||||
if filename == 'network.c':
|
||||
if line == 660: return
|
||||
# root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket
|
||||
if line == 944: return
|
||||
i = message.find('07 = GET_NODES')
|
||||
if i > 0:
|
||||
return
|
||||
if filename == 'TCP_common.c': return
|
||||
|
||||
i = message.find(' | ')
|
||||
if i > 0:
|
||||
message = message[:i]
|
||||
# message = filename +'#' +str(line) +':'+func +' '+message
|
||||
|
||||
name = 'core'
|
||||
# old level is meaningless
|
||||
level = 10 # LOG.level
|
||||
|
||||
# LOG._log(LOG.level, f"{level}: {message}", list())
|
||||
|
||||
i = message.find('(0: OK)')
|
||||
if i > 0:
|
||||
level = 10 # LOG.debug
|
||||
else:
|
||||
i = message.find('(1: ')
|
||||
if i > 0:
|
||||
level = 30 # LOG.warn
|
||||
else:
|
||||
level = 20 # LOG.info
|
||||
|
||||
o = LOG.makeRecord(filename, level, func, line, message, list(), None)
|
||||
# LOG.handle(o)
|
||||
LOG_TRACE(f"{level}: {func}{line} {message}")
|
||||
return
|
||||
|
||||
elif level == 1:
|
||||
LOG.critical(f"{level}: {message}")
|
||||
elif level == 2:
|
||||
LOG.error(f"{level}: {message}")
|
||||
elif level == 3:
|
||||
LOG.warn(f"{level}: {message}")
|
||||
elif level == 4:
|
||||
LOG.info(f"{level}: {message}")
|
||||
elif level == 5:
|
||||
LOG.debug(f"{level}: {message}")
|
||||
else:
|
||||
LOG_TRACE(f"{level}: {message}")
|
||||
|
||||
def vAddLoggerCallback(tox_options, callback=None):
|
||||
if callback is None:
|
||||
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
|
||||
tox_options._options_pointer,
|
||||
POINTER(None)())
|
||||
tox_options.self_logger_cb = None
|
||||
return
|
||||
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p)
|
||||
tox_options.self_logger_cb = c_callback(callback)
|
||||
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
|
||||
tox_options._options_pointer,
|
||||
tox_options.self_logger_cb)
|
||||
|
||||
def get_video_indexes():
|
||||
# Linux
|
||||
return [str(l[5:]) for l in os.listdir('/dev/') if l.startswith('video')]
|
||||
|
||||
def get_audio():
|
||||
with ignoreStderr():
|
||||
import pyaudio
|
||||
oPyA = pyaudio.PyAudio()
|
||||
|
||||
input_devices = output_devices = 0
|
||||
for i in range(oPyA.get_device_count()):
|
||||
device = oPyA.get_device_info_by_index(i)
|
||||
if device["maxInputChannels"]:
|
||||
input_devices += 1
|
||||
if device["maxOutputChannels"]:
|
||||
output_devices += 1
|
||||
# {'index': 21, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 64, 'maxOutputChannels': 64, 'defaultLowInputLatency': 0.008707482993197279, 'defaultLowOutputLatency': 0.008707482993197279, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
|
||||
audio = {'input': oPyA.get_default_input_device_info()['index'] if input_devices else -1,
|
||||
'output': oPyA.get_default_output_device_info()['index'] if output_devices else -1,
|
||||
'enabled': input_devices and output_devices}
|
||||
return audio
|
||||
|
||||
def oMainArgparser(_=None, iMode=0):
|
||||
# 'Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0'
|
||||
if not os.path.exists('/proc/sys/net/ipv6'):
|
||||
bIpV6 = 'False'
|
||||
else:
|
||||
bIpV6 = 'True'
|
||||
lIpV6Choices=[bIpV6, 'False']
|
||||
|
||||
sNodesJson = os.path.join(os.environ['HOME'], '.config', 'tox', 'DHTnodes.json')
|
||||
if not os.path.exists(sNodesJson): sNodesJson = ''
|
||||
|
||||
logfile = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'toxygen.log')
|
||||
if not os.path.exists(sNodesJson): logfile = ''
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=True)
|
||||
parser.add_argument('--proxy_host', '--proxy-host', type=str,
|
||||
# oddball - we want to use '' as a setting
|
||||
default='0.0.0.0',
|
||||
help='proxy host')
|
||||
parser.add_argument('--proxy_port', '--proxy-port', default=0, type=int,
|
||||
help='proxy port')
|
||||
parser.add_argument('--proxy_type', '--proxy-type', default=0, type=int,
|
||||
choices=[0,1,2],
|
||||
help='proxy type 1=http, 2=socks')
|
||||
parser.add_argument('--tcp_port', '--tcp-port', default=0, type=int,
|
||||
help='tcp port')
|
||||
parser.add_argument('--udp_enabled', type=str, default='True',
|
||||
choices=['True', 'False'],
|
||||
help='En/Disable udp')
|
||||
parser.add_argument('--ipv6_enabled', type=str, default=bIpV6,
|
||||
choices=lIpV6Choices,
|
||||
help=f"En/Disable ipv6 - default {bIpV6}")
|
||||
parser.add_argument('--trace_enabled',type=str,
|
||||
default='True' if os.environ.get('DEBUG') else 'False',
|
||||
choices=['True','False'],
|
||||
help='Debugging from toxcore logger_trace or env DEBUG=1')
|
||||
parser.add_argument('--download_nodes_list', type=str, default='False',
|
||||
choices=['True', 'False'],
|
||||
help='Download nodes list')
|
||||
parser.add_argument('--nodes_json', type=str,
|
||||
default=sNodesJson)
|
||||
parser.add_argument('--network', type=str,
|
||||
choices=['main', 'local'],
|
||||
default='main')
|
||||
parser.add_argument('--download_nodes_url', type=str,
|
||||
default='https://nodes.tox.chat/json')
|
||||
parser.add_argument('--logfile', default=logfile,
|
||||
help='Filename for logging - start with + for stdout too')
|
||||
parser.add_argument('--loglevel', default=logging.INFO, type=int,
|
||||
# choices=[logging.info,logging.trace,logging.debug,logging.error]
|
||||
help='Threshold for logging (lower is more) default: 20')
|
||||
parser.add_argument('--mode', type=int, default=iMode,
|
||||
choices=[0,1,2],
|
||||
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
|
||||
parser.add_argument('--hole_punching_enabled',type=str,
|
||||
default='False', choices=['True','False'],
|
||||
help='En/Enable hole punching')
|
||||
parser.add_argument('--dht_announcements_enabled',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='En/Disable DHT announcements')
|
||||
return parser
|
||||
|
||||
def vSetupLogging(oArgs):
|
||||
global LOG
|
||||
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
|
||||
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
|
||||
logging._defaultFormatter.default_msec_format = ''
|
||||
|
||||
add = None
|
||||
kwargs = dict(level=oArgs.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
if oArgs.logfile:
|
||||
add = oArgs.logfile.startswith('+')
|
||||
sub = oArgs.logfile.startswith('-')
|
||||
if add or sub:
|
||||
oArgs.logfile = oArgs.logfile[1:]
|
||||
kwargs['filename'] = oArgs.logfile
|
||||
|
||||
if coloredlogs:
|
||||
# https://pypi.org/project/coloredlogs/
|
||||
aKw = dict(level=oArgs.loglevel,
|
||||
logger=LOG,
|
||||
stream=sys.stdout,
|
||||
fmt='%(name)s %(levelname)s %(message)s'
|
||||
)
|
||||
coloredlogs.install(**aKw)
|
||||
if oArgs.logfile:
|
||||
oHandler = logging.FileHandler(oArgs.logfile)
|
||||
LOG.addHandler(oHandler)
|
||||
else:
|
||||
logging.basicConfig(**kwargs)
|
||||
if add:
|
||||
oHandler = logging.StreamHandler(sys.stdout)
|
||||
LOG.addHandler(oHandler)
|
||||
|
||||
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
|
||||
|
||||
|
||||
def setup_logging(oArgs):
|
||||
global LOG
|
||||
if coloredlogs:
|
||||
aKw = dict(level=oArgs.loglevel,
|
||||
logger=LOG,
|
||||
fmt='%(name)s %(levelname)s %(message)s')
|
||||
if oArgs.logfile:
|
||||
oFd = open(oArgs.logfile, 'wt')
|
||||
setattr(oArgs, 'log_oFd', oFd)
|
||||
aKw['stream'] = oFd
|
||||
coloredlogs.install(**aKw)
|
||||
if oArgs.logfile:
|
||||
oHandler = logging.StreamHandler(stream=sys.stdout)
|
||||
LOG.addHandler(oHandler)
|
||||
else:
|
||||
aKw = dict(level=oArgs.loglevel,
|
||||
format='%(name)s %(levelname)-4s %(message)s')
|
||||
if oArgs.logfile:
|
||||
aKw['filename'] = oArgs.logfile
|
||||
logging.basicConfig(**aKw)
|
||||
|
||||
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
|
||||
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
|
||||
logging._defaultFormatter.default_msec_format = ''
|
||||
|
||||
LOG.setLevel(oArgs.loglevel)
|
||||
# LOG.trace = lambda l: LOG.log(0, repr(l))
|
||||
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
|
||||
|
||||
def signal_handler(num, f):
|
||||
from trepan.api import debug
|
||||
from trepan.interfaces import server as Mserver
|
||||
connection_opts={'IO': 'TCP', 'PORT': 6666}
|
||||
intf = Mserver.ServerInterface(connection_opts=connection_opts)
|
||||
dbg_opts = {'interface': intf}
|
||||
LOG.info('Starting TCP server listening on port 6666.')
|
||||
debug(dbg_opts=dbg_opts)
|
||||
return
|
||||
|
||||
def merge_args_into_settings(args, settings):
|
||||
if args:
|
||||
if not hasattr(args, 'audio'):
|
||||
LOG.warn('No audio ' +repr(args))
|
||||
settings['audio'] = getattr(args, 'audio')
|
||||
if not hasattr(args, 'video'):
|
||||
LOG.warn('No video ' +repr(args))
|
||||
settings['video'] = getattr(args, 'video')
|
||||
for key in settings.keys():
|
||||
# proxy_type proxy_port proxy_host
|
||||
not_key = 'not_' +key
|
||||
if hasattr(args, key):
|
||||
val = getattr(args, key)
|
||||
if type(val) == bytes:
|
||||
# proxy_host - ascii?
|
||||
# filenames - ascii?
|
||||
val = str(val, 'UTF-8')
|
||||
settings[key] = val
|
||||
elif hasattr(args, not_key):
|
||||
val = not getattr(args, not_key)
|
||||
settings[key] = val
|
||||
clean_settings(settings)
|
||||
return
|
||||
|
||||
def clean_settings(self):
|
||||
# failsafe to ensure C tox is bytes and Py settings is str
|
||||
|
||||
# overrides
|
||||
self['mirror_mode'] = False
|
||||
# REQUIRED!!
|
||||
if not os.path.exists('/proc/sys/net/ipv6'):
|
||||
LOG.warn('Disabling IPV6 because /proc/sys/net/ipv6 does not exist')
|
||||
self['ipv6_enabled'] = False
|
||||
|
||||
if 'proxy_type' in self and self['proxy_type'] == 0:
|
||||
self['proxy_host'] = ''
|
||||
self['proxy_port'] = 0
|
||||
|
||||
if 'proxy_type' in self and self['proxy_type'] != 0 and \
|
||||
'proxy_host' in self and self['proxy_host'] != '' and \
|
||||
'proxy_port' in self and self['proxy_port'] != 0:
|
||||
if 'udp_enabled' in self and self['udp_enabled']:
|
||||
# We don't currently support UDP over proxy.
|
||||
LOG.info("UDP enabled and proxy set: disabling UDP")
|
||||
self['udp_enabled'] = False
|
||||
if 'local_discovery_enabled' in self and self['local_discovery_enabled']:
|
||||
LOG.info("local_discovery_enabled enabled and proxy set: disabling local_discovery_enabled")
|
||||
self['local_discovery_enabled'] = False
|
||||
if 'dht_announcements_enabled' in self and self['dht_announcements_enabled']:
|
||||
LOG.info("dht_announcements_enabled enabled and proxy set: disabling dht_announcements_enabled")
|
||||
self['dht_announcements_enabled'] = False
|
||||
|
||||
if 'auto_accept_path' in self and \
|
||||
type(self['auto_accept_path']) == bytes:
|
||||
self['auto_accept_path'] = str(self['auto_accept_path'], 'UTF-8')
|
||||
|
||||
LOG.debug("Cleaned settings")
|
||||
|
||||
def lSdSamplerates(iDev):
|
||||
try:
|
||||
import sounddevice as sd
|
||||
except ImportError:
|
||||
return []
|
||||
samplerates = (32000, 44100, 48000, 96000, )
|
||||
device = iDev
|
||||
supported_samplerates = []
|
||||
for fs in samplerates:
|
||||
try:
|
||||
sd.check_output_settings(device=device, samplerate=fs)
|
||||
except Exception as e:
|
||||
# LOG.debug(f"Sample rate not supported {fs}" +' '+str(e))
|
||||
pass
|
||||
else:
|
||||
supported_samplerates.append(fs)
|
||||
return supported_samplerates
|
||||
|
||||
def _get_nodes_path(oArgs=None):
|
||||
if oArgs and oArgs.nodes_json and os.path.isfile(oArgs.nodes_json):
|
||||
LOG.debug("_get_nodes_path: " +oArgs.nodes_json)
|
||||
default = oArgs.nodes_json
|
||||
elif get_user_config_path:
|
||||
default = os.path.join(get_user_config_path(), 'toxygen_nodes.json')
|
||||
else:
|
||||
# Windwoes
|
||||
default = os.path.join(os.getenv('HOME'), '.config', 'tox', 'toxygen_nodes.json')
|
||||
LOG.debug("_get_nodes_path: " +default)
|
||||
return default
|
||||
|
||||
DEFAULT_NODES_COUNT = 8
|
||||
|
||||
global aNODES
|
||||
aNODES = {}
|
||||
|
||||
|
||||
# @functools.lru_cache(maxsize=12) TypeError: unhashable type: 'Namespace'
|
||||
def generate_nodes(oArgs=None,
|
||||
nodes_count=DEFAULT_NODES_COUNT,
|
||||
ipv='ipv4',
|
||||
udp_not_tcp=True):
|
||||
global aNODES
|
||||
sKey = ipv
|
||||
sKey += ',0' if udp_not_tcp else ',1'
|
||||
if sKey in aNODES and aNODES[sKey]:
|
||||
return aNODES[sKey]
|
||||
sFile = _get_nodes_path(oArgs=oArgs)
|
||||
assert os.path.exists(sFile), sFile
|
||||
lNodes = generate_nodes_from_file(sFile,
|
||||
nodes_count=nodes_count,
|
||||
ipv=ipv, udp_not_tcp=udp_not_tcp)
|
||||
assert lNodes
|
||||
aNODES[sKey] = lNodes
|
||||
return aNODES[sKey]
|
||||
|
||||
aNODES_CACHE = {}
|
||||
def generate_nodes_from_file(sFile,
|
||||
nodes_count=DEFAULT_NODES_COUNT,
|
||||
ipv='ipv4',
|
||||
udp_not_tcp=True,
|
||||
):
|
||||
"""https://github.com/TokTok/c-toxcore/issues/469
|
||||
I had a conversation with @irungentoo on IRC about whether we really need to call tox_bootstrap() when having UDP disabled and why. The answer is yes, because in addition to TCP relays (tox_add_tcp_relay()), toxcore also needs to know addresses of UDP onion nodes in order to work correctly. The DHT, however, is not used when UDP is disabled. tox_bootstrap() function resolves the address passed to it as argument and calls onion_add_bs_node_path() and DHT_bootstrap() functions. Although calling DHT_bootstrap() is not really necessary as DHT is not used, we still need to resolve the address of the DHT node in order to populate the onion routes with onion_add_bs_node_path() call.
|
||||
"""
|
||||
global aNODES_CACHE
|
||||
|
||||
key = ipv
|
||||
key += ',0' if udp_not_tcp else ',1'
|
||||
if key in aNODES_CACHE:
|
||||
sorted_nodes = aNODES_CACHE[key]
|
||||
else:
|
||||
if not os.path.exists(sFile):
|
||||
LOG.error("generate_nodes_from_file file not found " +sFile)
|
||||
return []
|
||||
try:
|
||||
with open(sFile, 'rt') as fl:
|
||||
json_nodes = json.loads(fl.read())['nodes']
|
||||
except Exception as e:
|
||||
LOG.error(f"generate_nodes_from_file error {sFile}\n{e}")
|
||||
return []
|
||||
else:
|
||||
LOG.debug("generate_nodes_from_file " +sFile)
|
||||
|
||||
if udp_not_tcp:
|
||||
nodes = [(node[ipv], node['port'], node['public_key'],) for
|
||||
node in json_nodes if node[ipv] != 'NONE' \
|
||||
and node["status_udp"] in [True, "true"]
|
||||
]
|
||||
else:
|
||||
nodes = []
|
||||
elts = [(node[ipv], node['tcp_ports'], node['public_key'],) \
|
||||
for node in json_nodes if node[ipv] != 'NONE' \
|
||||
and node["status_tcp"] in [True, "true"]
|
||||
]
|
||||
for (ipv, ports, public_key,) in elts:
|
||||
for port in ports:
|
||||
nodes += [(ipv, port, public_key)]
|
||||
if not nodes:
|
||||
LOG.warn(f'empty generate_nodes from {sFile} {json_nodes!r}')
|
||||
return []
|
||||
sorted_nodes = nodes
|
||||
aNODES_CACHE[key] = sorted_nodes
|
||||
|
||||
random.shuffle(sorted_nodes)
|
||||
if nodes_count is not None and len(sorted_nodes) > nodes_count:
|
||||
sorted_nodes = sorted_nodes[-nodes_count:]
|
||||
LOG.debug(f"generate_nodes_from_file {sFile} len={len(sorted_nodes)}")
|
||||
return sorted_nodes
|
||||
|
||||
def tox_bootstrapd_port():
|
||||
port = 33446
|
||||
sFile = '/etc/tox-bootstrapd.conf'
|
||||
if os.path.exists(sFile):
|
||||
with open(sFile, 'rt') as oFd:
|
||||
for line in oFd.readlines():
|
||||
if line.startswith('port = '):
|
||||
port = int(line[7:])
|
||||
return port
|
||||
|
||||
def bootstrap_local(elts, lToxes, oArgs=None):
|
||||
if os.path.exists('/run/tox-bootstrapd/tox-bootstrapd.pid'):
|
||||
LOG.debug('/run/tox-bootstrapd/tox-bootstrapd.pid')
|
||||
iRet = True
|
||||
else:
|
||||
iRet = os.system("netstat -nle4|grep -q :33")
|
||||
if iRet > 0:
|
||||
LOG.warn(f'bootstraping local No local DHT running')
|
||||
LOG.info(f'bootstraping local')
|
||||
return bootstrap_udp(elts, lToxes, oArgs)
|
||||
|
||||
def lDNSClean(l):
|
||||
global lDEAD_BS
|
||||
# list(set(l).difference(set(lDEAD_BS)))
|
||||
return [elt for elt in l if elt not in lDEAD_BS]
|
||||
|
||||
def lExitExcluder(oArgs, iPort=9051):
|
||||
"""
|
||||
https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py
|
||||
"""
|
||||
if not stem:
|
||||
LOG.warn('please install the stem Python package')
|
||||
return ''
|
||||
LOG.debug('lExcludeExitNodes')
|
||||
|
||||
try:
|
||||
controller = oGetStemController(log_level=10)
|
||||
# generator
|
||||
relays = controller.get_server_descriptors()
|
||||
except Exception as e:
|
||||
LOG.error(f'Failed to get relay descriptors {e}')
|
||||
return None
|
||||
|
||||
if controller.is_set('ExcludeExitNodes'):
|
||||
LOG.info('ExcludeExitNodes is in use already.')
|
||||
return None
|
||||
|
||||
exit_excludelist=[]
|
||||
LOG.debug("Excluded exit relays:")
|
||||
for relay in relays:
|
||||
if relay.exit_policy.is_exiting_allowed() and not relay.contact:
|
||||
if is_valid_fingerprint(relay.fingerprint):
|
||||
exit_excludelist.append(relay.fingerprint)
|
||||
LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint)
|
||||
else:
|
||||
LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint)
|
||||
|
||||
try:
|
||||
controller.set_conf('ExcludeExitNodes', exit_excludelist)
|
||||
LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist))
|
||||
except Exception as e:
|
||||
LOG.exception('ExcludeExitNodes ' +str(e))
|
||||
return exit_excludelist
|
||||
|
||||
aHOSTS = {}
|
||||
@functools.lru_cache(maxsize=20)
|
||||
def sDNSLookup(host):
|
||||
global aHOSTS
|
||||
ipv = 0
|
||||
if host in lDEAD_BS:
|
||||
# LOG.warn(f"address skipped because in lDEAD_BS {host}")
|
||||
return ''
|
||||
if host in aHOSTS:
|
||||
return aHOSTS[host]
|
||||
|
||||
try:
|
||||
s = host.replace('.','')
|
||||
int(s)
|
||||
ipv = 4
|
||||
except:
|
||||
try:
|
||||
s = host.replace(':','')
|
||||
int(s)
|
||||
ipv = 6
|
||||
except: pass
|
||||
|
||||
if ipv > 0:
|
||||
# LOG.debug(f"v={ipv} IP address {host}")
|
||||
return host
|
||||
|
||||
LOG.debug(f"sDNSLookup {host}")
|
||||
ip = ''
|
||||
if host.endswith('.tox') or host.endswith('.onion'):
|
||||
if False and stem:
|
||||
ip = sMapaddressResolv(host)
|
||||
if ip: return ip
|
||||
|
||||
ip = sTorResolve(host)
|
||||
if ip: return ip
|
||||
|
||||
if not bHAVE_TORR:
|
||||
LOG.warn(f"onion address skipped because no tor-resolve {host}")
|
||||
return ''
|
||||
try:
|
||||
sout = f"/tmp/TR{os.getpid()}.log"
|
||||
i = os.system(f"tor-resolve -4 {host} > {sout}")
|
||||
if not i:
|
||||
LOG.warn(f"onion address skipped because tor-resolve on {host}")
|
||||
return ''
|
||||
ip = open(sout, 'rt').read()
|
||||
if ip.endswith('failed.'):
|
||||
LOG.warn(f"onion address skipped because tor-resolve failed on {host}")
|
||||
return ''
|
||||
LOG.debug(f"onion address tor-resolve {ip} on {host}")
|
||||
return ip
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
ip = socket.gethostbyname(host)
|
||||
LOG.debug(f"host={host} gethostbyname IP address {ip}")
|
||||
if ip:
|
||||
aHOSTS[host] = ip
|
||||
return ip
|
||||
# drop through
|
||||
except:
|
||||
# drop through
|
||||
pass
|
||||
|
||||
if ip == '':
|
||||
try:
|
||||
sout = f"/tmp/TR{os.getpid()}.log"
|
||||
i = os.system(f"dig {host} +timeout=15|grep ^{host}|sed -e 's/.* //'> {sout}")
|
||||
if not i:
|
||||
LOG.warn(f"address skipped because dig failed on {host}")
|
||||
return ''
|
||||
ip = open(sout, 'rt').read().strip()
|
||||
LOG.debug(f"address dig {ip} on {host}")
|
||||
aHOSTS[host] = ip
|
||||
return ip
|
||||
except:
|
||||
ip = host
|
||||
LOG.debug(f'sDNSLookup {host} -> {ip}')
|
||||
if ip and ip != host:
|
||||
aHOSTS[host] = ip
|
||||
return ip
|
||||
|
||||
def bootstrap_udp(lelts, lToxes, oArgs=None):
|
||||
lelts = lDNSClean(lelts)
|
||||
socket.setdefaulttimeout(15.0)
|
||||
for oTox in lToxes:
|
||||
random.shuffle(lelts)
|
||||
if hasattr(oTox, 'oArgs'):
|
||||
oArgs = oTox.oArgs
|
||||
if hasattr(oArgs, 'contents') and oArgs.contents.proxy_type != 0:
|
||||
lelts = lelts[:1]
|
||||
|
||||
# LOG.debug(f'bootstrap_udp DHT bootstraping {oTox.name} {len(lelts)}')
|
||||
for largs in lelts:
|
||||
assert len(largs) == 3
|
||||
host, port, key = largs
|
||||
assert host; assert port; assert key
|
||||
if host in lDEAD_BS: continue
|
||||
ip = sDNSLookup(host)
|
||||
if not ip:
|
||||
LOG.warn(f'bootstrap_udp to host={host} port={port} did not resolve ip={ip}')
|
||||
continue
|
||||
|
||||
if type(port) == str:
|
||||
port = int(port)
|
||||
try:
|
||||
assert len(key) == 64, key
|
||||
# NOT ip
|
||||
oRet = oTox.bootstrap(host,
|
||||
port,
|
||||
key)
|
||||
except Exception as e:
|
||||
if oArgs is None or (
|
||||
hasattr(oArgs, 'contents') and oArgs.contents.proxy_type == 0):
|
||||
pass
|
||||
# LOG.error(f'bootstrap_udp failed to host={host} port={port} {e}')
|
||||
continue
|
||||
if not oRet:
|
||||
LOG.warn(f'bootstrap_udp failed to {host} : {oRet}')
|
||||
elif oTox.self_get_connection_status() != TOX_CONNECTION['NONE']:
|
||||
LOG.info(f'bootstrap_udp to {host} connected')
|
||||
break
|
||||
else:
|
||||
# LOG.debug(f'bootstrap_udp to {host} not connected')
|
||||
pass
|
||||
|
||||
def bootstrap_tcp(lelts, lToxes, oArgs=None):
|
||||
lelts = lDNSClean(lelts)
|
||||
for oTox in lToxes:
|
||||
if hasattr(oTox, 'oArgs'): oArgs = oTox.oArgs
|
||||
random.shuffle(lelts)
|
||||
# LOG.debug(f'bootstrap_tcp bootstapping {oTox.name} {len(lelts)}')
|
||||
for (host, port, key,) in lelts:
|
||||
assert host; assert port;assert key
|
||||
if host in lDEAD_BS: continue
|
||||
ip = sDNSLookup(host)
|
||||
if not ip:
|
||||
LOG.warn(f'bootstrap_tcp to {host} did not resolve ip={ip}')
|
||||
# continue
|
||||
ip = host
|
||||
if host.endswith('.onion') and stem:
|
||||
l = lIntroductionPoints(host)
|
||||
if not l:
|
||||
LOG.warn(f'bootstrap_tcp to {host} has no introduction points')
|
||||
continue
|
||||
if type(port) == str:
|
||||
port = int(port)
|
||||
try:
|
||||
assert len(key) == 64, key
|
||||
oRet = oTox.add_tcp_relay(ip,
|
||||
port,
|
||||
key)
|
||||
except Exception as e:
|
||||
LOG.error(f'bootstrap_tcp to {host} : ' +str(e))
|
||||
continue
|
||||
if not oRet:
|
||||
LOG.warn(f'bootstrap_tcp failed to {host} : {oRet}')
|
||||
elif oTox.mycon_time == 1:
|
||||
LOG.info(f'bootstrap_tcp to {host} not yet connected last=1')
|
||||
elif oTox.mycon_status is False:
|
||||
LOG.info(f'bootstrap_tcp to {host} not True' \
|
||||
+f" last={int(oTox.mycon_time)}" )
|
||||
elif oTox.self_get_connection_status() != TOX_CONNECTION['NONE']:
|
||||
LOG.info(f'bootstrap_tcp to {host} connected' \
|
||||
+f" last={int(oTox.mycon_time)}" )
|
||||
break
|
||||
else:
|
||||
LOG.debug(f'bootstrap_tcp to {host} but not connected' \
|
||||
+f" last={int(oTox.mycon_time)}" )
|
||||
pass
|
||||
|
||||
def iNmapInfoNmap(sProt, sHost, sPort, key=None, environ=None, cmd=''):
|
||||
if sHost in ['-', 'NONE']: return 0
|
||||
if not nmap: return 0
|
||||
nmps = nmap.PortScanner
|
||||
if sProt in ['socks', 'socks5', 'tcp4']:
|
||||
prot = 'tcp'
|
||||
cmd = f" -Pn -n -sT -p T:{sPort}"
|
||||
else:
|
||||
prot = 'udp'
|
||||
cmd = f" -Pn -n -sU -p U:{sPort}"
|
||||
LOG.debug(f"iNmapInfoNmap cmd={cmd}")
|
||||
sys.stdout.flush()
|
||||
o = nmps().scan(hosts=sHost, arguments=cmd)
|
||||
aScan = o['scan']
|
||||
ip = list(aScan.keys())[0]
|
||||
state = aScan[ip][prot][sPort]['state']
|
||||
LOG.info(f"iNmapInfoNmap: to {sHost} {state}")
|
||||
return 0
|
||||
|
||||
def iNmapInfo(sProt, sHost, sPort, key=None, environ=None, cmd='nmap'):
|
||||
if sHost in ['-', 'NONE']: return 0
|
||||
sFile = os.path.join("/tmp", f"{sHost}.{os.getpid()}.nmap")
|
||||
if sProt in ['socks', 'socks5', 'tcp4']:
|
||||
cmd += f" -Pn -n -sT -p T:{sPort} {sHost} | grep /tcp "
|
||||
else:
|
||||
cmd += f" -Pn -n -sU -p U:{sPort} {sHost} | grep /udp "
|
||||
LOG.debug(f"iNmapInfo cmd={cmd}")
|
||||
sys.stdout.flush()
|
||||
iRet = os.system('sudo ' +cmd +f" >{sFile} 2>&1 ")
|
||||
LOG.debug(f"iNmapInfo cmd={cmd} iRet={iRet}")
|
||||
if iRet != 0:
|
||||
return iRet
|
||||
assert os.path.exists(sFile), sFile
|
||||
with open(sFile, 'rt') as oFd:
|
||||
l = oFd.readlines()
|
||||
assert len(l)
|
||||
l = [line for line in l if line and not line.startswith('WARNING:')]
|
||||
s = '\n'.join([s.strip() for s in l])
|
||||
LOG.info(f"iNmapInfo: to {sHost}\n{s}")
|
||||
return 0
|
||||
|
||||
def bootstrap_iNmapInfo(lElts, oArgs, protocol="tcp4", bIS_LOCAL=False, iNODES=iNODES, cmd='nmap'):
|
||||
if not bIS_LOCAL and not bAreWeConnected():
|
||||
LOG.warn(f"bootstrap_iNmapInfo not local and NOT CONNECTED")
|
||||
return True
|
||||
if os.environ['USER'] != 'root':
|
||||
LOG.warn(f"bootstrap_iNmapInfo not ROOT")
|
||||
return True
|
||||
|
||||
lRetval = []
|
||||
for elts in lElts[:iNODES]:
|
||||
host, port, key = elts
|
||||
ip = sDNSLookup(host)
|
||||
if not ip:
|
||||
LOG.info('bootstrap_iNmapInfo to {host} did not resolve ip={ip}')
|
||||
continue
|
||||
if type(port) == str:
|
||||
port = int(port)
|
||||
iRet = -1
|
||||
try:
|
||||
if not nmap:
|
||||
iRet = iNmapInfo(protocol, ip, port, key, cmd=cmd)
|
||||
else:
|
||||
iRet = iNmapInfoNmap(protocol, ip, port, key)
|
||||
if iRet != 0:
|
||||
LOG.warn('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
|
||||
lRetval += [False]
|
||||
else:
|
||||
LOG.debug('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
|
||||
lRetval += [True]
|
||||
except Exception as e:
|
||||
LOG.exception('iNmapInfo to {host} : ' +str(e)
|
||||
)
|
||||
lRetval += [False]
|
||||
return any(lRetval)
|
||||
|
||||
def caseFactory(cases):
|
||||
"""We want the tests run in order."""
|
||||
if len(cases) > 1:
|
||||
ordered_cases = sorted(cases, key=lambda f: inspect.findsource(f)[1])
|
||||
else:
|
||||
ordered_cases = cases
|
||||
return ordered_cases
|
||||
|
||||
def suiteFactory(*testcases):
|
||||
"""We want the tests run in order."""
|
||||
linen = lambda f: getattr(tc, f).__code__.co_firstlineno
|
||||
lncmp = lambda a, b: linen(a) - linen(b)
|
||||
|
||||
test_suite = unittest.TestSuite()
|
||||
for tc in testcases:
|
||||
test_suite.addTest(unittest.makeSuite(tc, sortUsing=lncmp))
|
||||
return test_suite
|
1885
toxygen/wrapper_tests/tests_wrapper.py
Normal file
1885
toxygen/wrapper_tests/tests_wrapper.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue