2022-10-26 10:44:57 +02:00
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
2013-12-01 16:09:54 +01:00
import sys
2022-10-24 00:11:12 +02:00
import os
2013-12-01 16:09:54 +01:00
import socket
import select
import re
2014-02-20 12:38:20 +01:00
import pickle
2022-10-24 00:11:12 +02:00
import logging
import ctypes
2013-12-01 16:09:54 +01:00
from time import sleep
2014-04-06 02:16:18 +02:00
from threading import Thread
2022-10-24 00:11:12 +02:00
from random import shuffle
2022-10-26 10:44:57 +02:00
from OpenSSL import SSL
2013-12-01 16:09:54 +01:00
2022-10-24 00:11:12 +02:00
import wrapper
from wrapper . tox import Tox
from wrapper . toxav import ToxAV
import wrapper . toxcore_enums_and_consts as enums
from wrapper . toxcore_enums_and_consts import \
TOX_CONNECTION , TOX_USER_STATUS , TOX_MESSAGE_TYPE , \
TOX_SECRET_KEY_SIZE , TOX_FILE_CONTROL , TOX_ADDRESS_SIZE , \
TOX_GROUP_PRIVACY_STATE , TOX_GROUP_ROLE
2022-10-26 10:44:57 +02:00
from wrapper_tests import socks
2022-10-24 00:11:12 +02:00
try :
import support_testing as ts
except ImportError :
import wrapper_tests . support_testing as ts
2022-10-26 10:44:57 +02:00
import wrapper . toxencryptsave as tox_encrypt_save
2013-12-01 16:09:54 +01:00
2022-10-24 00:11:12 +02:00
global LOG
LOG = logging . getLogger ( ' app. ' + ' ts ' )
2013-12-01 16:09:54 +01:00
2014-04-09 22:48:24 +02:00
2022-10-26 10:44:57 +02:00
NAME = ' SyniTox '
# possible CA locations picks the first one
lCAs = [ ' /etc/ssl/cacert.pem ' ]
2014-04-06 02:16:18 +02:00
2022-10-24 00:11:12 +02:00
bot_toxname = ' SyniTox '
2022-10-26 10:44:57 +02:00
# tox.py can be called by callbacks
def LOG_ERROR ( a ) : print ( ' EROR> ' + a )
def LOG_WARN ( a ) : print ( ' WARN> ' + a )
def LOG_INFO ( a ) :
bVERBOSE = hasattr ( __builtins__ , ' app ' ) and app . oArgs . loglevel < = 20
if bVERBOSE : print ( ' INFO> ' + a )
def LOG_DEBUG ( a ) :
bVERBOSE = hasattr ( __builtins__ , ' app ' ) and app . oArgs . loglevel < = 10 - 1
if bVERBOSE : print ( ' DBUG> ' + a )
def LOG_TRACE ( a ) :
bVERBOSE = hasattr ( __builtins__ , ' app ' ) and app . oArgs . loglevel < 10
if bVERBOSE : print ( ' TRAC> ' + a )
# https://wiki.python.org/moin/SSL
def ssl_verify_cb ( HOST ) :
# wrapps host
def ssl_verify ( * args ) :
"""
callback for certificate validation
should return true if verification passes and false otherwise
"""
LOG . debug ( f " ssl_verify { len ( args ) } { args } " )
ssl_conn , x509 , error_num , depth , return_code = args
if error_num != 0 :
return False
if depth != 0 :
# don't validate names of root certificates
return True
if x509 . get_subject ( ) . commonName == HOST :
return True
LOG . warn ( f " ssl_verify { x509 . get_subject ( ) . commonName } { HOST } " )
# allow matching subdomains
have , want = x509 . get_subject ( ) . commonName , HOST
if len ( have . split ( ' . ' ) ) == len ( want . split ( ' . ' ) ) and len ( want . split ( ' . ' ) ) > 2 :
if have . split ( ' . ' ) [ 1 : ] == want . split ( ' . ' ) [ 1 : ] :
return True
return False
return ssl_verify
2022-10-24 00:11:12 +02:00
class SyniTox ( Tox ) :
2022-10-26 10:44:57 +02:00
def __init__ ( self ,
oArgs ,
oOpts ,
2022-10-24 00:11:12 +02:00
GROUP_BOT_PK = ' ' ,
sMEMORY_DB = ' '
) :
2022-10-26 10:44:57 +02:00
opts = oTOX_OPTIONS
2022-10-24 00:11:12 +02:00
self . _opts = opts
2022-10-26 10:44:57 +02:00
self . _oArgs = oArgs
# self._oArgs.profile
self . load_profile ( self . _opts , self . _oArgs , self . _oArgs . password )
Tox . __init__ ( self , tox_options = self . _opts )
self . _address = self . self_get_address ( )
2022-10-24 00:11:12 +02:00
self . _app = None
self . _settings = { }
self . av = self . AV
self . irc = None
self . bid = - 1
self . _bRouted = None
2022-10-26 10:44:57 +02:00
self . _ssl_context = None
self . _irc_id = ' '
self . _toxes = None
self . joined = None
self . request = None
self . memory = { }
self . readbuffer = b ' '
#? tox_group_id
self . _peers = [ ]
self . _groups = { }
2022-10-24 00:11:12 +02:00
2022-10-26 10:44:57 +02:00
self . sMEMORY_DB = sMEMORY_DB
self . sGROUP_BOT_PK = GROUP_BOT_PK
self . sGROUP_BOT_NUM = - 1
def load_profile ( self , tox_options , oArgs , password = ' ' ) :
if oArgs . profile and os . path . exists ( oArgs . profile ) :
data = open ( oArgs . profile , ' rb ' ) . read ( )
else :
data = None
if data and self . has_password ( ) :
data = self . pass_decrypt ( data )
if data : # load existing profile
tox_options . contents . savedata_type = enums . TOX_SAVEDATA_TYPE [ ' TOX_SAVE ' ]
tox_options . contents . savedata_data = ctypes . 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
def _save_profile ( self , data = None ) :
LOG . debug ( " _save_profile " )
data = data or self . get_savedata ( )
if self . has_password ( ) :
data = self . pass_encrypt ( data )
try :
suf = f " { os . getpid ( ) } "
with open ( self . _oArgs . profile + suf , ' wb ' ) as fl :
fl . write ( data )
stat = os . stat ( self . _oArgs . profile + suf )
if hasattr ( stat , ' st_blocks ' ) :
assert stat . st_blocks > 0 , f " Zero length file { self . _oArgs . profile + suf } "
os . rename ( self . _oArgs . profile + suf , self . _oArgs . profile )
LOG . info ( ' Profile saved successfully to ' + self . _oArgs . profile )
except Exception as e :
LOG . warn ( f " Profile save failed to { self . _oArgs . profile } \n { e } " )
2022-10-24 00:11:12 +02:00
def start ( self ) :
2022-10-26 10:44:57 +02:00
self . _tox = self
self . _toxes = tox_encrypt_save . ToxEncryptSave ( )
self . self_set_name ( self . _oArgs . bot_name )
2022-10-24 00:11:12 +02:00
self . self_set_status_message ( " Send me a message with the word ' invite ' " )
LOG . info ( ' Our ToxID: %s ' % self . self_get_toxid ( ) )
2013-12-01 16:09:54 +01:00
self . tox_group_id = None
2022-10-26 10:44:57 +02:00
self . init_callbacks ( )
2022-10-24 00:11:12 +02:00
if os . path . exists ( self . sMEMORY_DB ) :
with open ( self . sMEMORY_DB , ' r ' ) as f :
2014-02-20 12:38:20 +01:00
self . memory = pickle . load ( f )
2022-10-26 10:44:57 +02:00
if self . _oArgs . irc_ssl != ' ' :
self . start_ssl ( self . _oArgs . irc_host )
def start_ssl ( self , HOST ) :
if not self . _ssl_context :
# TLSv1_3_METHOD does not exist
context = SSL . Context ( SSL . TLSv1_2_METHOD )
context . set_options ( SSL . OP_NO_SSLv2 | SSL . OP_NO_SSLv3 | SSL . OP_NO_TLSv1 )
if self . _oArgs . irc_pem :
val = SSL . VERIFY_PEER | SSL . VERIFY_FAIL_IF_NO_PEER_CERT
LOG . info ( ' Using keyfile: %s ' % self . _oArgs . irc_pem )
context . use_privatekey_file ( self . _oArgs . irc_pem )
else :
val = SSL . VERIFY_PEER
context . set_verify ( val , ssl_verify_cb ( self . _oArgs . irc_host ) )
assert os . path . exists ( self . _oArgs . irc_ca ) , self . _oArgs . irc_ca
if os . path . isdir ( self . _oArgs . irc_ca ) :
context . load_verify_locations ( capath = self . _oArgs . irc_ca )
else :
context . load_verify_locations ( cafile = self . _oArgs . irc_ca )
if self . _oArgs . irc_ssl == ' tls1.2 ' :
context . set_min_proto_version ( SSL . TLS1_2_VERSION )
elif self . _oArgs . irc_ssl == ' tls1.3 ' :
context . set_min_proto_version ( SSL . TLS1_3_VERSION )
self . _ssl_context = context
return self . _ssl_context
2022-10-24 00:11:12 +02:00
def bRouted ( self ) :
2022-10-26 10:44:57 +02:00
if self . _oArgs . network in [ ' local ' ] :
return True
b = ts . bAreWeConnected ( )
if b is None :
i = os . system ( ' ip route|grep ^def ' )
if i > 0 :
b = False
else :
b = True
self . _bRouted = b
return b
2022-10-24 00:11:12 +02:00
def test_net ( self , lElts = None , oThread = None , iMax = 4 ) :
2022-10-26 10:44:57 +02:00
LOG . debug ( " test_net network= " + self . _oArgs . network )
2022-10-24 00:11:12 +02:00
# bootstrap
lNodes = ts . generate_nodes ( oArgs = self . _oArgs ,
ipv = ' ipv4 ' ,
udp_not_tcp = True )
2022-10-26 10:44:57 +02:00
self . _settings [ ' current_nodes_udp ' ] = ts . sDNSClean ( lNodes )
2022-10-24 00:11:12 +02:00
if not lNodes :
LOG . warn ( ' empty generate_nodes udp ' )
else :
2022-10-26 10:44:57 +02:00
LOG . info ( f ' Called generate_nodes: udp { len ( lNodes ) } ' )
2022-10-24 00:11:12 +02:00
lNodes = ts . generate_nodes ( oArgs = self . _oArgs ,
ipv = ' ipv4 ' ,
udp_not_tcp = False )
2022-10-26 10:44:57 +02:00
self . _settings [ ' current_nodes_tcp ' ] = ts . sDNSClean ( lNodes )
2022-10-24 00:11:12 +02:00
if not lNodes :
LOG . warn ( ' empty generate_nodes tcp ' )
else :
2022-10-26 10:44:57 +02:00
LOG . info ( f ' Called generate_nodes: tcp { len ( lNodes ) } ' )
2022-10-24 00:11:12 +02:00
# if oThread and oThread._stop_thread: return
return True
2022-10-26 10:44:57 +02:00
def add_friend ( self , pk ) :
self . friend_add_norequest ( pk )
assert self . friend_exists ( pk )
assert pk in self . self_get_friend_list ( )
friend_number = self . friend_by_public_key ( pk )
return friend_number
def start_groups ( self ) :
if not self . bRouted ( ) : return False
if not self . group_is_connected ( self . sGROUP_BOT_NUM ) :
self . group_reconnect ( self . sGROUP_BOT_NUM )
if not self . group_is_connected ( self . sGROUP_BOT_NUM ) :
return False
assert self . sGROUP_BOT_NUM
num = self . sGROUP_BOT_NUM
self . group_self_set_status ( num , TOX_USER_STATUS [ ' NONE ' ] )
# add myself as a peer in the group or am I in as founder?
2022-10-24 00:11:12 +02:00
self . group_send_message ( num , TOX_MESSAGE_TYPE [ ' NORMAL ' ] , " hi " )
2022-10-26 10:44:57 +02:00
# The code in tests_wrapper need extending and then
# wiring up to here.
#
if self . _oArgs . group_invite :
pk = self . _oArgs . group_invite
if pk not in self . self_get_friend_list ( ) :
friend_number = self . add_friend ( pk )
else :
friend_number = self . friend_by_public_key ( pk )
b = self . group_invite_friend ( num , friend_number )
LOG . info ( f " A PK to invite to the group { b } " )
return True
if self . _oArgs . group_moderator :
pk = self . _oArgs . group_moderator
if pk not in self . self_get_friend_list ( ) :
friend_number = self . add_friend ( pk )
else :
friend_number = self . friend_by_public_key ( pk )
role = TOX_GROUP_ROLE [ ' MODERATOR ' ]
# dunno
peer_id = friend_number
b = self . group_mod_set_role ( num , peer_id , role )
LOG . info ( " A PK to invite to the group as moderator {b} " )
return True
if self . _oArgs . group_ignore :
pk = self . _oArgs . group_ignore
if pk not in self . self_get_friend_list ( ) :
friend_number = self . add_friend ( pk )
else :
friend_number = self . friend_by_public_key ( pk )
# dunno
peer_id = friend_number
b = self . group_toggle_set_ignore ( num , peer_id , True )
LOG . info ( " A PK to ignore in the group {b} " )
return True
return None
def create_group ( self ) :
privacy_state = TOX_GROUP_PRIVACY_STATE [ self . _oArgs . group_state . upper ( ) ]
nick = self . _oArgs . group_nick
group_name = self . _oArgs . group_name
if not group_name :
group_name = self . _oArgs . bot_name + self . _oArgs . irc_chan
self . _oArgs . group_name = group_name
status = TOX_USER_STATUS [ ' NONE ' ]
num = self . group_new ( privacy_state , group_name , nick , status )
assert num > = 0 , num
self . group_set_topic ( num , f " { group_name } IRC on { self . _oArgs . irc_host } " )
# self.tox_group_id = self.group_invite_accept(b'', friendid, nick)
chat_id = self . group_get_chat_id ( num )
if self . _oArgs . profile and os . path . exists ( os . path . dirname ( self . _oArgs . profile ) ) :
f = os . path . splitext ( self . _oArgs . profile ) [ 0 ] + ' .chatid '
open ( f , ' rt ' ) . write ( chat_id )
LOG . info ( f " Chat Id: { chat_id } written to { f } " )
else :
LOG . info ( f " Chat Id: { chat_id } " )
# dunno
if self . self_get_friend_list ( ) :
friendid = self . self_get_friend_list ( ) [ 0 ]
i = on_group_invite ( friendid , b ' ' , 0 )
assert i
self . tox_group_id = i
return num
def join_group ( self ) :
password = self . _oArgs . group_pass
nick = self . _oArgs . group_nick
# is the chat_id the pk?
chat_id = self . _oArgs . group_chatid
num = self . group_join ( chat_id , password , nick , status = ' ' )
self . sGROUP_BOT_NUM = num
self . group_self_set_status ( num , TOX_USER_STATUS [ ' NONE ' ] )
return num
def init_groups ( self ) :
LOG . debug ( f " init_groups proxy= { self . _oArgs . proxy_type } " )
group_name = self . _oArgs . bot_name + ' Test ' + self . _oArgs . irc_chan
if self . sGROUP_BOT_NUM < 0 :
# ToDo: look for the first group of the profile
i = self . group_get_number_groups ( )
if i == 0 :
if not self . bRouted ( ) : return False
num = self . create_group ( )
self . sGROUP_BOT_NUM = num
elif i > 1 :
LOG . error ( ' There are more than one groups in this profile ' )
for ig in range ( i ) :
LOG . warn ( f " group # { ig } { self . group_self_get_name ( ig ) } " )
raise RuntimeError ( " select one of the groups at the cmdline " )
else :
if not self . bRouted ( ) : return False
num = self . join_group ( )
LOG . info ( f " init_groups GROUP_BOT_PK= { self . sGROUP_BOT_PK } " )
if self . bRouted ( ) :
try :
self . start_groups ( )
except Exception as e :
LOG . warn ( f " init_groups self.start_groups { e } " )
return False
# TOX_GROUP_ROLE['FOUNDER']
return True
2022-10-24 00:11:12 +02:00
def init_callbacks ( self ) :
2022-10-26 10:44:57 +02:00
# wraps self with
LOG . info ( " Adding Tox init_callbacks " )
2022-10-24 00:11:12 +02:00
def gi_wrapped ( iTox , friendid , invite_data , invite_len , * args ) :
invite_data = str ( invite_data , ' UTF-8 ' )
2022-10-26 10:44:57 +02:00
LOG . debug ( f ' on_group_invite { friendid } { invite_data } ' )
self . on_group_invite ( friendid , invite_data , 0 )
2022-10-24 00:11:12 +02:00
self . callback_group_invite ( gi_wrapped , 0 )
2022-10-26 10:44:57 +02:00
def scs_wrapped ( iTox , friendid , status , * args ) :
LOG . debug ( f ' on_connection_status { friendId } { status } . ' )
self . on_connection_status ( friendid , status )
2022-10-24 00:11:12 +02:00
self . callback_self_connection_status ( scs_wrapped )
2022-10-26 10:44:57 +02:00
2022-10-24 00:11:12 +02:00
def gm_wrapped ( iTox , groupnumber , peer_id , type_ , message , mlen , * args ) :
message = str ( message , ' UTF-8 ' )
2022-10-26 10:44:57 +02:00
LOG . debug ( f ' on_group_message { groupnumber } { peer_id } { message } ' )
2022-10-24 00:11:12 +02:00
self . on_group_message ( groupnumber , peer_id , message )
self . callback_group_message ( gm_wrapped , 0 )
2022-10-26 10:44:57 +02:00
2022-10-24 00:11:12 +02:00
def ga_wrapped ( iTox , groupnumber , peer_id , type_ , action , mlen , * args ) :
2022-10-26 10:44:57 +02:00
LOG . debug ( f ' on_group_action(groupnumber, peer_id, action) ' )
2022-10-24 00:11:12 +02:00
self . on_group_action ( groupnumber , peer_id , action )
2022-10-26 10:44:57 +02:00
2022-10-24 00:11:12 +02:00
#? self.callback_group_action(ga_wrapped, 0)
def fr_wrapped ( iTox , pk , message , mlen , * args ) :
message = str ( message , ' UTF-8 ' )
2022-10-26 10:44:57 +02:00
LOG . debug ( f ' on_friend_request(pk, message) ' )
self . on_friend_request ( pk , message )
2022-10-24 00:11:12 +02:00
self . callback_friend_request ( fr_wrapped )
2022-10-26 10:44:57 +02:00
2022-10-24 00:11:12 +02:00
def fm_wrapped ( iTox , peer_id , message , mlen , * args ) :
message = str ( message , ' UTF-8 ' )
2022-10-26 10:44:57 +02:00
LOG . debug ( f ' on_friend_request(peer_id, message) ' )
self . on_friend_request ( peer_id , message )
2022-10-24 00:11:12 +02:00
self . callback_friend_request ( fm_wrapped )
def del_callbacks ( self ) :
self . callback_group_invite ( None , 0 )
self . callback_self_connection_status ( None )
self . callback_group_message ( None , 0 )
# self.callback_group_action(None, 0)
self . callback_friend_request ( None )
self . callback_friend_request ( None )
2014-02-02 13:46:06 +01:00
def irc_init ( self ) :
2022-10-24 00:11:12 +02:00
if not self . bRouted ( ) : return
2022-10-26 10:44:57 +02:00
nick = self . _oArgs . irc_nick
realname = self . _oArgs . irc_name
ident = self . _oArgs . irc_ident
2022-10-24 00:11:12 +02:00
LOG . info ( f " irc_init proxy= { self . _oArgs . proxy_type } " )
try :
2022-10-26 10:44:57 +02:00
if self . _oArgs . proxy_type == 2 :
socks . setdefaultproxy ( socks . PROXY_TYPE_SOCKS5 ,
self . _oArgs . proxy_host ,
self . _oArgs . proxy_port )
irc = socks . socksocket ( )
elif self . _oArgs . proxy_type == 1 :
socks . setdefaultproxy ( socks . PROXY_TYPE_HTTP ,
self . _oArgs . proxy_host ,
self . _oArgs . proxy_port )
irc = socks . socksocket ( )
else :
irc = socket . socket ( )
if self . _oArgs . irc_ssl :
if not self . _ssl_context :
self . start_ssl ( self . _oArgs . irc_host )
irc = SSL . Connection ( self . _ssl_context , irc )
irc . connect ( ( self . _oArgs . irc_host , self . _oArgs . irc_port ) )
irc . do_handshake ( )
LOG . info ( ' IRC SSL connected ' )
else :
irc . connect ( ( self . _oArgs . irc_host , self . _oArgs . irc_port ) )
LOG . info ( ' IRC connected ' )
except ( SSL . Error , ) as e :
LOG . warn ( f " SSL error: { e . args } " )
return
except ( SSL . SysCallError , ) as e :
LOG . warn ( f " SSL error: { e . args } " )
return
2022-10-24 00:11:12 +02:00
except Exception as e :
2022-10-26 10:44:57 +02:00
LOG . warn ( f " Error: { e } " )
return
self . irc = irc
self . irc . send ( bytes ( ' NICK ' + nick + ' \r \n ' , ' UTF-8 ' ) )
self . irc . send ( bytes ( ' USER %s %s bla : %s \r \n ' % (
ident , self . _oArgs . irc_host , realname ) , ' UTF-8 ' ) )
2022-10-24 00:11:12 +02:00
def dht_init ( self ) :
if not self . bRouted ( ) : return
if ' current_nodes_udp ' not in self . _settings :
self . test_net ( )
lNodes = self . _settings [ ' current_nodes_udp ' ]
shuffle ( lNodes )
if self . _oArgs . proxy_type == 0 :
2022-10-26 10:44:57 +02:00
ts . bootstrap_udp ( lNodes [ : 6 ] , [ self ] )
2022-10-24 00:11:12 +02:00
else :
2022-10-26 10:44:57 +02:00
if self . _bRouted is None :
LOG . info ( f ' UDP bootstapping 1 ' )
ts . bootstrap_udp ( [ lNodes [ 0 ] ] , [ self ] )
2022-10-24 00:11:12 +02:00
if ' current_nodes_tcp ' not in self . _settings :
self . test_net ( )
lNodes = self . _settings [ ' current_nodes_tcp ' ]
shuffle ( lNodes )
2022-10-26 10:44:57 +02:00
LOG . info ( f ' TCP bootstapping 6 ' )
ts . bootstrap_tcp ( lNodes [ : 6 ] , [ self ] )
def get_all_groups ( self ) :
try :
group_numbers = range ( self . _tox . group_get_number_groups ( ) )
except Exception as e :
return None
groups = map ( lambda n : self . get_group_by_number ( n ) , group_numbers )
return list ( groups )
def get_group_by_number ( self , group_number ) :
try :
public_key = self . _tox . group_get_chat_id ( group_number )
# LOG.info(f"group_get_chat_id {group_number} {public_key}")
return self . get_group_by_public_key ( public_key )
except Exception as e :
LOG . warn ( f " group_get_chat_id { group_number } { e } " )
return None
def get_group_by_public_key ( self , public_key , group ) :
self . _groups [ public_key ] = group
# -----------------------------------------------------------------------------------------------------------------
# Group peers
# -----------------------------------------------------------------------------------------------------------------
def get_all_group_peers ( self ) :
return list ( )
def get_group_peer_by_public_key ( self , group , public_key ) :
peer = group . get_peer_by_public_key ( public_key )
return self . _get_group_peer ( group , peer )
def get_peer_by_id ( self , peer_id ) :
peers = list ( filter ( lambda p : p . id == peer_id , self . _peers ) )
if peers :
return peers [ 0 ]
else :
LOG_WARN ( f " get_peer_by_id empty peers for { peer_id } " )
return [ ]
2022-10-24 00:11:12 +02:00
def ensure_exe ( self , func , * args ) :
2013-12-11 16:55:40 +01:00
count = 0
2013-12-11 17:02:01 +01:00
THRESHOLD = 50
2013-12-11 16:55:40 +01:00
while True :
try :
return func ( * args )
except :
assert count < THRESHOLD
count + = 1
2022-10-24 00:11:12 +02:00
self . do ( )
2013-12-01 16:09:54 +01:00
2022-10-24 00:11:12 +02:00
def do ( self , n = 50 ) :
interval = self . iteration_interval ( )
for i in range ( n ) :
self . iterate ( )
sleep ( interval / 1000.0 * 10 )
def unroute ( self ) :
if self . irc :
2022-10-26 10:44:57 +02:00
try : self . irc . close ( )
2022-10-24 00:11:12 +02:00
except : pass
self . irc = None
def irc_check ( self , lines ) :
if b ' NOTICE AUTH ' in lines [ 0 ] :
for line in lines [ : 99 ] :
if b ' NOTICE AUTH ' not in line : return
line = str ( line , ' UTF-8 ' ) . strip ( )
print ( line )
else :
for line in lines [ : 5 ] :
line = str ( line , ' UTF-8 ' ) . strip ( ) . lower ( )
if ' banned ' in line :
raise RuntimeError ( line )
if ' error ' in line and ' closing ' in line :
raise RuntimeError ( line )
2022-10-26 10:44:57 +02:00
def irc_readlines ( self ) :
nick = self . _oArgs . irc_nick
pwd = self . _oArgs . irc_pass
fp = self . _oArgs . irc_fp
email = self . _oArgs . irc_email
self . readbuffer + = self . irc . recv ( 4096 )
lines = self . readbuffer . split ( b ' \n ' )
self . irc_check ( lines )
LOG . debug ( f ' Waited on IRC and got { len ( lines ) } lines. ' )
self . readbuffer = lines . pop ( )
for line in lines :
line = str ( line , ' UTF-8 ' )
l = line . rstrip ( ) . split ( )
if len ( l ) < 2 :
print ( line )
elif l [ 1 ] not in [ ' 372 ' ] :
i = line . find ( ' ' )
print ( line [ i + 1 : ] )
rx = re . match ( r ' :(.*?)!.*? PRIVMSG %s :(.*?) \ r ' %
self . _oArgs . irc_chan , line , re . S )
if rx :
self . relay_message ( rx )
elif l [ 0 ] == ' PING ' :
self . irc_send ( ' PONG %s \r \n ' % l [ 1 ] )
elif len ( l ) < 2 :
pass
elif l [ 1 ] == ' 376 ' :
# :End of /MOTD command
if email == ' ' :
self . irc . send ( bytes ( ' PRIVMSG NickServ IDENTIFY %s %s \r \n '
% ( nick , pwd , ) , ' UTF-8 ' ) )
else :
self . irc . send ( bytes ( ' PRIVMSG NickServ REGISTER %s %s \r \n '
% ( pwd , email , ) , ' UTF-8 ' ) )
if False and fp :
LOG . info ( f " PRIVMSG NickServ CERT ADD " )
# self.irc.send(bytes(f'PRIVMSG NickServ CERT ADD {fp}\r\n', 'UTF-8'))
#
self . irc . send ( bytes ( ' JOIN %s \r \n ' % self . _oArgs . irc_chan , ' UTF-8 ' ) )
# put off init_groups until you have joined IRC
self . init_groups ( )
# Make sure we are in
elif l [ 1 ] == ' 042 ' :
# 042 SyniTox 8VQAADOD0 :your unique ID
self . _irc_id = line . replace ( ' :your unique ID ' , ' ' ) . \
replace ( ' 042 ' + nick + ' ' , ' ' )
elif l [ 1 ] == ' 421 ' :
# 421 SyniTox .PRIVMSG :Unknown command
pass
elif l [ 1 ] == ' 477 ' :
#477 SyniTox #tor :Cannot join channel (Need to be identified and verified to join this channel, '/msg NickServ help' to learn how to register and verify.)
LOG . info ( f " PRIVMSG NickServ STATUS { nick } " )
i = line . find ( " ' /msg NickServ help ' " )
if i > 0 :
line = line [ : i ]
raise RuntimeError ( line )
def relay_message ( self , rx ) :
print ( ' IRC> %s : %s ' % rx . groups ( ) )
msg = ' [ %s ]: %s ' % rx . groups ( )
content = rx . group ( 2 )
if self . sGROUP_BOT_NUM > = 0 :
if content [ 1 : ] . startswith ( ' ACTION ' ) :
action = ' [ %s ]: %s ' % ( rx . group ( 1 ) ,
rx . group ( 2 ) [ 8 : - 1 ] )
type_ = TOX_MESSAGE_TYPE [ ' ACTION ' ]
self . ensure_exe ( self . group_send_message ,
self . sGROUP_BOT_NUM , type_ , action )
else :
type_ = TOX_MESSAGE_TYPE [ ' NORMAL ' ]
self . ensure_exe ( self . group_send_message ,
self . sGROUP_BOT_NUM , type_ , msg )
if content . startswith ( ' ^ ' ) :
self . handle_command ( content )
def spin ( self , n = 20 ) :
readable = False
waiti = 0
while not readable :
waiti + = 1
readable , _ , _ = select . select ( [ self . irc ] , [ ] , [ ] , n / 1000.0 )
self . do ( n )
if waiti > 100 : break
return readable
2022-10-24 00:11:12 +02:00
def iLoop ( self ) :
2022-10-26 10:44:57 +02:00
group_connected = False
routed = None
2013-12-01 16:09:54 +01:00
self . joined = False
2013-12-11 17:02:01 +01:00
self . request = False
2022-10-26 10:44:57 +02:00
iCount = 0
iDelay = 10
nick = self . _oArgs . irc_nick
pwd = self . _oArgs . irc_pass
email = self . _oArgs . irc_email
LOG . info ( f " Looping for Tox and IRC connections " )
if iCount < self . _oArgs . max_sleep :
2013-12-01 16:09:54 +01:00
while True :
2022-10-26 10:44:57 +02:00
iCount + = 1
# LOG.debug(f"Looping {iCount}")
2022-10-24 00:11:12 +02:00
b = self . bRouted ( )
if not b :
self . unroute ( )
2022-10-26 10:44:57 +02:00
group_connected = False
iDelay = iDelay + iDelay / / 10
if routed != b :
if iCount % 10 == 1 :
LOG . info ( f ' Not routed { iCount } sleeping { iDelay } seconds ' )
sleep ( iDelay )
2022-10-24 00:11:12 +02:00
continue
2022-10-26 10:44:57 +02:00
elif b != routed or routed is None :
LOG . debug ( f ' Routed { iCount } - resetting count ' )
iDelay = 10
routed = b
dht_conneted = self . self_get_connection_status ( )
if not dht_conneted :
self . dht_init ( )
LOG . info ( f ' Not DHT connected { iCount } iterating { 10 + iDelay } seconds ' )
self . do ( 10 + iDelay )
#drop through
if not group_connected and dht_conneted :
2022-10-24 00:11:12 +02:00
LOG . info ( ' Connected to DHT. ' )
2022-10-26 10:44:57 +02:00
group_connected = True
2013-12-11 17:02:01 +01:00
try :
2022-10-26 10:44:57 +02:00
#? self.bid = self.friend_by_public_key(self.sGROUP_BOT_PK)
r = self . group_reconnect ( self . sGROUP_BOT_NUM )
LOG . info ( f ' Connected to group { r } ' )
2022-10-24 00:11:12 +02:00
except ctypes . ArgumentError as e :
self . bid = None
2022-10-26 10:44:57 +02:00
2022-10-24 00:11:12 +02:00
if self . bid == None :
self . ensure_exe ( self . friend_add_norequest , self . sGROUP_BOT_PK )
LOG . info ( f ' friend_add_n to group { self . sGROUP_BOT_PK [ : 8 ] } ' )
self . bid = self . friend_by_public_key ( self . sGROUP_BOT_PK )
LOG . info ( f ' Added to group { self . bid } ' )
2022-10-26 10:44:57 +02:00
num = self . sGROUP_BOT_NUM
2022-10-24 00:11:12 +02:00
my_pk = self . group_self_get_public_key ( num )
LOG . info ( f ' Connected to group as { my_pk [ : 8 ] } ' )
2022-10-26 10:44:57 +02:00
if group_connected and not dht_conneted :
2022-10-24 00:11:12 +02:00
LOG . info ( ' Disconnected from DHT. ' )
self . dht_init ( )
2022-10-26 10:44:57 +02:00
group_connected = False
2022-10-24 00:11:12 +02:00
if not self . irc :
LOG . info ( ' Disconnected from IRC. ' )
2022-10-26 10:44:57 +02:00
self . irc_init ( )
2022-10-24 00:11:12 +02:00
if not self . irc :
2022-10-26 10:44:57 +02:00
self . do ( 20 )
2022-10-24 00:11:12 +02:00
continue
2022-10-26 10:44:57 +02:00
2022-10-24 00:11:12 +02:00
LOG . info ( ' Waiting on IRC. ' )
2022-10-26 10:44:57 +02:00
iDelay = 10
2022-10-24 00:11:12 +02:00
2022-10-26 10:44:57 +02:00
readable = self . spin ( 20 )
2022-10-24 00:11:12 +02:00
if not readable :
LOG . info ( ' Waited on IRC but nothing to read. ' )
2022-10-26 10:44:57 +02:00
continue
try :
self . irc_readlines ( )
except Exception as e :
LOG . exception ( f ' IRC Error during read: { e } ' )
# close irc?
try : self . irc . close ( )
except : pass
self . irc = None
self . irc_init ( )
continue
return 0
2013-12-01 16:24:33 +01:00
2022-10-24 00:11:12 +02:00
def quit ( self ) :
self . del_callbacks ( )
self . save_to_file ( )
def save_to_file ( self ) :
pass
2013-12-01 16:09:54 +01:00
2014-02-02 13:46:06 +01:00
def irc_send ( self , msg ) :
success = False
while not success :
try :
2022-10-24 00:11:12 +02:00
self . irc . send ( bytes ( msg , ' UTF-8 ' ) )
2014-02-02 13:46:06 +01:00
success = True
break
except socket . error :
self . irc_init ( )
sleep ( 1 )
2013-12-11 16:55:40 +01:00
def on_connection_status ( self , friendId , status ) :
2022-10-26 10:44:57 +02:00
# scs_wrapped
2013-12-11 17:02:01 +01:00
if not self . request and not self . joined \
and friendId == self . bid and status :
2022-10-26 10:44:57 +02:00
LOG . info ( ' Groupbot online, trying to get invited to group chat. ' )
2013-12-11 17:02:01 +01:00
self . request = True
2022-10-26 10:44:57 +02:00
type_ = TOX_MESSAGE_TYPE [ ' NORMAL ' ]
# the bot is sending a message to myself self.bid
self . ensure_exe ( self . friend_send_message , self . bid , type_ , ' invite ' )
2013-12-02 20:18:52 +01:00
2022-10-26 10:44:57 +02:00
# gi_wrapped
2022-10-24 00:11:12 +02:00
def on_group_invite ( self , friendid , invite_data , user_data ) :
2013-12-01 16:09:54 +01:00
if not self . joined :
self . joined = True
2022-10-26 10:44:57 +02:00
nick = self . _oArgs . group_nick
self . tox_group_id = self . group_invite_accept ( invite_data , friendid , nick )
2022-10-24 00:11:12 +02:00
LOG . info ( ' Joined groupchat. ' )
2013-12-01 16:09:54 +01:00
2022-10-26 10:44:57 +02:00
def group_peername ( self , groupnumber , peer_id ) :
#dunno
return ' '
2022-10-24 00:11:12 +02:00
def on_group_message ( self , groupnumber , peer_id , message ) :
name = self . group_peername ( groupnumber , peer_id )
2014-04-06 02:12:34 +02:00
if len ( name ) and name != NAME :
2013-12-01 16:09:54 +01:00
print ( ' TOX> %s : %s ' % ( name , message ) )
2014-02-20 19:28:35 +01:00
if message . startswith ( ' > ' ) :
2014-02-20 19:34:59 +01:00
message = ' \x03 09 %s \x03 ' % message
2014-03-13 20:31:26 +01:00
2022-10-24 00:11:12 +02:00
self . irc_send ( b ' PRIVMSG %s :[ %s ]: %s \r \n ' %
2022-10-26 10:44:57 +02:00
( self . _oArgs . irc_chan , name , message ) )
2014-02-20 10:17:17 +01:00
if message . startswith ( ' ^ ' ) :
2014-02-20 12:04:54 +01:00
self . handle_command ( message )
2013-12-01 16:09:54 +01:00
2022-10-24 00:11:12 +02:00
def on_group_action ( self , groupnumber , peer_id , action ) :
""" old? message type action? """
name = self . group_peername ( groupnumber , peer_id )
2022-10-26 10:44:57 +02:00
if name and name != NAME :
2013-12-22 17:33:15 +01:00
print ( ' TOX> %s : %s ' % ( name , action ) )
2014-02-20 19:28:35 +01:00
if action . startswith ( ' > ' ) :
2014-02-20 19:34:59 +01:00
action = ' \x03 09 %s \x03 ' % action
2014-02-20 10:01:45 +01:00
self . irc_send ( ' PRIVMSG %s : \x01 ACTION [ %s ]: %s \x01 \r \n ' %
2022-10-26 10:44:57 +02:00
( self . _oArgs . irc_chan , name , action ) )
2013-12-22 17:33:15 +01:00
2013-12-02 07:50:28 +01:00
def on_friend_request ( self , pk , message ) :
2022-10-24 00:11:12 +02:00
LOG . info ( ' Friend request from %s : %s ' % ( pk , message ) )
2022-10-26 10:44:57 +02:00
self . friend_add_norequest ( pk )
2022-10-24 00:11:12 +02:00
LOG . info ( ' Accepted. ' )
2013-12-02 07:50:28 +01:00
def on_friend_message ( self , friendid , message ) :
2022-10-26 10:44:57 +02:00
if message . startswith ( ' invite ' ) :
2014-03-13 20:33:34 +01:00
if not self . tox_group_id is None :
2022-10-26 10:44:57 +02:00
LOG . info ( ' Inviting %s ' % self . friend_get_name ( friendid ) )
self . group_invite_friend ( self . sGROUP_BOT_NUM , friendid )
2014-03-13 20:33:34 +01:00
return
else :
message = ' Waiting for GroupBot, please try again in 1 min. '
2022-10-26 10:44:57 +02:00
type_ = TOX_MESSAGE_TYPE [ ' NORMAL ' ]
self . ensure_exe ( self . friend_send_message , friendid , type_ , message )
2013-12-02 07:50:28 +01:00
2014-02-20 12:27:49 +01:00
def send_both ( self , content ) :
2022-10-26 10:44:57 +02:00
type_ = TOX_MESSAGE_TYPE [ ' NORMAL ' ]
self . ensure_exe ( self . group_send_message , self . sGROUP_BOT_NUM , type_ , content )
self . irc_send ( ' PRIVMSG %s : %s \r \n ' % ( self . _oArgs . irc_chan , content ) )
2014-02-20 12:27:49 +01:00
2014-02-20 12:02:07 +01:00
def handle_command ( self , cmd ) :
2014-02-20 12:27:49 +01:00
cmd = cmd [ 1 : ]
if cmd in [ ' syncbot ' , ' echobot ' ] :
2022-10-26 10:44:57 +02:00
self . send_both ( self . self_get_address ( ) )
2014-03-13 20:33:34 +01:00
elif cmd == ' resync ' :
sys . exit ( 0 )
2014-02-20 12:27:49 +01:00
elif cmd . startswith ( ' remember ' ) :
args = cmd [ 9 : ] . split ( ' ' )
subject = args [ 0 ]
desc = ' ' . join ( args [ 1 : ] )
self . memory [ subject ] = desc
2022-10-24 00:11:12 +02:00
if self . sMEMORY_DB :
with open ( self . sMEMORY_DB , ' w ' ) as f :
pickle . dump ( self . memory , f )
2014-02-20 12:27:49 +01:00
self . send_both ( ' Remembering ^ %s : %s ' % ( subject , desc ) )
elif self . memory . has_key ( cmd ) :
self . send_both ( self . memory [ cmd ] )
2022-10-26 10:44:57 +02:00
def is_data_encrypted ( self , data ) :
return len ( data ) > 0 and self . _toxes . is_data_encrypted ( data )
def pass_encrypt ( self , data ) :
return self . _toxes . pass_encrypt ( data , self . _oArgs . password )
def has_password ( self ) :
return self . _oArgs . password
def pass_decrypt ( self , data ) :
return self . _toxes . pass_decrypt ( data , self . _oArgs . password )
2014-02-20 12:02:07 +01:00
2022-10-26 10:44:57 +02:00
def iMain ( oArgs , oOpts ) :
2022-10-24 00:11:12 +02:00
assert oTOX_OPTIONS
assert oTOX_OARGS
2022-10-26 10:44:57 +02:00
try :
o = SyniTox ( oArgs , oOpts )
__builtins__ . app = o
o . start ( )
ret = o . iLoop ( )
except KeyboardInterrupt :
ret = 0
except ( SSL . Error , ) as e :
LOG . error ( f " SSL error: { e . args } " )
ret = 1
except ( SSL . SysCallError , ) as e :
# OpenSSL.SSL.SysCallError: (9, 'EBADF')
LOG . error ( f " SSL error: { e . args } " )
ret = 1
except Exception as e :
LOG . exception ( f ' Error running program: \n { e } ' )
ret = 2
else :
ret = 0
o . quit ( )
2022-10-24 00:11:12 +02:00
return ret
def oToxygenToxOptions ( oArgs ) :
tox_options = wrapper . tox . Tox . options_new ( )
if oArgs . proxy_type :
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
else :
tox_options . contents . udp_enabled = oArgs . udp_enabled
if not os . path . exists ( ' /proc/sys/net/ipv6 ' ) :
oArgs . ipv6_enabled = False
tox_options . contents . tcp_port = int ( oArgs . tcp_port )
# overrides
tox_options . contents . local_discovery_enabled = False
tox_options . contents . dht_announcements_enabled = True
tox_options . contents . hole_punching_enabled = False
tox_options . contents . experimental_thread_safety = False
# REQUIRED!!
if oArgs . ipv6_enabled and not os . path . exists ( ' /proc/sys/net/ipv6 ' ) :
LOG . warn ( ' Disabling IPV6 because /proc/sys/net/ipv6 does not exist ' + repr ( oArgs . ipv6_enabled ) )
tox_options . contents . ipv6_enabled = False
else :
tox_options . contents . ipv6_enabled = bool ( oArgs . ipv6_enabled )
#? tox_options.contents.log_callback = LOG
2022-10-26 10:44:57 +02:00
if oArgs . trace_enabled and tox_options . _options_pointer :
2022-10-24 00:11:12 +02:00
# LOG.debug("Adding logging to tox_options._options_pointer ")
ts . vAddLoggerCallback ( tox_options , ts . on_log )
else :
LOG . 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 = None ,
2022-10-26 10:44:57 +02:00
help = ' Path to Tox profile - new groups will be saved there ' )
CAcs = [ ]
for elt in lCAs :
if os . path . exists ( elt ) :
CAcs . append ( elt )
break
parser . add_argument ( ' --bot_name ' , type = str , default = bot_toxname )
parser . add_argument ( ' --max_sleep ' , type = int , default = 3600 ,
help = " max time to sleep waiting for routing before exiting " )
parser . add_argument ( ' --password ' , type = str , default = ' ' ,
help = " password for the profile if encrypted " )
# parser.add_argument('--irc_type', type=str, default='',
# choices=['', 'startls', 'direct')
# does host == connect ?
parser . add_argument ( ' --irc_host ' , type = str , default = ' irc.oftc.net ' ,
help = " irc.libera.chat will not work over Tor " )
parser . add_argument ( ' --irc_port ' , type = int , default = 6667 ,
help = " default 6667, but may be 6697 with SSL " )
parser . add_argument ( ' --irc_chan ' , type = str , default = ' #tor ' ,
help = " IRC channel to join - include the # " )
#
parser . add_argument ( ' --irc_ssl ' , type = str , default = ' ' ,
help = " TLS version; empty is no SSL " ,
choices = [ ' ' , ' tls1.2 ' , ' tls1.3 ' ] )
parser . add_argument ( ' --irc_ca ' , type = str ,
help = " Certificate Authority file or directory " ,
default = CAcs [ 0 ] )
parser . add_argument ( ' --irc_pem ' , type = str , default = ' ' ,
help = " Certificate and key as pem; use openssl req -x509 -nodes -newkey rsa:2048 " )
parser . add_argument ( ' --irc_fp ' , type = str , default = ' ' ,
help = " fingerprint of the pem added with CERT ADD; use openssl x509 -noout -fingerprint -SHA1 -text " )
parser . add_argument ( ' --irc_nick ' , type = str , default = ' ' ,
help = " IRC Nickname " )
parser . add_argument ( ' --irc_name ' , type = str , default = ' ' ,
help = " Third field in USER " )
parser . add_argument ( ' --irc_ident ' , type = str , default = ' ' ,
help = " First field in USER " )
parser . add_argument ( ' --irc_pass ' , type = str , default = ' ' ,
help = " password for INDENTIFY or REGISTER " )
parser . add_argument ( ' --irc_email ' , type = str , default = ' ' ,
help = " Use email to REGISTER with _pass " )
#
parser . add_argument ( ' --group_pass ' , type = str , default = ' ' ,
help = " password for the group - optional " )
parser . add_argument ( ' --group_state ' , type = str , default = ' public ' ,
choices = [ ' public ' , ' private ' ] ,
help = " state for the group - default public " )
parser . add_argument ( ' --group_chatid ' , type = str , default = ' ' ,
help = " chat_id of the group - will be created on first use " )
parser . add_argument ( ' --group_name ' , type = str , default = ' ' ,
help = " name for the group " )
parser . add_argument ( ' --group_nick ' , type = str , default = ' ' ,
help = " Nickname of the group founder " )
parser . add_argument ( ' --group_invite ' , type = str , default = ' ' ,
help = " A PK to invite to the group " )
parser . add_argument ( ' --group_moderator ' , type = str , default = ' ' ,
help = " A PK to invite to the group as moderator " )
parser . add_argument ( ' --group_ignore ' , type = str , default = ' ' ,
help = " A PK to ignore by the group " )
2022-10-24 00:11:12 +02:00
oArgs = parser . parse_args ( lArgv )
for key in ts . lBOOLEANS :
if key not in oArgs : continue
val = getattr ( oArgs , key )
setattr ( oArgs , key , bool ( val ) )
if hasattr ( oArgs , ' sleep ' ) :
if oArgs . sleep == ' qt ' :
pass # broken or gevent.sleep(idle_period)
elif oArgs . sleep == ' gevent ' :
pass # broken or gevent.sleep(idle_period)
else :
oArgs . sleep = ' time '
return oArgs
def main ( lArgs = None ) :
2022-10-26 10:44:57 +02:00
2022-10-24 00:11:12 +02:00
if lArgs is None : lArgs = [ ]
2022-10-26 10:44:57 +02:00
global oTOX_OARGS
oTOX_OARGS = oArgparse ( lArgs )
2022-10-24 00:11:12 +02:00
global oTOX_OPTIONS
2022-10-26 10:44:57 +02:00
oTOX_OPTIONS = oToxygenToxOptions ( oTOX_OARGS )
ts . vSetupLogging ( oTOX_OARGS )
2022-10-24 00:11:12 +02:00
# ts.setup_logging(oArgs)
2022-10-26 10:44:57 +02:00
return iMain ( oTOX_OARGS , oTOX_OPTIONS )
2022-10-24 00:11:12 +02:00
if __name__ == ' __main__ ' :
sys . exit ( main ( sys . argv [ 1 : ] ) )
# Ran 34 tests in 86.589s OK (skipped=12)