simple updates

This commit is contained in:
emdee 2023-12-10 02:39:58 +00:00
parent dcde8e3d1e
commit 4e77ddc2de
97 changed files with 16121 additions and 282 deletions

3
.gitignore vendored
View file

@ -25,4 +25,5 @@ Toxygen.egg-info
*.tox
.cache
*.db
*~
Makefile

0
.rsync Normal file
View file

130
_Bugs/segv.err Normal file
View 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)]

View 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
View 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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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():

View file

@ -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):

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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):
"""

View file

@ -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():

View file

@ -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

View file

@ -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):
"""

View file

@ -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):

View file

@ -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)

View file

@ -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():

View file

@ -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

View file

@ -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}")

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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,

View file

@ -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):

View file

@ -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,

View file

@ -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
View 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
View 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
View 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)

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

31
toxygen/plugins/en_GB.ts Normal file
View 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
View 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>

View 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
View 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

View file

@ -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

Binary file not shown.

32
toxygen/plugins/ru_RU.ts Normal file
View 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
View 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
View 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)

View 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
View 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
View 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!

View file

View 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

View 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
View 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]))

View 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
View 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()

View file

@ -0,0 +1 @@
https://github.com/akheron/cpython/raw/master/Lib/test/test_gdb.py

1885
toxygen/tests/tests_socks.py Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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 )

View file

@ -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

View file

@ -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,

View file

@ -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()

View file

@ -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)

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View file

@ -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:

View file

@ -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()

View file

View 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]))

View 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 ''

View 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)

View 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

File diff suppressed because it is too large Load diff