From 344ee23c6600b751742917c1f5e64d0dbfb5b75a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 21 Apr 2016 22:55:31 +0300 Subject: [PATCH] initial commit --- .gitignore | 5 + README.md | 44 ++ bootstrap.py | 87 +++ bot.py | 382 ++++++++++ callbacks.py | 120 +++ file_transfers.py | 125 ++++ main.py | 45 ++ settings.json | 1 + settings.py | 63 ++ tox.py | 1412 +++++++++++++++++++++++++++++++++++ toxcore_enums_and_consts.py | 209 ++++++ util.py | 22 + 12 files changed, 2515 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bootstrap.py create mode 100644 bot.py create mode 100644 callbacks.py create mode 100644 file_transfers.py create mode 100644 main.py create mode 100644 settings.json create mode 100644 settings.py create mode 100644 tox.py create mode 100644 toxcore_enums_and_consts.py create mode 100644 util.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b67f665 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +*~ +.idea/ +test/ +libs/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d04e586 --- /dev/null +++ b/README.md @@ -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 - send file to friend (get access) +share --all - send file to all friends (get access) +size - get size of file (get access) +get - get file with specified filename (get access) +del - remove file with specified filename (delete access) +rename --new - rename file (delete access) +user - new rights (example: rwdm) for user (masters only) +status - new status message (masters only) +name - new name (masters only) +message - send message to friend (masters only) + message --all - send message to all friends (masters only)`` \ No newline at end of file diff --git a/bootstrap.py b/bootstrap.py new file mode 100644 index 0000000..340af37 --- /dev/null +++ b/bootstrap.py @@ -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) diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..151d48a --- /dev/null +++ b/bot.py @@ -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 - send file to friend (get access)\n + share --all - send file to all friends (get access)\n + size - get size of file (get access)\n + get - get file with specified filename (get access)\n + del - remove file with specified filename (delete access)\n + rename --new - rename file (delete access)\n + user - new rights (example: rwdm) for user (masters only)\n + status - new status message (masters only)\n + name - new name (masters only)\n + message - send message to friend (masters only)\n + message --all - 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) diff --git a/callbacks.py b/callbacks.py new file mode 100644 index 0000000..171e637 --- /dev/null +++ b/callbacks.py @@ -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) diff --git a/file_transfers.py b/file_transfers.py new file mode 100644 index 0000000..32aebcc --- /dev/null +++ b/file_transfers.py @@ -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 diff --git a/main.py b/main.py new file mode 100644 index 0000000..a105aa1 --- /dev/null +++ b/main.py @@ -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') + diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..f651e37 --- /dev/null +++ b/settings.json @@ -0,0 +1 @@ +{"write": ["56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5"], "auto_rights": "r", "master": [], "read": ["56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5"], "folder": "/home/tox_user/tox/shared", "delete": ["56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5"]} diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..9adc568 --- /dev/null +++ b/settings.py @@ -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) + diff --git a/tox.py b/tox.py new file mode 100644 index 0000000..daf3225 --- /dev/null +++ b/tox.py @@ -0,0 +1,1412 @@ +# -*- coding: utf-8 -*- +from ctypes import c_char_p, Structure, CDLL, c_bool, addressof, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64 +from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8 +from platform import system +from toxcore_enums_and_consts import * + + +class ToxOptions(Structure): + _fields_ = [ + ('ipv6_enabled', c_bool), + ('udp_enabled', c_bool), + ('proxy_type', c_int), + ('proxy_host', c_char_p), + ('proxy_port', c_uint16), + ('start_port', c_uint16), + ('end_port', c_uint16), + ('tcp_port', c_uint16), + ('savedata_type', c_int), + ('savedata_data', c_char_p), + ('savedata_length', c_size_t) + ] + + +class LibToxCore(object): + def __init__(self): + if system() == 'Linux': + # be sure that libtoxcore and libsodium are installed in your os + self._libtoxcore = CDLL('libtoxcore.so') + elif system() == 'Windows': + self._libtoxcore = CDLL('libs/libtox.dll') + else: + raise OSError('Unknown system.') + + def __getattr__(self, item): + return self._libtoxcore.__getattr__(item) + + +def string_to_bin(tox_id): + return c_char_p(tox_id.decode('hex')) if tox_id is not None else None + + +def bin_to_string(raw_id, length): + res = ''.join('{:02x}'.format(ord(raw_id[i])) for i in xrange(length)) + return res.upper() + + +class Tox(object): + libtoxcore = LibToxCore() + + def __init__(self, tox_options=None, tox_pointer=None): + """ + Creates and initialises a new Tox instance with the options passed. + + This function will bring the instance into a valid state. Running the event loop with a new instance will + operate correctly. + + :param tox_options: An options object. If this parameter is None, the default options are used. + :param tox_pointer: Tox instance pointer. If this parameter is not None, tox_options will be ignored. + """ + if tox_pointer is not None: + self._tox_pointer = tox_pointer + else: + tox_err_new = c_int() + Tox.libtoxcore.tox_new.restype = POINTER(c_void_p) + self._tox_pointer = Tox.libtoxcore.tox_new(tox_options, addressof(tox_err_new)) + tox_err_new = tox_err_new.value + if tox_err_new == TOX_ERR_NEW['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_new == TOX_ERR_NEW['MALLOC']: + raise MemoryError('The function was unable to allocate enough ' + 'memory to store the internal structures for the Tox object.') + elif tox_err_new == TOX_ERR_NEW['PORT_ALLOC']: + raise MemoryError('The function was unable to bind to a port. This may mean that all ports have already' + ' been bound, e.g. by other Tox instances, or it may mean a permission error. You may' + ' be able to gather more information from errno.') + elif tox_err_new == TOX_ERR_NEW['PROXY_BAD_TYPE']: + raise ArgumentError('proxy_type was invalid.') + elif tox_err_new == TOX_ERR_NEW['PROXY_BAD_HOST']: + raise ArgumentError('proxy_type was valid but the proxy_host passed had an invalid format or was NULL.') + elif tox_err_new == TOX_ERR_NEW['PROXY_BAD_PORT']: + raise ArgumentError('proxy_type was valid, but the proxy_port was invalid.') + elif tox_err_new == TOX_ERR_NEW['PROXY_NOT_FOUND']: + raise ArgumentError('The proxy address passed could not be resolved.') + elif tox_err_new == TOX_ERR_NEW['LOAD_ENCRYPTED']: + raise ArgumentError('The byte array to be loaded contained an encrypted save.') + elif tox_err_new == TOX_ERR_NEW['LOAD_BAD_FORMAT']: + raise ArgumentError('The data format was invalid. This can happen when loading data that was saved by' + ' an older version of Tox, or when the data has been corrupted. When loading from' + ' badly formatted data, some data may have been loaded, and the rest is discarded.' + ' Passing an invalid length parameter also causes this error.') + + self.self_connection_status_cb = None + self.friend_name_cb = None + self.friend_status_message_cb = None + self.friend_status_cb = None + self.friend_connection_status_cb = None + self.friend_request_cb = None + self.friend_read_receipt_cb = None + self.friend_typing_cb = None + self.friend_message_cb = None + self.file_recv_control_cb = None + self.file_chunk_request_cb = None + self.file_recv_cb = None + self.file_recv_chunk_cb = None + + def __del__(self): + Tox.libtoxcore.tox_kill(self._tox_pointer) + + # ----------------------------------------------------------------------------------------------------------------- + # Startup options + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def options_default(tox_options): + """ + Initialises a Tox_Options object with the default options. + + The result of this function is independent of the original options. All values will be overwritten, no values + will be read (so it is permissible to pass an uninitialised object). + + If options is NULL, this function has no effect. + + :param tox_options: A pointer to options object to be filled with default options. + """ + Tox.libtoxcore.tox_options_default(tox_options) + + @staticmethod + def options_new(): + """ + Allocates a new Tox_Options object and initialises it with the default options. This function can be used to + preserve long term ABI compatibility by giving the responsibility of allocation and deallocation to the Tox + library. + + Objects returned from this function must be freed using the tox_options_free function. + + :return: A pointer to new ToxOptions object with default options or raise MemoryError. + """ + tox_err_options_new = c_int() + f = Tox.libtoxcore.tox_options_new + f.restype = POINTER(ToxOptions) + result = f(addressof(tox_err_options_new)) + tox_err_options_new = tox_err_options_new.value + if tox_err_options_new == TOX_ERR_OPTIONS_NEW['OK']: + return result + elif tox_err_options_new == TOX_ERR_OPTIONS_NEW['MALLOC']: + raise MemoryError('The function failed to allocate enough memory for the options struct.') + + @staticmethod + def options_free(tox_options): + """ + Releases all resources associated with an options objects. + + Passing a pointer that was not returned by tox_options_new results in undefined behaviour. + + :param tox_options: A pointer to new ToxOptions object + """ + Tox.libtoxcore.tox_options_free(tox_options) + + # ----------------------------------------------------------------------------------------------------------------- + # Creation and destruction + # ----------------------------------------------------------------------------------------------------------------- + + def get_savedata_size(self): + """ + Calculates the number of bytes required to store the tox instance with tox_get_savedata. + This function cannot fail. The result is always greater than 0. + + :return: number of bytes + """ + return Tox.libtoxcore.tox_get_savedata_size(self._tox_pointer) + + def get_savedata(self, savedata=None): + """ + Store all information associated with the tox instance to a byte array. + + :param savedata: pointer (c_char_p) to a memory region large enough to store the tox instance data. + Call tox_get_savedata_size to find the number of bytes required. If this parameter is None, this function + allocates memory for the tox instance data. + :return: pointer (c_char_p) to a memory region with the tox instance data + """ + if savedata is None: + savedata_size = self.get_savedata_size() + savedata = create_string_buffer(savedata_size) + Tox.libtoxcore.tox_get_savedata(self._tox_pointer, savedata) + return savedata[:] + + # ----------------------------------------------------------------------------------------------------------------- + # Connection lifecycle and event loop + # ----------------------------------------------------------------------------------------------------------------- + + def bootstrap(self, address, port, public_key): + """ + Sends a "get nodes" request to the given bootstrap node with IP, port, and public key to setup connections. + + This function will attempt to connect to the node using UDP. You must use this function even if + Tox_Options.udp_enabled was set to false. + + :param address: The hostname or IP address (IPv4 or IPv6) of the node. + :param port: The port on the host on which the bootstrap Tox instance is listening. + :param public_key: The long term public key of the bootstrap node (TOX_PUBLIC_KEY_SIZE bytes). + :return: True on success. + """ + tox_err_bootstrap = c_int() + result = Tox.libtoxcore.tox_bootstrap(self._tox_pointer, c_char_p(address), c_uint16(port), + string_to_bin(public_key), addressof(tox_err_bootstrap)) + tox_err_bootstrap = tox_err_bootstrap.value + if tox_err_bootstrap == TOX_ERR_BOOTSTRAP['OK']: + return bool(result) + elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_HOST']: + raise ArgumentError('The address could not be resolved to an IP ' + 'address, or the IP address passed was invalid.') + elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_PORT']: + raise ArgumentError('The port passed was invalid. The valid port range is (1, 65535).') + + def add_tcp_relay(self, address, port, public_key): + """ + Adds additional host:port pair as TCP relay. + + This function can be used to initiate TCP connections to different ports on the same bootstrap node, or to add + TCP relays without using them as bootstrap nodes. + + :param address: The hostname or IP address (IPv4 or IPv6) of the TCP relay. + :param port: The port on the host on which the TCP relay is listening. + :param public_key: The long term public key of the TCP relay (TOX_PUBLIC_KEY_SIZE bytes). + :return: True on success. + """ + tox_err_bootstrap = c_int() + result = Tox.libtoxcore.tox_add_tcp_relay(self._tox_pointer, c_char_p(address), c_uint16(port), + c_char_p(public_key), addressof(tox_err_bootstrap)) + tox_err_bootstrap = tox_err_bootstrap.value + if tox_err_bootstrap == TOX_ERR_BOOTSTRAP['OK']: + return bool(result) + elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_HOST']: + raise ArgumentError('The address could not be resolved to an IP ' + 'address, or the IP address passed was invalid.') + elif tox_err_bootstrap == TOX_ERR_BOOTSTRAP['BAD_PORT']: + raise ArgumentError('The port passed was invalid. The valid port range is (1, 65535).') + + def self_get_connection_status(self): + """ + Return whether we are connected to the DHT. The return value is equal to the last value received through the + `self_connection_status` callback. + + :return: TOX_CONNECTION + """ + return Tox.libtoxcore.tox_self_get_connection_status(self._tox_pointer) + + def callback_self_connection_status(self, callback, user_data): + """ + Set the callback for the `self_connection_status` event. Pass None to unset. + + This event is triggered whenever there is a change in the DHT connection state. When disconnected, a client may + choose to call tox_bootstrap again, to reconnect to the DHT. Note that this state may frequently change for + short amounts of time. Clients should therefore not immediately bootstrap on receiving a disconnect. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + TOX_CONNECTION (c_int), + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_int, c_void_p) + self.self_connection_status_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_self_connection_status(self._tox_pointer, + self.self_connection_status_cb, user_data) + + def iteration_interval(self): + """ + Return the time in milliseconds before tox_iterate() should be called again for optimal performance. + :return: time in milliseconds + """ + return Tox.libtoxcore.tox_iteration_interval(self._tox_pointer) + + def iterate(self): + """ + The main loop that needs to be run in intervals of tox_iteration_interval() milliseconds. + """ + Tox.libtoxcore.tox_iterate(self._tox_pointer) + + # ----------------------------------------------------------------------------------------------------------------- + # Internal client information (Tox address/id) + # ----------------------------------------------------------------------------------------------------------------- + + def self_get_address(self, address=None): + """ + Writes the Tox friend address of the client to a byte array. The address is not in human-readable format. If a + client wants to display the address, formatting is required. + + :param address: pointer (c_char_p) to a memory region of at least TOX_ADDRESS_SIZE bytes. If this parameter is + None, this function allocates memory for address. + :return: Tox friend address + """ + if address is None: + address = create_string_buffer(TOX_ADDRESS_SIZE) + Tox.libtoxcore.tox_self_get_address(self._tox_pointer, address) + return bin_to_string(address, TOX_ADDRESS_SIZE) + + def self_set_nospam(self, nospam): + """ + Set the 4-byte nospam part of the address. + + :param nospam: Any 32 bit unsigned integer. + """ + Tox.libtoxcore.tox_self_set_nospam(self._tox_pointer, c_uint32(nospam)) + + def self_get_nospam(self): + """ + Get the 4-byte nospam part of the address. + + :return: nospam part of the address + """ + return Tox.libtoxcore.tox_self_get_nospam(self._tox_pointer) + + def self_get_public_key(self, public_key=None): + """ + Copy the Tox Public Key (long term) from the Tox object. + + :param public_key: A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this parameter is NULL, this + function allocates memory for Tox Public Key. + :return: Tox Public Key + """ + if public_key is None: + public_key = create_string_buffer(TOX_PUBLIC_KEY_SIZE) + Tox.libtoxcore.tox_self_get_public_key(self._tox_pointer, public_key) + return bin_to_string(public_key, TOX_PUBLIC_KEY_SIZE) + + def self_get_secret_key(self, secret_key=None): + """ + Copy the Tox Secret Key from the Tox object. + + :param secret_key: pointer (c_char_p) to a memory region of at least TOX_SECRET_KEY_SIZE bytes. If this + parameter is NULL, this function allocates memory for Tox Secret Key. + :return: Tox Secret Key + """ + if secret_key is None: + secret_key = create_string_buffer(TOX_SECRET_KEY_SIZE) + Tox.libtoxcore.tox_self_get_secret_key(self._tox_pointer, secret_key) + return bin_to_string(secret_key, TOX_SECRET_KEY_SIZE) + + # ----------------------------------------------------------------------------------------------------------------- + # User-visible client information (nickname/status) + # ----------------------------------------------------------------------------------------------------------------- + + def self_set_name(self, name): + """ + Set the nickname for the Tox client. + + Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is 0, the name parameter is ignored + (it can be None), and the nickname is set back to empty. + :param name: New nickname. + :return: True on success. + """ + tox_err_set_info = c_int() + result = Tox.libtoxcore.tox_self_set_name(self._tox_pointer, c_char_p(name), + c_size_t(len(name)), addressof(tox_err_set_info)) + tox_err_set_info = tox_err_set_info.value + if tox_err_set_info == TOX_ERR_SET_INFO['OK']: + return bool(result) + elif tox_err_set_info == TOX_ERR_SET_INFO['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_set_info == TOX_ERR_SET_INFO['TOO_LONG']: + raise ArgumentError('Information length exceeded maximum permissible size.') + + def self_get_name_size(self): + """ + Return the length of the current nickname as passed to tox_self_set_name. + + If no nickname was set before calling this function, the name is empty, and this function returns 0. + + :return: length of the current nickname + """ + return Tox.libtoxcore.tox_self_get_name_size(self._tox_pointer) + + def self_get_name(self, name=None): + """ + Write the nickname set by tox_self_set_name to a byte array. + + If no nickname was set before calling this function, the name is empty, and this function has no effect. + + Call tox_self_get_name_size to find out how much memory to allocate for the result. + + :param name: pointer (c_char_p) to a memory region location large enough to hold the nickname. If this parameter + is NULL, the function allocates memory for the nickname. + :return: nickname + """ + if name is None: + name = create_string_buffer(self.self_get_name_size()) + Tox.libtoxcore.tox_self_get_name(self._tox_pointer, name) + return name.value.decode('utf8') + + def self_set_status_message(self, status_message): + """ + Set the client's status message. + + Status message length cannot exceed TOX_MAX_STATUS_MESSAGE_LENGTH. If length is 0, the status parameter is + ignored, and the user status is set back to empty. + + :param status_message: new status message + :return: True on success. + """ + tox_err_set_info = c_int() + result = Tox.libtoxcore.tox_self_set_status_message(self._tox_pointer, c_char_p(status_message), + c_size_t(len(status_message)), addressof(tox_err_set_info)) + tox_err_set_info = tox_err_set_info.value + if tox_err_set_info == TOX_ERR_SET_INFO['OK']: + return bool(result) + elif tox_err_set_info == TOX_ERR_SET_INFO['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_set_info == TOX_ERR_SET_INFO['TOO_LONG']: + raise ArgumentError('Information length exceeded maximum permissible size.') + + def self_get_status_message_size(self): + """ + Return the length of the current status message as passed to tox_self_set_status_message. + + If no status message was set before calling this function, the status is empty, and this function returns 0. + + :return: length of the current status message + """ + return Tox.libtoxcore.tox_self_get_status_message_size(self._tox_pointer) + + def self_get_status_message(self, status_message=None): + """ + Write the status message set by tox_self_set_status_message to a byte array. + + If no status message was set before calling this function, the status is empty, and this function has no effect. + + Call tox_self_get_status_message_size to find out how much memory to allocate for the result. + + :param status_message: pointer (c_char_p) to a valid memory location large enough to hold the status message. + If this parameter is None, the function allocates memory for the status message. + :return: status message + """ + if status_message is None: + status_message = create_string_buffer(self.self_get_status_message_size()) + Tox.libtoxcore.tox_self_get_status_message(self._tox_pointer, status_message) + return status_message.value.decode('utf8') + + def self_set_status(self, status): + """ + Set the client's user status. + + :param status: One of the user statuses listed in the enumeration TOX_USER_STATUS. + """ + Tox.libtoxcore.tox_self_set_status(self._tox_pointer, c_int(status)) + + def self_get_status(self): + """ + Returns the client's user status. + + :return: client's user status + """ + return Tox.libtoxcore.tox_self_get_status(self._tox_pointer) + + # ----------------------------------------------------------------------------------------------------------------- + # Friend list management + # ----------------------------------------------------------------------------------------------------------------- + + def friend_add(self, address, message): + """ + Add a friend to the friend list and send a friend request. + + A friend request message must be at least 1 byte long and at most TOX_MAX_FRIEND_REQUEST_LENGTH. + + Friend numbers are unique identifiers used in all functions that operate on friends. Once added, a friend number + is stable for the lifetime of the Tox object. After saving the state and reloading it, the friend numbers may + not be the same as before. Deleting a friend creates a gap in the friend number set, which is filled by the next + adding of a friend. Any pattern in friend numbers should not be relied on. + + If more than INT32_MAX friends are added, this function causes undefined behaviour. + + :param address: The address of the friend (returned by tox_self_get_address of the friend you wish to add) it + must be TOX_ADDRESS_SIZE bytes. + :param message: The message that will be sent along with the friend request. + :return: the friend number on success, UINT32_MAX on failure. + """ + tox_err_friend_add = c_int() + result = Tox.libtoxcore.tox_friend_add(self._tox_pointer, string_to_bin(address), c_char_p(message), + c_size_t(len(message)), addressof(tox_err_friend_add)) + tox_err_friend_add = tox_err_friend_add.value + if tox_err_friend_add == TOX_ERR_FRIEND_ADD['OK']: + return result + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['TOO_LONG']: + raise ArgumentError('The length of the friend request message exceeded TOX_MAX_FRIEND_REQUEST_LENGTH.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NO_MESSAGE']: + raise ArgumentError('The friend request message was empty. This, and the TOO_LONG code will never be' + ' returned from tox_friend_add_norequest.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['OWN_KEY']: + raise ArgumentError('The friend address belongs to the sending client.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['ALREADY_SENT']: + raise ArgumentError('A friend request has already been sent, or the address belongs to a friend that is' + ' already on the friend list.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['BAD_CHECKSUM']: + raise ArgumentError('The friend address checksum failed.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['SET_NEW_NOSPAM']: + raise ArgumentError('The friend was already there, but the nospam value was different.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['MALLOC']: + raise MemoryError('A memory allocation failed when trying to increase the friend list size.') + + def friend_add_norequest(self, public_key): + """ + Add a friend without sending a friend request. + + This function is used to add a friend in response to a friend request. If the client receives a friend request, + it can be reasonably sure that the other client added this client as a friend, eliminating the need for a friend + request. + + This function is also useful in a situation where both instances are controlled by the same entity, so that this + entity can perform the mutual friend adding. In this case, there is no need for a friend request, either. + + :param public_key: A byte array of length TOX_PUBLIC_KEY_SIZE containing the Public Key (not the Address) of the + friend to add. + :return: the friend number on success, UINT32_MAX on failure. + """ + tox_err_friend_add = c_int() + result = Tox.libtoxcore.tox_friend_add_norequest(self._tox_pointer, string_to_bin(public_key), + addressof(tox_err_friend_add)) + tox_err_friend_add = tox_err_friend_add.value + if tox_err_friend_add == TOX_ERR_FRIEND_ADD['OK']: + return result + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['TOO_LONG']: + raise ArgumentError('The length of the friend request message exceeded TOX_MAX_FRIEND_REQUEST_LENGTH.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['NO_MESSAGE']: + raise ArgumentError('The friend request message was empty. This, and the TOO_LONG code will never be' + ' returned from tox_friend_add_norequest.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['OWN_KEY']: + raise ArgumentError('The friend address belongs to the sending client.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['ALREADY_SENT']: + raise ArgumentError('A friend request has already been sent, or the address belongs to a friend that is' + ' already on the friend list.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['BAD_CHECKSUM']: + raise ArgumentError('The friend address checksum failed.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['SET_NEW_NOSPAM']: + raise ArgumentError('The friend was already there, but the nospam value was different.') + elif tox_err_friend_add == TOX_ERR_FRIEND_ADD['MALLOC']: + raise MemoryError('A memory allocation failed when trying to increase the friend list size.') + + def friend_delete(self, friend_number): + """ + Remove a friend from the friend list. + + This does not notify the friend of their deletion. After calling this function, this client will appear offline + to the friend and no communication can occur between the two. + + :param friend_number: Friend number for the friend to be deleted. + :return: True on success. + """ + tox_err_friend_delete = c_int() + result = Tox.libtoxcore.tox_friend_delete(self._tox_pointer, c_uint32(friend_number), + addressof(tox_err_friend_delete)) + tox_err_friend_delete = tox_err_friend_delete.value + if tox_err_friend_delete == TOX_ERR_FRIEND_DELETE['OK']: + return bool(result) + elif tox_err_friend_delete == TOX_ERR_FRIEND_DELETE['FRIEND_NOT_FOUND']: + raise ArgumentError('There was no friend with the given friend number. No friends were deleted.') + + # ----------------------------------------------------------------------------------------------------------------- + # Friend list queries + # ----------------------------------------------------------------------------------------------------------------- + + def friend_by_public_key(self, public_key): + """ + Return the friend number associated with that Public Key. + + :param public_key: A byte array containing the Public Key. + :return: friend number + """ + tox_err_friend_by_public_key = c_int() + result = Tox.libtoxcore.tox_friend_by_public_key(self._tox_pointer, string_to_bin(public_key), + addressof(tox_err_friend_by_public_key)) + tox_err_friend_by_public_key = tox_err_friend_by_public_key.value + if tox_err_friend_by_public_key == TOX_ERR_FRIEND_BY_PUBLIC_KEY['OK']: + return result + elif tox_err_friend_by_public_key == TOX_ERR_FRIEND_BY_PUBLIC_KEY['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_friend_by_public_key == TOX_ERR_FRIEND_BY_PUBLIC_KEY['NOT_FOUND']: + raise ArgumentError('No friend with the given Public Key exists on the friend list.') + + def friend_exists(self, friend_number): + """ + Checks if a friend with the given friend number exists and returns true if it does. + """ + return bool(Tox.libtoxcore.tox_friend_exists(self._tox_pointer, c_uint32(friend_number))) + + def self_get_friend_list_size(self): + """ + Return the number of friends on the friend list. + + This function can be used to determine how much memory to allocate for tox_self_get_friend_list. + + :return: number of friends + """ + return Tox.libtoxcore.tox_self_get_friend_list_size(self._tox_pointer) + + def self_get_friend_list(self, friend_list=None): + """ + Copy a list of valid friend numbers into an array. + + Call tox_self_get_friend_list_size to determine the number of elements to allocate. + + :param friend_list: pointer (c_char_p) to a memory region with enough space to hold the friend list. If this + parameter is None, this function allocates memory for the friend list. + :return: friend list + """ + friend_list_size = self.self_get_friend_list_size() + if friend_list is None: + friend_list = create_string_buffer(sizeof(c_uint32) * friend_list_size) + friend_list = POINTER(c_uint32)(friend_list) + Tox.libtoxcore.tox_self_get_friend_list(self._tox_pointer, friend_list) + return friend_list[0:friend_list_size] + + def friend_get_public_key(self, friend_number, public_key=None): + """ + Copies the Public Key associated with a given friend number to a byte array. + + :param friend_number: The friend number you want the Public Key of. + :param public_key: pointer (c_char_p) to a memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this + parameter is None, this function allocates memory for Tox Public Key. + :return: Tox Public Key + """ + if public_key is None: + public_key = create_string_buffer(TOX_PUBLIC_KEY_SIZE) + tox_err_friend_get_public_key = c_int() + Tox.libtoxcore.tox_friend_get_public_key(self._tox_pointer, c_uint32(friend_number), public_key, + addressof(tox_err_friend_get_public_key)) + tox_err_friend_get_public_key = tox_err_friend_get_public_key.value + if tox_err_friend_get_public_key == TOX_ERR_FRIEND_GET_PUBLIC_KEY['OK']: + return bin_to_string(public_key, TOX_PUBLIC_KEY_SIZE) + elif tox_err_friend_get_public_key == TOX_ERR_FRIEND_GET_PUBLIC_KEY['FRIEND_NOT_FOUND']: + raise ArgumentError('No friend with the given number exists on the friend list.') + + def friend_get_last_online(self, friend_number): + """ + Return a unix-time timestamp of the last time the friend associated with a given friend number was seen online. + This function will return UINT64_MAX on error. + + :param friend_number: The friend number you want to query. + :return: unix-time timestamp + """ + tox_err_last_online = c_int() + result = Tox.libtoxcore.tox_friend_get_last_online(self._tox_pointer, c_uint32(friend_number), + addressof(tox_err_last_online)) + tox_err_last_online = tox_err_last_online.value + if tox_err_last_online == TOX_ERR_FRIEND_GET_LAST_ONLINE['OK']: + return result + elif tox_err_last_online == TOX_ERR_FRIEND_GET_LAST_ONLINE['FRIEND_NOT_FOUND']: + raise ArgumentError('No friend with the given number exists on the friend list.') + + # ----------------------------------------------------------------------------------------------------------------- + # Friend-specific state queries (can also be received through callbacks) + # ----------------------------------------------------------------------------------------------------------------- + + def friend_get_name_size(self, friend_number): + """ + Return the length of the friend's name. If the friend number is invalid, the return value is unspecified. + + The return value is equal to the `length` argument received by the last `friend_name` callback. + """ + tox_err_friend_query = c_int() + result = Tox.libtoxcore.tox_friend_get_name_size(self._tox_pointer, c_uint32(friend_number), + addressof(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return result + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']: + raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike' + ' the `_self_` variants of these functions, which have no effect when a parameter is' + ' NULL, these functions return an error in that case.') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number did not designate a valid friend.') + + def friend_get_name(self, friend_number, name=None): + """ + Write the name of the friend designated by the given friend number to a byte array. + + Call tox_friend_get_name_size to determine the allocation size for the `name` parameter. + + The data written to `name` is equal to the data received by the last `friend_name` callback. + + :param name: pointer (c_char_p) to a valid memory region large enough to store the friend's name. + :return: name of the friend + """ + if name is None: + name = create_string_buffer(self.friend_get_name_size(friend_number)) + tox_err_friend_query = c_int() + Tox.libtoxcore.tox_friend_get_name(self._tox_pointer, c_uint32(friend_number), name, + addressof(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return name.value.decode('utf8') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']: + raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike' + ' the `_self_` variants of these functions, which have no effect when a parameter is' + ' NULL, these functions return an error in that case.') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number did not designate a valid friend.') + + def callback_friend_name(self, callback, user_data): + """ + Set the callback for the `friend_name` event. Pass None to unset. + + This event is triggered when a friend changes their name. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend whose name changed, + A byte array (c_char_p) containing the same data as tox_friend_get_name would write to its `name` parameter, + A value (c_size_t) equal to the return value of tox_friend_get_name_size, + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) + self.friend_name_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_name(self._tox_pointer, self.friend_name_cb, user_data) + + def friend_get_status_message_size(self, friend_number): + """ + Return the length of the friend's status message. If the friend number is invalid, the return value is SIZE_MAX. + + :return: length of the friend's status message + """ + tox_err_friend_query = c_int() + result = Tox.libtoxcore.tox_friend_get_status_message_size(self._tox_pointer, c_uint32(friend_number), + addressof(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return result + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']: + raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike' + ' the `_self_` variants of these functions, which have no effect when a parameter is' + ' NULL, these functions return an error in that case.') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number did not designate a valid friend.') + + def friend_get_status_message(self, friend_number, status_message=None): + """ + Write the status message of the friend designated by the given friend number to a byte array. + + Call tox_friend_get_status_message_size to determine the allocation size for the `status_name` parameter. + + The data written to `status_message` is equal to the data received by the last `friend_status_message` callback. + + :param friend_number: + :param status_message: pointer (c_char_p) to a valid memory region large enough to store the friend's status + message. + :return: status message of the friend + """ + if status_message is None: + status_message = create_string_buffer(self.friend_get_status_message_size(friend_number)) + tox_err_friend_query = c_int() + Tox.libtoxcore.tox_friend_get_status_message(self._tox_pointer, c_uint32(friend_number), status_message, + addressof(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return status_message.value.decode('utf8') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']: + raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike' + ' the `_self_` variants of these functions, which have no effect when a parameter is' + ' NULL, these functions return an error in that case.') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number did not designate a valid friend.') + + def callback_friend_status_message(self, callback, user_data): + """ + Set the callback for the `friend_status_message` event. Pass NULL to unset. + + This event is triggered when a friend changes their status message. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend whose status message changed, + A byte array (c_char_p) containing the same data as tox_friend_get_status_message would write to its + `status_message` parameter, + A value (c_size_t) equal to the return value of tox_friend_get_status_message_size, + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) + self.friend_status_message_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_status_message(self._tox_pointer, + self.friend_status_message_cb, c_void_p(user_data)) + + def friend_get_status(self, friend_number): + """ + Return the friend's user status (away/busy/...). If the friend number is invalid, the return value is + unspecified. + + The status returned is equal to the last status received through the `friend_status` callback. + + :return: TOX_USER_STATUS + """ + tox_err_friend_query = c_int() + result = Tox.libtoxcore.tox_friend_get_status(self._tox_pointer, c_uint32(friend_number), + addressof(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return result + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']: + raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike' + ' the `_self_` variants of these functions, which have no effect when a parameter is' + ' NULL, these functions return an error in that case.') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number did not designate a valid friend.') + + def callback_friend_status(self, callback, user_data): + """ + Set the callback for the `friend_status` event. Pass None to unset. + + This event is triggered when a friend changes their user status. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend whose user status changed, + The new user status (TOX_USER_STATUS), + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) + self.friend_status_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_status(self._tox_pointer, self.friend_status_cb, c_void_p(user_data)) + + def friend_get_connection_status(self, friend_number): + """ + Check whether a friend is currently connected to this client. + + The result of this function is equal to the last value received by the `friend_connection_status` callback. + + :param friend_number: The friend number for which to query the connection status. + :return: the friend's connection status (TOX_CONNECTION) as it was received through the + `friend_connection_status` event. + """ + tox_err_friend_query = c_int() + result = Tox.libtoxcore.tox_friend_get_connection_status(self._tox_pointer, c_uint32(friend_number), + addressof(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return result + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']: + raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike' + ' the `_self_` variants of these functions, which have no effect when a parameter is' + ' NULL, these functions return an error in that case.') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number did not designate a valid friend.') + + def callback_friend_connection_status(self, callback, user_data): + """ + Set the callback for the `friend_connection_status` event. Pass NULL to unset. + + This event is triggered when a friend goes offline after having been online, or when a friend goes online. + + This callback is not called when adding friends. It is assumed that when adding friends, their connection status + is initially offline. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend whose connection status changed, + The result of calling tox_friend_get_connection_status (TOX_CONNECTION) on the passed friend_number, + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) + self.friend_connection_status_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_connection_status(self._tox_pointer, + self.friend_connection_status_cb, c_void_p(user_data)) + + def friend_get_typing(self, friend_number): + """ + Check whether a friend is currently typing a message. + + :param friend_number: The friend number for which to query the typing status. + :return: true if the friend is typing. + """ + tox_err_friend_query = c_int() + result = Tox.libtoxcore.tox_friend_get_typing(self._tox_pointer, c_uint32(friend_number), + addressof(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return bool(result) + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['NULL']: + raise ArgumentError('The pointer parameter for storing the query result (name, message) was NULL. Unlike' + ' the `_self_` variants of these functions, which have no effect when a parameter is' + ' NULL, these functions return an error in that case.') + elif tox_err_friend_query == TOX_ERR_FRIEND_QUERY['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number did not designate a valid friend.') + + def callback_friend_typing(self, callback, user_data): + """ + Set the callback for the `friend_typing` event. Pass NULL to unset. + + This event is triggered when a friend starts or stops typing. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend who started or stopped typing, + The result of calling tox_friend_get_typing (c_bool) on the passed friend_number, + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_void_p) + self.friend_typing_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_typing(self._tox_pointer, self.friend_typing_cb, c_void_p(user_data)) + + # ----------------------------------------------------------------------------------------------------------------- + # Sending private messages + # ----------------------------------------------------------------------------------------------------------------- + + def self_set_typing(self, friend_number, typing): + """ + Set the client's typing status for a friend. + + The client is responsible for turning it on or off. + + :param friend_number: The friend to which the client is typing a message. + :param typing: The typing status. True means the client is typing. + :return: True on success. + """ + tox_err_set_typing = c_int() + result = Tox.libtoxcore.tox_self_set_typing(self._tox_pointer, c_uint32(friend_number), + c_bool(typing), addressof(tox_err_set_typing)) + tox_err_set_typing = tox_err_set_typing.value + if tox_err_set_typing == TOX_ERR_SET_TYPING['OK']: + return bool(result) + elif tox_err_set_typing == TOX_ERR_SET_TYPING['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend number did not designate a valid friend.') + + def friend_send_message(self, friend_number, message_type, message): + """ + Send a text chat message to an online friend. + + This function creates a chat message packet and pushes it into the send queue. + + The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages must be split by the client and sent + as separate messages. Other clients can then reassemble the fragments. Messages may not be empty. + + The return value of this function is the message ID. If a read receipt is received, the triggered + `friend_read_receipt` event will be passed this message ID. + + Message IDs are unique per friend. The first message ID is 0. Message IDs are incremented by 1 each time a + message is sent. If UINT32_MAX messages were sent, the next message ID is 0. + + :param friend_number: The friend number of the friend to send the message to. + :param message_type: Message type (TOX_MESSAGE_TYPE). + :param message: A non-None message text. + :return: message ID + """ + tox_err_friend_send_message = c_int() + result = Tox.libtoxcore.tox_friend_send_message(self._tox_pointer, c_uint32(friend_number), + c_int(message_type), c_char_p(message), c_size_t(len(message)), + addressof(tox_err_friend_send_message)) + tox_err_friend_send_message = tox_err_friend_send_message.value + if tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['OK']: + return result + elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend number did not designate a valid friend.') + elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['FRIEND_NOT_CONNECTED']: + raise ArgumentError('This client is currently not connected to the friend.') + elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['SENDQ']: + raise ArgumentError('An allocation error occurred while increasing the send queue size.') + elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['TOO_LONG']: + raise ArgumentError('Message length exceeded TOX_MAX_MESSAGE_LENGTH.') + elif tox_err_friend_send_message == TOX_ERR_FRIEND_SEND_MESSAGE['EMPTY']: + raise ArgumentError('Attempted to send a zero-length message.') + + def callback_friend_read_receipt(self, callback, user_data): + """ + Set the callback for the `friend_read_receipt` event. Pass None to unset. + + This event is triggered when the friend receives the message sent with tox_friend_send_message with the + corresponding message ID. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend who received the message, + The message ID (c_uint32) as returned from tox_friend_send_message corresponding to the message sent, + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p) + self.friend_read_receipt_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_read_receipt(self._tox_pointer, + self.friend_read_receipt_cb, c_void_p(user_data)) + + # ----------------------------------------------------------------------------------------------------------------- + # Receiving private messages and friend requests + # ----------------------------------------------------------------------------------------------------------------- + + def callback_friend_request(self, callback, user_data): + """ + Set the callback for the `friend_request` event. Pass None to unset. + + This event is triggered when a friend request is received. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The Public Key (c_char_p) of the user who sent the friend request, + The message (c_char_p) they sent along with the request, + The size (c_size_t) of the message byte array, + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p, c_size_t, c_void_p) + self.friend_request_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_request(self._tox_pointer, self.friend_request_cb, c_void_p(user_data)) + + def callback_friend_message(self, callback, user_data): + """ + Set the callback for the `friend_message` event. Pass None to unset. + + This event is triggered when a message from a friend is received. + + :param callback: Python function. Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend who sent the message, + Message type (TOX_MESSAGE_TYPE), + The message data (c_char_p) they sent, + The size (c_size_t) of the message byte array. + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_char_p, c_size_t, c_void_p) + self.friend_message_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_friend_message(self._tox_pointer, self.friend_message_cb, c_void_p(user_data)) + + # ----------------------------------------------------------------------------------------------------------------- + # File transmission: common between sending and receiving + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def hash(data, hash=None): + """ + Generates a cryptographic hash of the given data. + + This function may be used by clients for any purpose, but is provided primarily for validating cached avatars. + This use is highly recommended to avoid unnecessary avatar updates. + + If hash is NULL or data is NULL while length is not 0 the function returns false, otherwise it returns true. + + This function is a wrapper to internal message-digest functions. + + :param hash: A valid memory location the hash data. It must be at least TOX_HASH_LENGTH bytes in size. + :param data: Data to be hashed or NULL. + :return: true if hash was not NULL. + """ + if hash is None: + hash = create_string_buffer(TOX_HASH_LENGTH) + Tox.libtoxcore.tox_hash(hash, c_char_p(data), c_size_t(len(data))) + return bin_to_string(hash, TOX_HASH_LENGTH) + + def file_control(self, friend_number, file_number, control): + """ + Sends a file control command to a friend for a given file transfer. + + :param friend_number: The friend number of the friend the file is being transferred to or received from. + :param file_number: The friend-specific identifier for the file transfer. + :param control: The control (TOX_FILE_CONTROL) command to send. + :return: True on success. + """ + tox_err_file_control = c_int() + result = Tox.libtoxcore.tox_file_control(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number), + c_int(control), addressof(tox_err_file_control)) + tox_err_file_control = tox_err_file_control.value + if tox_err_file_control == TOX_ERR_FILE_CONTROL['OK']: + return bool(result) + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number passed did not designate a valid friend.') + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['FRIEND_NOT_CONNECTED']: + raise RuntimeError('This client is currently not connected to the friend.') + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['NOT_FOUND']: + raise ArgumentError('No file transfer with the given file number was found for the given friend.') + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['NOT_PAUSED']: + raise ArgumentError('A RESUME control was sent, but the file transfer is running normally.') + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['DENIED']: + raise ArgumentError('A RESUME control was sent, but the file transfer was paused by the other party. Only ' + 'the party that paused the transfer can resume it.') + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['ALREADY_PAUSED']: + raise ArgumentError('A PAUSE control was sent, but the file transfer was already paused.') + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['SENDQ']: + raise RuntimeError('Packet queue is full.') + + def callback_file_recv_control(self, callback, user_data): + """ + Set the callback for the `file_recv_control` event. Pass NULL to unset. + + This event is triggered when a file control command is received from a friend. + + :param callback: Python function. + When receiving TOX_FILE_CONTROL_CANCEL, the client should release the resources associated with the file number + and consider the transfer failed. + + Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend who is sending the file. + The friend-specific file number (c_uint32) the data received is associated with. + The file control (TOX_FILE_CONTROL) command received. + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p) + self.file_recv_control_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_file_recv_control(self._tox_pointer, + self.file_recv_control_cb, user_data) + + def file_seek(self, friend_number, file_number, position): + """ + Sends a file seek control command to a friend for a given file transfer. + + This function can only be called to resume a file transfer right before TOX_FILE_CONTROL_RESUME is sent. + + :param friend_number: The friend number of the friend the file is being received from. + :param file_number: The friend-specific identifier for the file transfer. + :param position: The position that the file should be seeked to. + :return: True on success. + """ + tox_err_file_seek = c_int() + result = Tox.libtoxcore.tox_file_control(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number), + c_uint64(position), addressof(tox_err_file_seek)) + tox_err_file_seek = tox_err_file_seek.value + if tox_err_file_seek == TOX_ERR_FILE_SEEK['OK']: + return bool(result) + elif tox_err_file_seek == TOX_ERR_FILE_SEEK['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number passed did not designate a valid friend.') + elif tox_err_file_seek == TOX_ERR_FILE_SEEK['FRIEND_NOT_CONNECTED']: + raise RuntimeError('This client is currently not connected to the friend.') + elif tox_err_file_seek == TOX_ERR_FILE_SEEK['NOT_FOUND']: + raise ArgumentError('No file transfer with the given file number was found for the given friend.') + elif tox_err_file_seek == TOX_ERR_FILE_SEEK['SEEK_DENIED']: + raise ArgumentError('File was not in a state where it could be seeked.') + elif tox_err_file_seek == TOX_ERR_FILE_SEEK['INVALID_POSITION']: + raise ArgumentError('Seek position was invalid') + elif tox_err_file_seek == TOX_ERR_FILE_SEEK['SENDQ']: + raise ArgumentError('Packet queue is full.') + + def file_get_file_id(self, friend_number, file_number, file_id=None): + """ + Copy the file id associated to the file transfer to a byte array. + + :param friend_number: The friend number of the friend the file is being transferred to or received from. + :param file_number: The friend-specific identifier for the file transfer. + :param file_id: A pointer (c_char_p) to memory region of at least TOX_FILE_ID_LENGTH bytes. If this parameter is + None, this function has no effect. + :return: file id. + """ + if file_id is None: + file_id = create_string_buffer(TOX_FILE_ID_LENGTH) + tox_err_file_get = c_int() + Tox.libtoxcore.tox_file_control(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number), file_id, + addressof(tox_err_file_get)) + tox_err_file_get = tox_err_file_get.value + if tox_err_file_get == TOX_ERR_FILE_GET['OK']: + return bin_to_string(file_id, TOX_FILE_ID_LENGTH) + elif tox_err_file_get == TOX_ERR_FILE_GET['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_file_get == TOX_ERR_FILE_GET['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number passed did not designate a valid friend.') + elif tox_err_file_get == TOX_ERR_FILE_GET['NOT_FOUND']: + raise ArgumentError('No file transfer with the given file number was found for the given friend.') + + # ----------------------------------------------------------------------------------------------------------------- + # File transmission: sending + # ----------------------------------------------------------------------------------------------------------------- + + def file_send(self, friend_number, kind, file_size, file_id, filename): + """ + Send a file transmission request. + + Maximum filename length is TOX_MAX_FILENAME_LENGTH bytes. The filename should generally just be a file name, not + a path with directory names. + + If a non-UINT64_MAX file size is provided, it can be used by both sides to determine the sending progress. File + size can be set to UINT64_MAX for streaming data of unknown size. + + File transmission occurs in chunks, which are requested through the `file_chunk_request` event. + + When a friend goes offline, all file transfers associated with the friend are purged from core. + + If the file contents change during a transfer, the behaviour is unspecified in general. What will actually + happen depends on the mode in which the file was modified and how the client determines the file size. + + - If the file size was increased + - and sending mode was streaming (file_size = UINT64_MAX), the behaviour will be as expected. + - and sending mode was file (file_size != UINT64_MAX), the file_chunk_request callback will receive length = + 0 when Core thinks the file transfer has finished. If the client remembers the file size as it was when + sending the request, it will terminate the transfer normally. If the client re-reads the size, it will think + the friend cancelled the transfer. + - If the file size was decreased + - and sending mode was streaming, the behaviour is as expected. + - and sending mode was file, the callback will return 0 at the new (earlier) end-of-file, signalling to the + friend that the transfer was cancelled. + - If the file contents were modified + - at a position before the current read, the two files (local and remote) will differ after the transfer + terminates. + - at a position after the current read, the file transfer will succeed as expected. + - In either case, both sides will regard the transfer as complete and successful. + + :param friend_number: The friend number of the friend the file send request should be sent to. + :param kind: The meaning of the file to be sent. + :param file_size: Size in bytes of the file the client wants to send, UINT64_MAX if unknown or streaming. + :param file_id: A file identifier of length TOX_FILE_ID_LENGTH that can be used to uniquely identify file + transfers across core restarts. If NULL, a random one will be generated by core. It can then be obtained by + using tox_file_get_file_id(). + :param filename: Name of the file. Does not need to be the actual name. This name will be sent along with the + file send request. + :return: A file number used as an identifier in subsequent callbacks. This number is per friend. File numbers + are reused after a transfer terminates. On failure, this function returns UINT32_MAX. Any pattern in file + numbers should not be relied on. + """ + tox_err_file_send = c_int() + result = self.libtoxcore.tox_file_send(self._tox_pointer, c_uint32(friend_number), c_uint32(kind), + c_uint64(file_size), string_to_bin(file_id), c_char_p(filename), + c_size_t(len(filename)), addressof(tox_err_file_send)) + tox_err_file_send = tox_err_file_send.value + if tox_err_file_send == TOX_ERR_FILE_SEND['OK']: + return result + elif tox_err_file_send == TOX_ERR_FILE_SEND['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_file_send == TOX_ERR_FILE_SEND['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number passed did not designate a valid friend.') + elif tox_err_file_send == TOX_ERR_FILE_SEND['FRIEND_NOT_CONNECTED']: + raise RuntimeError('This client is currently not connected to the friend.') + elif tox_err_file_send == TOX_ERR_FILE_SEND['NAME_TOO_LONG']: + raise ArgumentError('Filename length exceeded TOX_MAX_FILENAME_LENGTH bytes.') + elif tox_err_file_send == TOX_ERR_FILE_SEND['TOO_MANY']: + raise RuntimeError('Too many ongoing transfers. The maximum number of concurrent file transfers is 256 per' + 'friend per direction (sending and receiving).') + + def file_send_chunk(self, friend_number, file_number, position, data): + """ + Send a chunk of file data to a friend. + + This function is called in response to the `file_chunk_request` callback. The length parameter should be equal + to the one received though the callback. If it is zero, the transfer is assumed complete. For files with known + size, Core will know that the transfer is complete after the last byte has been received, so it is not necessary + (though not harmful) to send a zero-length chunk to terminate. For streams, core will know that the transfer is + finished if a chunk with length less than the length requested in the callback is sent. + + :param friend_number: The friend number of the receiving friend for this file. + :param file_number: The file transfer identifier returned by tox_file_send. + :param position: The file or stream position from which to continue reading. + :param data: Chunk of file data + :return: true on success. + """ + tox_err_file_send_chunk = c_int() + result = self.libtoxcore.tox_file_send_chunk(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number), + c_uint64(position), c_char_p(data), c_size_t(len(data)), + addressof(tox_err_file_send_chunk)) + tox_err_file_send_chunk = tox_err_file_send_chunk.value + if tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['OK']: + return bool(result) + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['NULL']: + raise ArgumentError('The length parameter was non-zero, but data was NULL.') + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['FRIEND_NOT_FOUND']: + ArgumentError('The friend_number passed did not designate a valid friend.') + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['FRIEND_NOT_CONNECTED']: + raise ArgumentError('This client is currently not connected to the friend.') + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['NOT_FOUND']: + raise ArgumentError('No file transfer with the given file number was found for the given friend.') + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['NOT_TRANSFERRING']: + raise ArgumentError('File transfer was found but isn\'t in a transferring state: (paused, done, broken, ' + 'etc...) (happens only when not called from the request chunk callback).') + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['INVALID_LENGTH']: + raise ArgumentError('Attempted to send more or less data than requested. The requested data size is ' + 'adjusted according to maximum transmission unit and the expected end of the file. ' + 'Trying to send less or more than requested will return this error.') + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['SENDQ']: + raise ArgumentError('Packet queue is full.') + elif tox_err_file_send_chunk == TOX_ERR_FILE_SEND_CHUNK['WRONG_POSITION']: + raise ArgumentError('Position parameter was wrong.') + + def callback_file_chunk_request(self, callback, user_data): + """ + Set the callback for the `file_chunk_request` event. Pass None to unset. + + This event is triggered when Core is ready to send more file data. + + :param callback: Python function. + If the length parameter is 0, the file transfer is finished, and the client's resources associated with the file + number should be released. After a call with zero length, the file number can be reused for future file + transfers. + + If the requested position is not equal to the client's idea of the current file or stream position, it will need + to seek. In case of read-once streams, the client should keep the last read chunk so that a seek back can be + supported. A seek-back only ever needs to read from the last requested chunk. This happens when a chunk was + requested, but the send failed. A seek-back request can occur an arbitrary number of times for any given chunk. + + In response to receiving this callback, the client should call the function `tox_file_send_chunk` with the + requested chunk. If the number of bytes sent through that function is zero, the file transfer is assumed + complete. A client must send the full length of data requested with this callback. + + Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the receiving friend for this file. + The file transfer identifier (c_uint32) returned by tox_file_send. + The file or stream position (c_uint64) from which to continue reading. + The number of bytes (c_size_t) requested for the current chunk. + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, c_size_t, c_void_p) + self.file_chunk_request_cb = c_callback(callback) + self.libtoxcore.tox_callback_file_chunk_request(self._tox_pointer, self.file_chunk_request_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # File transmission: receiving + # ----------------------------------------------------------------------------------------------------------------- + + def callback_file_recv(self, callback, user_data): + """ + Set the callback for the `file_recv` event. Pass None to unset. + + This event is triggered when a file transfer request is received. + + :param callback: Python function. + The client should acquire resources to be associated with the file transfer. Incoming file transfers start in + the PAUSED state. After this callback returns, a transfer can be rejected by sending a TOX_FILE_CONTROL_CANCEL + control command before any other control commands. It can be accepted by sending TOX_FILE_CONTROL_RESUME. + + Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend who is sending the file transfer request. + The friend-specific file number (c_uint32) the data received is associated with. + The meaning of the file (c_uint32) to be sent. + Size in bytes (c_uint64) of the file the client wants to send, UINT64_MAX if unknown or streaming. + Name of the file (c_char_p). Does not need to be the actual name. This name will be sent along with the file + send request. + Size in bytes (c_size_t) of the filename. + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t, c_void_p) + self.file_recv_cb = c_callback(callback) + self.libtoxcore.tox_callback_file_recv(self._tox_pointer, self.file_recv_cb, user_data) + + def callback_file_recv_chunk(self, callback, user_data): + """ + Set the callback for the `file_recv_chunk` event. Pass NULL to unset. + + This event is first triggered when a file transfer request is received, and subsequently when a chunk of file + data for an accepted request was received. + + :param callback: Python function. + When length is 0, the transfer is finished and the client should release the resources it acquired for the + transfer. After a call with length = 0, the file number can be reused for new file transfers. + + If position is equal to file_size (received in the file_receive callback) when the transfer finishes, the file + was received completely. Otherwise, if file_size was UINT64_MAX, streaming ended successfully when length is 0. + + Should take pointer (c_void_p) to Tox object, + The friend number (c_uint32) of the friend who is sending the file. + The friend-specific file number (c_uint32) the data received is associated with. + The file position (c_uint64) of the first byte in data. + A byte array (c_char_p) containing the received chunk. + The length (c_size_t) of the received chunk. + pointer (c_void_p) to user_data + :param user_data: pointer (c_void_p) to user data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, POINTER(c_uint8), c_size_t, c_void_p) + self.file_recv_chunk_cb = c_callback(callback) + self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Low-level network information + # ----------------------------------------------------------------------------------------------------------------- + + def self_get_dht_id(self, dht_id=None): + """ + Writes the temporary DHT public key of this instance to a byte array. + + This can be used in combination with an externally accessible IP address and the bound port (from + tox_self_get_udp_port) to run a temporary bootstrap node. + + Be aware that every time a new instance is created, the DHT public key changes, meaning this cannot be used to + run a permanent bootstrap node. + + :param dht_id: pointer (c_char_p) to a memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If this parameter is + None, this function allocates memory for dht_id. + :return: dht_id + """ + if dht_id is None: + dht_id = create_string_buffer(TOX_PUBLIC_KEY_SIZE) + Tox.libtoxcore.tox_self_get_dht_id(self._tox_pointer, dht_id) + return bin_to_string(dht_id, TOX_PUBLIC_KEY_SIZE) + + def self_get_udp_port(self): + """ + Return the UDP port this Tox instance is bound to. + """ + tox_err_get_port = c_int() + result = Tox.libtoxcore.tox_self_get_udp_port(self._tox_pointer, addressof(tox_err_get_port)) + tox_err_get_port = tox_err_get_port.value + if tox_err_get_port == TOX_ERR_GET_PORT['OK']: + return result + elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']: + raise RuntimeError('The instance was not bound to any port.') + + def self_get_tcp_port(self): + """ + Return the TCP port this Tox instance is bound to. This is only relevant if the instance is acting as a TCP + relay. + """ + tox_err_get_port = c_int() + result = Tox.libtoxcore.tox_self_get_tcp_port(self._tox_pointer, addressof(tox_err_get_port)) + tox_err_get_port = tox_err_get_port.value + if tox_err_get_port == TOX_ERR_GET_PORT['OK']: + return result + elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']: + raise RuntimeError('The instance was not bound to any port.') + + +if __name__ == '__main__': + tox = Tox(Tox.options_new()) + p = tox.get_savedata() + print type(p) + print p + del tox diff --git a/toxcore_enums_and_consts.py b/toxcore_enums_and_consts.py new file mode 100644 index 0000000..4d52837 --- /dev/null +++ b/toxcore_enums_and_consts.py @@ -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 diff --git a/util.py b/util.py new file mode 100644 index 0000000..7e9aae4 --- /dev/null +++ b/util.py @@ -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