initial commit

This commit is contained in:
ingvar1995 2016-04-21 22:55:31 +03:00
commit 344ee23c66
12 changed files with 2515 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.pyc
*~
.idea/
test/
libs/

44
README.md Normal file
View File

@ -0,0 +1,44 @@
FILE BOT - Share files with friends and family using [Tox](https://tox.chat/)
# Supported OS:
- Windows
- Linux
#Install:
### Windows
1. [Download and install latest Python 2.7](https://www.python.org/downloads/windows/)
2. [Download file bot](https://github.com/ingvar1995/filebot/archive/master.zip)
3. Unpack archive
4. Download latest [libtox.dll](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip) build, download latest [libsodium.a](https://build.tox.chat/view/libsodium/job/libsodium_build_windows_x86_static_release/lastSuccessfulBuild/artifact/libsodium_build_windows_x86_static_release.zip) build, put it into libs\
5. Run app:
``python main.py path_to_profile``
### Linux
1. [Download file bot](https://github.com/ingvar1995/filebot/archive/master.zip)
2. Unpack archive
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) in your system
4. Run app:
``python main.py path_to_profile``
#Commands:
``help - list of commands
rights - get access rights
files - show list of files (get access)
stats - downloads statistics (get access)
id - get bot's id (get access)
share <ToxID> <file_name> - send file to friend (get access)
share --all <file_name> - send file to all friends (get access)
size <file_name> - get size of file (get access)
get <file_name> - get file with specified filename (get access)
del <file_name> - remove file with specified filename (delete access)
rename <file_name> --new <new_file_name> - rename file (delete access)
user <ToxID> <rights> - new rights (example: rwdm) for user (masters only)
status <new_status> - new status message (masters only)
name <new_name> - new name (masters only)
message <ToxID> <message_text> - send message to friend (masters only)
message --all <message_text> - send message to all friends (masters only)``

87
bootstrap.py Normal file
View File

@ -0,0 +1,87 @@
import random
class Node(object):
def __init__(self, ip, port, tox_key, rand):
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
def get_data(self):
return self._ip, self._port, self._tox_key
def node_generator():
nodes = []
ips = [
"144.76.60.215", "23.226.230.47", "195.154.119.113", "biribiri.org",
"46.38.239.179", "178.62.250.138", "130.133.110.14", "104.167.101.29",
"205.185.116.116", "198.98.51.198", "80.232.246.79", "108.61.165.198",
"212.71.252.109", "194.249.212.109", "185.25.116.107", "192.99.168.140",
"46.101.197.175", "95.215.46.114", "5.189.176.217", "148.251.23.146",
"104.223.122.15", "78.47.114.252", "d4rk4.ru", "81.4.110.149",
"95.31.20.151", "104.233.104.126", "51.254.84.212", "home.vikingmakt.com.br",
"5.135.59.163", "185.58.206.164", "188.244.38.183", "mrflibble.c4.ee",
"82.211.31.116", "128.199.199.197", "103.230.156.174", "91.121.66.124",
"92.54.84.70", "tox1.privacydragon.me"
]
ports = [
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
443, 33445, 5190, 2306,
33445, 33445, 1813, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445, 33445, 33445,
33445, 33445
]
ids = [
"04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F",
"A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074",
"E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354",
"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67",
"F5A1A38EFB6BD3C2C8AF8B10D85F0F89E931704D349F1D0720C3C4059AF2440A",
"788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B",
"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F",
"5918AC3C06955962A75AD7DF4F80A5D7C34F7DB9E1498D2E0495DE35B3FE8A57",
"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702",
"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F",
"CF6CECA0A14A31717CC8501DA51BE27742B70746956E6676FF423A529F91ED5D",
"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832",
"C4CEB8C7AC607C6B374E2E782B3C00EA3A63B80D4910B8649CCACDD19F260819",
"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B",
"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43",
"6A4D0607A296838434A6A7DDF99F50EF9D60A2C510BBF31FE538A25CB6B4652F",
"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707",
"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23",
"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F",
"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147",
"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A",
"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976",
"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039",
"9E7BD4793FFECA7F32238FA2361040C09025ED3333744483CA6F3039BFF0211E",
"9CA69BB74DE7C056D1CC6B16AB8A0A38725C0349D187D8996766958584D39340",
"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414",
"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D",
"188E072676404ED833A4E947DC1D223DF8EFEBE5F5258573A236573688FB9761",
"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211",
"24156472041E5F220D1FA11D9DF32F7AD697D59845701CDD7BE7D1785EB9DB39",
"15A0F9684E2423F9F46CFA5A50B562AE42525580D840CC50E518192BF333EE38",
"FAAB17014F42F7F20949F61E55F66A73C230876812A9737F5F6D2DCE4D9E4207",
"AF97B76392A6474AF2FD269220FDCF4127D86A42EF3A242DF53A40A268A2CD7C",
"B05C8869DBB4EDDD308F43C1A974A20A725A36EACCA123862FDE9945BF9D3E09",
"5C4C7A60183D668E5BD8B3780D1288203E2F1BAE4EEF03278019E21F86174C1D",
"4E3F7D37295664BBD0741B6DBCB6431D6CD77FC4105338C2FC31567BF5C8224A",
"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802",
"31910C0497D347FF160D6F3A6C0E317BAFA71E8E03BC4CBB2A185C9D4FB8B31E"
]
for i in xrange(len(ips)):
nodes.append(Node(ips[i], ports[i], ids[i], random.randint(0, 1000000)))
arr = sorted(nodes, key=lambda x: x.rand)[:4]
for elem in arr:
yield elem.get_data()
if __name__ == "__main__":
for elem in node_generator():
print str(elem)

382
bot.py Normal file
View File

@ -0,0 +1,382 @@
from tox import Tox
import os
from settings import *
from toxcore_enums_and_consts import *
from ctypes import *
from util import log, Singleton
from file_transfers import *
from collections import defaultdict
class Bot(Singleton):
def __init__(self, tox):
"""
:param tox: tox instance
"""
super(Bot, self).__init__()
self._tox = tox
self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
self._downloads = defaultdict(int)
# -----------------------------------------------------------------------------------------------------------------
# Edit current user's data
# -----------------------------------------------------------------------------------------------------------------
def set_name(self, value):
self._tox.self_set_name(value.encode('utf-8'))
def set_status_message(self, value):
self._tox.self_set_status_message(value.encode('utf-8'))
# -----------------------------------------------------------------------------------------------------------------
# Private messages
# -----------------------------------------------------------------------------------------------------------------
def send_message(self, number, message, message_type=TOX_MESSAGE_TYPE['NORMAL']):
"""
Message splitting
:param number: friend's number
:param message_type: type of message
:param message: message text
"""
while len(message) > TOX_MAX_MESSAGE_LENGTH:
size = TOX_MAX_MESSAGE_LENGTH * 4 / 5
last_part = message[size:TOX_MAX_MESSAGE_LENGTH]
if ' ' in last_part:
index = last_part.index(' ')
elif ',' in last_part:
index = last_part.index(',')
elif '.' in last_part:
index = last_part.index('.')
else:
index = TOX_MAX_MESSAGE_LENGTH - size
index += size + 1
self._tox.friend_send_message(number, message_type, message[:index])
message = message[index:]
self._tox.friend_send_message(number, message_type, message)
def new_message(self, friend_num, message):
"""
New message
:param friend_num: number of friend who sent message
:param message: text of message
"""
id = self._tox.friend_get_public_key(friend_num)
settings = Settings.get_instance()
message = message.strip()
if message == 'files':
if id in settings['read']:
s = ''
for f in os.listdir(settings['folder']):
f = unicode(f)
if os.path.isfile(os.path.join(settings['folder'], f)):
s += u'{} ({} bytes)\n'.format(f, os.path.getsize(os.path.join(settings['folder'], f)))
if not s:
s = 'Nothing found'
self.send_message(friend_num, s.encode('utf-8'), TOX_MESSAGE_TYPE['NORMAL'])
else:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif message.startswith('get '):
if id in settings['read']:
path = settings['folder'] + '/' + unicode(message[4:])
if os.path.exists(unicode(path)):
self.send_file(unicode(path), friend_num)
else:
self.send_message(friend_num, 'Wrong file name'.encode('utf-8'))
else:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif message == 'help':
self.send_message(friend_num, """help - list of commands\n
rights - get access rights\n
files - show list of files (get access)\n
stats - downloads statistics (get access)\n
id - get bot's id (get access)\n
share <ToxID> <file_name> - send file to friend (get access)\n
share --all <file_name> - send file to all friends (get access)\n
size <file_name> - get size of file (get access)\n
get <file_name> - get file with specified filename (get access)\n
del <file_name> - remove file with specified filename (delete access)\n
rename <file_name> --new <new_file_name> - rename file (delete access)\n
user <ToxID> <rights> - new rights (example: rwdm) for user (masters only)\n
status <new_status> - new status message (masters only)\n
name <new_name> - new name (masters only)\n
message <ToxID> <message_text> - send message to friend (masters only)\n
message --all <message_text> - send message to all friends (masters only)
Users with write access can send files to bot.
""".encode('utf-8'))
elif message == 'rights':
self.send_message(friend_num, 'Read: {}\nWrite: {}\nDelete: {}\nMaster: {}'
.format('Yes' if id in settings['read'] else 'No',
'Yes' if id in settings['write'] else 'No',
'Yes' if id in settings['delete'] else 'No',
'Yes, sir!' if id in settings['master'] else 'No'))
elif message.startswith('del '):
if id in settings['delete']:
path = settings['folder'] + '/' + message[4:]
if os.path.exists(path):
os.remove(path)
self.send_message(friend_num, 'File was successfully deleted')
else:
self.send_message(friend_num, 'Wrong file name'.encode('utf-8'))
else:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif message.startswith('user '):
if id not in settings['master']:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
return
try:
rights = message.split(' ')[2]
except:
rights = ''
id = message.split(' ')[1][:TOX_PUBLIC_KEY_SIZE * 2]
if id in settings['read']:
settings['read'].remove(id)
if id in settings['write']:
settings['write'].remove(id)
if id in settings['delete']:
settings['delete'].remove(id)
if 'r' in rights:
settings['read'].append(id)
if 'w' in rights:
settings['write'].append(id)
if 'd' in rights:
settings['delete'].append(id)
if 'm' in rights:
settings['master'].append(id)
settings.save()
self.send_message(friend_num, 'Updated'.encode('utf-8'))
elif message.startswith('status '):
if id not in settings['master']:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
else:
self.set_status_message(message[7:])
elif message.startswith('name '):
if id not in settings['master']:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
else:
self.set_name(message[5:])
elif message.startswith('share '):
if id in settings['read']:
if '--all' not in message:
fl = ' '.join(message.split(' ')[2:])
try:
num = self._tox.friend_by_public_key(message.split(' ')[1][:TOX_PUBLIC_KEY_SIZE * 2])
print num
self.send_file(settings['folder'] + '/' + fl, num)
except Exception as ex:
print ex
self.send_message(friend_num, 'Friend not found'.encode('utf-8'))
else:
fl = ' '.join(message.split(' ')[2:])
for num in self._tox.self_get_friend_list():
if self._tox.friend_get_connection_status(num):
self.send_file(settings['folder'] + '/' + fl, num)
else:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif message.startswith('rename '):
ind = message.index(' --new ')
old = message[7:ind]
new = message[ind + 7:]
if id in settings['delete']:
if os.path.exists(settings['folder'] + '/' + old):
os.rename(settings['folder'] + '/' + old, settings['folder'] + '/' + new)
self.send_message(friend_num, 'Renamed'.encode('utf-8'))
else:
self.send_message(friend_num, 'File not found'.encode('utf-8'))
else:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif message == 'id':
if id in settings['read']:
tox_id = self._tox.self_get_address()
self.send_message(friend_num, tox_id.encode('utf-8'))
else:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif message.startswith('size '):
path = unicode(settings['folder'] + '/' + message[5:])
if id not in settings['read']:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif not os.path.exists(path):
self.send_message(friend_num, 'File not found'.encode('utf-8'))
else:
bytes_size = os.path.getsize(path)
if bytes_size < 1024:
size = u'{} B'.format(bytes_size)
elif bytes_size < 1024 * 1024:
size = u'{} KB'.format(bytes_size / 1024)
else:
size = u'{} MB'.format(bytes_size / 1024 * 1024)
s = u'size: {} ({} bytes)'.format(size, bytes_size)
self.send_message(friend_num, s.encode('utf-8'))
elif message.startswith('message '):
if id not in settings['master']:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
elif '--all' not in message:
tox_id = message.split(' ')[1][:TOX_PUBLIC_KEY_SIZE * 2]
s = ' '.join(message.split(' ')[2:])
num = self._tox.friend_by_public_key(tox_id)
try:
self.send_message(num, s.encode('utf-8'))
except:
self.send_message(friend_num, 'Friend is not online'.encode('utf-8'))
else:
s = ' '.join(message.split(' ')[2:])
for num in self._tox.self_get_friend_list():
if self._tox.friend_get_connection_status(num):
self.send_message(num, s.encode('utf-8'))
elif message == 'stats':
if id not in settings['read']:
self.send_message(friend_num, 'Not enough rights'.encode('utf-8'))
else:
s = ''
for f in os.listdir(settings['folder']):
f = unicode(f)
if os.path.isfile(os.path.join(settings['folder'], f)):
s += u'{} ({} downloads)\n'.format(f,
self._downloads[os.path.join(settings['folder'], f)])
if not s:
s = 'Nothing found'
else:
s += u'Downloads count: {}'.format(sum(self._downloads.values()))
self.send_message(friend_num, s.encode('utf-8'), TOX_MESSAGE_TYPE['NORMAL'])
else:
self.send_message(friend_num, 'Wrong command'.encode('utf-8'))
# -----------------------------------------------------------------------------------------------------------------
# Friend requests
# -----------------------------------------------------------------------------------------------------------------
def process_friend_request(self, tox_id, message):
"""
Accept or ignore friend request
:param tox_id: tox id of contact
:param message: message
"""
self._tox.friend_add_norequest(tox_id) # num - friend number
settings = Settings.get_instance()
if 'r' in settings['auto_rights'] and tox_id not in settings['read']:
settings['read'].append(tox_id)
if 'w' in settings['auto_rights'] and tox_id not in settings['write']:
settings['write'].append(tox_id)
if 'd' in settings['auto_rights'] and tox_id not in settings['delete']:
settings['delete'].append(tox_id)
if 'm' in settings['auto_rights'] and tox_id not in settings['master']:
settings['master'].append(tox_id)
settings.save()
# -----------------------------------------------------------------------------------------------------------------
# File transfers support
# -----------------------------------------------------------------------------------------------------------------
def incoming_file_transfer(self, friend_number, file_number, size, file_name):
"""
New transfer
:param friend_number: number of friend who sent file
:param file_number: file number
:param size: file size in bytes
:param file_name: file name without path
"""
id = self._tox.friend_get_public_key(friend_number)
settings = Settings.get_instance()
if id in settings['write']:
path = settings['folder']
new_file_name, i = file_name, 1
while os.path.isfile(path + '/' + new_file_name): # file with same name already exists
if '.' in file_name: # has extension
d = file_name.rindex('.')
else: # no extension
d = len(file_name)
new_file_name = file_name[:d] + ' ({})'.format(i) + file_name[d:]
i += 1
self.accept_transfer(path + '/' + new_file_name, friend_number, file_number, size)
else:
self.cancel_transfer(friend_number, file_number, False)
def cancel_transfer(self, friend_number, file_number, already_cancelled=False):
"""
Stop transfer
:param friend_number: number of friend
:param file_number: file number
:param already_cancelled: was cancelled by friend
"""
if (friend_number, file_number) in self._file_transfers:
tr = self._file_transfers[(friend_number, file_number)]
if not already_cancelled:
tr.cancel()
else:
tr.cancelled()
del self._file_transfers[(friend_number, file_number)]
else:
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
def accept_transfer(self, path, friend_number, file_number, size):
"""
:param path: path for saving
:param friend_number: friend number
:param file_number: file number
:param size: file size
"""
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number)
self._file_transfers[(friend_number, file_number)] = rt
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
def send_file(self, path, friend_number):
"""
Send file to current active friend
:param path: file path
"""
self._downloads[path] += 1
st = SendTransfer(path, self._tox, friend_number)
self._file_transfers[(friend_number, st.get_file_number())] = st
def incoming_chunk(self, friend_number, file_number, position, data):
if (friend_number, file_number) in self._file_transfers:
transfer = self._file_transfers[(friend_number, file_number)]
transfer.write_chunk(position, data)
if transfer.state:
del self._file_transfers[(friend_number, file_number)]
def outgoing_chunk(self, friend_number, file_number, position, size):
if (friend_number, file_number) in self._file_transfers:
transfer = self._file_transfers[(friend_number, file_number)]
transfer.send_chunk(position, size)
if transfer.state:
del self._file_transfers[(friend_number, file_number)]
def tox_factory(data=None, settings=None):
"""
:param data: user data from .tox file. None = no saved data, create new profile
:param settings: current profile settings. None = default settings will be used
:return: new tox instance
"""
if settings is None:
settings = {
'ipv6_enabled': True,
'udp_enabled': True,
'proxy_type': 0,
'proxy_host': '0',
'proxy_port': 0,
'start_port': 0,
'end_port': 0,
'tcp_port': 0
}
tox_options = Tox.options_new()
tox_options.contents.udp_enabled = settings['udp_enabled']
tox_options.contents.proxy_type = settings['proxy_type']
tox_options.contents.proxy_host = settings['proxy_host']
tox_options.contents.proxy_port = settings['proxy_port']
tox_options.contents.start_port = settings['start_port']
tox_options.contents.end_port = settings['end_port']
tox_options.contents.tcp_port = settings['tcp_port']
if data: # load existing profile
tox_options.contents.savedata_type = 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 = TOX_SAVEDATA_TYPE['NONE']
tox_options.contents.savedata_data = None
tox_options.contents.savedata_length = 0
return Tox(tox_options)

120
callbacks.py Normal file
View File

@ -0,0 +1,120 @@
from settings import Settings
from bot import Bot
from toxcore_enums_and_consts import *
from tox import bin_to_string
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - current user
# -----------------------------------------------------------------------------------------------------------------
def self_connection_status():
"""
Current user changed connection status (offline, UDP, TCP)
"""
def wrapped(tox, connection, user_data):
print 'Connection status: ', str(connection)
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - friends
# -----------------------------------------------------------------------------------------------------------------
def friend_connection_status(tox, friend_num, new_status, user_data):
"""
Check friend's connection status (offline, udp, tcp)
"""
print "Friend #{} connected! Friend's status: {}".format(friend_num, new_status)
def friend_message():
"""
New message from friend
"""
def wrapped(tox, friend_number, message_type, message, size, user_data):
print message.decode('utf-8')
Bot.get_instance().new_message(friend_number, message.decode('utf-8'))
# parse message
return wrapped
def friend_request(tox, public_key, message, message_size, user_data):
"""
Called when user get new friend request
"""
profile = Bot.get_instance()
tox_id = bin_to_string(public_key, TOX_PUBLIC_KEY_SIZE)
profile.process_friend_request(tox_id, message.decode('utf-8'))
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - file transfers
# -----------------------------------------------------------------------------------------------------------------
def tox_file_recv(tox_link):
"""
New incoming file
"""
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
profile = Bot.get_instance()
if file_type == TOX_FILE_KIND['DATA']:
print 'file'
file_name = unicode(file_name[:file_name_size].decode('utf-8'))
profile.incoming_file_transfer(friend_number, file_number, size, file_name)
else: # AVATAR
tox_link.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
return wrapped
def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data):
"""
Incoming chunk
"""
Bot.get_instance().incoming_chunk(
friend_number,
file_number,
position,
chunk[:length] if length else None)
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
"""
Outgoing chunk
"""
Bot.get_instance().outgoing_chunk(
friend_number,
file_number,
position,
size)
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
"""
Friend cancelled, paused or resumed file transfer
"""
if file_control == TOX_FILE_CONTROL['CANCEL']:
Bot.get_instance().cancel_transfer(friend_number, file_number, True)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization
# -----------------------------------------------------------------------------------------------------------------
def init_callbacks(tox):
"""
Initialization of all callbacks.
:param tox: tox instance
"""
tox.callback_self_connection_status(self_connection_status(), 0)
tox.callback_friend_message(friend_message(), 0)
tox.callback_friend_connection_status(friend_connection_status, 0)
tox.callback_friend_request(friend_request, 0)
tox.callback_file_recv(tox_file_recv(tox), 0)
tox.callback_file_recv_chunk(file_recv_chunk, 0)
tox.callback_file_chunk_request(file_chunk_request, 0)
tox.callback_file_recv_control(file_recv_control, 0)

125
file_transfers.py Normal file
View File

@ -0,0 +1,125 @@
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
from os.path import basename, getsize
from os import remove
from time import time
from tox import Tox
TOX_FILE_TRANSFER_STATE = {
'RUNNING': 0,
'PAUSED': 1,
'CANCELED': 2,
'FINISHED': 3,
}
class FileTransfer(object):
"""
Superclass for file transfers
"""
def __init__(self, path, tox, friend_number, size, file_number=None):
self._path = path
self._tox = tox
self._friend_number = friend_number
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
self._file_number = file_number
self._creation_time = time()
self._size = float(size)
self._done = 0
def set_tox(self, tox):
self._tox = tox
def get_file_number(self):
return self._file_number
def get_friend_number(self):
return self._friend_number
def cancel(self):
self.send_control(TOX_FILE_CONTROL['CANCEL'])
if hasattr(self, '_file'):
self._file.close()
def cancelled(self):
if hasattr(self, '_file'):
self._file.close()
def send_control(self, control):
if self._tox.file_control(self._friend_number, self._file_number, control):
self.state = control
def get_file_id(self):
return self._tox.file_get_file_id(self._friend_number, self._file_number)
# -----------------------------------------------------------------------------------------------------------------
# Send file
# -----------------------------------------------------------------------------------------------------------------
class SendTransfer(FileTransfer):
def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None):
if path is not None:
self._file = open(path, 'rb')
size = getsize(path)
else:
size = 0
super(SendTransfer, self).__init__(path, tox, friend_number, size)
self._file_number = tox.file_send(friend_number, kind, size, file_id,
basename(path).encode('utf-8') if path else '')
def send_chunk(self, position, size):
"""
Send chunk
:param position: start position in file
:param size: chunk max size
"""
if size:
self._file.seek(position)
data = self._file.read(size)
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
self._done += size
else:
self._file.close()
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
# -----------------------------------------------------------------------------------------------------------------
# Receive file
# -----------------------------------------------------------------------------------------------------------------
class ReceiveTransfer(FileTransfer):
def __init__(self, path, tox, friend_number, size, file_number):
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
self._file = open(self._path, 'wb')
self._file.truncate(0)
self._file_size = 0
def cancel(self):
super(ReceiveTransfer, self).cancel()
remove(self._path)
def write_chunk(self, position, data):
"""
Incoming chunk
:param position: position in file to save data
:param data: raw data (string)
"""
if data is None:
self._file.close()
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
else:
data = ''.join(chr(x) for x in data)
if self._file_size < position:
self._file.seek(0, 2)
self._file.write('\0' * (position - self._file_size))
self._file.seek(position)
self._file.write(data)
self._file.flush()
l = len(data)
if position + l > self._file_size:
self._file_size = position + l
self._done += l

45
main.py Normal file
View File

@ -0,0 +1,45 @@
from bootstrap import node_generator
from bot import *
from callbacks import init_callbacks
import time
import sys
class FileBot(object):
def __init__(self, path):
super(FileBot, self).__init__()
self.tox = None
self.stop = False
self.profile = None
self.path = path
def main(self):
self.tox = tox_factory(ProfileHelper.open_profile(self.path))
init_callbacks(self.tox)
# bootstrap
for data in node_generator():
self.tox.bootstrap(*data)
settings = Settings()
self.profile = Bot(self.tox)
print 'Iterate'
try:
while not self.stop:
self.tox.iterate()
time.sleep(self.tox.iteration_interval() / 1000.0)
except KeyboardInterrupt:
print ''
settings.save()
data = self.tox.get_savedata()
ProfileHelper.save_profile(data)
del self.tox
if __name__ == '__main__':
if len(sys.argv) > 1:
path = sys.argv[1]
bot = FileBot(path)
bot.main()
else:
raise IOError('Path to save file not found')

1
settings.json Normal file
View File

@ -0,0 +1 @@
{"write": ["56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5"], "auto_rights": "r", "master": [], "read": ["56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5"], "folder": "/home/tox_user/tox/shared", "delete": ["56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5"]}

63
settings.py Normal file
View File

@ -0,0 +1,63 @@
from platform import system
import json
import os
import locale
from util import Singleton, curr_directory
from toxcore_enums_and_consts import *
class Settings(Singleton, dict):
def __init__(self):
self.path = curr_directory() + '/settings.json'
if os.path.isfile(self.path):
with open(self.path) as fl:
data = fl.read()
super(self.__class__, self).__init__(json.loads(data))
else:
super(self.__class__, self).__init__(Settings.get_default_settings())
self.save()
self['read'] = map(lambda x: x[:TOX_PUBLIC_KEY_SIZE * 2], self['read'])
self['write'] = map(lambda x: x[:TOX_PUBLIC_KEY_SIZE * 2], self['write'])
self['delete'] = map(lambda x: x[:TOX_PUBLIC_KEY_SIZE * 2], self['delete'])
self['master'] = map(lambda x: x[:TOX_PUBLIC_KEY_SIZE * 2], self['master'])
@staticmethod
def get_default_settings():
return {
'read': [],
'write': [],
'delete': [],
'master': [],
'folder': curr_directory(),
'auto_rights': 'r'
}
def save(self):
print 'Saving'
text = json.dumps(self)
with open(self.path, 'w') as fl:
fl.write(text)
class ProfileHelper(object):
"""
Class with static methods for search, load and save profiles
"""
@staticmethod
def open_profile(path):
path = path.decode(locale.getpreferredencoding())
ProfileHelper._path = path
with open(ProfileHelper._path, 'rb') as fl:
data = fl.read()
if data:
return data
else:
raise IOError('Save file has zero size!')
@staticmethod
def save_profile(data):
with open(ProfileHelper._path, 'wb') as fl:
fl.write(data)

1412
tox.py Normal file

File diff suppressed because it is too large Load Diff

209
toxcore_enums_and_consts.py Normal file
View File

@ -0,0 +1,209 @@
TOX_USER_STATUS = {
'NONE': 0,
'AWAY': 1,
'BUSY': 2,
}
TOX_MESSAGE_TYPE = {
'NORMAL': 0,
'ACTION': 1,
}
TOX_PROXY_TYPE = {
'NONE': 0,
'HTTP': 1,
'SOCKS5': 2,
}
TOX_SAVEDATA_TYPE = {
'NONE': 0,
'TOX_SAVE': 1,
'SECRET_KEY': 2,
}
TOX_ERR_OPTIONS_NEW = {
'OK': 0,
'MALLOC': 1,
}
TOX_ERR_NEW = {
'OK': 0,
'NULL': 1,
'MALLOC': 2,
'PORT_ALLOC': 3,
'PROXY_BAD_TYPE': 4,
'PROXY_BAD_HOST': 5,
'PROXY_BAD_PORT': 6,
'PROXY_NOT_FOUND': 7,
'LOAD_ENCRYPTED': 8,
'LOAD_BAD_FORMAT': 9,
}
TOX_ERR_BOOTSTRAP = {
'OK': 0,
'NULL': 1,
'BAD_HOST': 2,
'BAD_PORT': 3,
}
TOX_CONNECTION = {
'NONE': 0,
'TCP': 1,
'UDP': 2,
}
TOX_ERR_SET_INFO = {
'OK': 0,
'NULL': 1,
'TOO_LONG': 2,
}
TOX_ERR_FRIEND_ADD = {
'OK': 0,
'NULL': 1,
'TOO_LONG': 2,
'NO_MESSAGE': 3,
'OWN_KEY': 4,
'ALREADY_SENT': 5,
'BAD_CHECKSUM': 6,
'SET_NEW_NOSPAM': 7,
'MALLOC': 8,
}
TOX_ERR_FRIEND_DELETE = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
'OK': 0,
'NULL': 1,
'NOT_FOUND': 2,
}
TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_GET_LAST_ONLINE = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_QUERY = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
}
TOX_ERR_SET_TYPING = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
}
TOX_ERR_FRIEND_SEND_MESSAGE = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'SENDQ': 4,
'TOO_LONG': 5,
'EMPTY': 6,
}
TOX_FILE_KIND = {
'DATA': 0,
'AVATAR': 1,
}
TOX_FILE_CONTROL = {
'RESUME': 0,
'PAUSE': 1,
'CANCEL': 2,
}
TOX_ERR_FILE_CONTROL = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
'FRIEND_NOT_CONNECTED': 2,
'NOT_FOUND': 3,
'NOT_PAUSED': 4,
'DENIED': 5,
'ALREADY_PAUSED': 6,
'SENDQ': 7,
}
TOX_ERR_FILE_SEEK = {
'OK': 0,
'FRIEND_NOT_FOUND': 1,
'FRIEND_NOT_CONNECTED': 2,
'NOT_FOUND': 3,
'DENIED': 4,
'INVALID_POSITION': 5,
'SENDQ': 6,
}
TOX_ERR_FILE_GET = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'NOT_FOUND': 3,
}
TOX_ERR_FILE_SEND = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'NAME_TOO_LONG': 4,
'TOO_MANY': 5,
}
TOX_ERR_FILE_SEND_CHUNK = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'NOT_FOUND': 4,
'NOT_TRANSFERRING': 5,
'INVALID_LENGTH': 6,
'SENDQ': 7,
'WRONG_POSITION': 8,
}
TOX_ERR_FRIEND_CUSTOM_PACKET = {
'OK': 0,
'NULL': 1,
'FRIEND_NOT_FOUND': 2,
'FRIEND_NOT_CONNECTED': 3,
'INVALID': 4,
'EMPTY': 5,
'TOO_LONG': 6,
'SENDQ': 7,
}
TOX_ERR_GET_PORT = {
'OK': 0,
'NOT_BOUND': 1,
}
TOX_PUBLIC_KEY_SIZE = 32
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
TOX_MAX_MESSAGE_LENGTH = 1372
TOX_MAX_NAME_LENGTH = 128
TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
TOX_SECRET_KEY_SIZE = 32
TOX_FILE_ID_LENGTH = 32
TOX_HASH_LENGTH = 32
TOX_MAX_CUSTOM_PACKET_SIZE = 1373

22
util.py Normal file
View File

@ -0,0 +1,22 @@
import os
def log(data):
with open(curr_directory() + '/logs.log', 'a') as fl:
fl.write(str(data) + '\n')
def curr_directory():
return os.path.dirname(os.path.realpath(__file__))
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
@classmethod
def get_instance(cls):
return cls._instance